@fictjs/hooks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2483 @@
1
+ // src/lifecycle/useMount.ts
2
+ import { onMount } from "@fictjs/runtime";
3
+ function useMount(callback) {
4
+ onMount(callback);
5
+ }
6
+
7
+ // src/lifecycle/useUnmount.ts
8
+ import { onDestroy } from "@fictjs/runtime";
9
+ function useUnmount(callback) {
10
+ onDestroy(callback);
11
+ }
12
+
13
+ // src/async/useAsyncState.ts
14
+ import { createSignal } from "@fictjs/runtime/advanced";
15
+ function useAsyncState(executor, initialState, options = {}) {
16
+ const state = createSignal(initialState);
17
+ const isLoading = createSignal(false);
18
+ const error = createSignal(null);
19
+ let callId = 0;
20
+ const execute = async (...args) => {
21
+ const id = ++callId;
22
+ if (options.resetOnExecute) {
23
+ state(initialState);
24
+ }
25
+ isLoading(true);
26
+ error(null);
27
+ try {
28
+ const result = await executor(...args);
29
+ if (id === callId) {
30
+ state(result);
31
+ }
32
+ return result;
33
+ } catch (err) {
34
+ if (id === callId) {
35
+ error(err);
36
+ }
37
+ options.onError?.(err);
38
+ throw err;
39
+ } finally {
40
+ if (id === callId) {
41
+ isLoading(false);
42
+ }
43
+ }
44
+ };
45
+ if (options.immediate) {
46
+ void execute(...[]).catch(() => {
47
+ });
48
+ }
49
+ return {
50
+ state,
51
+ isLoading,
52
+ error,
53
+ execute
54
+ };
55
+ }
56
+
57
+ // src/async/useFetch.ts
58
+ import { createSignal as createSignal2 } from "@fictjs/runtime/advanced";
59
+
60
+ // src/internal/value.ts
61
+ function toValue(value) {
62
+ return typeof value === "function" ? value() : value;
63
+ }
64
+ function toArray(value) {
65
+ return Array.isArray(value) ? value : [value];
66
+ }
67
+
68
+ // src/async/useFetch.ts
69
+ async function defaultParse(response) {
70
+ const contentType = response.headers.get("content-type") ?? "";
71
+ if (contentType.includes("application/json")) {
72
+ return await response.json();
73
+ }
74
+ return await response.text();
75
+ }
76
+ function useFetch(input, options = {}) {
77
+ const data = createSignal2(options.initialData ?? null);
78
+ const error = createSignal2(null);
79
+ const isLoading = createSignal2(false);
80
+ const status = createSignal2(null);
81
+ const aborted = createSignal2(false);
82
+ const fetcher = options.fetch ?? fetch;
83
+ const parse = options.parse ?? defaultParse;
84
+ let requestId = 0;
85
+ let controller;
86
+ const abort = () => {
87
+ if (controller) {
88
+ controller.abort();
89
+ controller = void 0;
90
+ aborted(true);
91
+ isLoading(false);
92
+ }
93
+ };
94
+ const execute = async (init) => {
95
+ const id = ++requestId;
96
+ abort();
97
+ error(null);
98
+ isLoading(true);
99
+ aborted(false);
100
+ controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
101
+ try {
102
+ const response = await fetcher(toValue(input), {
103
+ ...options.init,
104
+ ...init,
105
+ signal: controller?.signal
106
+ });
107
+ if (id !== requestId) {
108
+ return data();
109
+ }
110
+ status(response.status);
111
+ if (!response.ok) {
112
+ throw new Error(`Fetch failed with status ${response.status}`);
113
+ }
114
+ const parsed = await parse(response);
115
+ data(parsed);
116
+ return parsed;
117
+ } catch (err) {
118
+ if (id !== requestId) {
119
+ return data();
120
+ }
121
+ if (err instanceof DOMException && err.name === "AbortError") {
122
+ aborted(true);
123
+ return data();
124
+ }
125
+ error(err);
126
+ options.onError?.(err);
127
+ return data();
128
+ } finally {
129
+ if (id === requestId) {
130
+ isLoading(false);
131
+ }
132
+ }
133
+ };
134
+ if (options.immediate ?? true) {
135
+ void execute();
136
+ }
137
+ return {
138
+ data,
139
+ error,
140
+ isLoading,
141
+ status,
142
+ aborted,
143
+ execute,
144
+ abort
145
+ };
146
+ }
147
+
148
+ // src/async/useRequest.ts
149
+ import { onDestroy as onDestroy2 } from "@fictjs/runtime";
150
+ import { createSignal as createSignal3 } from "@fictjs/runtime/advanced";
151
+ var requestCache = /* @__PURE__ */ new Map();
152
+ function delay(ms) {
153
+ return new Promise((resolve) => setTimeout(resolve, ms));
154
+ }
155
+ function useRequest(service, options = {}) {
156
+ const data = createSignal3(void 0);
157
+ const error = createSignal3(null);
158
+ const loading = createSignal3(false);
159
+ const params = createSignal3(options.defaultParams);
160
+ let callId = 0;
161
+ let pollingTimer;
162
+ const applyCache = () => {
163
+ if (!options.cacheKey) {
164
+ return;
165
+ }
166
+ const entry = requestCache.get(options.cacheKey);
167
+ if (!entry) {
168
+ return;
169
+ }
170
+ const staleTime = options.staleTime ?? 0;
171
+ if (staleTime > 0 && Date.now() - entry.timestamp > staleTime) {
172
+ requestCache.delete(options.cacheKey);
173
+ return;
174
+ }
175
+ data(entry.data);
176
+ };
177
+ const saveCache = (value) => {
178
+ if (!options.cacheKey) {
179
+ return;
180
+ }
181
+ requestCache.set(options.cacheKey, {
182
+ data: value,
183
+ timestamp: Date.now()
184
+ });
185
+ };
186
+ const stopPolling = () => {
187
+ if (pollingTimer) {
188
+ clearTimeout(pollingTimer);
189
+ pollingTimer = void 0;
190
+ }
191
+ };
192
+ const schedulePolling = (currentParams) => {
193
+ stopPolling();
194
+ if (!options.pollingInterval || options.pollingInterval <= 0) {
195
+ return;
196
+ }
197
+ pollingTimer = setTimeout(() => {
198
+ void runAsync(...currentParams);
199
+ }, options.pollingInterval);
200
+ };
201
+ const runWithRetry = async (currentParams, currentId) => {
202
+ const retryCount = options.retryCount ?? 0;
203
+ const retryInterval = options.retryInterval ?? 1e3;
204
+ let attempt = 0;
205
+ while (true) {
206
+ try {
207
+ return await service(...currentParams);
208
+ } catch (err) {
209
+ if (currentId !== callId) {
210
+ throw err;
211
+ }
212
+ if (attempt >= retryCount) {
213
+ throw err;
214
+ }
215
+ attempt += 1;
216
+ await delay(retryInterval);
217
+ }
218
+ }
219
+ };
220
+ const runAsync = async (...currentParams) => {
221
+ const id = ++callId;
222
+ stopPolling();
223
+ loading(true);
224
+ error(null);
225
+ params(currentParams);
226
+ try {
227
+ const result = await runWithRetry(currentParams, id);
228
+ if (id !== callId) {
229
+ return data();
230
+ }
231
+ data(result);
232
+ saveCache(result);
233
+ options.onSuccess?.(result, currentParams);
234
+ schedulePolling(currentParams);
235
+ return result;
236
+ } catch (err) {
237
+ if (id !== callId) {
238
+ return data();
239
+ }
240
+ error(err);
241
+ options.onError?.(err, currentParams);
242
+ schedulePolling(currentParams);
243
+ return data();
244
+ } finally {
245
+ if (id === callId) {
246
+ loading(false);
247
+ }
248
+ options.onFinally?.(currentParams, data(), error());
249
+ }
250
+ };
251
+ const run = (...currentParams) => {
252
+ void runAsync(...currentParams);
253
+ };
254
+ const cancel = () => {
255
+ callId += 1;
256
+ loading(false);
257
+ stopPolling();
258
+ };
259
+ const refresh = async () => {
260
+ const currentParams = params() ?? options.defaultParams;
261
+ if (!currentParams) {
262
+ return data();
263
+ }
264
+ return runAsync(...currentParams);
265
+ };
266
+ const mutate = (value) => {
267
+ const next = typeof value === "function" ? value(data()) : value;
268
+ data(next);
269
+ saveCache(next);
270
+ };
271
+ applyCache();
272
+ if (!options.manual && options.defaultParams) {
273
+ void runAsync(...options.defaultParams);
274
+ }
275
+ onDestroy2(cancel);
276
+ return {
277
+ data,
278
+ error,
279
+ loading,
280
+ params,
281
+ run,
282
+ runAsync,
283
+ cancel,
284
+ refresh,
285
+ mutate
286
+ };
287
+ }
288
+
289
+ // src/clipboard/useClipboard.ts
290
+ import { onDestroy as onDestroy3 } from "@fictjs/runtime";
291
+ import { createSignal as createSignal4 } from "@fictjs/runtime/advanced";
292
+
293
+ // src/internal/env.ts
294
+ var isClient = typeof window !== "undefined" && typeof document !== "undefined";
295
+ var defaultWindow = isClient ? window : void 0;
296
+ var defaultDocument = isClient ? document : void 0;
297
+ var defaultNavigator = typeof navigator !== "undefined" ? navigator : void 0;
298
+
299
+ // src/clipboard/useClipboard.ts
300
+ function fallbackCopy(value, documentRef) {
301
+ const textarea = documentRef.createElement("textarea");
302
+ textarea.value = value;
303
+ textarea.setAttribute("readonly", "true");
304
+ textarea.style.position = "absolute";
305
+ textarea.style.left = "-9999px";
306
+ documentRef.body.appendChild(textarea);
307
+ textarea.select();
308
+ let ok = false;
309
+ try {
310
+ ok = documentRef.execCommand("copy");
311
+ } catch {
312
+ ok = false;
313
+ }
314
+ documentRef.body.removeChild(textarea);
315
+ return ok;
316
+ }
317
+ function useClipboard(options = {}) {
318
+ const navigatorRef = options.navigator === void 0 ? defaultNavigator : options.navigator;
319
+ const documentRef = options.document === void 0 ? defaultDocument : options.document;
320
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
321
+ const text = createSignal4("");
322
+ const copied = createSignal4(false);
323
+ const isSupported = createSignal4(
324
+ !!navigatorRef?.clipboard?.writeText || !!documentRef?.execCommand
325
+ );
326
+ const copiedDuring = options.copiedDuring ?? 1500;
327
+ let timer;
328
+ const resetCopiedLater = () => {
329
+ if (!windowRef) {
330
+ copied(false);
331
+ return;
332
+ }
333
+ if (timer) {
334
+ clearTimeout(timer);
335
+ }
336
+ timer = windowRef.setTimeout(() => {
337
+ copied(false);
338
+ timer = void 0;
339
+ }, copiedDuring);
340
+ };
341
+ const copy = async (value) => {
342
+ text(value);
343
+ if (navigatorRef?.clipboard?.writeText) {
344
+ try {
345
+ await navigatorRef.clipboard.writeText(value);
346
+ copied(true);
347
+ resetCopiedLater();
348
+ return true;
349
+ } catch {
350
+ copied(false);
351
+ return false;
352
+ }
353
+ }
354
+ if (documentRef) {
355
+ const ok = fallbackCopy(value, documentRef);
356
+ copied(ok);
357
+ if (ok) {
358
+ resetCopiedLater();
359
+ }
360
+ return ok;
361
+ }
362
+ copied(false);
363
+ return false;
364
+ };
365
+ onDestroy3(() => {
366
+ if (timer) {
367
+ clearTimeout(timer);
368
+ timer = void 0;
369
+ }
370
+ });
371
+ return {
372
+ text,
373
+ copied,
374
+ isSupported,
375
+ copy
376
+ };
377
+ }
378
+
379
+ // src/event/useEventListener.ts
380
+ import { createEffect, onCleanup } from "@fictjs/runtime";
381
+ import { createSignal as createSignal5 } from "@fictjs/runtime/advanced";
382
+
383
+ // src/internal/target.ts
384
+ function resolveTarget(target) {
385
+ if (target == null) {
386
+ return void 0;
387
+ }
388
+ if (typeof target === "function") {
389
+ return target() ?? void 0;
390
+ }
391
+ if (typeof target === "object" && "current" in target) {
392
+ return target.current ?? void 0;
393
+ }
394
+ return target;
395
+ }
396
+ function resolveMaybeTarget(target) {
397
+ const resolved = toValue(target);
398
+ return resolveTarget(resolved);
399
+ }
400
+ function resolveTargetList(target) {
401
+ return toArray(target).map((item) => resolveMaybeTarget(item)).filter((item) => item != null);
402
+ }
403
+ function resolveIgnoreElement(ignore, doc) {
404
+ if (typeof ignore === "string") {
405
+ return Array.from(doc.querySelectorAll(ignore));
406
+ }
407
+ return resolveMaybeTarget(ignore);
408
+ }
409
+
410
+ // src/internal/event.ts
411
+ function addEventListeners(targets, events, listener, options) {
412
+ const resolvedTargets = resolveTargetList(targets);
413
+ const names = toArray(events);
414
+ for (const target of resolvedTargets) {
415
+ for (const name of names) {
416
+ target.addEventListener(name, listener, options);
417
+ }
418
+ }
419
+ let active = true;
420
+ return {
421
+ stop() {
422
+ if (!active) {
423
+ return;
424
+ }
425
+ active = false;
426
+ for (const target of resolvedTargets) {
427
+ for (const name of names) {
428
+ target.removeEventListener(name, listener, options);
429
+ }
430
+ }
431
+ }
432
+ };
433
+ }
434
+
435
+ // src/event/useEventListener.ts
436
+ function useEventListener(target, event, handler, options = {}) {
437
+ const active = createSignal5(options.immediate ?? true);
438
+ let stopCurrent = () => {
439
+ };
440
+ const bind = () => {
441
+ const targets = resolveTargetList(target);
442
+ const eventNames = toArray(toValue(event));
443
+ if (targets.length === 0 || eventNames.length === 0) {
444
+ return void 0;
445
+ }
446
+ const listener = (eventObject) => {
447
+ handler(eventObject);
448
+ };
449
+ const listenerOptions = {
450
+ capture: options.capture,
451
+ once: options.once,
452
+ passive: options.passive
453
+ };
454
+ const controller = addEventListeners(targets, eventNames, listener, listenerOptions);
455
+ return () => controller.stop();
456
+ };
457
+ createEffect(() => {
458
+ stopCurrent();
459
+ stopCurrent = () => {
460
+ };
461
+ if (!active()) {
462
+ return;
463
+ }
464
+ const stop = bind();
465
+ if (!stop) {
466
+ return;
467
+ }
468
+ stopCurrent = () => {
469
+ stop();
470
+ stopCurrent = () => {
471
+ };
472
+ };
473
+ onCleanup(() => {
474
+ stopCurrent();
475
+ });
476
+ });
477
+ return {
478
+ start() {
479
+ if (!active()) {
480
+ active(true);
481
+ const stop = bind();
482
+ if (stop) {
483
+ stopCurrent = () => {
484
+ stop();
485
+ stopCurrent = () => {
486
+ };
487
+ };
488
+ }
489
+ }
490
+ },
491
+ stop() {
492
+ if (!active()) {
493
+ return;
494
+ }
495
+ active(false);
496
+ stopCurrent();
497
+ },
498
+ active
499
+ };
500
+ }
501
+
502
+ // src/event/useClickOutside.ts
503
+ function isNodeInside(elements, node) {
504
+ return elements.some((element) => element.contains(node));
505
+ }
506
+ function useClickOutside(target, handler, options = {}) {
507
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
508
+ const documentRef = options.document === void 0 ? defaultDocument : options.document;
509
+ const ignoreTargets = options.ignore ? toArray(options.ignore) : [];
510
+ let pointerDownOutside = false;
511
+ const isOutside = (event) => {
512
+ const node = event.target;
513
+ if (!node || !documentRef) {
514
+ return false;
515
+ }
516
+ const targetElements = resolveTargetList(target);
517
+ if (targetElements.length === 0) {
518
+ return false;
519
+ }
520
+ const ignoreElements = ignoreTargets.flatMap((item) => {
521
+ const resolved = resolveIgnoreElement(item, documentRef);
522
+ if (!resolved) {
523
+ return [];
524
+ }
525
+ return Array.isArray(resolved) ? resolved : [resolved];
526
+ });
527
+ if (isNodeInside(targetElements, node) || isNodeInside(ignoreElements, node)) {
528
+ return false;
529
+ }
530
+ return true;
531
+ };
532
+ const onPointerDown = (event) => {
533
+ pointerDownOutside = isOutside(event);
534
+ };
535
+ const onClick = (event) => {
536
+ if (pointerDownOutside && isOutside(event)) {
537
+ handler(event);
538
+ }
539
+ pointerDownOutside = false;
540
+ };
541
+ const downControls = useEventListener(windowRef, "pointerdown", onPointerDown, {
542
+ capture: options.capture ?? true,
543
+ passive: true
544
+ });
545
+ const clickControls = useEventListener(windowRef, "click", onClick, {
546
+ capture: options.capture ?? true,
547
+ passive: true
548
+ });
549
+ return {
550
+ start() {
551
+ downControls.start();
552
+ clickControls.start();
553
+ },
554
+ stop() {
555
+ downControls.stop();
556
+ clickControls.stop();
557
+ },
558
+ active() {
559
+ return downControls.active() && clickControls.active();
560
+ },
561
+ trigger(event) {
562
+ handler(event ?? new Event("click"));
563
+ }
564
+ };
565
+ }
566
+
567
+ // src/event/useFocusWithin.ts
568
+ import { createEffect as createEffect2 } from "@fictjs/runtime";
569
+ import { createSignal as createSignal6 } from "@fictjs/runtime/advanced";
570
+ function useFocusWithin(target, options = {}) {
571
+ const initialValue = options.initialValue ?? false;
572
+ const focused = createSignal6(initialValue);
573
+ let previousTarget;
574
+ useEventListener(target, "focusin", () => {
575
+ focused(true);
576
+ });
577
+ useEventListener(target, "focusout", (event) => {
578
+ const targetElement = resolveMaybeTarget(target);
579
+ if (!targetElement) {
580
+ focused(false);
581
+ return;
582
+ }
583
+ const relatedTarget = event.relatedTarget;
584
+ if (relatedTarget && targetElement.contains(relatedTarget)) {
585
+ return;
586
+ }
587
+ focused(false);
588
+ });
589
+ createEffect2(() => {
590
+ const currentTarget = resolveMaybeTarget(target);
591
+ if (currentTarget !== previousTarget) {
592
+ previousTarget = currentTarget;
593
+ focused(initialValue);
594
+ }
595
+ });
596
+ return { focused };
597
+ }
598
+
599
+ // src/event/useHover.ts
600
+ import { createEffect as createEffect3 } from "@fictjs/runtime";
601
+ import { createSignal as createSignal7 } from "@fictjs/runtime/advanced";
602
+ function useHover(target, options = {}) {
603
+ const initialValue = options.initialValue ?? false;
604
+ const hovered = createSignal7(initialValue);
605
+ let previousTarget;
606
+ useEventListener(target, "pointerenter", () => {
607
+ hovered(true);
608
+ });
609
+ useEventListener(target, "pointerleave", () => {
610
+ hovered(false);
611
+ });
612
+ createEffect3(() => {
613
+ const currentTarget = resolveMaybeTarget(target);
614
+ if (currentTarget !== previousTarget) {
615
+ previousTarget = currentTarget;
616
+ hovered(initialValue);
617
+ }
618
+ });
619
+ return { hovered };
620
+ }
621
+
622
+ // src/event/useKeyPress.ts
623
+ var modifierAliases = {
624
+ ctrl: "ctrl",
625
+ control: "ctrl",
626
+ alt: "alt",
627
+ option: "alt",
628
+ shift: "shift",
629
+ meta: "meta",
630
+ cmd: "meta",
631
+ command: "meta"
632
+ };
633
+ var keyAliases = {
634
+ esc: "escape",
635
+ return: "enter",
636
+ del: "delete",
637
+ space: " ",
638
+ spacebar: " "
639
+ };
640
+ function normalizeToken(token) {
641
+ return token.trim().toLowerCase();
642
+ }
643
+ function normalizeKey(key) {
644
+ const normalized = normalizeToken(key);
645
+ return keyAliases[normalized] ?? normalized;
646
+ }
647
+ function parseCombo(combo) {
648
+ return combo.split(/[.+]/g).map((part) => normalizeToken(part)).filter(Boolean);
649
+ }
650
+ function isModifierToken(token) {
651
+ return token in modifierAliases;
652
+ }
653
+ function isModifierActive(modifier, event) {
654
+ if (modifier === "ctrl") return event.ctrlKey;
655
+ if (modifier === "alt") return event.altKey;
656
+ if (modifier === "shift") return event.shiftKey;
657
+ return event.metaKey;
658
+ }
659
+ function activeModifierCount(event) {
660
+ return Number(event.ctrlKey) + Number(event.altKey) + Number(event.shiftKey) + Number(event.metaKey);
661
+ }
662
+ function matchesCombo(event, combo, exactMatch) {
663
+ const tokens = parseCombo(combo);
664
+ if (tokens.length === 0) {
665
+ return false;
666
+ }
667
+ const modifiers = tokens.filter(isModifierToken).map((token) => modifierAliases[token]);
668
+ const nonModifiers = tokens.filter((token) => !isModifierToken(token));
669
+ for (const modifier of modifiers) {
670
+ if (!isModifierActive(modifier, event)) {
671
+ return false;
672
+ }
673
+ }
674
+ const keyMatched = nonModifiers.length === 0 || nonModifiers.some((token) => normalizeKey(token) === normalizeKey(event.key));
675
+ if (!keyMatched) {
676
+ return false;
677
+ }
678
+ if (!exactMatch) {
679
+ return true;
680
+ }
681
+ const expectedModifierCount = new Set(modifiers).size;
682
+ return activeModifierCount(event) === expectedModifierCount;
683
+ }
684
+ function matchesFilter(event, filter, exactMatch) {
685
+ if (typeof filter === "function") {
686
+ return filter(event);
687
+ }
688
+ const combos = toArray(filter);
689
+ return combos.some((combo) => matchesCombo(event, combo, exactMatch));
690
+ }
691
+ function useKeyPress(filter, handler, options = {}) {
692
+ const events = toArray(options.events ?? "keydown");
693
+ const exactMatch = options.exactMatch ?? false;
694
+ const target = options.target === void 0 ? defaultWindow : options.target;
695
+ const passive = options.preventDefault ? false : options.passive;
696
+ return useEventListener(
697
+ target,
698
+ events,
699
+ (event) => {
700
+ const keyboardEvent = event;
701
+ if (!matchesFilter(keyboardEvent, filter, exactMatch)) {
702
+ return;
703
+ }
704
+ if (options.preventDefault) {
705
+ keyboardEvent.preventDefault();
706
+ }
707
+ handler(keyboardEvent);
708
+ },
709
+ {
710
+ passive,
711
+ capture: options.capture,
712
+ immediate: options.immediate
713
+ }
714
+ );
715
+ }
716
+
717
+ // src/internal/timing.ts
718
+ import { onDestroy as onDestroy4 } from "@fictjs/runtime";
719
+ function createDebouncedFn(fn, wait, options = {}) {
720
+ const leading = options.leading ?? false;
721
+ const trailing = options.trailing ?? true;
722
+ const maxWait = options.maxWait;
723
+ let timer;
724
+ let maxTimer;
725
+ let lastArgs;
726
+ let pending = false;
727
+ const clearTimers = () => {
728
+ if (timer) {
729
+ clearTimeout(timer);
730
+ timer = void 0;
731
+ }
732
+ if (maxTimer) {
733
+ clearTimeout(maxTimer);
734
+ maxTimer = void 0;
735
+ }
736
+ };
737
+ const invoke = () => {
738
+ if (!lastArgs) {
739
+ pending = false;
740
+ clearTimers();
741
+ return;
742
+ }
743
+ const args = lastArgs;
744
+ lastArgs = void 0;
745
+ pending = false;
746
+ clearTimers();
747
+ fn(...args);
748
+ };
749
+ const scheduleTimers = () => {
750
+ if (timer) {
751
+ clearTimeout(timer);
752
+ }
753
+ timer = setTimeout(() => {
754
+ if (trailing) {
755
+ invoke();
756
+ } else {
757
+ pending = false;
758
+ clearTimers();
759
+ }
760
+ }, wait);
761
+ if (maxWait != null && maxWait >= 0 && !maxTimer) {
762
+ maxTimer = setTimeout(() => {
763
+ invoke();
764
+ }, maxWait);
765
+ }
766
+ };
767
+ const run = (...args) => {
768
+ const shouldCallLeading = leading && !timer;
769
+ lastArgs = args;
770
+ pending = true;
771
+ if (shouldCallLeading) {
772
+ fn(...args);
773
+ if (!trailing) {
774
+ lastArgs = void 0;
775
+ pending = false;
776
+ }
777
+ }
778
+ scheduleTimers();
779
+ };
780
+ const cancel = () => {
781
+ pending = false;
782
+ lastArgs = void 0;
783
+ clearTimers();
784
+ };
785
+ const flush = () => {
786
+ if (pending) {
787
+ invoke();
788
+ }
789
+ };
790
+ onDestroy4(cancel);
791
+ return {
792
+ run,
793
+ cancel,
794
+ flush,
795
+ pending: () => pending
796
+ };
797
+ }
798
+ function createThrottledFn(fn, wait, options = {}) {
799
+ const leading = options.leading ?? true;
800
+ const trailing = options.trailing ?? true;
801
+ let timer;
802
+ let lastArgs;
803
+ let pending = false;
804
+ const invoke = (args) => {
805
+ fn(...args);
806
+ };
807
+ const tick = () => {
808
+ if (trailing && lastArgs) {
809
+ const args = lastArgs;
810
+ lastArgs = void 0;
811
+ pending = false;
812
+ invoke(args);
813
+ timer = setTimeout(tick, wait);
814
+ return;
815
+ }
816
+ timer = void 0;
817
+ pending = false;
818
+ };
819
+ const run = (...args) => {
820
+ if (!timer) {
821
+ if (leading) {
822
+ invoke(args);
823
+ } else if (trailing) {
824
+ lastArgs = args;
825
+ pending = true;
826
+ }
827
+ timer = setTimeout(tick, wait);
828
+ return;
829
+ }
830
+ if (trailing) {
831
+ lastArgs = args;
832
+ pending = true;
833
+ }
834
+ };
835
+ const cancel = () => {
836
+ if (timer) {
837
+ clearTimeout(timer);
838
+ timer = void 0;
839
+ }
840
+ lastArgs = void 0;
841
+ pending = false;
842
+ };
843
+ const flush = () => {
844
+ if (lastArgs) {
845
+ const args = lastArgs;
846
+ lastArgs = void 0;
847
+ pending = false;
848
+ invoke(args);
849
+ }
850
+ if (timer) {
851
+ clearTimeout(timer);
852
+ timer = void 0;
853
+ }
854
+ };
855
+ onDestroy4(cancel);
856
+ return {
857
+ run,
858
+ cancel,
859
+ flush,
860
+ pending: () => pending
861
+ };
862
+ }
863
+
864
+ // src/timing/useDebounceFn.ts
865
+ function useDebounceFn(fn, wait, options = {}) {
866
+ return createDebouncedFn(fn, wait, options);
867
+ }
868
+
869
+ // src/timing/useIntervalFn.ts
870
+ import { onDestroy as onDestroy5 } from "@fictjs/runtime";
871
+ import { createSignal as createSignal8 } from "@fictjs/runtime/advanced";
872
+ function useIntervalFn(callback, interval) {
873
+ const pending = createSignal8(false);
874
+ let timer;
875
+ const cancel = () => {
876
+ if (timer) {
877
+ clearInterval(timer);
878
+ timer = void 0;
879
+ }
880
+ pending(false);
881
+ };
882
+ const run = () => {
883
+ cancel();
884
+ const wait = Math.max(0, toValue(interval));
885
+ pending(true);
886
+ timer = setInterval(() => {
887
+ callback();
888
+ }, wait);
889
+ };
890
+ const flush = () => {
891
+ callback();
892
+ };
893
+ onDestroy5(cancel);
894
+ run();
895
+ return {
896
+ run,
897
+ cancel,
898
+ flush,
899
+ pending
900
+ };
901
+ }
902
+
903
+ // src/timing/useRafFn.ts
904
+ import { onDestroy as onDestroy6 } from "@fictjs/runtime";
905
+ import { createSignal as createSignal9 } from "@fictjs/runtime/advanced";
906
+ function useRafFn(callback, options = {}) {
907
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
908
+ const active = createSignal9(options.immediate ?? true);
909
+ let rafId = 0;
910
+ let lastTimestamp;
911
+ const loop = (timestamp) => {
912
+ if (!active()) {
913
+ return;
914
+ }
915
+ const delta = lastTimestamp == null ? 0 : timestamp - lastTimestamp;
916
+ lastTimestamp = timestamp;
917
+ callback(delta, timestamp);
918
+ if (windowRef?.requestAnimationFrame) {
919
+ rafId = windowRef.requestAnimationFrame(loop);
920
+ }
921
+ };
922
+ const start = () => {
923
+ if (active()) {
924
+ return;
925
+ }
926
+ active(true);
927
+ if (windowRef?.requestAnimationFrame) {
928
+ rafId = windowRef.requestAnimationFrame(loop);
929
+ }
930
+ };
931
+ const stop = () => {
932
+ active(false);
933
+ lastTimestamp = void 0;
934
+ if (rafId && windowRef?.cancelAnimationFrame) {
935
+ windowRef.cancelAnimationFrame(rafId);
936
+ rafId = 0;
937
+ }
938
+ };
939
+ if (active() && windowRef?.requestAnimationFrame) {
940
+ rafId = windowRef.requestAnimationFrame(loop);
941
+ }
942
+ onDestroy6(stop);
943
+ return {
944
+ active,
945
+ start,
946
+ stop
947
+ };
948
+ }
949
+
950
+ // src/timing/useThrottleFn.ts
951
+ function useThrottleFn(fn, wait, options = {}) {
952
+ return createThrottledFn(fn, wait, options);
953
+ }
954
+
955
+ // src/timing/useTimeoutFn.ts
956
+ import { createSignal as createSignal10 } from "@fictjs/runtime/advanced";
957
+ import { onDestroy as onDestroy7 } from "@fictjs/runtime";
958
+ function useTimeoutFn(callback, delay2) {
959
+ const pending = createSignal10(false);
960
+ let timer;
961
+ const cancel = () => {
962
+ if (timer) {
963
+ clearTimeout(timer);
964
+ timer = void 0;
965
+ }
966
+ pending(false);
967
+ };
968
+ const run = () => {
969
+ cancel();
970
+ const wait = Math.max(0, toValue(delay2));
971
+ pending(true);
972
+ timer = setTimeout(() => {
973
+ timer = void 0;
974
+ pending(false);
975
+ callback();
976
+ }, wait);
977
+ };
978
+ const flush = () => {
979
+ if (!pending()) {
980
+ return;
981
+ }
982
+ cancel();
983
+ callback();
984
+ };
985
+ onDestroy7(cancel);
986
+ run();
987
+ return {
988
+ run,
989
+ cancel,
990
+ flush,
991
+ pending
992
+ };
993
+ }
994
+
995
+ // src/internal/storage.ts
996
+ import { createSignal as createSignal11 } from "@fictjs/runtime/advanced";
997
+ import { onDestroy as onDestroy8 } from "@fictjs/runtime";
998
+ var syncEvent = "fict-storage-sync";
999
+ var jsonSerializer = {
1000
+ read: (raw) => JSON.parse(raw),
1001
+ write: (value) => JSON.stringify(value)
1002
+ };
1003
+ function inferSerializer(initial) {
1004
+ const kind = typeof initial;
1005
+ if (kind === "string") {
1006
+ return {
1007
+ read: (raw) => raw,
1008
+ write: (value) => String(value)
1009
+ };
1010
+ }
1011
+ if (kind === "number") {
1012
+ return {
1013
+ read: (raw) => Number(raw),
1014
+ write: (value) => String(value)
1015
+ };
1016
+ }
1017
+ if (kind === "boolean") {
1018
+ return {
1019
+ read: (raw) => raw === "true",
1020
+ write: (value) => String(value)
1021
+ };
1022
+ }
1023
+ if (initial instanceof Date) {
1024
+ return {
1025
+ read: (raw) => new Date(raw),
1026
+ write: (value) => value.toISOString()
1027
+ };
1028
+ }
1029
+ if (initial instanceof Map) {
1030
+ return {
1031
+ read: (raw) => new Map(JSON.parse(raw)),
1032
+ write: (value) => JSON.stringify(Array.from(value.entries()))
1033
+ };
1034
+ }
1035
+ if (initial instanceof Set) {
1036
+ return {
1037
+ read: (raw) => new Set(JSON.parse(raw)),
1038
+ write: (value) => JSON.stringify(Array.from(value.values()))
1039
+ };
1040
+ }
1041
+ return jsonSerializer;
1042
+ }
1043
+ function safeCall(onError, error) {
1044
+ if (!onError) {
1045
+ return;
1046
+ }
1047
+ onError(error);
1048
+ }
1049
+ function resolveNextValue(next, prev) {
1050
+ if (typeof next === "function") {
1051
+ return next(prev);
1052
+ }
1053
+ return next;
1054
+ }
1055
+ function createStorageHook(storage, key, initial, options = {}) {
1056
+ const windowRef = options.window ?? defaultWindow;
1057
+ const serializer = options.serializer ?? inferSerializer(initial);
1058
+ const emitSync = windowRef != null;
1059
+ const readStorage = () => {
1060
+ if (!storage) {
1061
+ return initial;
1062
+ }
1063
+ try {
1064
+ const raw = storage.getItem(key);
1065
+ if (raw == null) {
1066
+ if (options.writeDefaults ?? true) {
1067
+ storage.setItem(key, serializer.write(initial));
1068
+ }
1069
+ return initial;
1070
+ }
1071
+ return serializer.read(raw);
1072
+ } catch (error) {
1073
+ safeCall(options.onError, error);
1074
+ return initial;
1075
+ }
1076
+ };
1077
+ const state = createSignal11(readStorage());
1078
+ let paused = false;
1079
+ const writeState = (next) => {
1080
+ state(next);
1081
+ };
1082
+ const set = (next) => {
1083
+ const prev = state();
1084
+ const value = resolveNextValue(next, prev);
1085
+ if (!storage) {
1086
+ writeState(value);
1087
+ return;
1088
+ }
1089
+ try {
1090
+ const serialized = serializer.write(value);
1091
+ const current = storage.getItem(key);
1092
+ if (current === serialized) {
1093
+ writeState(value);
1094
+ return;
1095
+ }
1096
+ paused = true;
1097
+ storage.setItem(key, serialized);
1098
+ writeState(value);
1099
+ if (emitSync) {
1100
+ windowRef.dispatchEvent(
1101
+ new CustomEvent(syncEvent, {
1102
+ detail: {
1103
+ key,
1104
+ value: serialized
1105
+ }
1106
+ })
1107
+ );
1108
+ }
1109
+ } catch (error) {
1110
+ safeCall(options.onError, error);
1111
+ } finally {
1112
+ paused = false;
1113
+ }
1114
+ };
1115
+ const remove = () => {
1116
+ if (!storage) {
1117
+ writeState(initial);
1118
+ return;
1119
+ }
1120
+ try {
1121
+ paused = true;
1122
+ storage.removeItem(key);
1123
+ writeState(initial);
1124
+ if (emitSync) {
1125
+ windowRef.dispatchEvent(
1126
+ new CustomEvent(syncEvent, {
1127
+ detail: {
1128
+ key,
1129
+ value: null
1130
+ }
1131
+ })
1132
+ );
1133
+ }
1134
+ } catch (error) {
1135
+ safeCall(options.onError, error);
1136
+ } finally {
1137
+ paused = false;
1138
+ }
1139
+ };
1140
+ const syncFromRaw = (raw) => {
1141
+ if (paused) {
1142
+ return;
1143
+ }
1144
+ if (raw == null) {
1145
+ state(initial);
1146
+ return;
1147
+ }
1148
+ try {
1149
+ state(serializer.read(raw));
1150
+ } catch (error) {
1151
+ safeCall(options.onError, error);
1152
+ }
1153
+ };
1154
+ const listenToStorageChanges = options.listenToStorageChanges ?? true;
1155
+ if (windowRef && storage && listenToStorageChanges) {
1156
+ const storageListener = (event) => {
1157
+ if (event.storageArea !== storage || event.key !== key) {
1158
+ return;
1159
+ }
1160
+ syncFromRaw(event.newValue);
1161
+ };
1162
+ const customListener = (event) => {
1163
+ const custom = event;
1164
+ if (custom.detail?.key !== key) {
1165
+ return;
1166
+ }
1167
+ syncFromRaw(custom.detail.value);
1168
+ };
1169
+ windowRef.addEventListener("storage", storageListener);
1170
+ windowRef.addEventListener(syncEvent, customListener);
1171
+ onDestroy8(() => {
1172
+ windowRef.removeEventListener("storage", storageListener);
1173
+ windowRef.removeEventListener(syncEvent, customListener);
1174
+ });
1175
+ }
1176
+ return {
1177
+ value: state,
1178
+ set,
1179
+ remove
1180
+ };
1181
+ }
1182
+
1183
+ // src/storage/useLocalStorage.ts
1184
+ function useLocalStorage(key, initial, options = {}) {
1185
+ const windowRef = options.window ?? defaultWindow;
1186
+ return createStorageHook(windowRef?.localStorage, key, initial, {
1187
+ ...options,
1188
+ window: windowRef
1189
+ });
1190
+ }
1191
+
1192
+ // src/storage/useSessionStorage.ts
1193
+ function useSessionStorage(key, initial, options = {}) {
1194
+ const windowRef = options.window ?? defaultWindow;
1195
+ return createStorageHook(windowRef?.sessionStorage, key, initial, {
1196
+ ...options,
1197
+ window: windowRef
1198
+ });
1199
+ }
1200
+
1201
+ // src/storage/useStorage.ts
1202
+ function useStorage(key, initial, options = {}) {
1203
+ const windowRef = options.window ?? defaultWindow;
1204
+ const storage = options.storage === void 0 ? windowRef?.localStorage : options.storage;
1205
+ return createStorageHook(storage ?? void 0, key, initial, {
1206
+ window: windowRef,
1207
+ serializer: options.serializer,
1208
+ onError: options.onError,
1209
+ listenToStorageChanges: options.listenToStorageChanges,
1210
+ writeDefaults: options.writeDefaults
1211
+ });
1212
+ }
1213
+
1214
+ // src/observer/useIntersectionObserver.ts
1215
+ import { createEffect as createEffect4, onCleanup as onCleanup2 } from "@fictjs/runtime";
1216
+ import { createSignal as createSignal12 } from "@fictjs/runtime/advanced";
1217
+ function useIntersectionObserver(target, callback, options = {}) {
1218
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1219
+ const observerCtor = windowRef?.IntersectionObserver ?? globalThis.IntersectionObserver;
1220
+ const entries = createSignal12([]);
1221
+ const isSupported = createSignal12(!!observerCtor);
1222
+ const active = createSignal12(true);
1223
+ let cleanup = () => {
1224
+ };
1225
+ const setup = () => {
1226
+ const Observer = observerCtor;
1227
+ if (!Observer) {
1228
+ isSupported(false);
1229
+ return;
1230
+ }
1231
+ isSupported(true);
1232
+ const rootElement = options.root ? resolveMaybeTarget(options.root) : void 0;
1233
+ const observer = new Observer(
1234
+ (nextEntries, currentObserver) => {
1235
+ entries(nextEntries);
1236
+ callback?.(nextEntries, currentObserver);
1237
+ },
1238
+ {
1239
+ root: rootElement ?? null,
1240
+ rootMargin: options.rootMargin,
1241
+ threshold: options.threshold
1242
+ }
1243
+ );
1244
+ const targets = resolveTargetList(target);
1245
+ for (const element of targets) {
1246
+ observer.observe(element);
1247
+ }
1248
+ cleanup = () => {
1249
+ observer.disconnect();
1250
+ cleanup = () => {
1251
+ };
1252
+ };
1253
+ };
1254
+ createEffect4(() => {
1255
+ cleanup();
1256
+ if (!active()) {
1257
+ return;
1258
+ }
1259
+ setup();
1260
+ onCleanup2(() => {
1261
+ cleanup();
1262
+ });
1263
+ });
1264
+ return {
1265
+ entries,
1266
+ isSupported,
1267
+ start() {
1268
+ active(true);
1269
+ },
1270
+ stop() {
1271
+ active(false);
1272
+ cleanup();
1273
+ },
1274
+ active
1275
+ };
1276
+ }
1277
+
1278
+ // src/observer/useMutationObserver.ts
1279
+ import { createEffect as createEffect5, onCleanup as onCleanup3 } from "@fictjs/runtime";
1280
+ import { createSignal as createSignal13 } from "@fictjs/runtime/advanced";
1281
+ function useMutationObserver(target, callback, options = {}) {
1282
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1283
+ const observerCtor = windowRef?.MutationObserver ?? globalThis.MutationObserver;
1284
+ const records = createSignal13([]);
1285
+ const isSupported = createSignal13(!!observerCtor);
1286
+ const active = createSignal13(true);
1287
+ let cleanup = () => {
1288
+ };
1289
+ createEffect5(() => {
1290
+ cleanup();
1291
+ if (!active()) {
1292
+ return;
1293
+ }
1294
+ const Observer = observerCtor;
1295
+ if (!Observer) {
1296
+ isSupported(false);
1297
+ return;
1298
+ }
1299
+ isSupported(true);
1300
+ const observer = new Observer(
1301
+ (nextRecords, currentObserver) => {
1302
+ records(nextRecords);
1303
+ callback?.(nextRecords, currentObserver);
1304
+ }
1305
+ );
1306
+ const targets = resolveTargetList(target);
1307
+ const observeOptions = {
1308
+ subtree: options.subtree ?? true,
1309
+ childList: options.childList ?? true,
1310
+ attributes: options.attributes,
1311
+ characterData: options.characterData,
1312
+ attributeFilter: options.attributeFilter,
1313
+ attributeOldValue: options.attributeOldValue,
1314
+ characterDataOldValue: options.characterDataOldValue
1315
+ };
1316
+ for (const element of targets) {
1317
+ observer.observe(element, observeOptions);
1318
+ }
1319
+ cleanup = () => {
1320
+ observer.disconnect();
1321
+ cleanup = () => {
1322
+ };
1323
+ };
1324
+ onCleanup3(() => {
1325
+ cleanup();
1326
+ });
1327
+ });
1328
+ return {
1329
+ records,
1330
+ isSupported,
1331
+ active,
1332
+ start() {
1333
+ active(true);
1334
+ },
1335
+ stop() {
1336
+ active(false);
1337
+ cleanup();
1338
+ }
1339
+ };
1340
+ }
1341
+
1342
+ // src/observer/useResizeObserver.ts
1343
+ import { createEffect as createEffect6, onCleanup as onCleanup4 } from "@fictjs/runtime";
1344
+ import { createSignal as createSignal14 } from "@fictjs/runtime/advanced";
1345
+ function useResizeObserver(target, callback, options = {}) {
1346
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1347
+ const observerCtor = windowRef?.ResizeObserver ?? globalThis.ResizeObserver;
1348
+ const entries = createSignal14([]);
1349
+ const isSupported = createSignal14(!!observerCtor);
1350
+ const active = createSignal14(true);
1351
+ let cleanup = () => {
1352
+ };
1353
+ createEffect6(() => {
1354
+ cleanup();
1355
+ if (!active()) {
1356
+ return;
1357
+ }
1358
+ const Observer = observerCtor;
1359
+ if (!Observer) {
1360
+ isSupported(false);
1361
+ return;
1362
+ }
1363
+ isSupported(true);
1364
+ const observer = new Observer(
1365
+ (nextEntries, currentObserver) => {
1366
+ entries(nextEntries);
1367
+ callback?.(nextEntries, currentObserver);
1368
+ }
1369
+ );
1370
+ const targets = resolveTargetList(target);
1371
+ for (const element of targets) {
1372
+ observer.observe(element, options.box ? { box: options.box } : void 0);
1373
+ }
1374
+ cleanup = () => {
1375
+ observer.disconnect();
1376
+ cleanup = () => {
1377
+ };
1378
+ };
1379
+ onCleanup4(() => {
1380
+ cleanup();
1381
+ });
1382
+ });
1383
+ return {
1384
+ entries,
1385
+ isSupported,
1386
+ active,
1387
+ start() {
1388
+ active(true);
1389
+ },
1390
+ stop() {
1391
+ active(false);
1392
+ cleanup();
1393
+ }
1394
+ };
1395
+ }
1396
+
1397
+ // src/state/useCounter.ts
1398
+ import { createSignal as createSignal15 } from "@fictjs/runtime/advanced";
1399
+ function clamp(value, min, max) {
1400
+ let next = value;
1401
+ if (min != null) {
1402
+ next = Math.max(min, next);
1403
+ }
1404
+ if (max != null) {
1405
+ next = Math.min(max, next);
1406
+ }
1407
+ return next;
1408
+ }
1409
+ function useCounter(initial = 0, options = {}) {
1410
+ const start = clamp(initial, options.min, options.max);
1411
+ const count = createSignal15(start);
1412
+ return {
1413
+ count,
1414
+ set(next) {
1415
+ count(clamp(next, options.min, options.max));
1416
+ },
1417
+ inc(delta = 1) {
1418
+ count(clamp(count() + delta, options.min, options.max));
1419
+ },
1420
+ dec(delta = 1) {
1421
+ count(clamp(count() - delta, options.min, options.max));
1422
+ },
1423
+ reset() {
1424
+ count(start);
1425
+ }
1426
+ };
1427
+ }
1428
+
1429
+ // src/state/usePrevious.ts
1430
+ import { createEffect as createEffect7 } from "@fictjs/runtime";
1431
+ import { createSignal as createSignal16 } from "@fictjs/runtime/advanced";
1432
+ function usePrevious(value) {
1433
+ const previous = createSignal16(void 0);
1434
+ let lastValue;
1435
+ let initialized = false;
1436
+ createEffect7(() => {
1437
+ const current = toValue(value);
1438
+ if (initialized) {
1439
+ previous(lastValue);
1440
+ }
1441
+ lastValue = current;
1442
+ initialized = true;
1443
+ });
1444
+ return previous;
1445
+ }
1446
+
1447
+ // src/state/useToggle.ts
1448
+ import { createSignal as createSignal17 } from "@fictjs/runtime/advanced";
1449
+ function useToggle(initial = false) {
1450
+ const value = createSignal17(initial);
1451
+ return {
1452
+ value,
1453
+ toggle() {
1454
+ value(!value());
1455
+ },
1456
+ set(next) {
1457
+ value(next);
1458
+ },
1459
+ setTrue() {
1460
+ value(true);
1461
+ },
1462
+ setFalse() {
1463
+ value(false);
1464
+ }
1465
+ };
1466
+ }
1467
+
1468
+ // src/state/useVirtualList.ts
1469
+ import { createMemo } from "@fictjs/runtime";
1470
+ import { createSignal as createSignal18 } from "@fictjs/runtime/advanced";
1471
+ function useVirtualList(source, options) {
1472
+ const overscan = options.overscan ?? 2;
1473
+ const itemHeight = options.itemHeight;
1474
+ const scrollTop = createSignal18(options.initialScrollTop ?? 0);
1475
+ const totalHeight = createMemo(() => toValue(source).length * itemHeight);
1476
+ const start = createMemo(() => {
1477
+ const base = Math.floor(scrollTop() / itemHeight) - overscan;
1478
+ return Math.max(0, base);
1479
+ });
1480
+ const end = createMemo(() => {
1481
+ const items = toValue(source);
1482
+ const containerHeight = toValue(options.containerHeight);
1483
+ const visibleCount = Math.ceil(containerHeight / itemHeight) + overscan * 2;
1484
+ return Math.min(items.length, start() + visibleCount);
1485
+ });
1486
+ const list = createMemo(() => {
1487
+ const items = toValue(source);
1488
+ const from = start();
1489
+ const to = end();
1490
+ const result = [];
1491
+ for (let index = from; index < to; index += 1) {
1492
+ result.push({
1493
+ index,
1494
+ data: items[index],
1495
+ start: index * itemHeight,
1496
+ end: (index + 1) * itemHeight
1497
+ });
1498
+ }
1499
+ return result;
1500
+ });
1501
+ const setScrollTop = (value) => {
1502
+ scrollTop(Math.max(0, value));
1503
+ };
1504
+ const scrollTo = (index) => {
1505
+ setScrollTop(index * itemHeight);
1506
+ };
1507
+ const onScroll = (event) => {
1508
+ const element = event.target;
1509
+ if (!element) {
1510
+ return;
1511
+ }
1512
+ setScrollTop(element.scrollTop);
1513
+ };
1514
+ return {
1515
+ list,
1516
+ totalHeight,
1517
+ start,
1518
+ end,
1519
+ scrollTop,
1520
+ setScrollTop,
1521
+ scrollTo,
1522
+ onScroll
1523
+ };
1524
+ }
1525
+
1526
+ // src/browser/useDocumentVisibility.ts
1527
+ import { createMemo as createMemo2 } from "@fictjs/runtime";
1528
+ import { createSignal as createSignal19 } from "@fictjs/runtime/advanced";
1529
+ function useDocumentVisibility(options = {}) {
1530
+ const documentRef = options.document === void 0 ? defaultDocument : options.document;
1531
+ const fallback = options.initialVisibility ?? "visible";
1532
+ const visibility = createSignal19(documentRef?.visibilityState ?? fallback);
1533
+ const update = () => {
1534
+ visibility(documentRef?.visibilityState ?? fallback);
1535
+ };
1536
+ useEventListener(documentRef, "visibilitychange", update, { passive: true });
1537
+ const hidden = createMemo2(() => visibility() !== "visible");
1538
+ return {
1539
+ visibility,
1540
+ hidden
1541
+ };
1542
+ }
1543
+
1544
+ // src/browser/useMediaQuery.ts
1545
+ import { createEffect as createEffect8, onCleanup as onCleanup5 } from "@fictjs/runtime";
1546
+ import { createSignal as createSignal20 } from "@fictjs/runtime/advanced";
1547
+ function useMediaQuery(mediaQuery, options = {}) {
1548
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1549
+ const fallback = options.initialValue ?? false;
1550
+ const matches = createSignal20(fallback);
1551
+ const query = createSignal20(typeof mediaQuery === "string" ? mediaQuery : "");
1552
+ const isSupported = createSignal20(!!windowRef?.matchMedia);
1553
+ createEffect8(() => {
1554
+ const nextQuery = toValue(mediaQuery);
1555
+ query(nextQuery);
1556
+ if (!windowRef?.matchMedia) {
1557
+ isSupported(false);
1558
+ matches(fallback);
1559
+ return;
1560
+ }
1561
+ isSupported(true);
1562
+ const mql = windowRef.matchMedia(nextQuery);
1563
+ matches(mql.matches);
1564
+ const listener = (event) => {
1565
+ matches(event.matches);
1566
+ };
1567
+ mql.addEventListener("change", listener);
1568
+ onCleanup5(() => {
1569
+ mql.removeEventListener("change", listener);
1570
+ });
1571
+ });
1572
+ return {
1573
+ matches,
1574
+ query,
1575
+ isSupported
1576
+ };
1577
+ }
1578
+
1579
+ // src/browser/useNetwork.ts
1580
+ import { createSignal as createSignal21 } from "@fictjs/runtime/advanced";
1581
+ function resolveConnection(navigatorRef) {
1582
+ return navigatorRef?.connection ?? navigatorRef?.mozConnection ?? navigatorRef?.webkitConnection;
1583
+ }
1584
+ function useNetwork(options = {}) {
1585
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1586
+ const navigatorRef = options.navigator === void 0 ? defaultNavigator : options.navigator;
1587
+ const connection = resolveConnection(navigatorRef);
1588
+ const online = createSignal21(navigatorRef?.onLine ?? true);
1589
+ const downlink = createSignal21(connection?.downlink ?? null);
1590
+ const effectiveType = createSignal21(connection?.effectiveType ?? null);
1591
+ const rtt = createSignal21(connection?.rtt ?? null);
1592
+ const saveData = createSignal21(connection?.saveData ?? false);
1593
+ const type = createSignal21(connection?.type ?? null);
1594
+ const isSupported = createSignal21(navigatorRef != null);
1595
+ const update = () => {
1596
+ const nextConnection = resolveConnection(navigatorRef);
1597
+ online(navigatorRef?.onLine ?? true);
1598
+ downlink(nextConnection?.downlink ?? null);
1599
+ effectiveType(nextConnection?.effectiveType ?? null);
1600
+ rtt(nextConnection?.rtt ?? null);
1601
+ saveData(nextConnection?.saveData ?? false);
1602
+ type(nextConnection?.type ?? null);
1603
+ };
1604
+ useEventListener(windowRef, "online", update, { passive: true });
1605
+ useEventListener(windowRef, "offline", update, { passive: true });
1606
+ if (connection) {
1607
+ useEventListener(connection, "change", update, { passive: true });
1608
+ }
1609
+ update();
1610
+ return {
1611
+ online,
1612
+ downlink,
1613
+ effectiveType,
1614
+ rtt,
1615
+ saveData,
1616
+ type,
1617
+ isSupported
1618
+ };
1619
+ }
1620
+
1621
+ // src/browser/useScroll.ts
1622
+ import { createEffect as createEffect9 } from "@fictjs/runtime";
1623
+ import { createSignal as createSignal22 } from "@fictjs/runtime/advanced";
1624
+ function isWindowLike(target) {
1625
+ if (!target || typeof target !== "object") {
1626
+ return false;
1627
+ }
1628
+ return ("pageXOffset" in target || "scrollX" in target) && ("pageYOffset" in target || "scrollY" in target);
1629
+ }
1630
+ function readDocumentScrollPosition(documentRef, windowRef) {
1631
+ const view = documentRef.defaultView ?? windowRef;
1632
+ if (view) {
1633
+ return {
1634
+ x: view.pageXOffset ?? view.scrollX ?? 0,
1635
+ y: view.pageYOffset ?? view.scrollY ?? 0
1636
+ };
1637
+ }
1638
+ const scrolling = documentRef.scrollingElement ?? documentRef.documentElement ?? documentRef.body;
1639
+ return {
1640
+ x: scrolling?.scrollLeft ?? 0,
1641
+ y: scrolling?.scrollTop ?? 0
1642
+ };
1643
+ }
1644
+ function readScrollPosition(target, windowRef, fallback) {
1645
+ if (!target) {
1646
+ return fallback;
1647
+ }
1648
+ if ("documentElement" in target) {
1649
+ return readDocumentScrollPosition(target, windowRef ?? void 0);
1650
+ }
1651
+ if (isWindowLike(target)) {
1652
+ return {
1653
+ x: target.pageXOffset ?? target.scrollX ?? 0,
1654
+ y: target.pageYOffset ?? target.scrollY ?? 0
1655
+ };
1656
+ }
1657
+ return {
1658
+ x: target.scrollLeft ?? 0,
1659
+ y: target.scrollTop ?? 0
1660
+ };
1661
+ }
1662
+ function useScroll(options = {}) {
1663
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1664
+ const fallback = {
1665
+ x: options.initialX ?? 0,
1666
+ y: options.initialY ?? 0
1667
+ };
1668
+ const x = createSignal22(fallback.x);
1669
+ const y = createSignal22(fallback.y);
1670
+ let previous = { ...fallback };
1671
+ const resolveScrollTarget = () => {
1672
+ if (options.target === null) {
1673
+ return void 0;
1674
+ }
1675
+ if (options.target === void 0) {
1676
+ return windowRef ?? void 0;
1677
+ }
1678
+ return resolveMaybeTarget(options.target);
1679
+ };
1680
+ const update = () => {
1681
+ const next = readScrollPosition(resolveScrollTarget(), windowRef, fallback);
1682
+ const shouldUpdate = options.shouldUpdate?.(next, previous) ?? true;
1683
+ if (!shouldUpdate) {
1684
+ return;
1685
+ }
1686
+ if (next.x === previous.x && next.y === previous.y) {
1687
+ return;
1688
+ }
1689
+ previous = next;
1690
+ x(next.x);
1691
+ y(next.y);
1692
+ };
1693
+ useEventListener(() => resolveScrollTarget(), "scroll", update, {
1694
+ passive: options.passive ?? true,
1695
+ capture: options.capture
1696
+ });
1697
+ createEffect9(() => {
1698
+ update();
1699
+ });
1700
+ return {
1701
+ x,
1702
+ y
1703
+ };
1704
+ }
1705
+
1706
+ // src/browser/usePermission.ts
1707
+ import { createEffect as createEffect10, onDestroy as onDestroy9 } from "@fictjs/runtime";
1708
+ import { createSignal as createSignal23 } from "@fictjs/runtime/advanced";
1709
+ function normalizePermission(input) {
1710
+ if (typeof input === "string") {
1711
+ return { name: input };
1712
+ }
1713
+ return input;
1714
+ }
1715
+ function usePermission(permission, options = {}) {
1716
+ const navigatorRef = options.navigator === void 0 ? defaultNavigator : options.navigator;
1717
+ const initialState = options.initialState ?? "prompt";
1718
+ const isSupported = createSignal23(!!navigatorRef?.permissions?.query);
1719
+ const state = createSignal23(initialState);
1720
+ let activePermission = normalizePermission(toValue(permission));
1721
+ let cleanup = () => {
1722
+ };
1723
+ const bindStatus = (nextStatus) => {
1724
+ cleanup();
1725
+ state(nextStatus.state);
1726
+ const onChange = () => {
1727
+ state(nextStatus.state);
1728
+ };
1729
+ nextStatus.addEventListener("change", onChange);
1730
+ cleanup = () => {
1731
+ nextStatus.removeEventListener("change", onChange);
1732
+ cleanup = () => {
1733
+ };
1734
+ };
1735
+ };
1736
+ const query = async () => {
1737
+ if (!navigatorRef?.permissions?.query) {
1738
+ isSupported(false);
1739
+ return null;
1740
+ }
1741
+ isSupported(true);
1742
+ try {
1743
+ const nextStatus = await navigatorRef.permissions.query(activePermission);
1744
+ bindStatus(nextStatus);
1745
+ return nextStatus;
1746
+ } catch {
1747
+ state(initialState);
1748
+ return null;
1749
+ }
1750
+ };
1751
+ createEffect10(() => {
1752
+ activePermission = normalizePermission(toValue(permission));
1753
+ if (options.immediate ?? true) {
1754
+ void query();
1755
+ }
1756
+ });
1757
+ onDestroy9(() => {
1758
+ cleanup();
1759
+ });
1760
+ return {
1761
+ state,
1762
+ isSupported,
1763
+ query
1764
+ };
1765
+ }
1766
+
1767
+ // src/browser/useGeolocation.ts
1768
+ import { onDestroy as onDestroy10 } from "@fictjs/runtime";
1769
+ import { createSignal as createSignal24 } from "@fictjs/runtime/advanced";
1770
+ function createInitialCoords() {
1771
+ return {
1772
+ accuracy: 0,
1773
+ latitude: Number.POSITIVE_INFINITY,
1774
+ longitude: Number.POSITIVE_INFINITY,
1775
+ altitude: null,
1776
+ altitudeAccuracy: null,
1777
+ heading: null,
1778
+ speed: null
1779
+ };
1780
+ }
1781
+ function useGeolocation(options = {}) {
1782
+ const navigatorRef = options.navigator === void 0 ? defaultNavigator : options.navigator;
1783
+ const geolocationRef = navigatorRef?.geolocation;
1784
+ const isSupported = createSignal24(!!geolocationRef);
1785
+ const coords = createSignal24(createInitialCoords());
1786
+ const locatedAt = createSignal24(null);
1787
+ const error = createSignal24(null);
1788
+ const active = createSignal24(false);
1789
+ let watchId = null;
1790
+ const resume = () => {
1791
+ if (!geolocationRef || active()) {
1792
+ if (!geolocationRef) {
1793
+ isSupported(false);
1794
+ }
1795
+ return;
1796
+ }
1797
+ watchId = geolocationRef.watchPosition(
1798
+ (position) => {
1799
+ coords({
1800
+ accuracy: position.coords.accuracy,
1801
+ latitude: position.coords.latitude,
1802
+ longitude: position.coords.longitude,
1803
+ altitude: position.coords.altitude,
1804
+ altitudeAccuracy: position.coords.altitudeAccuracy,
1805
+ heading: position.coords.heading,
1806
+ speed: position.coords.speed
1807
+ });
1808
+ locatedAt(position.timestamp);
1809
+ error(null);
1810
+ },
1811
+ (nextError) => {
1812
+ error(nextError);
1813
+ },
1814
+ {
1815
+ enableHighAccuracy: options.enableHighAccuracy,
1816
+ timeout: options.timeout,
1817
+ maximumAge: options.maximumAge
1818
+ }
1819
+ );
1820
+ active(true);
1821
+ };
1822
+ const pause = () => {
1823
+ if (!geolocationRef || watchId == null) {
1824
+ active(false);
1825
+ return;
1826
+ }
1827
+ geolocationRef.clearWatch(watchId);
1828
+ watchId = null;
1829
+ active(false);
1830
+ };
1831
+ if (options.immediate ?? true) {
1832
+ resume();
1833
+ }
1834
+ onDestroy10(() => {
1835
+ pause();
1836
+ });
1837
+ return {
1838
+ isSupported,
1839
+ coords,
1840
+ locatedAt,
1841
+ error,
1842
+ active,
1843
+ resume,
1844
+ pause
1845
+ };
1846
+ }
1847
+
1848
+ // src/browser/useIdle.ts
1849
+ import { onDestroy as onDestroy11 } from "@fictjs/runtime";
1850
+ import { createSignal as createSignal25 } from "@fictjs/runtime/advanced";
1851
+ var DEFAULT_IDLE_EVENTS = [
1852
+ "mousemove",
1853
+ "mousedown",
1854
+ "resize",
1855
+ "keydown",
1856
+ "touchstart",
1857
+ "wheel",
1858
+ "pointerdown"
1859
+ ];
1860
+ function useIdle(options = {}) {
1861
+ const timeout = options.timeout ?? 6e4;
1862
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1863
+ const documentRef = options.document === void 0 ? defaultDocument : options.document;
1864
+ const events = options.events ?? [...DEFAULT_IDLE_EVENTS];
1865
+ const listenForVisibilityChange = options.listenForVisibilityChange ?? true;
1866
+ const idle = createSignal25(options.initialState ?? false);
1867
+ const lastActive = createSignal25(null);
1868
+ const isSupported = createSignal25(!!windowRef);
1869
+ const active = createSignal25(false);
1870
+ let timer = null;
1871
+ const clearTimer = () => {
1872
+ if (timer == null) {
1873
+ return;
1874
+ }
1875
+ clearTimeout(timer);
1876
+ timer = null;
1877
+ };
1878
+ const scheduleIdle = () => {
1879
+ clearTimer();
1880
+ if (!active() || !isSupported()) {
1881
+ return;
1882
+ }
1883
+ timer = setTimeout(() => {
1884
+ idle(true);
1885
+ }, timeout);
1886
+ };
1887
+ const markActive = () => {
1888
+ idle(false);
1889
+ lastActive(Date.now());
1890
+ scheduleIdle();
1891
+ };
1892
+ const activityListener = useEventListener(windowRef, events, markActive, {
1893
+ passive: true,
1894
+ immediate: false
1895
+ });
1896
+ const visibilityListener = useEventListener(
1897
+ documentRef,
1898
+ "visibilitychange",
1899
+ () => {
1900
+ if (!documentRef || documentRef.visibilityState !== "visible") {
1901
+ return;
1902
+ }
1903
+ markActive();
1904
+ },
1905
+ {
1906
+ passive: true,
1907
+ immediate: false
1908
+ }
1909
+ );
1910
+ const pause = () => {
1911
+ if (!active()) {
1912
+ return;
1913
+ }
1914
+ active(false);
1915
+ activityListener.stop();
1916
+ visibilityListener.stop();
1917
+ clearTimer();
1918
+ };
1919
+ const resume = () => {
1920
+ if (!windowRef || active()) {
1921
+ if (!windowRef) {
1922
+ isSupported(false);
1923
+ }
1924
+ return;
1925
+ }
1926
+ isSupported(true);
1927
+ active(true);
1928
+ activityListener.start();
1929
+ if (listenForVisibilityChange) {
1930
+ visibilityListener.start();
1931
+ }
1932
+ markActive();
1933
+ };
1934
+ const reset = () => {
1935
+ markActive();
1936
+ };
1937
+ if (options.immediate ?? true) {
1938
+ resume();
1939
+ }
1940
+ onDestroy11(() => {
1941
+ pause();
1942
+ });
1943
+ return {
1944
+ idle,
1945
+ lastActive,
1946
+ isSupported,
1947
+ active,
1948
+ reset,
1949
+ pause,
1950
+ resume
1951
+ };
1952
+ }
1953
+
1954
+ // src/browser/useSize.ts
1955
+ import { createEffect as createEffect11, onCleanup as onCleanup6 } from "@fictjs/runtime";
1956
+ import { createSignal as createSignal26 } from "@fictjs/runtime/advanced";
1957
+ function readRect(target) {
1958
+ const rect = target.getBoundingClientRect();
1959
+ return {
1960
+ width: rect.width,
1961
+ height: rect.height,
1962
+ top: rect.top,
1963
+ left: rect.left,
1964
+ x: rect.x ?? rect.left,
1965
+ y: rect.y ?? rect.top
1966
+ };
1967
+ }
1968
+ function useSize(target, options = {}) {
1969
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
1970
+ const observerCtor = windowRef?.ResizeObserver ?? globalThis.ResizeObserver;
1971
+ const width = createSignal26(options.initialWidth ?? 0);
1972
+ const height = createSignal26(options.initialHeight ?? 0);
1973
+ const top = createSignal26(options.initialTop ?? 0);
1974
+ const left = createSignal26(options.initialLeft ?? 0);
1975
+ const x = createSignal26(options.initialX ?? options.initialLeft ?? 0);
1976
+ const y = createSignal26(options.initialY ?? options.initialTop ?? 0);
1977
+ const isSupported = createSignal26(!!observerCtor);
1978
+ const active = createSignal26(options.immediate ?? true);
1979
+ let observer = null;
1980
+ const applyRect = (nextTarget) => {
1981
+ const rect = readRect(nextTarget);
1982
+ width(rect.width);
1983
+ height(rect.height);
1984
+ top(rect.top);
1985
+ left(rect.left);
1986
+ x(rect.x);
1987
+ y(rect.y);
1988
+ };
1989
+ const update = () => {
1990
+ const nextTarget = resolveMaybeTarget(target);
1991
+ if (!nextTarget) {
1992
+ return;
1993
+ }
1994
+ applyRect(nextTarget);
1995
+ };
1996
+ const resizeListener = useEventListener(windowRef, "resize", update, {
1997
+ passive: true,
1998
+ immediate: false
1999
+ });
2000
+ const stopObserver = () => {
2001
+ if (!observer) {
2002
+ return;
2003
+ }
2004
+ observer.disconnect();
2005
+ observer = null;
2006
+ };
2007
+ createEffect11(() => {
2008
+ stopObserver();
2009
+ const nextTarget = target ? resolveMaybeTarget(target) : void 0;
2010
+ if (!active() || !nextTarget) {
2011
+ resizeListener.stop();
2012
+ return;
2013
+ }
2014
+ applyRect(nextTarget);
2015
+ if (windowRef) {
2016
+ resizeListener.start();
2017
+ }
2018
+ const Observer = observerCtor;
2019
+ if (!Observer) {
2020
+ isSupported(false);
2021
+ return;
2022
+ }
2023
+ isSupported(true);
2024
+ observer = new Observer((entries) => {
2025
+ const entry = entries[0];
2026
+ if (entry?.contentRect) {
2027
+ width(entry.contentRect.width);
2028
+ height(entry.contentRect.height);
2029
+ applyRect(nextTarget);
2030
+ return;
2031
+ }
2032
+ applyRect(nextTarget);
2033
+ });
2034
+ observer.observe(nextTarget, options.box ? { box: options.box } : void 0);
2035
+ onCleanup6(() => {
2036
+ stopObserver();
2037
+ });
2038
+ });
2039
+ return {
2040
+ width,
2041
+ height,
2042
+ top,
2043
+ left,
2044
+ x,
2045
+ y,
2046
+ isSupported,
2047
+ active,
2048
+ update,
2049
+ start() {
2050
+ active(true);
2051
+ },
2052
+ stop() {
2053
+ active(false);
2054
+ resizeListener.stop();
2055
+ stopObserver();
2056
+ }
2057
+ };
2058
+ }
2059
+
2060
+ // src/browser/useWebSocket.ts
2061
+ import { onDestroy as onDestroy12 } from "@fictjs/runtime";
2062
+ import { createSignal as createSignal27 } from "@fictjs/runtime/advanced";
2063
+ function normalizeReconnectOptions(value) {
2064
+ if (!value) {
2065
+ return null;
2066
+ }
2067
+ if (value === true) {
2068
+ return { retries: Infinity, delay: 1e3 };
2069
+ }
2070
+ return {
2071
+ retries: value.retries ?? Infinity,
2072
+ delay: value.delay ?? 1e3
2073
+ };
2074
+ }
2075
+ function toStatus(value, socket) {
2076
+ switch (value) {
2077
+ case socket.CONNECTING:
2078
+ return "CONNECTING";
2079
+ case socket.OPEN:
2080
+ return "OPEN";
2081
+ case socket.CLOSING:
2082
+ return "CLOSING";
2083
+ default:
2084
+ return "CLOSED";
2085
+ }
2086
+ }
2087
+ function useWebSocket(url, options = {}) {
2088
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
2089
+ const windowSocketCtor = windowRef?.WebSocket;
2090
+ const webSocketCtor = options.webSocket === void 0 ? windowSocketCtor ?? globalThis.WebSocket : options.webSocket;
2091
+ const reconnectOptions = normalizeReconnectOptions(options.autoReconnect);
2092
+ const data = createSignal27(options.initialData ?? null);
2093
+ const error = createSignal27(null);
2094
+ const status = createSignal27("CLOSED");
2095
+ const isSupported = createSignal27(!!webSocketCtor);
2096
+ const reconnectCount = createSignal27(0);
2097
+ const serialize = options.serialize ?? ((payload) => payload);
2098
+ const deserialize = options.deserialize ?? ((event) => event.data);
2099
+ let socket = null;
2100
+ let manuallyClosed = false;
2101
+ let reconnectTimer = null;
2102
+ let reconnectAttempts = 0;
2103
+ let cleanupSocket = () => {
2104
+ };
2105
+ const stopReconnectTimer = () => {
2106
+ if (reconnectTimer == null) {
2107
+ return;
2108
+ }
2109
+ clearTimeout(reconnectTimer);
2110
+ reconnectTimer = null;
2111
+ };
2112
+ const resetReconnectAttempts = () => {
2113
+ reconnectAttempts = 0;
2114
+ reconnectCount(0);
2115
+ };
2116
+ const scheduleReconnect = () => {
2117
+ if (!reconnectOptions) {
2118
+ return;
2119
+ }
2120
+ const retries = reconnectOptions.retries ?? Infinity;
2121
+ if (reconnectAttempts >= retries) {
2122
+ return;
2123
+ }
2124
+ reconnectAttempts += 1;
2125
+ reconnectCount(reconnectAttempts);
2126
+ const delayValue = reconnectOptions.delay ?? 1e3;
2127
+ const delay2 = typeof delayValue === "function" ? delayValue(reconnectAttempts) : delayValue;
2128
+ stopReconnectTimer();
2129
+ reconnectTimer = setTimeout(
2130
+ () => {
2131
+ reconnectTimer = null;
2132
+ open();
2133
+ },
2134
+ Math.max(0, delay2)
2135
+ );
2136
+ };
2137
+ const open = () => {
2138
+ const resolvedUrl = toValue(url);
2139
+ if (!webSocketCtor || !resolvedUrl) {
2140
+ isSupported(false);
2141
+ return false;
2142
+ }
2143
+ if (socket && (socket.readyState === socket.CONNECTING || socket.readyState === socket.OPEN)) {
2144
+ return true;
2145
+ }
2146
+ stopReconnectTimer();
2147
+ manuallyClosed = false;
2148
+ error(null);
2149
+ let currentSocket;
2150
+ try {
2151
+ currentSocket = new webSocketCtor(resolvedUrl, options.protocols);
2152
+ } catch (nextError) {
2153
+ error(nextError);
2154
+ status("CLOSED");
2155
+ scheduleReconnect();
2156
+ return false;
2157
+ }
2158
+ socket = currentSocket;
2159
+ status(toStatus(currentSocket.readyState, currentSocket));
2160
+ if (options.binaryType) {
2161
+ currentSocket.binaryType = options.binaryType;
2162
+ }
2163
+ const onOpen = (event) => {
2164
+ if (socket !== currentSocket) {
2165
+ return;
2166
+ }
2167
+ status("OPEN");
2168
+ resetReconnectAttempts();
2169
+ options.onOpen?.(event);
2170
+ };
2171
+ const onMessage = (event) => {
2172
+ if (socket !== currentSocket) {
2173
+ return;
2174
+ }
2175
+ const messageEvent = event;
2176
+ const nextData = deserialize(messageEvent);
2177
+ data(nextData);
2178
+ options.onMessage?.(nextData, messageEvent);
2179
+ };
2180
+ const onError = (event) => {
2181
+ if (socket !== currentSocket) {
2182
+ return;
2183
+ }
2184
+ error(event);
2185
+ options.onError?.(event);
2186
+ };
2187
+ const onClose = (event) => {
2188
+ if (socket !== currentSocket) {
2189
+ return;
2190
+ }
2191
+ socket = null;
2192
+ cleanupSocket();
2193
+ status("CLOSED");
2194
+ options.onClose?.(event);
2195
+ if (!manuallyClosed) {
2196
+ scheduleReconnect();
2197
+ }
2198
+ };
2199
+ currentSocket.addEventListener("open", onOpen);
2200
+ currentSocket.addEventListener("message", onMessage);
2201
+ currentSocket.addEventListener("error", onError);
2202
+ currentSocket.addEventListener("close", onClose);
2203
+ cleanupSocket = () => {
2204
+ currentSocket.removeEventListener("open", onOpen);
2205
+ currentSocket.removeEventListener("message", onMessage);
2206
+ currentSocket.removeEventListener("error", onError);
2207
+ currentSocket.removeEventListener("close", onClose);
2208
+ cleanupSocket = () => {
2209
+ };
2210
+ };
2211
+ return true;
2212
+ };
2213
+ const close = (code, reason) => {
2214
+ stopReconnectTimer();
2215
+ resetReconnectAttempts();
2216
+ manuallyClosed = true;
2217
+ const currentSocket = socket;
2218
+ if (!currentSocket) {
2219
+ status("CLOSED");
2220
+ return;
2221
+ }
2222
+ socket = null;
2223
+ cleanupSocket();
2224
+ status("CLOSING");
2225
+ currentSocket.close(code, reason);
2226
+ status("CLOSED");
2227
+ };
2228
+ const reconnect = () => {
2229
+ stopReconnectTimer();
2230
+ if (socket) {
2231
+ const currentSocket = socket;
2232
+ socket = null;
2233
+ cleanupSocket();
2234
+ manuallyClosed = true;
2235
+ currentSocket.close();
2236
+ }
2237
+ manuallyClosed = false;
2238
+ return open();
2239
+ };
2240
+ const send = (payload) => {
2241
+ const currentSocket = socket;
2242
+ if (!currentSocket || currentSocket.readyState !== currentSocket.OPEN) {
2243
+ return false;
2244
+ }
2245
+ try {
2246
+ currentSocket.send(serialize(payload));
2247
+ return true;
2248
+ } catch (nextError) {
2249
+ error(nextError);
2250
+ return false;
2251
+ }
2252
+ };
2253
+ if (options.immediate ?? true) {
2254
+ open();
2255
+ }
2256
+ onDestroy12(() => {
2257
+ stopReconnectTimer();
2258
+ close();
2259
+ });
2260
+ return {
2261
+ data,
2262
+ error,
2263
+ status,
2264
+ isSupported,
2265
+ reconnectCount,
2266
+ open,
2267
+ close,
2268
+ reconnect,
2269
+ send
2270
+ };
2271
+ }
2272
+
2273
+ // src/browser/useWindowSize.ts
2274
+ import { createSignal as createSignal28 } from "@fictjs/runtime/advanced";
2275
+ function useWindowSize(options = {}) {
2276
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
2277
+ const width = createSignal28(windowRef?.innerWidth ?? options.initialWidth ?? 0);
2278
+ const height = createSignal28(windowRef?.innerHeight ?? options.initialHeight ?? 0);
2279
+ const update = () => {
2280
+ if (!windowRef) {
2281
+ return;
2282
+ }
2283
+ width(windowRef.innerWidth);
2284
+ height(windowRef.innerHeight);
2285
+ };
2286
+ useEventListener(windowRef, "resize", update, { passive: true });
2287
+ if (windowRef) {
2288
+ update();
2289
+ }
2290
+ return {
2291
+ width,
2292
+ height
2293
+ };
2294
+ }
2295
+
2296
+ // src/browser/useWindowScroll.ts
2297
+ function useWindowScroll(options = {}) {
2298
+ const windowRef = options.window === void 0 ? defaultWindow : options.window;
2299
+ return useScroll({
2300
+ ...options,
2301
+ window: windowRef,
2302
+ target: windowRef
2303
+ });
2304
+ }
2305
+
2306
+ // src/browser/useTitle.ts
2307
+ import { createEffect as createEffect12, onDestroy as onDestroy13 } from "@fictjs/runtime";
2308
+ import { createSignal as createSignal29 } from "@fictjs/runtime/advanced";
2309
+ function useTitle(value, options = {}) {
2310
+ const documentRef = options.document === void 0 ? defaultDocument : options.document;
2311
+ const initialTitle = documentRef?.title ?? "";
2312
+ const title = createSignal29(documentRef?.title ?? toValue(value));
2313
+ createEffect12(() => {
2314
+ const nextTitle = toValue(value);
2315
+ title(nextTitle);
2316
+ if (documentRef) {
2317
+ documentRef.title = nextTitle;
2318
+ }
2319
+ });
2320
+ if (options.restoreOnUnmount) {
2321
+ onDestroy13(() => {
2322
+ if (documentRef) {
2323
+ documentRef.title = initialTitle;
2324
+ }
2325
+ });
2326
+ }
2327
+ return { title };
2328
+ }
2329
+
2330
+ // src/browser/useFullscreen.ts
2331
+ import { createEffect as createEffect13, onDestroy as onDestroy14 } from "@fictjs/runtime";
2332
+ import { createSignal as createSignal30 } from "@fictjs/runtime/advanced";
2333
+ function getFullscreenElement(documentRef) {
2334
+ return documentRef.fullscreenElement ?? documentRef.webkitFullscreenElement ?? documentRef.mozFullScreenElement ?? documentRef.msFullscreenElement ?? null;
2335
+ }
2336
+ function isFullscreenSupported(documentRef) {
2337
+ if (!documentRef) {
2338
+ return false;
2339
+ }
2340
+ if (documentRef.fullscreenEnabled || documentRef.webkitFullscreenEnabled || documentRef.mozFullScreenEnabled || documentRef.msFullscreenEnabled) {
2341
+ return true;
2342
+ }
2343
+ return typeof documentRef.exitFullscreen === "function" || typeof documentRef.webkitExitFullscreen === "function" || typeof documentRef.mozCancelFullScreen === "function" || typeof documentRef.msExitFullscreen === "function";
2344
+ }
2345
+ function resolveRequestMethod(target) {
2346
+ return target.requestFullscreen ?? target.webkitRequestFullscreen ?? target.webkitRequestFullScreen ?? target.mozRequestFullScreen ?? target.msRequestFullscreen;
2347
+ }
2348
+ function resolveExitMethod(documentRef) {
2349
+ return documentRef.exitFullscreen ?? documentRef.webkitExitFullscreen ?? documentRef.mozCancelFullScreen ?? documentRef.msExitFullscreen;
2350
+ }
2351
+ function resolveTargetElement(options, documentRef) {
2352
+ if (!documentRef) {
2353
+ return void 0;
2354
+ }
2355
+ if (options.target === null) {
2356
+ return void 0;
2357
+ }
2358
+ if (options.target === void 0) {
2359
+ return documentRef.documentElement ?? void 0;
2360
+ }
2361
+ return resolveMaybeTarget(options.target);
2362
+ }
2363
+ function useFullscreen(options = {}) {
2364
+ const documentRef = options.document === void 0 ? defaultDocument : options.document;
2365
+ const isSupported = createSignal30(isFullscreenSupported(documentRef));
2366
+ const isFullscreen = createSignal30(false);
2367
+ const update = () => {
2368
+ if (!documentRef) {
2369
+ isFullscreen(false);
2370
+ isSupported(false);
2371
+ return;
2372
+ }
2373
+ isSupported(isFullscreenSupported(documentRef));
2374
+ const target = resolveTargetElement(options, documentRef);
2375
+ const fullscreenElement = getFullscreenElement(documentRef);
2376
+ isFullscreen(!!target && !!fullscreenElement && fullscreenElement === target);
2377
+ };
2378
+ useEventListener(
2379
+ documentRef,
2380
+ ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange"],
2381
+ update,
2382
+ { passive: true }
2383
+ );
2384
+ createEffect13(() => {
2385
+ resolveTargetElement(options, documentRef);
2386
+ update();
2387
+ });
2388
+ const enter = async () => {
2389
+ if (!documentRef || !isSupported()) {
2390
+ return false;
2391
+ }
2392
+ const target = resolveTargetElement(options, documentRef);
2393
+ if (!target) {
2394
+ return false;
2395
+ }
2396
+ const request = resolveRequestMethod(target);
2397
+ if (!request) {
2398
+ return false;
2399
+ }
2400
+ try {
2401
+ await request.call(target);
2402
+ update();
2403
+ return true;
2404
+ } catch {
2405
+ update();
2406
+ return false;
2407
+ }
2408
+ };
2409
+ const exit = async () => {
2410
+ if (!documentRef || !isSupported()) {
2411
+ return false;
2412
+ }
2413
+ const exitMethod = resolveExitMethod(documentRef);
2414
+ if (!exitMethod) {
2415
+ return false;
2416
+ }
2417
+ try {
2418
+ await exitMethod.call(documentRef);
2419
+ update();
2420
+ return true;
2421
+ } catch {
2422
+ update();
2423
+ return false;
2424
+ }
2425
+ };
2426
+ const toggle = async () => {
2427
+ return isFullscreen() ? exit() : enter();
2428
+ };
2429
+ if (options.autoExit) {
2430
+ onDestroy14(() => {
2431
+ void exit();
2432
+ });
2433
+ }
2434
+ return {
2435
+ isSupported,
2436
+ isFullscreen,
2437
+ enter,
2438
+ exit,
2439
+ toggle
2440
+ };
2441
+ }
2442
+ export {
2443
+ useAsyncState,
2444
+ useClickOutside,
2445
+ useClipboard,
2446
+ useCounter,
2447
+ useDebounceFn,
2448
+ useDocumentVisibility,
2449
+ useEventListener,
2450
+ useFetch,
2451
+ useFocusWithin,
2452
+ useFullscreen,
2453
+ useGeolocation,
2454
+ useHover,
2455
+ useIdle,
2456
+ useIntersectionObserver,
2457
+ useIntervalFn,
2458
+ useKeyPress,
2459
+ useLocalStorage,
2460
+ useMediaQuery,
2461
+ useMount,
2462
+ useMutationObserver,
2463
+ useNetwork,
2464
+ usePermission,
2465
+ usePrevious,
2466
+ useRafFn,
2467
+ useRequest,
2468
+ useResizeObserver,
2469
+ useScroll,
2470
+ useSessionStorage,
2471
+ useSize,
2472
+ useStorage,
2473
+ useThrottleFn,
2474
+ useTimeoutFn,
2475
+ useTitle,
2476
+ useToggle,
2477
+ useUnmount,
2478
+ useVirtualList,
2479
+ useWebSocket,
2480
+ useWindowScroll,
2481
+ useWindowSize
2482
+ };
2483
+ //# sourceMappingURL=index.js.map