@stacksjs/composables 0.70.87 → 0.70.90

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.
Files changed (333) hide show
  1. package/dist/_shared.d.ts +49 -0
  2. package/dist/_shared.js +23 -0
  3. package/dist/cloneFnJSON.d.ts +5 -0
  4. package/dist/cloneFnJSON.js +3 -0
  5. package/dist/computedAsync.d.ts +28 -0
  6. package/dist/computedAsync.js +20 -0
  7. package/dist/computedEager.d.ts +27 -0
  8. package/dist/computedEager.js +10 -0
  9. package/dist/createEventHook.d.ts +16 -0
  10. package/dist/createEventHook.js +15 -0
  11. package/dist/createFetch.d.ts +14 -0
  12. package/dist/createFetch.js +8 -0
  13. package/dist/createGlobalState.d.ts +17 -0
  14. package/dist/createGlobalState.js +10 -0
  15. package/dist/createSharedComposable.d.ts +8 -0
  16. package/dist/createSharedComposable.js +9 -0
  17. package/dist/extendRef.d.ts +9 -0
  18. package/dist/extendRef.js +6 -0
  19. package/dist/formatTimeAgo.d.ts +23 -0
  20. package/dist/formatTimeAgo.js +33 -0
  21. package/dist/index.d.ts +271 -0
  22. package/dist/index.js +210 -2
  23. package/dist/isDefined.d.ts +5 -0
  24. package/dist/isDefined.js +3 -0
  25. package/dist/makeDestructurable.d.ts +18 -0
  26. package/dist/makeDestructurable.js +16 -0
  27. package/dist/onClickOutside.d.ts +14 -0
  28. package/dist/onClickOutside.js +25 -0
  29. package/dist/onKeyStroke.d.ts +26 -0
  30. package/dist/onKeyStroke.js +31 -0
  31. package/dist/onLongPress.d.ts +14 -0
  32. package/dist/onLongPress.js +43 -0
  33. package/dist/onStartTyping.d.ts +8 -0
  34. package/dist/onStartTyping.js +23 -0
  35. package/dist/refAutoReset.d.ts +11 -0
  36. package/dist/refAutoReset.js +15 -0
  37. package/dist/refDefault.d.ts +9 -0
  38. package/dist/refDefault.js +8 -0
  39. package/dist/resolveRef.d.ts +7 -0
  40. package/dist/resolveRef.js +7 -0
  41. package/dist/ssrHandlers.d.ts +13 -0
  42. package/dist/ssrHandlers.js +8 -0
  43. package/dist/syncRef.d.ts +21 -0
  44. package/dist/syncRef.js +35 -0
  45. package/dist/tryOnMounted.d.ts +22 -0
  46. package/dist/tryOnMounted.js +22 -0
  47. package/dist/unrefElement.d.ts +6 -0
  48. package/dist/unrefElement.js +9 -0
  49. package/dist/until.d.ts +34 -0
  50. package/dist/until.js +62 -0
  51. package/dist/useActiveElement.d.ts +6 -0
  52. package/dist/useActiveElement.js +19 -0
  53. package/dist/useAnimate.d.ts +18 -0
  54. package/dist/useAnimate.js +48 -0
  55. package/dist/useArrayUtils.d.ts +42 -0
  56. package/dist/useArrayUtils.js +114 -0
  57. package/dist/useAsyncQueue.d.ts +18 -0
  58. package/dist/useAsyncQueue.js +22 -0
  59. package/dist/useAsyncState.d.ts +23 -0
  60. package/dist/useAsyncState.js +35 -0
  61. package/dist/useBase64.d.ts +12 -0
  62. package/dist/useBase64.js +45 -0
  63. package/dist/useBattery.d.ts +15 -0
  64. package/dist/useBattery.js +38 -0
  65. package/dist/useBreakpoints.d.ts +96 -0
  66. package/dist/useBreakpoints.js +97 -0
  67. package/dist/useBroadcastChannel.d.ts +14 -0
  68. package/dist/useBroadcastChannel.js +31 -0
  69. package/dist/useBrowserLocation.d.ts +18 -0
  70. package/dist/useBrowserLocation.js +64 -0
  71. package/dist/useClipboard.d.ts +17 -0
  72. package/dist/useClipboard.js +32 -0
  73. package/dist/useClipboardItems.d.ts +11 -0
  74. package/dist/useClipboardItems.js +15 -0
  75. package/dist/useCloned.d.ts +15 -0
  76. package/dist/useCloned.js +16 -0
  77. package/dist/useColorMode.d.ts +18 -0
  78. package/dist/useColorMode.js +60 -0
  79. package/dist/useConfirmDialog.d.ts +14 -0
  80. package/dist/useConfirmDialog.js +34 -0
  81. package/dist/useCounter.d.ts +20 -0
  82. package/dist/useCounter.js +28 -0
  83. package/dist/useCssVar.d.ts +10 -0
  84. package/dist/useCssVar.js +23 -0
  85. package/dist/useCycleList.d.ts +18 -0
  86. package/dist/useCycleList.js +27 -0
  87. package/dist/useDark.d.ts +11 -0
  88. package/dist/useDark.js +38 -0
  89. package/dist/useDateFormat.d.ts +10 -0
  90. package/dist/useDateFormat.js +11 -0
  91. package/dist/useDebounce.d.ts +10 -0
  92. package/dist/useDebounce.js +16 -0
  93. package/dist/useDebounceFn.d.ts +10 -0
  94. package/dist/useDebounceFn.js +18 -0
  95. package/dist/useDebouncedRef.d.ts +11 -0
  96. package/dist/useDebouncedRef.js +22 -0
  97. package/dist/useDebouncedRefHistory.d.ts +5 -0
  98. package/dist/useDebouncedRefHistory.js +36 -0
  99. package/dist/useDeviceMotion.d.ts +27 -0
  100. package/dist/useDeviceMotion.js +53 -0
  101. package/dist/useDeviceOrientation.d.ts +15 -0
  102. package/dist/useDeviceOrientation.js +23 -0
  103. package/dist/useDevicePixelRatio.d.ts +5 -0
  104. package/dist/useDevicePixelRatio.js +24 -0
  105. package/dist/useDocumentVisibility.d.ts +5 -0
  106. package/dist/useDocumentVisibility.js +16 -0
  107. package/dist/useDraggable.d.ts +23 -0
  108. package/dist/useDraggable.js +58 -0
  109. package/dist/useDropZone.d.ts +15 -0
  110. package/dist/useDropZone.js +52 -0
  111. package/dist/useElementBounding.d.ts +21 -0
  112. package/dist/useElementBounding.js +59 -0
  113. package/dist/useElementByPoint.d.ts +10 -0
  114. package/dist/useElementByPoint.js +18 -0
  115. package/dist/useElementHover.d.ts +9 -0
  116. package/dist/useElementHover.js +28 -0
  117. package/dist/useElementSize.d.ts +19 -0
  118. package/dist/useElementSize.js +34 -0
  119. package/dist/useElementVisibility.d.ts +10 -0
  120. package/dist/useElementVisibility.js +10 -0
  121. package/dist/useEventBus.d.ts +14 -0
  122. package/dist/useEventBus.js +41 -0
  123. package/dist/useEventListener.d.ts +14 -0
  124. package/dist/useEventListener.js +29 -0
  125. package/dist/useEventSource.d.ts +32 -0
  126. package/dist/useEventSource.js +72 -0
  127. package/dist/useEyeDropper.d.ts +10 -0
  128. package/dist/useEyeDropper.js +17 -0
  129. package/dist/useFavicon.d.ts +8 -0
  130. package/dist/useFavicon.js +22 -0
  131. package/dist/useFetch.d.ts +35 -0
  132. package/dist/useFetch.js +61 -0
  133. package/dist/useFileDialog.d.ts +15 -0
  134. package/dist/useFileDialog.js +24 -0
  135. package/dist/useFocus.d.ts +13 -0
  136. package/dist/useFocus.js +42 -0
  137. package/dist/useFocusWithin.d.ts +9 -0
  138. package/dist/useFocusWithin.js +29 -0
  139. package/dist/useForm.d.ts +97 -0
  140. package/dist/useForm.js +182 -0
  141. package/dist/useFps.d.ts +7 -0
  142. package/dist/useFps.js +22 -0
  143. package/dist/useFullscreen.d.ts +16 -0
  144. package/dist/useFullscreen.js +67 -0
  145. package/dist/useGeolocation.d.ts +26 -0
  146. package/dist/useGeolocation.js +55 -0
  147. package/dist/useIdle.d.ts +18 -0
  148. package/dist/useIdle.js +46 -0
  149. package/dist/useImage.d.ts +18 -0
  150. package/dist/useImage.js +32 -0
  151. package/dist/useInfiniteScroll.d.ts +11 -0
  152. package/dist/useInfiniteScroll.js +50 -0
  153. package/dist/useIntersectionObserver.d.ts +16 -0
  154. package/dist/useIntersectionObserver.js +27 -0
  155. package/dist/useInterval.d.ts +13 -0
  156. package/dist/useInterval.js +32 -0
  157. package/dist/useIntervalFn.d.ts +16 -0
  158. package/dist/useIntervalFn.js +24 -0
  159. package/dist/useKeyModifier.d.ts +6 -0
  160. package/dist/useKeyModifier.js +22 -0
  161. package/dist/useLastChanged.d.ts +11 -0
  162. package/dist/useLastChanged.js +8 -0
  163. package/dist/useLocalStorage.d.ts +17 -0
  164. package/dist/useLocalStorage.js +4 -0
  165. package/dist/useMagicKeys.d.ts +16 -0
  166. package/dist/useMagicKeys.js +81 -0
  167. package/dist/useManualRefHistory.d.ts +27 -0
  168. package/dist/useManualRefHistory.js +64 -0
  169. package/dist/useMath.d.ts +17 -0
  170. package/dist/useMath.js +52 -0
  171. package/dist/useMediaQuery.d.ts +9 -0
  172. package/dist/useMediaQuery.js +19 -0
  173. package/dist/useMemoize.d.ts +13 -0
  174. package/dist/useMemoize.js +24 -0
  175. package/dist/useMounted.d.ts +6 -0
  176. package/dist/useMounted.js +8 -0
  177. package/dist/useMouse.d.ts +19 -0
  178. package/dist/useMouse.js +67 -0
  179. package/dist/useMouseInElement.d.ts +17 -0
  180. package/dist/useMouseInElement.js +31 -0
  181. package/dist/useMousePressed.d.ts +10 -0
  182. package/dist/useMousePressed.js +31 -0
  183. package/dist/useMutationObserver.d.ts +16 -0
  184. package/dist/useMutationObserver.js +27 -0
  185. package/dist/useNavigatorLanguage.d.ts +5 -0
  186. package/dist/useNavigatorLanguage.js +17 -0
  187. package/dist/useNetwork.d.ts +18 -0
  188. package/dist/useNetwork.js +58 -0
  189. package/dist/useNow.d.ts +8 -0
  190. package/dist/useNow.js +10 -0
  191. package/dist/useObjectUrl.d.ts +8 -0
  192. package/dist/useObjectUrl.js +23 -0
  193. package/dist/useOffsetPagination.d.ts +21 -0
  194. package/dist/useOffsetPagination.js +45 -0
  195. package/dist/useOnline.d.ts +7 -0
  196. package/dist/useOnline.js +20 -0
  197. package/dist/usePageLeave.d.ts +9 -0
  198. package/dist/usePageLeave.js +27 -0
  199. package/dist/useParallax.d.ts +15 -0
  200. package/dist/useParallax.js +33 -0
  201. package/dist/useParentElement.d.ts +6 -0
  202. package/dist/useParentElement.js +8 -0
  203. package/dist/usePermission.d.ts +7 -0
  204. package/dist/usePermission.js +21 -0
  205. package/dist/usePointer.d.ts +15 -0
  206. package/dist/usePointer.js +32 -0
  207. package/dist/usePointerLock.d.ts +13 -0
  208. package/dist/usePointerLock.js +33 -0
  209. package/dist/usePointerSwipe.d.ts +20 -0
  210. package/dist/usePointerSwipe.js +52 -0
  211. package/dist/usePreferredColorScheme.d.ts +6 -0
  212. package/dist/usePreferredColorScheme.js +25 -0
  213. package/dist/usePreferredContrast.d.ts +6 -0
  214. package/dist/usePreferredContrast.js +29 -0
  215. package/dist/usePreferredLanguages.d.ts +5 -0
  216. package/dist/usePreferredLanguages.js +16 -0
  217. package/dist/usePreferredReducedMotion.d.ts +6 -0
  218. package/dist/usePreferredReducedMotion.js +18 -0
  219. package/dist/usePrevious.d.ts +9 -0
  220. package/dist/usePrevious.js +8 -0
  221. package/dist/useQuery.d.ts +106 -0
  222. package/dist/useQuery.js +265 -0
  223. package/dist/useRafFn.d.ts +19 -0
  224. package/dist/useRafFn.js +37 -0
  225. package/dist/useRefHistory.d.ts +28 -0
  226. package/dist/useRefHistory.js +72 -0
  227. package/dist/useResizeObserver.d.ts +16 -0
  228. package/dist/useResizeObserver.js +27 -0
  229. package/dist/useScreenOrientation.d.ts +13 -0
  230. package/dist/useScreenOrientation.js +27 -0
  231. package/dist/useScreenSafeArea.d.ts +12 -0
  232. package/dist/useScreenSafeArea.js +25 -0
  233. package/dist/useScriptTag.d.ts +20 -0
  234. package/dist/useScriptTag.js +52 -0
  235. package/dist/useScroll.d.ts +36 -0
  236. package/dist/useScroll.js +116 -0
  237. package/dist/useScrollLock.d.ts +10 -0
  238. package/dist/useScrollLock.js +39 -0
  239. package/dist/useSessionStorage.d.ts +17 -0
  240. package/dist/useSessionStorage.js +4 -0
  241. package/dist/useShare.d.ts +15 -0
  242. package/dist/useShare.js +11 -0
  243. package/dist/useSorted.d.ts +10 -0
  244. package/dist/useSorted.js +10 -0
  245. package/dist/useStepper.d.ts +23 -0
  246. package/dist/useStepper.js +46 -0
  247. package/dist/useStorage.d.ts +18 -0
  248. package/dist/useStorage.js +54 -0
  249. package/dist/useStyleTag.d.ts +17 -0
  250. package/dist/useStyleTag.js +39 -0
  251. package/dist/useSupported.d.ts +8 -0
  252. package/dist/useSupported.js +10 -0
  253. package/dist/useSwipe.d.ts +20 -0
  254. package/dist/useSwipe.js +58 -0
  255. package/dist/useTextDirection.d.ts +9 -0
  256. package/dist/useTextDirection.js +16 -0
  257. package/dist/useTextSelection.d.ts +10 -0
  258. package/dist/useTextSelection.js +27 -0
  259. package/dist/useTextareaAutosize.d.ts +10 -0
  260. package/dist/useTextareaAutosize.js +20 -0
  261. package/dist/useThrottle.d.ts +9 -0
  262. package/dist/useThrottle.js +23 -0
  263. package/dist/useThrottleFn.d.ts +11 -0
  264. package/dist/useThrottleFn.js +36 -0
  265. package/dist/useThrottledRef.d.ts +11 -0
  266. package/dist/useThrottledRef.js +30 -0
  267. package/dist/useThrottledRefHistory.d.ts +5 -0
  268. package/dist/useThrottledRefHistory.js +43 -0
  269. package/dist/useTimeAgo.d.ts +16 -0
  270. package/dist/useTimeAgo.js +58 -0
  271. package/dist/useTimeout.d.ts +12 -0
  272. package/dist/useTimeout.js +32 -0
  273. package/dist/useTimeoutFn.d.ts +16 -0
  274. package/dist/useTimeoutFn.js +27 -0
  275. package/dist/useTimeoutPoll.d.ts +10 -0
  276. package/dist/useTimeoutPoll.js +33 -0
  277. package/dist/useTimestamp.d.ts +15 -0
  278. package/dist/useTimestamp.js +43 -0
  279. package/dist/useTitle.d.ts +10 -0
  280. package/dist/useTitle.js +12 -0
  281. package/dist/useToNumber.d.ts +11 -0
  282. package/dist/useToNumber.js +25 -0
  283. package/dist/useToString.d.ts +6 -0
  284. package/dist/useToString.js +10 -0
  285. package/dist/useToggle.d.ts +6 -0
  286. package/dist/useToggle.js +12 -0
  287. package/dist/useTransition.d.ts +28 -0
  288. package/dist/useTransition.js +89 -0
  289. package/dist/useUrlSearchParams.d.ts +18 -0
  290. package/dist/useUrlSearchParams.js +72 -0
  291. package/dist/useVibrate.d.ts +13 -0
  292. package/dist/useVibrate.js +14 -0
  293. package/dist/useVirtualList.d.ts +23 -0
  294. package/dist/useVirtualList.js +49 -0
  295. package/dist/useWakeLock.d.ts +12 -0
  296. package/dist/useWakeLock.js +32 -0
  297. package/dist/useWebNotification.d.ts +25 -0
  298. package/dist/useWebNotification.js +39 -0
  299. package/dist/useWebSocket.d.ts +38 -0
  300. package/dist/useWebSocket.js +103 -0
  301. package/dist/useWebWorker.d.ts +18 -0
  302. package/dist/useWebWorker.js +56 -0
  303. package/dist/useWindowFocus.d.ts +5 -0
  304. package/dist/useWindowFocus.js +20 -0
  305. package/dist/useWindowScroll.d.ts +9 -0
  306. package/dist/useWindowScroll.js +17 -0
  307. package/dist/useWindowSize.d.ts +15 -0
  308. package/dist/useWindowSize.js +28 -0
  309. package/dist/watchArray.d.ts +5 -0
  310. package/dist/watchArray.js +9 -0
  311. package/dist/watchAtMost.d.ts +5 -0
  312. package/dist/watchAtMost.js +12 -0
  313. package/dist/watchDebounced.d.ts +23 -0
  314. package/dist/watchDebounced.js +20 -0
  315. package/dist/watchDeep.d.ts +7 -0
  316. package/dist/watchDeep.js +13 -0
  317. package/dist/watchIgnorable.d.ts +12 -0
  318. package/dist/watchIgnorable.js +19 -0
  319. package/dist/watchImmediate.d.ts +5 -0
  320. package/dist/watchImmediate.js +7 -0
  321. package/dist/watchOnce.d.ts +19 -0
  322. package/dist/watchOnce.js +17 -0
  323. package/dist/watchPausable.d.ts +13 -0
  324. package/dist/watchPausable.js +23 -0
  325. package/dist/watchThrottled.d.ts +23 -0
  326. package/dist/watchThrottled.js +37 -0
  327. package/dist/watchTriggerable.d.ts +5 -0
  328. package/dist/watchTriggerable.js +10 -0
  329. package/dist/watchWithFilter.d.ts +7 -0
  330. package/dist/watchWithFilter.js +6 -0
  331. package/dist/whenever.d.ts +18 -0
  332. package/dist/whenever.js +7 -0
  333. package/package.json +2 -2
@@ -0,0 +1,34 @@
1
+ import { onUnmounted, ref } from "@stacksjs/stx";
2
+ import { unref, noop } from "./_shared";
3
+ export function useElementSize(target, options) {
4
+ const width = ref(options?.initialSize?.width ?? 0), height = ref(options?.initialSize?.height ?? 0), isSupported = typeof window < "u" && "ResizeObserver" in window;
5
+ let cleanup = noop;
6
+ if (isSupported) {
7
+ const el = unref(target);
8
+ if (el) {
9
+ const observer = new ResizeObserver((entries) => {
10
+ for (const entry of entries)
11
+ if (entry.contentBoxSize) {
12
+ const boxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize;
13
+ width.value = boxSize.inlineSize;
14
+ height.value = boxSize.blockSize;
15
+ } else {
16
+ width.value = entry.contentRect.width;
17
+ height.value = entry.contentRect.height;
18
+ }
19
+ });
20
+ observer.observe(el);
21
+ cleanup = () => {
22
+ observer.disconnect();
23
+ cleanup = noop;
24
+ };
25
+ }
26
+ }
27
+ const stop = () => {
28
+ cleanup();
29
+ };
30
+ try {
31
+ onUnmounted(stop);
32
+ } catch {}
33
+ return { width, height, stop };
34
+ }
@@ -0,0 +1,10 @@
1
+ import { type MaybeRef } from './_shared';
2
+ import type { Ref } from '@stacksjs/stx';
3
+ /**
4
+ * Reactive element visibility.
5
+ * Uses IntersectionObserver to track whether the target element is visible in the viewport.
6
+ *
7
+ * @param target - The element to observe (or a Ref to one)
8
+ * @returns A ref that is true when the element is visible
9
+ */
10
+ export declare function useElementVisibility(target: MaybeRef<HTMLElement | null>): Ref<boolean>;
@@ -0,0 +1,10 @@
1
+ import { ref } from "@stacksjs/stx";
2
+ import { useIntersectionObserver } from "./useIntersectionObserver";
3
+ export function useElementVisibility(target) {
4
+ const isVisible = ref(!1);
5
+ useIntersectionObserver(target, (entries) => {
6
+ for (const entry of entries)
7
+ isVisible.value = entry.isIntersecting;
8
+ }, { threshold: 0 });
9
+ return isVisible;
10
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A reactive event bus for cross-component communication.
3
+ *
4
+ * @param key - A unique key identifying the event bus channel
5
+ */
6
+ export declare function useEventBus<T = any>(key: string | symbol): UseEventBusReturn<T>;
7
+ export declare interface UseEventBusReturn<T = any> {
8
+ on: (listener: EventBusListener<T>) => () => void
9
+ once: (listener: EventBusListener<T>) => () => void
10
+ emit: (event: T) => void
11
+ off: (listener: EventBusListener<T>) => void
12
+ reset: () => void
13
+ }
14
+ export type EventBusListener<T = any> = (_event: T) => void;
@@ -0,0 +1,41 @@
1
+ import { onUnmounted } from "@stacksjs/stx";
2
+ const buses = new Map;
3
+ export function useEventBus(key) {
4
+ if (!buses.has(key))
5
+ buses.set(key, new Set);
6
+ const listeners = buses.get(key), localListeners = [];
7
+ function on(listener) {
8
+ listeners.add(listener);
9
+ localListeners.push(listener);
10
+ return () => off(listener);
11
+ }
12
+ function once(listener) {
13
+ const wrapper = (event) => {
14
+ listener(event);
15
+ off(wrapper);
16
+ };
17
+ return on(wrapper);
18
+ }
19
+ function emit(event) {
20
+ for (const listener of listeners)
21
+ listener(event);
22
+ }
23
+ function off(listener) {
24
+ listeners.delete(listener);
25
+ const idx = localListeners.indexOf(listener);
26
+ if (idx !== -1)
27
+ localListeners.splice(idx, 1);
28
+ }
29
+ function reset() {
30
+ listeners.clear();
31
+ localListeners.length = 0;
32
+ }
33
+ try {
34
+ onUnmounted(() => {
35
+ for (const listener of localListeners)
36
+ listeners.delete(listener);
37
+ localListeners.length = 0;
38
+ });
39
+ } catch {}
40
+ return { on, once, emit, off, reset };
41
+ }
@@ -0,0 +1,14 @@
1
+ import { type MaybeRef } from './_shared';
2
+ /**
3
+ * Auto-cleanup event listener.
4
+ * Registers an event listener on the target and automatically removes it on unmount.
5
+ * Supports reactive targets via MaybeRef.
6
+ *
7
+ * @param target - The event target (Window, Document, HTMLElement, or a Ref to one)
8
+ * @param event - The event name
9
+ * @param handler - The event handler
10
+ * @param options - AddEventListenerOptions
11
+ * @returns A cleanup function to manually remove the listener
12
+ */
13
+ export declare function useEventListener(target: MaybeRef<EventTarget | null | undefined> | EventTarget, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): () => void;
14
+ declare type EventTarget = Window | Document | HTMLElement | Element;
@@ -0,0 +1,29 @@
1
+ import { onUnmounted, watch } from "@stacksjs/stx";
2
+ import { isRef, unref, noop, defaultWindow } from "./_shared";
3
+ export function useEventListener(target, event, handler, options) {
4
+ let cleanup = noop;
5
+ const register = (el) => {
6
+ if (!el)
7
+ return noop;
8
+ el.addEventListener(event, handler, options);
9
+ return () => {
10
+ el.removeEventListener(event, handler, options);
11
+ };
12
+ };
13
+ if (isRef(target)) {
14
+ watch(target, (_newTarget, _oldTarget) => {
15
+ cleanup();
16
+ cleanup = register(unref(target));
17
+ });
18
+ cleanup = register(target.value);
19
+ } else
20
+ cleanup = register(target);
21
+ const stop = () => {
22
+ cleanup();
23
+ cleanup = noop;
24
+ };
25
+ try {
26
+ onUnmounted(stop);
27
+ } catch {}
28
+ return stop;
29
+ }
@@ -0,0 +1,32 @@
1
+ import type { MaybeRefOrGetter } from './_shared';
2
+ import type { Ref } from '@stacksjs/stx';
3
+ /**
4
+ * Reactive Server-Sent Events (EventSource) connection.
5
+ *
6
+ * @param url - The EventSource URL (can be a ref, getter, or raw string)
7
+ * @param events - Array of event names to listen for (in addition to 'message')
8
+ * @param options - Configuration options
9
+ * @returns Reactive EventSource state and control methods
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const { data, event, status, close } = useEventSource('/api/events', ['update', 'delete'], {
14
+ * withCredentials: true,
15
+ * })
16
+ * ```
17
+ */
18
+ export declare function useEventSource(url: MaybeRefOrGetter<string>, events?: string[], options?: UseEventSourceOptions): UseEventSourceReturn;
19
+ export declare interface UseEventSourceOptions {
20
+ withCredentials?: boolean
21
+ immediate?: boolean
22
+ }
23
+ export declare interface UseEventSourceReturn {
24
+ data: Ref<string | null>
25
+ event: Ref<string | null>
26
+ status: Ref<EventSourceStatus>
27
+ error: Ref<Event | null>
28
+ eventSource: Ref<EventSource | null>
29
+ close: () => void
30
+ open: () => void
31
+ }
32
+ export type EventSourceStatus = 'CONNECTING' | 'OPEN' | 'CLOSED';
@@ -0,0 +1,72 @@
1
+ import { onUnmounted, ref } from "@stacksjs/stx";
2
+ import { toValue } from "./_shared";
3
+ export function useEventSource(url, events = [], options = {}) {
4
+ const {
5
+ withCredentials = !1,
6
+ immediate = !0
7
+ } = options, data = ref(null), event = ref(null), status = ref("CLOSED"), error = ref(null), eventSourceRef = ref(null);
8
+ let listeners = [];
9
+ function close() {
10
+ const es = eventSourceRef.value;
11
+ if (es) {
12
+ for (const { eventName, handler } of listeners)
13
+ es.removeEventListener(eventName, handler);
14
+ listeners = [];
15
+ es.close();
16
+ eventSourceRef.value = null;
17
+ status.value = "CLOSED";
18
+ }
19
+ }
20
+ function open() {
21
+ if (typeof EventSource > "u")
22
+ return;
23
+ close();
24
+ const resolvedUrl = toValue(url);
25
+ status.value = "CONNECTING";
26
+ const es = new EventSource(resolvedUrl, { withCredentials });
27
+ eventSourceRef.value = es;
28
+ es.onopen = () => {
29
+ status.value = "OPEN";
30
+ error.value = null;
31
+ };
32
+ es.onerror = (e) => {
33
+ error.value = e;
34
+ if (es.readyState === EventSource.CLOSED)
35
+ status.value = "CLOSED";
36
+ else
37
+ status.value = "CONNECTING";
38
+ };
39
+ const messageHandler = (e) => {
40
+ const messageEvent = e;
41
+ data.value = messageEvent.data;
42
+ event.value = "message";
43
+ };
44
+ es.addEventListener("message", messageHandler);
45
+ listeners.push({ eventName: "message", handler: messageHandler });
46
+ for (const eventName of events) {
47
+ const handler = (e) => {
48
+ const messageEvent = e;
49
+ data.value = messageEvent.data;
50
+ event.value = eventName;
51
+ };
52
+ es.addEventListener(eventName, handler);
53
+ listeners.push({ eventName, handler });
54
+ }
55
+ }
56
+ if (immediate)
57
+ open();
58
+ try {
59
+ onUnmounted(() => {
60
+ close();
61
+ });
62
+ } catch {}
63
+ return {
64
+ data,
65
+ event,
66
+ status,
67
+ error,
68
+ eventSource: eventSourceRef,
69
+ close,
70
+ open
71
+ };
72
+ }
@@ -0,0 +1,10 @@
1
+ import type { Ref } from '@stacksjs/stx';
2
+ /**
3
+ * Reactive EyeDropper API.
4
+ */
5
+ export declare function useEyeDropper(): UseEyeDropperReturn;
6
+ export declare interface UseEyeDropperReturn {
7
+ isSupported: Ref<boolean>
8
+ sRGBHex: Ref<string>
9
+ open: () => Promise<{ sRGBHex: string } | undefined>
10
+ }
@@ -0,0 +1,17 @@
1
+ import { ref } from "@stacksjs/stx";
2
+ export function useEyeDropper() {
3
+ const isSupported = ref(typeof window < "u" && "EyeDropper" in window), sRGBHex = ref("");
4
+ async function open() {
5
+ if (!isSupported.value)
6
+ return;
7
+ const dropper = new window.EyeDropper;
8
+ try {
9
+ const result = await dropper.open();
10
+ sRGBHex.value = result.sRGBHex;
11
+ return result;
12
+ } catch {
13
+ return;
14
+ }
15
+ }
16
+ return { isSupported, sRGBHex, open };
17
+ }
@@ -0,0 +1,8 @@
1
+ import type { MaybeRefOrGetter } from './_shared';
2
+ import type { Ref } from '@stacksjs/stx';
3
+ /**
4
+ * Reactive favicon.
5
+ *
6
+ * @param newIcon - URL of the favicon or a reactive ref/getter
7
+ */
8
+ export declare function useFavicon(newIcon?: MaybeRefOrGetter<string | null | undefined>): Ref<string | null>;
@@ -0,0 +1,22 @@
1
+ import { ref, watch } from "@stacksjs/stx";
2
+ import { toValue } from "./_shared";
3
+ export function useFavicon(newIcon) {
4
+ const favicon = ref(toValue(newIcon ?? null));
5
+ function applyIcon(icon) {
6
+ if (typeof document > "u" || !icon)
7
+ return;
8
+ let link = document.querySelector('link[rel*="icon"]');
9
+ if (!link) {
10
+ link = document.createElement("link");
11
+ link.rel = "icon";
12
+ link.type = "image/x-icon";
13
+ document.head.appendChild(link);
14
+ }
15
+ link.href = icon;
16
+ }
17
+ watch(favicon, (val) => {
18
+ applyIcon(val);
19
+ });
20
+ applyIcon(favicon.value);
21
+ return favicon;
22
+ }
@@ -0,0 +1,35 @@
1
+ import type { Ref } from '@stacksjs/stx';
2
+ /**
3
+ * Chainable reactive fetch composable. Pass the expected success body
4
+ * shape as the type argument to get a typed `data.value`.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * interface Post { id: number, title: string }
9
+ * const { error, data } = await useFetch<Post[]>('/api/posts').get().json()
10
+ * // data: Ref<Post[] | null>
11
+ * const { data } = await useFetch<Post>('/api/posts').post(JSON.stringify(body)).json()
12
+ * const { error } = await useFetch('/api/posts/1').delete().json()
13
+ * ```
14
+ */
15
+ export declare function useFetch<T = unknown>(url: string): FetchBuilder<T>;
16
+ /**
17
+ * Result of a terminal `.json()` call. Generic over the success body
18
+ * `T` (stacksjs/stacks#1924) so `data.value` is typed instead of
19
+ * `any`. `error` stays `unknown` — it can be a parsed error body OR a
20
+ * thrown network error, so the caller should narrow before use.
21
+ */
22
+ export declare interface UseFetchResult<T = unknown> {
23
+ data: Ref<T | null>
24
+ error: Ref<unknown>
25
+ isFetching: Ref<boolean>
26
+ then: (resolve: (value: { data: Ref<T | null>, error: Ref<unknown> }) => void) => Promise<void>
27
+ }
28
+ export declare interface FetchBuilder<T = unknown> {
29
+ get: () => FetchBuilder<T>
30
+ post: (body?: string) => FetchBuilder<T>
31
+ patch: (body?: string) => FetchBuilder<T>
32
+ put: (body?: string) => FetchBuilder<T>
33
+ delete: () => FetchBuilder<T>
34
+ json: () => UseFetchResult<T>
35
+ }
@@ -0,0 +1,61 @@
1
+ import { ref } from "@stacksjs/stx";
2
+ export function useFetch(url) {
3
+ let method = "GET", body;
4
+ const builder = {
5
+ get() {
6
+ method = "GET";
7
+ body = void 0;
8
+ return builder;
9
+ },
10
+ post(b) {
11
+ method = "POST";
12
+ body = b;
13
+ return builder;
14
+ },
15
+ patch(b) {
16
+ method = "PATCH";
17
+ body = b;
18
+ return builder;
19
+ },
20
+ put(b) {
21
+ method = "PUT";
22
+ body = b;
23
+ return builder;
24
+ },
25
+ delete() {
26
+ method = "DELETE";
27
+ body = void 0;
28
+ return builder;
29
+ },
30
+ json() {
31
+ const data = ref(null), error = ref(null), isFetching = ref(!0), init = {
32
+ method,
33
+ headers: { Accept: "application/json" }
34
+ };
35
+ if (body && method !== "GET" && method !== "DELETE") {
36
+ init.headers = { ...init.headers, "Content-Type": "application/json" };
37
+ init.body = body;
38
+ }
39
+ const result = { data, error }, fetchPromise = fetch(url, init).then(async (response) => {
40
+ const json = await response.json();
41
+ if (response.ok)
42
+ data.value = json;
43
+ else
44
+ error.value = json;
45
+ }).catch((e) => {
46
+ error.value = e;
47
+ }).finally(() => {
48
+ isFetching.value = !1;
49
+ });
50
+ return {
51
+ data,
52
+ error,
53
+ isFetching,
54
+ then(resolve) {
55
+ return fetchPromise.then(() => resolve(result));
56
+ }
57
+ };
58
+ }
59
+ };
60
+ return builder;
61
+ }
@@ -0,0 +1,15 @@
1
+ import type { Ref } from '@stacksjs/stx';
2
+ /**
3
+ * Open a file selection dialog programmatically.
4
+ */
5
+ export declare function useFileDialog(options?: UseFileDialogOptions): UseFileDialogReturn;
6
+ export declare interface UseFileDialogOptions {
7
+ accept?: string
8
+ multiple?: boolean
9
+ capture?: string
10
+ }
11
+ export declare interface UseFileDialogReturn {
12
+ files: Ref<FileList | null>
13
+ open: (options?: UseFileDialogOptions) => void
14
+ reset: () => void
15
+ }
@@ -0,0 +1,24 @@
1
+ import { ref } from "@stacksjs/stx";
2
+ export function useFileDialog(options = {}) {
3
+ const files = ref(null);
4
+ function open(overrideOptions) {
5
+ if (typeof document > "u")
6
+ return;
7
+ const opts = { ...options, ...overrideOptions }, input = document.createElement("input");
8
+ input.type = "file";
9
+ if (opts.accept)
10
+ input.accept = opts.accept;
11
+ if (opts.multiple)
12
+ input.multiple = !0;
13
+ if (opts.capture)
14
+ input.setAttribute("capture", opts.capture);
15
+ input.onchange = () => {
16
+ files.value = input.files;
17
+ };
18
+ input.click();
19
+ }
20
+ function reset() {
21
+ files.value = null;
22
+ }
23
+ return { files, open, reset };
24
+ }
@@ -0,0 +1,13 @@
1
+ import type { MaybeRef } from './_shared';
2
+ import type { Ref } from '@stacksjs/stx';
3
+ /**
4
+ * Reactive element focus state.
5
+ * Setting `focused.value = true` will focus the element, and vice versa.
6
+ */
7
+ export declare function useFocus(target: MaybeRef<HTMLElement | null | undefined>, options?: UseFocusOptions): UseFocusReturn;
8
+ export declare interface UseFocusOptions {
9
+ initialValue?: boolean
10
+ }
11
+ export declare interface UseFocusReturn {
12
+ focused: Ref<boolean>
13
+ }
@@ -0,0 +1,42 @@
1
+ import { onUnmounted, ref, watch } from "@stacksjs/stx";
2
+ import { unref } from "./_shared";
3
+ export function useFocus(target, options = {}) {
4
+ const { initialValue = !1 } = options, focused = ref(initialValue);
5
+ let cleanup = () => {};
6
+ function setup(el) {
7
+ cleanup();
8
+ if (!el)
9
+ return;
10
+ const onFocus = () => {
11
+ focused.value = !0;
12
+ }, onBlur = () => {
13
+ focused.value = !1;
14
+ };
15
+ el.addEventListener("focus", onFocus, { passive: !0 });
16
+ el.addEventListener("blur", onBlur, { passive: !0 });
17
+ cleanup = () => {
18
+ el.removeEventListener("focus", onFocus);
19
+ el.removeEventListener("blur", onBlur);
20
+ cleanup = () => {};
21
+ };
22
+ if (document.activeElement === el)
23
+ focused.value = !0;
24
+ if (initialValue)
25
+ el.focus();
26
+ }
27
+ const el = unref(target);
28
+ setup(el);
29
+ watch(focused, (val) => {
30
+ const element = unref(target);
31
+ if (!element)
32
+ return;
33
+ if (val)
34
+ element.focus();
35
+ else
36
+ element.blur();
37
+ });
38
+ try {
39
+ onUnmounted(() => cleanup());
40
+ } catch {}
41
+ return { focused };
42
+ }
@@ -0,0 +1,9 @@
1
+ import type { MaybeRef } from './_shared';
2
+ import type { Ref } from '@stacksjs/stx';
3
+ /**
4
+ * Reactive state of whether focus is contained within the target element.
5
+ *
6
+ * @param target - The target element or a ref to one
7
+ * @returns focused - true when focus is within the element or its descendants
8
+ */
9
+ export declare function useFocusWithin(target: MaybeRef<HTMLElement | null | undefined>): { focused: Ref<boolean> };
@@ -0,0 +1,29 @@
1
+ import { onUnmounted, ref } from "@stacksjs/stx";
2
+ import { unref } from "./_shared";
3
+ export function useFocusWithin(target) {
4
+ const focused = ref(!1);
5
+ let cleanup = () => {};
6
+ function setup(el) {
7
+ cleanup();
8
+ if (!el)
9
+ return;
10
+ const onFocusIn = () => {
11
+ focused.value = !0;
12
+ }, onFocusOut = (e) => {
13
+ if (!el.contains(e.relatedTarget))
14
+ focused.value = !1;
15
+ };
16
+ el.addEventListener("focusin", onFocusIn, { passive: !0 });
17
+ el.addEventListener("focusout", onFocusOut);
18
+ cleanup = () => {
19
+ el.removeEventListener("focusin", onFocusIn);
20
+ el.removeEventListener("focusout", onFocusOut);
21
+ cleanup = () => {};
22
+ };
23
+ }
24
+ setup(unref(target));
25
+ try {
26
+ onUnmounted(() => cleanup());
27
+ } catch {}
28
+ return { focused };
29
+ }
@@ -0,0 +1,97 @@
1
+ import type { Ref } from '@stacksjs/stx';
2
+ /**
3
+ * Reactive form primitive with schema validation + submit orchestration.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const form = useForm({
8
+ * initialValues: { email: '', password: '' },
9
+ * schema: {
10
+ * email: schema.string().email().required(),
11
+ * password: schema.string().min(6).required(),
12
+ * },
13
+ * onSubmit: async (values) => {
14
+ * const r = await authStore.login(values)
15
+ * if (!r.ok) form.setError('form', r.error)
16
+ * },
17
+ * })
18
+ * ```
19
+ */
20
+ export declare function useForm<TValues extends Record<string, unknown>>(opts: UseFormOptions<TValues>): UseFormResult<TValues>;
21
+ /**
22
+ * useForm (stacksjs/stacks#1940 — Phase 1).
23
+ *
24
+ * Per-field state + schema validation + submit orchestration.
25
+ * Builds on `@stacksjs/validation`'s ts-validation surface — pass any
26
+ * field-keyed bag of `schema.string()...` etc. and the composable
27
+ * runs `.validate(value)` to surface the first message.
28
+ *
29
+ * Phase 1 deliberately ships the primitive only; the `<Field>` /
30
+ * `<SubmitButton>` / `<ErrorMessage>` companion components, field
31
+ * arrays, server-error auto-surfacing on 422, and progressive HTML
32
+ * fallback are scoped to Phases 2-5 of the issue.
33
+ */
34
+ /** Minimal Validator surface this composable depends on. */
35
+ export declare interface FormValidator {
36
+ validate: (value: unknown) => { valid: boolean, errors: Array<{ message: string }> | Record<string, Array<{ message: string }>> }
37
+ }
38
+ export declare interface UseFormOptions<TValues extends Record<string, unknown>> {
39
+ initialValues: TValues
40
+ schema?: Partial<Record<keyof TValues, FormValidator>>
41
+ onSubmit: (values: TValues) => Promise<void> | void
42
+ onError?: (error: Error | Record<string, string>) => void
43
+ validateOn?: ValidationMode
44
+ }
45
+ /**
46
+ * A ready-to-spread bag of input attributes + handlers for one field
47
+ * (stacksjs/stacks#1940 Phase 2). Encodes the a11y wiring a `<Field>`
48
+ * component would otherwise re-derive by hand: `aria-invalid` flips on a
49
+ * surfaced error, and `aria-describedby` points at the error element's
50
+ * stable id so screen readers announce the message.
51
+ */
52
+ export declare interface FieldInputProps<T> {
53
+ name: string
54
+ value: T
55
+ onInput: (eventOrValue: unknown) => void
56
+ onBlur: () => void
57
+ 'aria-invalid': boolean
58
+ 'aria-describedby': string | undefined
59
+ }
60
+ export declare interface FormFieldAccessor<T> {
61
+ value: T
62
+ error: string
63
+ touched: boolean
64
+ dirty: boolean
65
+ errorId: string
66
+ setValue: (next: T) => void
67
+ onBlur: () => void
68
+ validate: () => boolean
69
+ inputProps: () => FieldInputProps<T>
70
+ }
71
+ export declare interface UseFormResult<TValues extends Record<string, unknown>> {
72
+ values: Ref<TValues>
73
+ errors: Ref<Record<string, string>>
74
+ touched: Ref<Record<string, boolean>>
75
+ dirty: Ref<Record<string, boolean>>
76
+ isSubmitting: Ref<boolean>
77
+ isValid: Ref<boolean>
78
+ isDirty: Ref<boolean>
79
+ field: <K extends keyof TValues>(name: K) => FormFieldAccessor<TValues[K]>
80
+ setValue: <K extends keyof TValues>(name: K, value: TValues[K]) => void
81
+ setError: (name: keyof TValues | 'form', message: string) => void
82
+ setErrors: (errorMap: Partial<Record<keyof TValues | 'form', string | string[]>>) => void
83
+ clearErrors: (name?: keyof TValues | 'form') => void
84
+ setTouched: (name: keyof TValues, touched?: boolean) => void
85
+ validate: () => boolean
86
+ handleSubmit: () => Promise<void>
87
+ reset: () => void
88
+ submitButtonProps: () => { type: 'submit', disabled: boolean, 'aria-busy': boolean }
89
+ firstErrorField: () => keyof TValues | null
90
+ }
91
+ /**
92
+ * Trigger that causes a field to validate.
93
+ * - 'change' — every time the value changes (eager — best for password strength meters)
94
+ * - 'blur' — when the user moves focus away from the field (default — most natural)
95
+ * - 'submit' — only at submit time (lazy — best for short forms with required-only checks)
96
+ */
97
+ export type ValidationMode = 'change' | 'blur' | 'submit';