@nikala-ui/core 0.9.1 → 0.9.3

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 (44) hide show
  1. package/package.json +1 -1
  2. package/registry/create-active-element.json +13 -0
  3. package/registry/create-audio.json +7 -0
  4. package/registry/create-battery.json +13 -0
  5. package/registry/create-click-outside.json +13 -0
  6. package/registry/create-clipboard.json +13 -0
  7. package/registry/create-color-mode.json +13 -0
  8. package/registry/create-controllable-signal.json +13 -0
  9. package/registry/create-debounce.json +13 -0
  10. package/registry/create-disclosure.json +13 -0
  11. package/registry/create-document-title.json +13 -0
  12. package/registry/create-event-source.json +13 -0
  13. package/registry/create-favicon.json +13 -0
  14. package/registry/create-fetch.json +13 -0
  15. package/registry/create-focus-trap.json +13 -0
  16. package/registry/create-form.json +13 -0
  17. package/registry/create-fullscreen.json +13 -0
  18. package/registry/create-geolocation.json +13 -0
  19. package/registry/create-hover.json +13 -0
  20. package/registry/create-idle.json +13 -0
  21. package/registry/create-infinite-scroll.json +13 -0
  22. package/registry/create-input-mask.json +13 -0
  23. package/registry/create-intersection-observer.json +13 -0
  24. package/registry/create-keybindings.json +13 -0
  25. package/registry/create-lock-scroll.json +13 -0
  26. package/registry/create-long-press.json +13 -0
  27. package/registry/create-media-query.json +13 -0
  28. package/registry/create-mouse-position.json +13 -0
  29. package/registry/create-network-status.json +13 -0
  30. package/registry/create-orientation.json +13 -0
  31. package/registry/create-permission.json +13 -0
  32. package/registry/create-previous.json +13 -0
  33. package/registry/create-resize-observer.json +13 -0
  34. package/registry/create-scroll-into-view.json +13 -0
  35. package/registry/create-scroll-position.json +13 -0
  36. package/registry/create-storage.json +13 -0
  37. package/registry/create-timer.json +13 -0
  38. package/registry/create-undo-redo.json +13 -0
  39. package/registry/create-web-notification.json +13 -0
  40. package/registry/create-websocket.json +13 -0
  41. package/registry/create-window-size.json +13 -0
  42. package/registry/index.json +240 -0
  43. package/src/registry/index.ts +2 -2
  44. package/src/registry/metadata.ts +166 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikala-ui/core",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Core component definitions, design tokens, and registry for Nikala UI",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-active-element",
3
+ "title": "createActiveElement",
4
+ "description": "SolidJS reactive primitive for tracking the currently focused DOM element",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-active-element.ts",
9
+ "content": "import { createSignal, onMount, onCleanup, type Accessor } from \"solid-js\";\nimport { isServer } from \"solid-js/web\";\n\nexport interface CreateActiveElementReturn {\n /** Accessor returning the currently focused DOM element, or null */\n activeElement: Accessor<Element | null>;\n /** Accessor returning true if any element (non-body) is focused */\n hasFocus: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for tracking the currently focused (active) DOM element.\n * Listens to focus/blur events on the document and reactively updates `activeElement` and `hasFocus`.\n */\nexport function createActiveElement(): CreateActiveElementReturn {\n // SSR: return static defaults\n if (isServer) {\n const [activeElement] = createSignal<Element | null>(null);\n const [hasFocus] = createSignal(false);\n return { activeElement, hasFocus };\n }\n\n const [activeElement, setActiveElement] = createSignal<Element | null>(null);\n\n const hasFocus = () => {\n const el = activeElement();\n return el !== null && el !== document.body;\n };\n\n const handleFocusChange = () => {\n setActiveElement(document.activeElement);\n };\n\n onMount(() => {\n // Set initial value\n setActiveElement(document.activeElement);\n\n document.addEventListener(\"focusin\", handleFocusChange, true);\n document.addEventListener(\"focusout\", handleFocusChange, true);\n\n onCleanup(() => {\n document.removeEventListener(\"focusin\", handleFocusChange, true);\n document.removeEventListener(\"focusout\", handleFocusChange, true);\n });\n });\n\n return {\n activeElement,\n hasFocus,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "create-audio",
3
+ "title": "createAudio & createVideo",
4
+ "description": "SolidJS reactive primitives for controlling HTML audio and video playback, duration, volume, and seeking",
5
+ "type": "registry:hook",
6
+ "files": []
7
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-battery",
3
+ "title": "createBattery",
4
+ "description": "SolidJS reactive primitive for observing device battery status, charge level, and charging metrics",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-battery.ts",
9
+ "content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface BatteryState {\n /** Battery charge level ratio from 0.0 (empty) to 1.0 (full). */\n level: number;\n /** Whether the device battery is currently charging. */\n charging: boolean;\n /** Seconds remaining until fully charged (0 if already full or unknown). */\n chargingTime: number;\n /** Seconds remaining until fully discharged. */\n dischargingTime: number;\n}\n\nexport interface CreateBatteryReturn {\n /** Signal accessor containing battery state metrics. */\n battery: Accessor<BatteryState>;\n /** Signal accessor indicating whether Battery Status API is supported in browser environment. */\n isSupported: Accessor<boolean>;\n}\n\nconst initialBatteryState: BatteryState = {\n level: 1,\n charging: true,\n chargingTime: 0,\n dischargingTime: Infinity,\n};\n\n/**\n * SolidJS reactive primitive for observing device battery status, charge level, and charging metrics.\n */\nexport function createBattery(): CreateBatteryReturn {\n const [battery, setBattery] = createSignal<BatteryState>(initialBatteryState);\n\n const isSupported = (): boolean =>\n typeof window !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n \"getBattery\" in navigator;\n\n let batteryManager: any = null;\n\n const updateBatteryStatus = (): void => {\n if (!batteryManager) return;\n setBattery({\n level: batteryManager.level ?? 1,\n charging: Boolean(batteryManager.charging),\n chargingTime: batteryManager.chargingTime ?? 0,\n dischargingTime: batteryManager.dischargingTime ?? Infinity,\n });\n };\n\n createEffect(() => {\n if (!isSupported()) return;\n\n (navigator as any).getBattery().then((manager: any) => {\n batteryManager = manager;\n updateBatteryStatus();\n\n manager.addEventListener(\"levelchange\", updateBatteryStatus);\n manager.addEventListener(\"chargingchange\", updateBatteryStatus);\n manager.addEventListener(\"chargingtimechange\", updateBatteryStatus);\n manager.addEventListener(\"dischargingtimechange\", updateBatteryStatus);\n }).catch(() => {});\n\n onCleanup(() => {\n if (batteryManager) {\n batteryManager.removeEventListener(\"levelchange\", updateBatteryStatus);\n batteryManager.removeEventListener(\"chargingchange\", updateBatteryStatus);\n batteryManager.removeEventListener(\"chargingtimechange\", updateBatteryStatus);\n batteryManager.removeEventListener(\"dischargingtimechange\", updateBatteryStatus);\n }\n });\n });\n\n return {\n battery,\n isSupported,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-click-outside",
3
+ "title": "createClickOutside",
4
+ "description": "SolidJS reactive primitive for detecting click and pointer interactions outside target elements",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-click-outside.ts",
9
+ "content": "import { onMount, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateClickOutsideOptions {\n /** Target HTML element or accessor returning the container element */\n target:\n | HTMLElement\n | Accessor<HTMLElement | undefined>\n | (HTMLElement | Accessor<HTMLElement | undefined>)[];\n /** Callback fired when a click/pointer event occurs outside the target element(s) */\n onInteractOutside: (event: MouseEvent | PointerEvent | TouchEvent) => void;\n /** Whether the listener is active. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n /** Optional element or accessor to ignore when checking outside clicks (e.g. trigger button) */\n ignore?:\n | HTMLElement\n | Accessor<HTMLElement | undefined>\n | (HTMLElement | Accessor<HTMLElement | undefined>)[];\n}\n\n/**\n * SolidJS reactive primitive for detecting user interactions outside specified element(s).\n *\n * @param options Configuration options including target element(s), callback, and optional ignore elements.\n */\nexport function createClickOutside(options: CreateClickOutsideOptions): void {\n const isEnabled = () => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n const getElement = (\n el: HTMLElement | Accessor<HTMLElement | undefined> | undefined\n ): HTMLElement | undefined => {\n if (!el) return undefined;\n if (typeof el === \"function\") {\n return (el as Accessor<HTMLElement | undefined>)();\n }\n return el;\n };\n\n const getElements = (\n targets:\n | HTMLElement\n | Accessor<HTMLElement | undefined>\n | (HTMLElement | Accessor<HTMLElement | undefined>)[]\n | undefined\n ): HTMLElement[] => {\n if (!targets) return [];\n const list = Array.isArray(targets) ? targets : [targets];\n return list.map(getElement).filter((e): e is HTMLElement => e !== undefined);\n };\n\n const handlePointerDown = (event: MouseEvent | PointerEvent | TouchEvent) => {\n if (!isEnabled()) return;\n\n const targetNode = event.target as Node | null;\n if (!targetNode) return;\n\n const mainElements = getElements(options.target);\n if (mainElements.length === 0) return;\n\n const isInsideTarget = mainElements.some((el) => el.contains(targetNode));\n if (isInsideTarget) return;\n\n const ignoreElements = getElements(options.ignore);\n const isInsideIgnore = ignoreElements.some((el) => el.contains(targetNode));\n if (isInsideIgnore) return;\n\n options.onInteractOutside(event);\n };\n\n onMount(() => {\n if (typeof window === \"undefined\") return;\n\n window.addEventListener(\"pointerdown\", handlePointerDown, true);\n onCleanup(() => {\n window.removeEventListener(\"pointerdown\", handlePointerDown, true);\n });\n });\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-clipboard",
3
+ "title": "createClipboard",
4
+ "description": "SolidJS reactive primitive for copying text to clipboard with automatic status reset",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-clipboard.ts",
9
+ "content": "import { createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateClipboardOptions {\n /** Time in milliseconds to maintain the copied state before resetting. Defaults to 2000ms. */\n timeout?: number;\n}\n\nexport interface CreateClipboardReturn {\n /** Signal accessor indicating if content was recently copied */\n copied: Accessor<boolean>;\n /** Function to copy text to clipboard */\n copy: (text: string) => Promise<boolean>;\n /** Error state if clipboard write fails */\n error: Accessor<Error | undefined>;\n}\n\n/**\n * SolidJS reactive primitive for copying text to clipboard with automatic reset state.\n *\n * @param options Configuration options including copied status reset timeout duration.\n */\nexport function createClipboard(options: CreateClipboardOptions = {}): CreateClipboardReturn {\n const timeoutDuration = options.timeout ?? 2000;\n const [copied, setCopied] = createSignal(false);\n const [error, setError] = createSignal<Error | undefined>(undefined);\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const clearTimer = () => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const copy = async (text: string): Promise<boolean> => {\n clearTimer();\n setError(undefined);\n\n try {\n if (typeof window !== \"undefined\" && navigator.clipboard && navigator.clipboard.writeText) {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n\n timer = setTimeout(() => {\n setCopied(false);\n }, timeoutDuration);\n\n return true;\n }\n\n throw new Error(\"Clipboard API not supported\");\n } catch (err) {\n const copyError = err instanceof Error ? err : new Error(String(err));\n setError(copyError);\n setCopied(false);\n return false;\n }\n };\n\n onCleanup(() => {\n clearTimer();\n });\n\n return {\n copied,\n copy,\n error,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-color-mode",
3
+ "title": "createColorMode",
4
+ "description": "SolidJS reactive primitive for managing dark/light themes and system preferences",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-color-mode.ts",
9
+ "content": "import { createEffect, createSignal, onCleanup, onMount, type Accessor } from \"solid-js\";\n\nexport type ColorMode = \"light\" | \"dark\" | \"system\";\n\nexport interface CreateColorModeOptions {\n /** Initial color mode fallback. Defaults to 'system'. */\n initialValue?: ColorMode;\n /** LocalStorage key for persisting color mode state. Defaults to 'nikala-color-mode'. */\n storageKey?: string;\n /** HTML attribute to apply dark mode class on document.documentElement. Defaults to 'class'. */\n attribute?: string;\n}\n\nexport interface CreateColorModeReturn {\n /** Accessor for current active color mode ('light', 'dark', 'system') */\n mode: Accessor<ColorMode>;\n /** Function to update current color mode */\n setMode: (mode: ColorMode) => void;\n /** Function to toggle color mode between 'light' and 'dark' */\n toggleColorMode: () => void;\n /** Accessor indicating if current resolved mode is dark */\n isDark: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for managing dark/light color mode themes and system preferences.\n *\n * @param options Configuration options including storage key and initial mode.\n */\nexport function createColorMode(\n options: CreateColorModeOptions = {}\n): CreateColorModeReturn {\n const initialMode = options.initialValue ?? \"system\";\n const storageKey = options.storageKey ?? \"nikala-color-mode\";\n const attribute = options.attribute ?? \"class\";\n\n const [mode, setModeSignal] = createSignal<ColorMode>(initialMode);\n const [systemDark, setSystemDark] = createSignal(false);\n\n const isDark = () => {\n const currentMode = mode();\n if (currentMode === \"system\") return systemDark();\n return currentMode === \"dark\";\n };\n\n const applyTheme = (dark: boolean) => {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n if (attribute === \"class\") {\n if (dark) root.classList.add(\"dark\");\n else root.classList.remove(\"dark\");\n } else {\n root.setAttribute(attribute, dark ? \"dark\" : \"light\");\n }\n };\n\n const setMode = (newMode: ColorMode) => {\n setModeSignal(newMode);\n if (typeof window !== \"undefined\") {\n try {\n localStorage.setItem(storageKey, newMode);\n } catch (e) {\n console.warn(`[nikala-ui/hooks] Error setting color mode storage:`, e);\n }\n }\n };\n\n const toggleColorMode = () => {\n const next = isDark() ? \"light\" : \"dark\";\n setMode(next);\n };\n\n createEffect(() => {\n applyTheme(isDark());\n });\n\n onMount(() => {\n if (typeof window === \"undefined\") return;\n\n // Check system preference\n const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\");\n setSystemDark(mediaQuery.matches);\n\n const handleMediaChange = (e: MediaQueryListEvent) => {\n setSystemDark(e.matches);\n };\n\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener(\"change\", handleMediaChange);\n }\n\n // Check localStorage\n try {\n const stored = localStorage.getItem(storageKey) as ColorMode | null;\n if (stored && [\"light\", \"dark\", \"system\"].includes(stored)) {\n setModeSignal(stored);\n }\n } catch (e) {\n console.warn(`[nikala-ui/hooks] Error reading color mode storage:`, e);\n }\n\n onCleanup(() => {\n if (mediaQuery.removeEventListener) {\n mediaQuery.removeEventListener(\"change\", handleMediaChange);\n }\n });\n });\n\n return {\n mode,\n setMode,\n toggleColorMode,\n isDark,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-controllable-signal",
3
+ "title": "createControllableSignal",
4
+ "description": "SolidJS reactive primitive supporting both controlled and uncontrolled state management",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-controllable-signal.ts",
9
+ "content": "import { createSignal, type Accessor } from \"solid-js\";\n\nexport interface CreateControllableSignalOptions<T> {\n /** Controlled value accessor or raw value */\n value?: Accessor<T | undefined> | T;\n /** Uncontrolled default initial value */\n defaultValue?: T;\n /** Callback fired whenever the value changes */\n onChange?: (value: T) => void;\n}\n\n/**\n * SolidJS reactive primitive for managing state supporting both controlled and uncontrolled modes.\n *\n * @param options Configuration options for value, defaultValue, and onChange callback.\n * @returns A tuple containing [valueAccessor, setValueFunction].\n */\nexport function createControllableSignal<T>(\n options: CreateControllableSignalOptions<T>\n): [Accessor<T | undefined>, (nextValue: T | ((prev: T | undefined) => T)) => void] {\n const [internalValue, setInternalValue] = createSignal<T | undefined>(\n options.defaultValue\n );\n\n const getValue = (): T | undefined => {\n if (typeof options.value === \"function\") {\n return (options.value as Accessor<T | undefined>)();\n }\n return options.value;\n };\n\n const isControlled = () => getValue() !== undefined;\n\n const value = () => {\n const controlledVal = getValue();\n return controlledVal !== undefined ? controlledVal : internalValue();\n };\n\n const setValue = (nextValue: T | ((prev: T | undefined) => T)) => {\n const current = value();\n const resolvedNext =\n typeof nextValue === \"function\"\n ? (nextValue as (prev: T | undefined) => T)(current)\n : nextValue;\n\n if (!isControlled()) {\n setInternalValue(resolvedNext as any);\n }\n\n if (typeof options.onChange === \"function\") {\n options.onChange(resolvedNext);\n }\n };\n\n return [value, setValue];\n}",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-debounce",
3
+ "title": "createDebounce",
4
+ "description": "SolidJS reactive primitives for debouncing and throttling rate-limited function execution",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-debounce.ts",
9
+ "content": "import { onCleanup, type Accessor } from \"solid-js\";\n\nexport interface DebounceThrottleReturn<T extends (...args: any[]) => any> {\n (...args: Parameters<T>): void;\n clear: () => void;\n}\n\n/**\n * SolidJS reactive primitive for debouncing function execution.\n *\n * @param fn Function to debounce.\n * @param delay Delay in milliseconds (number or accessor).\n */\nexport function createDebounce<T extends (...args: any[]) => any>(\n fn: T,\n delay: number | Accessor<number>\n): DebounceThrottleReturn<T> {\n const getDelay = (): number => (typeof delay === \"function\" ? delay() : delay);\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const clear = () => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const debounced = (...args: Parameters<T>) => {\n clear();\n timer = setTimeout(() => {\n fn(...args);\n }, getDelay());\n };\n\n debounced.clear = clear;\n\n onCleanup(() => {\n clear();\n });\n\n return debounced;\n}\n\n/**\n * SolidJS reactive primitive for throttling function execution.\n *\n * @param fn Function to throttle.\n * @param delay Interval in milliseconds (number or accessor).\n */\nexport function createThrottle<T extends (...args: any[]) => any>(\n fn: T,\n delay: number | Accessor<number>\n): DebounceThrottleReturn<T> {\n const getDelay = (): number => (typeof delay === \"function\" ? delay() : delay);\n let lastCall = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const clear = () => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const throttled = (...args: Parameters<T>) => {\n const now = Date.now();\n const interval = getDelay();\n const remaining = interval - (now - lastCall);\n\n if (remaining <= 0) {\n clear();\n lastCall = now;\n fn(...args);\n } else if (!timer) {\n timer = setTimeout(() => {\n lastCall = Date.now();\n timer = undefined;\n fn(...args);\n }, remaining);\n }\n };\n\n throttled.clear = clear;\n\n onCleanup(() => {\n clear();\n });\n\n return throttled;\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-disclosure",
3
+ "title": "createDisclosure",
4
+ "description": "SolidJS reactive primitive for managing boolean open/close disclosure state with helper controls",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-disclosure.ts",
9
+ "content": "import { createSignal, type Accessor } from \"solid-js\";\n\nexport interface CreateDisclosureOptions {\n /** Uncontrolled initial open state. Defaults to false. */\n defaultIsOpen?: boolean;\n /** Controlled open state accessor */\n isOpen?: boolean | Accessor<boolean | undefined>;\n /** Callback triggered when state transitions to open */\n onOpen?: () => void;\n /** Callback triggered when state transitions to closed */\n onClose?: () => void;\n /** Callback triggered whenever open state changes */\n onChange?: (isOpen: boolean) => void;\n}\n\nexport interface CreateDisclosureReturn {\n /** Signal accessor indicating if disclosure is open */\n isOpen: Accessor<boolean>;\n /** Function to open the disclosure */\n open: () => void;\n /** Function to close the disclosure */\n close: () => void;\n /** Function to toggle the disclosure open state */\n toggle: () => void;\n /** Setter function to programmatically set open state */\n setOpen: (open: boolean) => void;\n}\n\n/**\n * SolidJS reactive primitive for managing boolean disclosure (open/close) state with controlled and uncontrolled support.\n *\n * @param options Configuration options including initial state and callbacks.\n */\nexport function createDisclosure(options: CreateDisclosureOptions = {}): CreateDisclosureReturn {\n const [internalOpen, setInternalOpen] = createSignal<boolean>(\n options.defaultIsOpen ?? false\n );\n\n const isControlled = () => {\n if (typeof options.isOpen === \"function\") {\n return options.isOpen() !== undefined;\n }\n return options.isOpen !== undefined;\n };\n\n const isOpen = (): boolean => {\n if (typeof options.isOpen === \"function\") {\n const val = options.isOpen();\n return val !== undefined ? val : internalOpen();\n }\n return options.isOpen !== undefined ? options.isOpen : internalOpen();\n };\n\n const setOpenState = (nextState: boolean) => {\n const currentState = isOpen();\n if (currentState === nextState) return;\n\n if (!isControlled()) {\n setInternalOpen(nextState);\n }\n\n options.onChange?.(nextState);\n\n if (nextState) {\n options.onOpen?.();\n } else {\n options.onClose?.();\n }\n };\n\n const open = () => setOpenState(true);\n const close = () => setOpenState(false);\n const toggle = () => setOpenState(!isOpen());\n\n return {\n isOpen,\n open,\n close,\n toggle,\n setOpen: setOpenState,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-document-title",
3
+ "title": "createDocumentTitle",
4
+ "description": "SolidJS reactive primitive for managing document title dynamically",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-document-title.ts",
9
+ "content": "import { createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateDocumentTitleOptions {\n /** Whether to restore original title on component unmount. Defaults to true. */\n restoreOnUnmount?: boolean;\n}\n\n/**\n * SolidJS reactive primitive for managing document title dynamically.\n */\nexport function createDocumentTitle(\n title: string | Accessor<string>,\n options: CreateDocumentTitleOptions = {}\n): void {\n const getTitle = (): string => (typeof title === \"function\" ? title() : title);\n\n createEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const originalTitle = document.title;\n const newTitle = getTitle();\n\n if (newTitle) {\n document.title = newTitle;\n }\n\n onCleanup(() => {\n if (typeof document !== \"undefined\" && (options.restoreOnUnmount ?? true)) {\n document.title = originalTitle;\n }\n });\n });\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-event-source",
3
+ "title": "createEventSource",
4
+ "description": "SolidJS reactive primitive for subscribing to Server-Sent Events (SSE) streams",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-event-source.ts",
9
+ "content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport type EventSourceStatus = \"CONNECTING\" | \"OPEN\" | \"CLOSED\";\n\nexport interface CreateEventSourceOptions {\n /** Event names to listen to on the EventSource. Defaults to ['message']. */\n events?: string[];\n /** Include credentials in CORS requests. */\n withCredentials?: boolean;\n /** Whether to open connection immediately. Defaults to true. */\n immediate?: boolean;\n /** Callback fired when connection is opened. */\n onOpen?: (event: Event) => void;\n /** Callback fired when message event is received. */\n onMessage?: (event: MessageEvent) => void;\n /** Callback fired when error occurs. */\n onError?: (event: Event) => void;\n}\n\nexport interface CreateEventSourceReturn<T = unknown> {\n /** Signal accessor containing latest received SSE event data (parsed if JSON). */\n data: Accessor<T | null>;\n /** Signal accessor containing current EventSource status. */\n status: Accessor<EventSourceStatus>;\n /** Signal accessor containing last raw MessageEvent. */\n event: Accessor<MessageEvent | null>;\n /** Open or reconnect EventSource stream. */\n open: () => void;\n /** Close active EventSource stream. */\n close: () => void;\n /** Signal accessor indicating whether EventSource is supported in browser environment. */\n isSupported: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for subscribing to Server-Sent Events (SSE) streams.\n */\nexport function createEventSource<T = unknown>(\n url: string | Accessor<string>,\n options: CreateEventSourceOptions = {}\n): CreateEventSourceReturn<T> {\n const [data, setData] = createSignal<T | null>(null);\n const [event, setEvent] = createSignal<MessageEvent | null>(null);\n const [status, setStatus] = createSignal<EventSourceStatus>(\"CLOSED\");\n\n const getUrl = (): string => (typeof url === \"function\" ? url() : url);\n\n const isSupported = (): boolean =>\n typeof window !== \"undefined\" && \"EventSource\" in window;\n\n let es: EventSource | null = null;\n\n const close = (): void => {\n if (es) {\n es.close();\n es = null;\n setStatus(\"CLOSED\");\n }\n };\n\n const open = (): void => {\n if (!isSupported()) return;\n\n close();\n\n setStatus(\"CONNECTING\");\n\n try {\n const source = new EventSource(getUrl(), {\n withCredentials: options.withCredentials,\n });\n es = source;\n\n source.onopen = (e) => {\n setStatus(\"OPEN\");\n options.onOpen?.(e);\n };\n\n source.onerror = (e) => {\n if (source.readyState === EventSource.CLOSED) {\n setStatus(\"CLOSED\");\n } else if (source.readyState === EventSource.CONNECTING) {\n setStatus(\"CONNECTING\");\n }\n options.onError?.(e);\n };\n\n const eventList = options.events ?? [\"message\"];\n eventList.forEach((eventName) => {\n source.addEventListener(eventName, (e) => {\n const msgEvt = e as MessageEvent;\n setEvent(() => msgEvt);\n try {\n const parsed = JSON.parse(msgEvt.data);\n setData(() => parsed);\n } catch {\n setData(() => msgEvt.data as unknown as T);\n }\n options.onMessage?.(msgEvt);\n });\n });\n } catch {\n setStatus(\"CLOSED\");\n es = null;\n }\n };\n\n createEffect(() => {\n if (options.immediate ?? true) {\n open();\n }\n\n onCleanup(() => {\n close();\n });\n });\n\n return {\n data,\n status,\n event,\n open,\n close,\n isSupported,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-favicon",
3
+ "title": "createFavicon",
4
+ "description": "SolidJS reactive primitive for dynamically updating browser favicon element",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-favicon.ts",
9
+ "content": "import { createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateFaviconOptions {\n /** Favicon rel attribute value. Defaults to 'icon'. */\n rel?: string;\n /** Favicon image mime-type format (e.g., 'image/x-icon', 'image/svg+xml', 'image/png'). */\n type?: string;\n /** Whether to restore original favicon on component unmount. Defaults to true. */\n restoreOnUnmount?: boolean;\n}\n\n/**\n * SolidJS reactive primitive for dynamically updating browser favicon element.\n */\nexport function createFavicon(\n href: string | Accessor<string>,\n options: CreateFaviconOptions = {}\n): void {\n const getHref = (): string => (typeof href === \"function\" ? href() : href);\n\n createEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const rel = options.rel ?? \"icon\";\n let linkElement: HTMLLinkElement | null = document.querySelector(\n `link[rel*=\"${rel}\"]`\n );\n\n const originalHref = linkElement ? linkElement.href : \"\";\n\n if (!linkElement) {\n linkElement = document.createElement(\"link\");\n linkElement.rel = rel;\n if (options.type) linkElement.type = options.type;\n document.head.appendChild(linkElement);\n }\n\n const newHref = getHref();\n if (newHref) {\n linkElement.href = newHref;\n if (options.type) linkElement.type = options.type;\n }\n\n onCleanup(() => {\n if (typeof document !== \"undefined\" && (options.restoreOnUnmount ?? true) && linkElement) {\n if (originalHref) {\n linkElement.href = originalHref;\n }\n }\n });\n });\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-fetch",
3
+ "title": "createFetch",
4
+ "description": "SolidJS reactive primitive for HTTP REST API fetching, request loading states, error handling, and refetching",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-fetch.ts",
9
+ "content": "import { createSignal, createEffect, type Accessor } from \"solid-js\";\n\nexport interface CreateFetchOptions<T> extends RequestInit {\n /** Whether the request should be refetched automatically on window focus. Defaults to false. */\n refetchOnFocus?: boolean;\n /** Custom transform function to process raw JSON / text response into required shape. */\n transform?: (data: unknown) => T;\n /** Whether the initial request should execute immediately. Defaults to true. */\n immediate?: boolean;\n}\n\nexport interface CreateFetchReturn<T> {\n /** Signal containing fetched data. */\n data: Accessor<T | null>;\n /** Signal indicating whether request is loading. */\n isLoading: Accessor<boolean>;\n /** Signal containing request error if fetch failed. */\n error: Accessor<Error | null>;\n /** Imperative function to refetch data manually. */\n refetch: () => Promise<void>;\n /** Abort ongoing HTTP request. */\n abort: () => void;\n}\n\n/**\n * SolidJS reactive primitive for handling HTTP fetch requests, loading states, errors, and manual refetching.\n */\nexport function createFetch<T = unknown>(\n url: string | Accessor<string>,\n options: CreateFetchOptions<T> = {}\n): CreateFetchReturn<T> {\n const [data, setData] = createSignal<T | null>(null);\n const [isLoading, setIsLoading] = createSignal(options.immediate ?? true);\n const [error, setError] = createSignal<Error | null>(null);\n\n let controller: AbortController | null = null;\n\n const getUrl = (): string => {\n return typeof url === \"function\" ? url() : url;\n };\n\n const abort = (): void => {\n if (controller) {\n controller.abort();\n controller = null;\n }\n };\n\n const executeFetch = async (): Promise<void> => {\n if (typeof window === \"undefined\") return;\n\n abort();\n controller = new AbortController();\n\n setIsLoading(true);\n setError(null);\n\n try {\n const response = await fetch(getUrl(), {\n ...options,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`);\n }\n\n const raw = await response.json();\n const result = options.transform ? options.transform(raw) : (raw as T);\n\n setData(() => result);\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n };\n\n createEffect(() => {\n if (options.immediate ?? true) {\n executeFetch();\n }\n });\n\n return {\n data,\n isLoading,\n error,\n refetch: executeFetch,\n abort,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-focus-trap",
3
+ "title": "createFocusTrap",
4
+ "description": "SolidJS reactive primitive for trapping keyboard focus inside target container element for accessibility",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-focus-trap.ts",
9
+ "content": "import { createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateFocusTrapOptions {\n /** Whether focus trap is actively enabled. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n /** Whether to return focus to previously focused element on cleanup. Defaults to true. */\n returnFocusOnDeactivate?: boolean;\n}\n\nconst FOCUSABLE_SELECTOR =\n 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex=\"-1\"]), [contenteditable]';\n\n/**\n * SolidJS reactive primitive for trapping keyboard focus inside target container element.\n *\n * @param target Target element or accessor returning HTML element.\n * @param options Configuration options for focus trap behavior.\n */\nexport function createFocusTrap(\n target: HTMLElement | Accessor<HTMLElement | undefined>,\n options: CreateFocusTrapOptions = {}\n): void {\n const getTarget = (): HTMLElement | undefined => {\n if (typeof target === \"function\") {\n return (target as Accessor<HTMLElement | undefined>)();\n }\n return target;\n };\n\n const isEnabled = (): boolean => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n createEffect(() => {\n if (typeof window === \"undefined\" || !isEnabled()) return;\n\n const container = getTarget();\n if (!container) return;\n\n const previousActiveElement = document.activeElement as HTMLElement | null;\n\n const getFocusableElements = (): HTMLElement[] => {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0\n );\n };\n\n const focusable = getFocusableElements();\n if (focusable.length > 0) {\n focusable[0].focus();\n } else {\n container.focus();\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Tab\") return;\n\n const elements = getFocusableElements();\n if (elements.length === 0) {\n event.preventDefault();\n return;\n }\n\n const firstElement = elements[0];\n const lastElement = elements[elements.length - 1];\n const activeElement = document.activeElement;\n\n if (event.shiftKey) {\n if (activeElement === firstElement || !container.contains(activeElement)) {\n event.preventDefault();\n lastElement.focus();\n }\n } else {\n if (activeElement === lastElement || !container.contains(activeElement)) {\n event.preventDefault();\n firstElement.focus();\n }\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n\n onCleanup(() => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n\n if (options.returnFocusOnDeactivate !== false && previousActiveElement) {\n previousActiveElement.focus?.();\n }\n });\n });\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-form",
3
+ "title": "createForm",
4
+ "description": "SolidJS reactive primitive for form state management, field validation, errors, and submission",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-form.ts",
9
+ "content": "import { createSignal, createMemo, type Accessor } from \"solid-js\";\n\nexport type FormErrors<T> = Partial<Record<keyof T, string>>;\nexport type FormTouched<T> = Partial<Record<keyof T, boolean>>;\n\nexport interface CreateFormOptions<T extends Record<string, any>> {\n /** Initial form field values object */\n initialValues: T;\n /** Custom validation function returning error messages object */\n validate?: (values: T) => FormErrors<T> | Promise<FormErrors<T>>;\n /** Submit handler callback invoked when validation succeeds */\n onSubmit?: (values: T) => void | Promise<void>;\n}\n\nexport interface CreateFormReturn<T extends Record<string, any>> {\n /** Accessor for current form field values */\n values: Accessor<T>;\n /** Accessor for form field validation error messages */\n errors: Accessor<FormErrors<T>>;\n /** Accessor for form field touched states */\n touched: Accessor<FormTouched<T>>;\n /** Accessor indicating if form is currently submitting */\n isSubmitting: Accessor<boolean>;\n /** Accessor indicating if form has zero validation errors */\n isValid: Accessor<boolean>;\n /** Accessor indicating if form values differ from initial values */\n isDirty: Accessor<boolean>;\n /** Update specific form field value */\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void;\n /** Set specific form field validation error */\n setFieldError: <K extends keyof T>(field: K, error: string | undefined) => void;\n /** Set specific form field touched state */\n setFieldTouched: <K extends keyof T>(field: K, isTouched?: boolean) => void;\n /** Input change event listener helper factory function */\n handleChange: <K extends keyof T>(field: K) => (e: Event & { currentTarget: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement }) => void;\n /** Input blur event listener helper factory function */\n handleBlur: <K extends keyof T>(field: K) => () => void;\n /** Form onSubmit event handler */\n handleSubmit: (e?: Event) => void;\n /** Reset form values, errors, and touched states to initial values */\n resetForm: () => void;\n}\n\n/**\n * SolidJS reactive primitive for managing form field state, validation, errors, and submission.\n *\n * @param options Form configuration options including initialValues and validate function.\n */\nexport function createForm<T extends Record<string, any>>(\n options: CreateFormOptions<T>\n): CreateFormReturn<T> {\n const initialValues = { ...options.initialValues };\n\n const [values, setValues] = createSignal<T>({ ...initialValues });\n const [errors, setErrors] = createSignal<FormErrors<T>>({});\n const [touched, setTouched] = createSignal<FormTouched<T>>({});\n const [isSubmitting, setIsSubmitting] = createSignal(false);\n\n const isDirty = createMemo(() => {\n const current = values();\n return Object.keys(initialValues).some((key) => current[key] !== initialValues[key]);\n });\n\n const isValid = createMemo(() => {\n const errs = errors();\n return Object.keys(errs).length === 0;\n });\n\n const runValidation = async (currentValues: T): Promise<FormErrors<T>> => {\n if (!options.validate) return {};\n const result = await options.validate(currentValues);\n const newErrors = result || {};\n setErrors(() => newErrors);\n return newErrors;\n };\n\n const setFieldValue = <K extends keyof T>(field: K, value: T[K]) => {\n const next = { ...values(), [field]: value };\n setValues(() => next);\n runValidation(next);\n };\n\n const setFieldError = <K extends keyof T>(field: K, error: string | undefined) => {\n setErrors((prev) => {\n const next = { ...prev };\n if (error) next[field] = error;\n else delete next[field];\n return next;\n });\n };\n\n const setFieldTouched = <K extends keyof T>(field: K, isTouched = true) => {\n setTouched((prev) => ({ ...prev, [field]: isTouched }));\n };\n\n const handleChange = <K extends keyof T>(field: K) => {\n return (e: Event & { currentTarget: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement }) => {\n const val = e.currentTarget.value;\n setFieldValue(field, val as any);\n };\n };\n\n const handleBlur = <K extends keyof T>(field: K) => {\n return () => {\n setFieldTouched(field, true);\n };\n };\n\n const handleSubmit = async (e?: Event) => {\n e?.preventDefault();\n setIsSubmitting(true);\n\n // Mark all fields as touched on submit\n const allTouched = Object.keys(values()).reduce((acc, key) => {\n acc[key as keyof T] = true;\n return acc;\n }, {} as FormTouched<T>);\n setTouched(() => allTouched);\n\n const validationErrors = await runValidation(values());\n if (Object.keys(validationErrors).length === 0) {\n if (options.onSubmit) {\n await options.onSubmit(values());\n }\n }\n\n setIsSubmitting(false);\n };\n\n const resetForm = () => {\n setValues(() => ({ ...initialValues }));\n setErrors(() => ({}));\n setTouched(() => ({}));\n setIsSubmitting(false);\n };\n\n return {\n values,\n errors,\n touched,\n isSubmitting,\n isValid,\n isDirty,\n setFieldValue,\n setFieldError,\n setFieldTouched,\n handleChange,\n handleBlur,\n handleSubmit,\n resetForm,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-fullscreen",
3
+ "title": "createFullscreen",
4
+ "description": "SolidJS reactive primitive for requesting and monitoring element or document fullscreen status",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-fullscreen.ts",
9
+ "content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateFullscreenOptions {\n /** Target element accessor or reference. Defaults to document.documentElement. */\n target?: HTMLElement | Accessor<HTMLElement | undefined>;\n /** Callback fired when entering fullscreen mode. */\n onEnter?: () => void;\n /** Callback fired when exiting fullscreen mode. */\n onExit?: () => void;\n /** Callback fired when fullscreen request fails. */\n onError?: (err: Event) => void;\n}\n\nexport interface CreateFullscreenReturn {\n /** Signal indicating whether fullscreen mode is currently active. */\n isFullscreen: Accessor<boolean>;\n /** Request full screen mode for target element. */\n enter: () => Promise<void>;\n /** Exit full screen mode. */\n exit: () => Promise<void>;\n /** Toggle full screen mode. */\n toggle: () => Promise<void>;\n}\n\n/**\n * SolidJS reactive primitive for requesting and monitoring element fullscreen status.\n */\nexport function createFullscreen(\n options: CreateFullscreenOptions = {}\n): CreateFullscreenReturn {\n const [isFullscreen, setIsFullscreen] = createSignal(false);\n\n const getTarget = (): HTMLElement | undefined => {\n if (typeof document === \"undefined\") return undefined;\n if (typeof options.target === \"function\") {\n return options.target() ?? document.documentElement;\n }\n return options.target ?? document.documentElement;\n };\n\n const updateStatus = (): void => {\n if (typeof document === \"undefined\") return;\n const activeEl = document.fullscreenElement;\n const isTargetFullscreen = Boolean(activeEl && activeEl === getTarget());\n setIsFullscreen(isTargetFullscreen);\n };\n\n const enter = async (): Promise<void> => {\n if (typeof window === \"undefined\") return;\n const el = getTarget();\n if (el?.requestFullscreen) {\n await el.requestFullscreen();\n }\n };\n\n const exit = async (): Promise<void> => {\n if (typeof document === \"undefined\") return;\n if (document.fullscreenElement && document.exitFullscreen) {\n await document.exitFullscreen();\n }\n };\n\n const toggle = async (): Promise<void> => {\n if (isFullscreen()) {\n await exit();\n } else {\n await enter();\n }\n };\n\n createEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const handleFullscreenChange = (): void => {\n const activeEl = document.fullscreenElement;\n const isTarget = Boolean(activeEl && activeEl === getTarget());\n setIsFullscreen(isTarget);\n\n if (isTarget) {\n options.onEnter?.();\n } else {\n options.onExit?.();\n }\n };\n\n const handleFullscreenError = (err: Event): void => {\n options.onError?.(err);\n };\n\n document.addEventListener(\"fullscreenchange\", handleFullscreenChange);\n document.addEventListener(\"fullscreenerror\", handleFullscreenError);\n\n onCleanup(() => {\n document.removeEventListener(\"fullscreenchange\", handleFullscreenChange);\n document.removeEventListener(\"fullscreenerror\", handleFullscreenError);\n });\n });\n\n return {\n isFullscreen,\n enter,\n exit,\n toggle,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-geolocation",
3
+ "title": "createGeolocation",
4
+ "description": "SolidJS reactive primitive for tracking browser Geolocation position, coordinates, speed, and GPS accuracy",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-geolocation.ts",
9
+ "content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateGeolocationOptions extends PositionOptions {\n /** Whether to start watching position immediately upon primitive initialization. Defaults to true. */\n immediate?: boolean;\n}\n\nexport interface GeolocationState {\n /** Current latitude coordinate in degrees. */\n latitude: number | null;\n /** Current longitude coordinate in degrees. */\n longitude: number | null;\n /** Current altitude above sea level in meters. */\n altitude: number | null;\n /** Position accuracy level in meters. */\n accuracy: number | null;\n /** Altitude accuracy in meters. */\n altitudeAccuracy: number | null;\n /** Current heading direction in degrees relative to true north. */\n heading: number | null;\n /** Current speed in meters per second. */\n speed: number | null;\n /** Timestamp when location was captured. */\n timestamp: number | null;\n}\n\nexport interface CreateGeolocationReturn {\n /** Signal accessor containing current geolocation coordinates and metrics. */\n coords: Accessor<GeolocationState>;\n /** Signal accessor indicating whether position retrieval is in progress. */\n loading: Accessor<boolean>;\n /** Signal accessor containing GeolocationPositionError if request failed. */\n error: Accessor<GeolocationPositionError | Error | null>;\n /** Signal accessor indicating whether Geolocation API is supported in browser environment. */\n isSupported: Accessor<boolean>;\n /** Imperative function to fetch current position once. */\n getCurrentPosition: () => void;\n}\n\nconst initialCoords: GeolocationState = {\n latitude: null,\n longitude: null,\n altitude: null,\n accuracy: null,\n altitudeAccuracy: null,\n heading: null,\n speed: null,\n timestamp: null,\n};\n\n/**\n * SolidJS reactive primitive for tracking user geographic location and GPS metrics.\n */\nexport function createGeolocation(\n options: CreateGeolocationOptions = {}\n): CreateGeolocationReturn {\n const [coords, setCoords] = createSignal<GeolocationState>(initialCoords);\n const [loading, setLoading] = createSignal(false);\n const [error, setError] = createSignal<GeolocationPositionError | Error | null>(null);\n\n const isSupported = (): boolean => {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") return false;\n return \"geolocation\" in navigator;\n };\n\n const getOptions = (): PositionOptions => ({\n timeout: options.timeout ?? 10000,\n maximumAge: options.maximumAge ?? 5000,\n enableHighAccuracy: options.enableHighAccuracy ?? false,\n });\n\n let watchId: number | null = null;\n\n const updatePosition = (position: GeolocationPosition): void => {\n setCoords({\n latitude: position.coords.latitude,\n longitude: position.coords.longitude,\n altitude: position.coords.altitude,\n accuracy: position.coords.accuracy,\n altitudeAccuracy: position.coords.altitudeAccuracy,\n heading: position.coords.heading,\n speed: position.coords.speed,\n timestamp: position.timestamp,\n });\n setLoading(false);\n setError(null);\n };\n\n const handleError = (err: GeolocationPositionError): void => {\n setError(err);\n setLoading(false);\n };\n\n const getCurrentPosition = (): void => {\n if (!isSupported()) {\n setError(new Error(\"Geolocation API is not supported in this browser environment.\"));\n return;\n }\n\n setLoading(true);\n setError(null);\n\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n updatePosition(pos);\n },\n (err) => {\n handleError(err);\n },\n getOptions()\n );\n };\n\n createEffect(() => {\n if (!isSupported()) return;\n\n if (options.immediate ?? true) {\n setLoading(true);\n watchId = navigator.geolocation.watchPosition(\n updatePosition,\n handleError,\n getOptions()\n );\n }\n\n onCleanup(() => {\n if (watchId !== null && typeof window !== \"undefined\" && \"geolocation\" in navigator) {\n navigator.geolocation.clearWatch(watchId);\n }\n });\n });\n\n return {\n coords,\n loading,\n error,\n isSupported,\n getCurrentPosition,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-hover",
3
+ "title": "createHover",
4
+ "description": "SolidJS reactive primitive for tracking element hover state with entrance and exit delays",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-hover.ts",
9
+ "content": "import { createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateHoverOptions {\n /** Delay in milliseconds before setting hover state to true */\n delayEnter?: number;\n /** Delay in milliseconds before setting hover state to false */\n delayLeave?: number;\n /** Callback fired when hover state transitions to true */\n onHoverStart?: () => void;\n /** Callback fired when hover state transitions to false */\n onHoverEnd?: () => void;\n}\n\nexport interface CreateHoverReturn {\n /** Accessor indicating whether target element is hovered */\n isHovered: Accessor<boolean>;\n /** Event listeners props to spread onto target JSX element */\n props: {\n onPointerEnter: (e: PointerEvent) => void;\n onPointerLeave: (e: PointerEvent) => void;\n };\n}\n\n/**\n * SolidJS reactive primitive for tracking element hover state with optional entrance/exit delays.\n *\n * @param options Configuration options for hover delays and callbacks.\n */\nexport function createHover(options: CreateHoverOptions = {}): CreateHoverReturn {\n const [isHovered, setIsHovered] = createSignal(false);\n let enterTimer: ReturnType<typeof setTimeout> | undefined;\n let leaveTimer: ReturnType<typeof setTimeout> | undefined;\n\n const clearTimers = () => {\n if (enterTimer) {\n clearTimeout(enterTimer);\n enterTimer = undefined;\n }\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = undefined;\n }\n };\n\n const onPointerEnter = () => {\n clearTimers();\n const delay = options.delayEnter ?? 0;\n\n if (delay > 0) {\n enterTimer = setTimeout(() => {\n setIsHovered(true);\n options.onHoverStart?.();\n enterTimer = undefined;\n }, delay);\n } else {\n setIsHovered(true);\n options.onHoverStart?.();\n }\n };\n\n const onPointerLeave = () => {\n clearTimers();\n const delay = options.delayLeave ?? 0;\n\n if (delay > 0) {\n leaveTimer = setTimeout(() => {\n setIsHovered(false);\n options.onHoverEnd?.();\n leaveTimer = undefined;\n }, delay);\n } else {\n setIsHovered(false);\n options.onHoverEnd?.();\n }\n };\n\n onCleanup(() => {\n clearTimers();\n });\n\n return {\n isHovered,\n props: {\n onPointerEnter,\n onPointerLeave,\n },\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-idle",
3
+ "title": "createIdle",
4
+ "description": "SolidJS reactive primitive for detecting user inactivity with customizable timeout",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-idle.ts",
9
+ "content": "import { createSignal, onMount, onCleanup, type Accessor } from \"solid-js\";\nimport { isServer } from \"solid-js/web\";\n\nexport interface CreateIdleOptions {\n /** Timeout in milliseconds before user is considered idle (default: 60000 = 60s) */\n timeout?: number;\n /** Initial idle state (default: false) */\n initialState?: boolean;\n /** DOM events to listen for user activity */\n events?: string[];\n /** Callback fired when user becomes idle */\n onIdle?: () => void;\n /** Callback fired when user becomes active after being idle */\n onActive?: () => void;\n}\n\nexport interface CreateIdleReturn {\n /** Accessor returning true if user has been inactive for timeout period */\n isIdle: Accessor<boolean>;\n /** Accessor returning timestamp in ms of last detected user interaction */\n lastActive: Accessor<number>;\n /** Reset idle state and restart timer */\n reset: () => void;\n}\n\nconst DEFAULT_EVENTS = [\n \"mousemove\",\n \"mousedown\",\n \"keydown\",\n \"touchstart\",\n \"scroll\",\n \"wheel\",\n];\n\n/**\n * SolidJS reactive primitive for detecting user inactivity (idle state) with customizable timeout and event triggers.\n *\n * @param options Configuration options including timeout in ms and event handlers.\n */\nexport function createIdle(options: CreateIdleOptions = {}): CreateIdleReturn {\n const initialIdle = options.initialState ?? false;\n\n // SSR: return static defaults — no timers, no listeners\n if (isServer) {\n const [isIdle] = createSignal(initialIdle);\n const [lastActive] = createSignal(0);\n return { isIdle, lastActive, reset: () => {} };\n }\n\n const timeoutMs = options.timeout ?? 60000;\n const events = options.events ?? DEFAULT_EVENTS;\n\n const [isIdle, setIsIdle] = createSignal(initialIdle);\n const [lastActive, setLastActive] = createSignal(0);\n\n let timerId: any = null;\n\n const startTimer = () => {\n if (timerId) clearTimeout(timerId);\n timerId = setTimeout(() => {\n if (!isIdle()) {\n setIsIdle(true);\n if (options.onIdle) options.onIdle();\n }\n }, timeoutMs);\n };\n\n const handleUserActivity = () => {\n setLastActive(Date.now());\n\n if (isIdle()) {\n setIsIdle(false);\n if (options.onActive) options.onActive();\n }\n\n startTimer();\n };\n\n const reset = () => {\n setLastActive(Date.now());\n setIsIdle(false);\n startTimer();\n };\n\n onMount(() => {\n // Set initial lastActive to now on client mount\n setLastActive(Date.now());\n\n events.forEach((evt) => {\n window.addEventListener(evt, handleUserActivity, { passive: true });\n });\n\n startTimer();\n\n onCleanup(() => {\n if (timerId) clearTimeout(timerId);\n events.forEach((evt) => {\n window.removeEventListener(evt, handleUserActivity);\n });\n });\n });\n\n return {\n isIdle,\n lastActive,\n reset,\n };\n}\n\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-infinite-scroll",
3
+ "title": "createInfiniteScroll",
4
+ "description": "SolidJS reactive primitive for dynamic infinite scrolling, auto-fetching pages, and scroll pagination",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-infinite-scroll.ts",
9
+ "content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateInfiniteScrollOptions {\n /** Target element to observe or trigger scroll on. Defaults to document / scroll parent. */\n target?: HTMLElement | Accessor<HTMLElement | undefined>;\n /** Distance threshold from bottom in pixels to trigger fetch. Defaults to 100. */\n threshold?: number;\n /** Whether loading is currently enabled or auto-fetching is active. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n /** Callback function when scrolled near bottom to fetch next items. */\n onLoadMore: () => Promise<void> | void;\n}\n\nexport interface CreateInfiniteScrollReturn {\n /** Sentinel ref function to bind to a DOM element at the bottom of the list. */\n setSentinelRef: (el: HTMLElement | null) => void;\n /** Signal indicating whether fetching is currently in progress. */\n isLoading: Accessor<boolean>;\n /** Signal indicating whether an error occurred during last fetch. */\n error: Accessor<Error | null>;\n /** Imperative function to manually trigger next page load. */\n loadMore: () => Promise<void>;\n}\n\n/**\n * SolidJS reactive primitive for dynamic infinite scrolling / auto-fetching.\n */\nexport function createInfiniteScroll(\n options: CreateInfiniteScrollOptions\n): CreateInfiniteScrollReturn {\n const [sentinelEl, setSentinelEl] = createSignal<HTMLElement | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const isEnabled = (): boolean => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n const loadMore = async (): Promise<void> => {\n if (isLoading() || !isEnabled()) return;\n setIsLoading(true);\n setError(null);\n try {\n await options.onLoadMore();\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n };\n\n createEffect(() => {\n if (typeof window === \"undefined\" || !window.IntersectionObserver) return;\n if (!isEnabled()) return;\n\n const el = sentinelEl();\n if (!el) return;\n\n const rootMargin = `${options.threshold ?? 100}px`;\n\n const observer = new IntersectionObserver(\n (entries) => {\n const entry = entries[0];\n if (entry?.isIntersecting && !isLoading()) {\n loadMore();\n }\n },\n {\n rootMargin,\n threshold: 0,\n }\n );\n\n observer.observe(el);\n\n onCleanup(() => {\n observer.disconnect();\n });\n });\n\n return {\n setSentinelRef: setSentinelEl,\n isLoading,\n error,\n loadMore,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-input-mask",
3
+ "title": "createInputMask",
4
+ "description": "SolidJS reactive primitive for input value masking (phone numbers, credit cards, dates)",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-input-mask.ts",
9
+ "content": "import { createSignal, type Accessor } from \"solid-js\";\n\nexport interface CreateInputMaskOptions {\n /** Mask pattern template (e.g. '+995 ### ##-##-##' or '#### #### #### ####') */\n mask: string;\n /** Initial default value */\n defaultValue?: string;\n}\n\nexport interface CreateInputMaskReturn {\n /** Accessor returning formatted masked input string */\n value: Accessor<string>;\n /** Accessor returning raw unmasked user digits string */\n unmaskedValue: Accessor<string>;\n /** Function to programmatically update input value */\n setValue: (val: string) => void;\n /** JSX props object to spread onto target HTMLInputElement */\n props: {\n value: () => string;\n onInput: (e: Event & { currentTarget: HTMLInputElement }) => void;\n };\n}\n\n/**\n * Format raw unmasked text against a mask pattern template.\n */\nexport function formatMask(rawInput: string, pattern: string): { masked: string; unmasked: string } {\n if (!rawInput) {\n return { masked: \"\", unmasked: \"\" };\n }\n\n // Find static prefix of pattern before first slot placeholder (#, 0, X)\n let staticPrefix = \"\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i];\n if (char === \"#\" || char === \"0\" || char === \"X\") break;\n staticPrefix += char;\n }\n const staticPrefixDigits = staticPrefix.replace(/\\D/g, \"\");\n\n let digits = rawInput.replace(/\\D/g, \"\");\n\n // Strip static prefix digits if present at the start\n if (staticPrefixDigits && digits.startsWith(staticPrefixDigits)) {\n digits = digits.slice(staticPrefixDigits.length);\n }\n\n if (!digits) {\n return { masked: \"\", unmasked: \"\" };\n }\n\n let masked = \"\";\n let digitIndex = 0;\n\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i];\n if (char === \"#\" || char === \"0\" || char === \"X\") {\n if (digitIndex < digits.length) {\n masked += digits[digitIndex++];\n } else {\n break;\n }\n } else {\n if (digitIndex < digits.length) {\n masked += char;\n } else {\n break;\n }\n }\n }\n\n return { masked, unmasked: digits };\n}\n\n/**\n * SolidJS reactive primitive for input value masking (phone numbers, credit cards, dates).\n *\n * @param options Configuration options including mask pattern.\n */\nexport function createInputMask(options: CreateInputMaskOptions): CreateInputMaskReturn {\n const initial = formatMask(options.defaultValue || \"\", options.mask);\n const [value, setFormattedValue] = createSignal(initial.masked);\n const [unmaskedValue, setRawValue] = createSignal(initial.unmasked);\n\n const setValue = (newVal: string) => {\n const formatted = formatMask(newVal, options.mask);\n setFormattedValue(formatted.masked);\n setRawValue(formatted.unmasked);\n };\n\n const onInput = (e: Event & { currentTarget: HTMLInputElement }) => {\n const inputVal = e.currentTarget.value;\n const formatted = formatMask(inputVal, options.mask);\n\n setFormattedValue(formatted.masked);\n setRawValue(formatted.unmasked);\n e.currentTarget.value = formatted.masked;\n };\n\n return {\n value,\n unmaskedValue,\n setValue,\n props: {\n value: () => value(),\n onInput,\n },\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-intersection-observer",
3
+ "title": "createIntersectionObserver",
4
+ "description": "SolidJS reactive primitives for observing element visibility and viewport intersection status",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-intersection-observer.ts",
9
+ "content": "import { createEffect, createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateIntersectionObserverOptions extends IntersectionObserverInit {\n /** Whether the observer is active. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for observing element visibility and intersection with viewport or root element.\n *\n * @param target Target element or accessor returning HTML element.\n * @param callback Observer callback invoked on intersection state change.\n * @param options IntersectionObserver options (root, rootMargin, threshold, enabled).\n */\nexport function createIntersectionObserver(\n target: HTMLElement | Accessor<HTMLElement | undefined>,\n callback: IntersectionObserverCallback,\n options: CreateIntersectionObserverOptions = {}\n): void {\n const getTarget = (): HTMLElement | undefined => {\n if (typeof target === \"function\") {\n return (target as Accessor<HTMLElement | undefined>)();\n }\n return target;\n };\n\n const isEnabled = (): boolean => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n createEffect(() => {\n if (typeof window === \"undefined\" || !window.IntersectionObserver) {\n return;\n }\n\n if (!isEnabled()) return;\n\n const el = getTarget();\n if (!el) return;\n\n const observer = new IntersectionObserver(callback, {\n root: options.root,\n rootMargin: options.rootMargin,\n threshold: options.threshold,\n });\n\n observer.observe(el);\n\n onCleanup(() => {\n observer.disconnect();\n });\n });\n}\n\n/**\n * SolidJS reactive primitive returning a boolean accessor indicating if element is currently visible in viewport.\n *\n * @param target Target element or accessor returning HTML element.\n * @param options IntersectionObserver options.\n */\nexport function createInView(\n target: HTMLElement | Accessor<HTMLElement | undefined>,\n options: CreateIntersectionObserverOptions = {}\n): Accessor<boolean> {\n const [isInView, setIsInView] = createSignal(false);\n\n createIntersectionObserver(\n target,\n (entries) => {\n const entry = entries[0];\n if (entry) {\n setIsInView(entry.isIntersecting);\n }\n },\n options\n );\n\n return isInView;\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-keybindings",
3
+ "title": "createKeybindings",
4
+ "description": "SolidJS reactive primitives for listening to keyboard shortcuts, key combinations, and Escape key presses",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-keybindings.ts",
9
+ "content": "import { onMount, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface KeybindingDefinition {\n /** Key combination string, e.g. \"meta+k\", \"ctrl+k\", \"Escape\", \"Shift+Enter\" */\n key: string | string[];\n /** Callback handler invoked when the key combination is triggered */\n handler: (event: KeyboardEvent) => void;\n /** Whether to prevent default browser action. Defaults to false. */\n preventDefault?: boolean;\n /** Whether to stop event propagation. Defaults to false. */\n stopPropagation?: boolean;\n}\n\nexport interface CreateKeybindingsOptions {\n /** Event target to attach listener to. Defaults to window. */\n target?: HTMLElement | Window | Accessor<HTMLElement | Window | undefined>;\n /** Event type: \"keydown\" (default) or \"keyup\" */\n eventType?: \"keydown\" | \"keyup\";\n /** Whether keybinding listeners are active. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n}\n\n/**\n * Normalizes key string into standardized combination signature (e.g. \"ctrl+meta+k\").\n */\nfunction normalizeKeyCombination(keyStr: string): string {\n const parts = keyStr.toLowerCase().split(\"+\").map((p) => p.trim());\n const modifiers = new Set<string>();\n let mainKey = \"\";\n\n for (const part of parts) {\n if (part === \"meta\" || part === \"cmd\" || part === \"command\" || part === \"⌘\") {\n modifiers.add(\"meta\");\n } else if (part === \"ctrl\" || part === \"control\") {\n modifiers.add(\"ctrl\");\n } else if (part === \"alt\" || part === \"option\" || part === \"⌥\") {\n modifiers.add(\"alt\");\n } else if (part === \"shift\" || part === \"⇧\") {\n modifiers.add(\"shift\");\n } else {\n mainKey = part;\n }\n }\n\n const sortedMods = Array.from(modifiers).sort();\n return [...sortedMods, mainKey].join(\"+\");\n}\n\n/**\n * Checks if a KeyboardEvent matches a normalized key combination.\n */\nfunction isEventMatch(event: KeyboardEvent, targetCombination: string): boolean {\n const modifiers = new Set<string>();\n if (event.metaKey) modifiers.add(\"meta\");\n if (event.ctrlKey) modifiers.add(\"ctrl\");\n if (event.altKey) modifiers.add(\"alt\");\n if (event.shiftKey) modifiers.add(\"shift\");\n\n const mainKey = event.key.toLowerCase();\n const eventCombination = [...Array.from(modifiers).sort(), mainKey].join(\"+\");\n\n return eventCombination === targetCombination;\n}\n\n/**\n * SolidJS reactive primitive for listening to single or multiple keyboard shortcuts.\n *\n * @param bindings Array of keybinding definitions or single definition object.\n * @param options Configuration options including target, event type, and enabled state.\n */\nexport function createKeybindings(\n bindings: KeybindingDefinition | KeybindingDefinition[],\n options: CreateKeybindingsOptions = {}\n): void {\n const bindingList = Array.isArray(bindings) ? bindings : [bindings];\n\n const isEnabled = () => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n const getTargetElement = (): HTMLElement | Window | undefined => {\n if (!options.target) {\n return typeof window !== \"undefined\" ? window : undefined;\n }\n if (typeof options.target === \"function\") {\n return (options.target as Accessor<HTMLElement | Window | undefined>)();\n }\n return options.target;\n };\n\n const handleKeyboardEvent = (event: KeyboardEvent) => {\n if (!isEnabled()) return;\n\n for (const binding of bindingList) {\n const keys = Array.isArray(binding.key) ? binding.key : [binding.key];\n\n for (const keyCombo of keys) {\n const normalizedCombo = normalizeKeyCombination(keyCombo);\n\n if (isEventMatch(event, normalizedCombo)) {\n if (binding.preventDefault) {\n event.preventDefault();\n }\n if (binding.stopPropagation) {\n event.stopPropagation();\n }\n binding.handler(event);\n break;\n }\n }\n }\n };\n\n onMount(() => {\n const target = getTargetElement();\n if (!target) return;\n\n const eventType = options.eventType || \"keydown\";\n target.addEventListener(eventType, handleKeyboardEvent as EventListener);\n\n onCleanup(() => {\n target.removeEventListener(eventType, handleKeyboardEvent as EventListener);\n });\n });\n}\n\n/**\n * SolidJS primitive specialized for handling Escape key presses.\n *\n * @param handler Callback invoked when Escape key is pressed.\n * @param options Keybindings options.\n */\nexport function createEscapeKey(\n handler: (event: KeyboardEvent) => void,\n options: CreateKeybindingsOptions = {}\n): void {\n createKeybindings(\n {\n key: \"Escape\",\n handler,\n preventDefault: true,\n },\n options\n );\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-lock-scroll",
3
+ "title": "createLockScroll",
4
+ "description": "SolidJS reactive primitive for locking body or container scrolling when overlays are active",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-lock-scroll.ts",
9
+ "content": "import { createEffect, createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateLockScrollOptions {\n /** Target element to lock scroll for. Defaults to document.body. */\n target?: HTMLElement | Accessor<HTMLElement | undefined>;\n /** Initial lock state. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n}\n\nexport interface CreateLockScrollReturn {\n /** Accessor indicating whether scroll is currently locked */\n isLocked: Accessor<boolean>;\n /** Setter function to programmatically lock or unlock scroll */\n setLocked: (locked: boolean) => void;\n}\n\n/**\n * SolidJS reactive primitive for locking target element (or document.body) scroll.\n *\n * @param options Configuration options including target element and initial enabled state.\n */\nexport function createLockScroll(options: CreateLockScrollOptions = {}): CreateLockScrollReturn {\n const [internalLocked, setInternalLocked] = createSignal(false);\n\n const getTarget = (): HTMLElement | undefined => {\n if (typeof window === \"undefined\") return undefined;\n\n if (!options.target) {\n return document.body;\n }\n if (typeof options.target === \"function\") {\n return (options.target as Accessor<HTMLElement | undefined>)();\n }\n return options.target;\n };\n\n const isEnabled = (): boolean => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n let originalOverflow: string | undefined;\n\n const applyLock = (element: HTMLElement) => {\n if (originalOverflow === undefined) {\n originalOverflow = element.style.overflow;\n }\n element.style.overflow = \"hidden\";\n setInternalLocked(true);\n };\n\n const removeLock = (element: HTMLElement) => {\n if (originalOverflow !== undefined) {\n element.style.overflow = originalOverflow;\n originalOverflow = undefined;\n } else {\n element.style.overflow = \"\";\n }\n setInternalLocked(false);\n };\n\n createEffect(() => {\n const el = getTarget();\n if (!el) return;\n\n if (isEnabled()) {\n applyLock(el);\n } else {\n removeLock(el);\n }\n });\n\n onCleanup(() => {\n const el = getTarget();\n if (el) {\n removeLock(el);\n }\n });\n\n const setLocked = (locked: boolean) => {\n const el = getTarget();\n if (!el) return;\n\n if (locked) {\n applyLock(el);\n } else {\n removeLock(el);\n }\n };\n\n return {\n isLocked: internalLocked,\n setLocked,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-long-press",
3
+ "title": "createLongPress",
4
+ "description": "SolidJS reactive primitive for detecting long press / hold touch and pointer interactions",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-long-press.ts",
9
+ "content": "import { createSignal, onCleanup } from \"solid-js\";\n\nexport interface CreateLongPressOptions {\n /** Long press hold threshold duration in milliseconds. Defaults to 500. */\n threshold?: number;\n /** Callback fired when long press hold starts */\n onStart?: () => void;\n /** Callback fired when long press is successfully completed */\n onFinish?: () => void;\n /** Callback fired when long press is cancelled before threshold */\n onCancel?: () => void;\n}\n\nexport interface CreateLongPressReturn {\n /** Accessor indicating if long press is currently being held */\n isPressed: () => boolean;\n /** JSX Event Handlers object to spread or attach onto target element */\n props: {\n onPointerDown: (e: PointerEvent) => void;\n onPointerUp: (e: PointerEvent) => void;\n onPointerLeave: (e: PointerEvent) => void;\n onCancel: (e: Event) => void;\n };\n}\n\n/**\n * SolidJS reactive primitive for detecting long press / hold interactions on elements.\n *\n * @param handler Callback function invoked when long press threshold is reached.\n * @param options Options for threshold duration and state callbacks.\n */\nexport function createLongPress(\n handler: (event: Event) => void,\n options: CreateLongPressOptions = {}\n): CreateLongPressReturn {\n const threshold = options.threshold ?? 500;\n const [isPressed, setIsPressed] = createSignal(false);\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const cancel = (e?: Event) => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n if (isPressed()) {\n setIsPressed(false);\n options.onCancel?.();\n }\n };\n\n const start = (e: Event) => {\n cancel();\n setIsPressed(true);\n options.onStart?.();\n\n timer = setTimeout(() => {\n handler(e);\n options.onFinish?.();\n timer = undefined;\n }, threshold);\n };\n\n const finish = (e: Event) => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n options.onCancel?.();\n }\n setIsPressed(false);\n };\n\n onCleanup(() => {\n cancel();\n });\n\n return {\n isPressed,\n props: {\n onPointerDown: start,\n onPointerUp: finish,\n onPointerLeave: cancel,\n onCancel: cancel,\n },\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-media-query",
3
+ "title": "createMediaQuery",
4
+ "description": "SolidJS reactive primitives for tracking CSS media queries and responsive Tailwind breakpoints",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-media-query.ts",
9
+ "content": "import { createEffect, createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport const DEFAULT_BREAKPOINTS = {\n sm: \"(min-width: 640px)\",\n md: \"(min-width: 768px)\",\n lg: \"(min-width: 1024px)\",\n xl: \"(min-width: 1280px)\",\n \"2xl\": \"(min-width: 1536px)\",\n};\n\n/**\n * SolidJS reactive primitive for tracking CSS media query match state.\n *\n * @param query Media query string or accessor returning media query string.\n */\nexport function createMediaQuery(query: string | Accessor<string>): Accessor<boolean> {\n const getQuery = (): string => {\n return typeof query === \"function\" ? query() : query;\n };\n\n const [matches, setMatches] = createSignal<boolean>(false);\n\n createEffect(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) {\n setMatches(false);\n return;\n }\n\n const currentQuery = getQuery();\n const mediaQueryList = window.matchMedia(currentQuery);\n\n setMatches(mediaQueryList.matches);\n\n const listener = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n if (mediaQueryList.addEventListener) {\n mediaQueryList.addEventListener(\"change\", listener);\n onCleanup(() => mediaQueryList.removeEventListener(\"change\", listener));\n } else {\n mediaQueryList.addListener(listener);\n onCleanup(() => mediaQueryList.removeListener(listener));\n }\n });\n\n return matches;\n}\n\nexport interface CreateBreakpointReturn {\n /** Accessor returning active breakpoint key (e.g. 'sm', 'md', 'lg') */\n active: Accessor<string>;\n /** Accessor indicating if screen width matches mobile (<768px) */\n isMobile: Accessor<boolean>;\n /** Accessor indicating if screen width matches tablet (768px - 1024px) */\n isTablet: Accessor<boolean>;\n /** Accessor indicating if screen width matches desktop (>=1024px) */\n isDesktop: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for tracking responsive Tailwind CSS design breakpoints.\n *\n * @param customBreakpoints Custom breakpoint definitions mapping key names to media query strings.\n */\nexport function createBreakpoint(\n customBreakpoints: Record<string, string> = DEFAULT_BREAKPOINTS\n): CreateBreakpointReturn {\n const isSm = createMediaQuery(customBreakpoints.sm || DEFAULT_BREAKPOINTS.sm);\n const isMd = createMediaQuery(customBreakpoints.md || DEFAULT_BREAKPOINTS.md);\n const isLg = createMediaQuery(customBreakpoints.lg || DEFAULT_BREAKPOINTS.lg);\n const isXl = createMediaQuery(customBreakpoints.xl || DEFAULT_BREAKPOINTS.xl);\n const is2Xl = createMediaQuery(customBreakpoints[\"2xl\"] || DEFAULT_BREAKPOINTS[\"2xl\"]);\n\n const active = (): string => {\n if (is2Xl()) return \"2xl\";\n if (isXl()) return \"xl\";\n if (isLg()) return \"lg\";\n if (isMd()) return \"md\";\n if (isSm()) return \"sm\";\n return \"xs\";\n };\n\n const isMobile = (): boolean => !isMd();\n const isTablet = (): boolean => isMd() && !isLg();\n const isDesktop = (): boolean => isLg();\n\n return {\n active,\n isMobile,\n isTablet,\n isDesktop,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-mouse-position",
3
+ "title": "createMousePosition",
4
+ "description": "SolidJS reactive primitive for tracking global and element-relative mouse pointer coordinates",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-mouse-position.ts",
9
+ "content": "import { createEffect, createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateMousePositionOptions {\n /** Target element to calculate element-relative mouse coordinates for. Defaults to window. */\n target?: HTMLElement | Window | Accessor<HTMLElement | Window | undefined>;\n}\n\nexport interface CreateMousePositionReturn {\n /** Accessor for global page X mouse position */\n x: Accessor<number>;\n /** Accessor for global page Y mouse position */\n y: Accessor<number>;\n /** Accessor for element-relative X mouse coordinate */\n elementX: Accessor<number>;\n /** Accessor for element-relative Y mouse coordinate */\n elementY: Accessor<number>;\n /** Accessor indicating if mouse pointer is inside target element bounds */\n isInside: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for tracking global and element-relative mouse pointer coordinates.\n *\n * @param options Configuration options including target element.\n */\nexport function createMousePosition(\n options: CreateMousePositionOptions = {}\n): CreateMousePositionReturn {\n const [x, setX] = createSignal(0);\n const [y, setY] = createSignal(0);\n const [elementX, setElementX] = createSignal(0);\n const [elementY, setElementY] = createSignal(0);\n const [isInside, setIsInside] = createSignal(false);\n\n const getTarget = (): HTMLElement | Window | undefined => {\n if (typeof window === \"undefined\") return undefined;\n if (!options.target) return window;\n if (typeof options.target === \"function\") {\n return (options.target as Accessor<HTMLElement | Window | undefined>)();\n }\n return options.target;\n };\n\n const handleMouseMove = (event: MouseEvent) => {\n const pageX = event.pageX;\n const pageY = event.pageY;\n\n setX(pageX);\n setY(pageY);\n\n const target = getTarget();\n if (target && target !== window) {\n const el = target as HTMLElement;\n const rect = el.getBoundingClientRect();\n const relX = event.clientX - rect.left;\n const relY = event.clientY - rect.top;\n\n setElementX(relX);\n setElementY(relY);\n\n const inside =\n relX >= 0 && relX <= rect.width && relY >= 0 && relY <= rect.height;\n setIsInside(inside);\n } else {\n setElementX(pageX);\n setElementY(pageY);\n setIsInside(true);\n }\n };\n\n createEffect(() => {\n if (typeof window === \"undefined\") return;\n\n window.addEventListener(\"mousemove\", handleMouseMove, { passive: true });\n onCleanup(() => {\n window.removeEventListener(\"mousemove\", handleMouseMove);\n });\n });\n\n return {\n x,\n y,\n elementX,\n elementY,\n isInside,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-network-status",
3
+ "title": "createNetworkStatus",
4
+ "description": "SolidJS reactive primitives for tracking browser network connectivity and connection quality metrics",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-network-status.ts",
9
+ "content": "import { createEffect, createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateNetworkStatusReturn {\n /** Accessor indicating if browser is currently connected to the network */\n isOnline: Accessor<boolean>;\n /** Date timestamp when network went offline, if applicable */\n offlineAt: Accessor<Date | undefined>;\n /** Date timestamp when network re-connected online, if applicable */\n onlineAt: Accessor<Date | undefined>;\n /** Network connection estimated downlink speed in Mbps */\n downlink: Accessor<number | undefined>;\n /** Network connection estimated round-trip time in ms */\n rtt: Accessor<number | undefined>;\n /** Network connection data saver mode enabled status */\n saveData: Accessor<boolean | undefined>;\n /** Network connection effective type ('slow-2g', '2g', '3g', '4g') */\n effectiveType: Accessor<\"slow-2g\" | \"2g\" | \"3g\" | \"4g\" | undefined>;\n}\n\n/**\n * SolidJS reactive primitive for tracking browser network connectivity and connection quality metrics.\n */\nexport function createNetworkStatus(): CreateNetworkStatusReturn {\n const getInitialOnline = () => (typeof navigator !== \"undefined\" ? navigator.onLine : true);\n\n const [isOnline, setIsOnline] = createSignal(getInitialOnline());\n const [offlineAt, setOfflineAt] = createSignal<Date | undefined>(undefined);\n const [onlineAt, setOnlineAt] = createSignal<Date | undefined>(undefined);\n\n const getNetworkConnection = () => {\n if (typeof navigator === \"undefined\") return undefined;\n return (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;\n };\n\n const conn = getNetworkConnection();\n\n const [downlink, setDownlink] = createSignal<number | undefined>(conn?.downlink);\n const [rtt, setRtt] = createSignal<number | undefined>(conn?.rtt);\n const [saveData, setSaveData] = createSignal<boolean | undefined>(conn?.saveData);\n const [effectiveType, setEffectiveType] = createSignal<\"slow-2g\" | \"2g\" | \"3g\" | \"4g\" | undefined>(conn?.effectiveType);\n\n const updateNetworkInfo = () => {\n const currentConn = getNetworkConnection();\n if (currentConn) {\n setDownlink(currentConn.downlink);\n setRtt(currentConn.rtt);\n setSaveData(currentConn.saveData);\n setEffectiveType(currentConn.effectiveType);\n }\n };\n\n const handleOnline = () => {\n setIsOnline(true);\n setOnlineAt(new Date());\n updateNetworkInfo();\n };\n\n const handleOffline = () => {\n setIsOnline(false);\n setOfflineAt(new Date());\n };\n\n createEffect(() => {\n if (typeof window === \"undefined\") return;\n\n window.addEventListener(\"online\", handleOnline);\n window.addEventListener(\"offline\", handleOffline);\n\n const currentConn = getNetworkConnection();\n if (currentConn && currentConn.addEventListener) {\n currentConn.addEventListener(\"change\", updateNetworkInfo);\n }\n\n onCleanup(() => {\n window.removeEventListener(\"online\", handleOnline);\n window.removeEventListener(\"offline\", handleOffline);\n if (currentConn && currentConn.removeEventListener) {\n currentConn.removeEventListener(\"change\", updateNetworkInfo);\n }\n });\n });\n\n return {\n isOnline,\n offlineAt,\n onlineAt,\n downlink,\n rtt,\n saveData,\n effectiveType,\n };\n}\n\n/**\n * SolidJS reactive primitive for checking if browser is connected online.\n */\nexport function createOnline(): Accessor<boolean> {\n const { isOnline } = createNetworkStatus();\n return isOnline;\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-orientation",
3
+ "title": "createOrientation",
4
+ "description": "SolidJS reactive primitive for observing mobile and desktop screen orientation changes and rotation angles",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-orientation.ts",
9
+ "content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport type ScreenOrientationType =\n | \"portrait-primary\"\n | \"portrait-secondary\"\n | \"landscape-primary\"\n | \"landscape-secondary\"\n | \"portrait\"\n | \"landscape\"\n | \"unknown\";\n\nexport interface CreateOrientationOptions {\n /** Callback fired when screen orientation changes. */\n onChange?: (orientation: ScreenOrientationType, angle: number) => void;\n}\n\nexport interface CreateOrientationReturn {\n /** Signal indicating current orientation type. */\n type: Accessor<ScreenOrientationType>;\n /** Signal indicating current orientation angle in degrees (0, 90, 180, 270). */\n angle: Accessor<number>;\n /** Signal indicating if device screen is in portrait mode. */\n isPortrait: Accessor<boolean>;\n /** Signal indicating if device screen is in landscape mode. */\n isLandscape: Accessor<boolean>;\n /** Lock screen orientation if supported by device/browser. */\n lock: (orientation: string) => Promise<void>;\n /** Unlock screen orientation. */\n unlock: () => void;\n}\n\n/**\n * SolidJS reactive primitive for observing mobile/desktop screen orientation and angle.\n */\nexport function createOrientation(\n options: CreateOrientationOptions = {}\n): CreateOrientationReturn {\n const [type, setType] = createSignal<ScreenOrientationType>(\"unknown\");\n const [angle, setAngle] = createSignal<number>(0);\n\n const getOrientationState = (): { type: ScreenOrientationType; angle: number } => {\n if (typeof window === \"undefined\") {\n return { type: \"unknown\", angle: 0 };\n }\n\n if (window.screen?.orientation) {\n return {\n type: window.screen.orientation.type as ScreenOrientationType,\n angle: window.screen.orientation.angle || 0,\n };\n }\n\n /* Fallback for older browsers using window.orientation */\n const legacyAngle = (window as unknown as { orientation?: number }).orientation ?? 0;\n const isPortraitMode = Math.abs(legacyAngle) !== 90;\n return {\n type: isPortraitMode ? \"portrait\" : \"landscape\",\n angle: Number(legacyAngle),\n };\n };\n\n createEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const updateState = (): void => {\n const state = getOrientationState();\n setType(state.type);\n setAngle(state.angle);\n options.onChange?.(state.type, state.angle);\n };\n\n updateState();\n\n if (window.screen?.orientation) {\n window.screen.orientation.addEventListener(\"change\", updateState);\n onCleanup(() => {\n window.screen.orientation.removeEventListener(\"change\", updateState);\n });\n } else {\n window.addEventListener(\"orientationchange\", updateState);\n window.addEventListener(\"resize\", updateState);\n onCleanup(() => {\n window.removeEventListener(\"orientationchange\", updateState);\n window.removeEventListener(\"resize\", updateState);\n });\n }\n });\n\n const isPortrait = (): boolean => {\n const currentType = type();\n return currentType.startsWith(\"portrait\");\n };\n\n const isLandscape = (): boolean => {\n const currentType = type();\n return currentType.startsWith(\"landscape\");\n };\n\n const lock = async (orientation: string): Promise<void> => {\n if (typeof window !== \"undefined\" && window.screen?.orientation) {\n const orientationApi = window.screen.orientation as unknown as {\n lock?: (orient: string) => Promise<void>;\n };\n if (typeof orientationApi.lock === \"function\") {\n await orientationApi.lock(orientation);\n }\n }\n };\n\n const unlock = (): void => {\n if (typeof window !== \"undefined\" && window.screen?.orientation) {\n const orientationApi = window.screen.orientation as unknown as {\n unlock?: () => void;\n };\n if (typeof orientationApi.unlock === \"function\") {\n orientationApi.unlock();\n }\n }\n };\n\n return {\n type,\n angle,\n isPortrait,\n isLandscape,\n lock,\n unlock,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }