@nikala-ui/hooks 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Giorgi Magradze
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO QUALITY AND FITNESS FOR A PARTICULAR
17
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,116 +1,161 @@
1
1
  # @nikala-ui/hooks
2
2
 
3
- Reactive SolidJS state primitives and custom hooks for **Nikala UI**.
4
-
5
- Honoring the iconic Georgian painter **Niko Pirosmani (Nikala)**.
6
-
7
- Official Documentation & Interactive Demos: [nikala.dev](https://nikala.dev)
8
-
9
- ---
10
-
11
- ## Overview
12
-
13
- This package provides reusable, fine-grained reactive primitives designed natively for SolidJS applications. It simplifies managing complex component state such as controlled and uncontrolled inputs, state synchronization, browser APIs, and reactive event callbacks.
14
-
15
- ---
16
-
17
- ## Core Features & Available Hooks
18
-
19
- - **`createControllableSignal`** — SolidJS reactive primitive for managing state supporting both controlled and uncontrolled modes with a unified setter API.
20
- - **`createClickOutside`** — SolidJS reactive primitive for detecting user interactions outside specified element(s) or refs.
21
- - **`createClipboard`** — SolidJS reactive primitive for copying text to clipboard with automatic status reset.
22
- - **`createKeybindings` / `createEscapeKey`** — SolidJS reactive primitives for listening to keyboard shortcuts, key combinations, and Escape key presses.
23
- - **`createLockScroll`** — SolidJS reactive primitive for locking body or container scrolling when overlays are active.
24
- - **`createDisclosure`** — SolidJS reactive primitive for managing boolean open/close state with toggle, open, and close helpers.
25
- - **`createMediaQuery` / `createBreakpoint`** — SolidJS reactive primitives for tracking CSS media queries and responsive Tailwind breakpoints.
26
- - **`createDebounce` / `createThrottle`** — SolidJS reactive primitives for debouncing and throttling rate-limited function execution.
27
- - **`createIntersectionObserver` / `createInView`** — SolidJS reactive primitives for detecting element viewport visibility and scroll animation triggers.
28
- - **`createTimer` / `createCountdown`** — SolidJS reactive primitives for recurring interval ticks and formatted countdown timers.
29
- - **`createResizeObserver` / `createElementSize`** — SolidJS reactive primitives for tracking element width and height dimensions dynamically.
30
- - **`createWindowSize`** — SolidJS reactive primitive for tracking window viewport inner width and height.
31
- - **`createScrollPosition`** — SolidJS reactive primitive for tracking scroll position, scroll direction, and top/bottom container status.
32
- - **`createFocusTrap`** — SolidJS reactive primitive for trapping keyboard focus inside target container element for accessibility (WCAG).
33
- - **`createMousePosition`** — SolidJS reactive primitive for tracking global and element-relative mouse pointer coordinates.
34
- - **`createLongPress`** — SolidJS reactive primitive for detecting long press / hold touch and pointer interactions.
35
- - **`createHover`** — SolidJS reactive primitive for tracking element hover state with entrance/exit delays.
36
- - **`createLocalStorage` / `createSessionStorage`** — SolidJS reactive primitives for Web Storage state synchronization across tabs.
37
- - **`createPrevious`** — SolidJS reactive primitive for tracking previous value of a signal accessor.
38
- - **`createNetworkStatus` / `createOnline`** — SolidJS reactive primitives for tracking browser network connectivity and connection quality metrics.
39
- - **`createColorMode`** — SolidJS reactive primitive for managing dark/light themes and system preferences.
40
- - **`createForm`** — SolidJS reactive primitive for form state management, field validation, errors, and submission.
41
- - **`createInputMask`** — SolidJS reactive primitive for input value masking (phone numbers, credit cards, dates).
42
- - **`createIdle`** — SolidJS reactive primitive for detecting user inactivity with customizable timeout and activity events.
43
- - **`createActiveElement`** — SolidJS reactive primitive for tracking the currently focused DOM element.
44
- - **`createInfiniteScroll`** — SolidJS reactive primitive for infinite scrolling data fetching and threshold triggers.
45
- - **`createFullscreen`** — SolidJS reactive primitive for toggling and observing Fullscreen API state.
46
- - **`createAudio` / `createVideo`** — SolidJS reactive primitives for media playback, volume, duration, and control tracking.
47
- - **`createOrientation`** — SolidJS reactive primitive for observing device screen orientation and angle.
48
- - **`createUndoRedo`** — SolidJS reactive primitive for managing state history, undo/redo stacks, and reset capabilities.
49
- - **`createFetch`** — SolidJS reactive primitive for reactive HTTP data fetching, loading states, and error handling.
50
- - **`createGeolocation`** — SolidJS reactive primitive for tracking device GPS coordinates and location errors.
51
- - **`createPermission`** — SolidJS reactive primitive for querying and observing browser permission status changes.
52
- - **`createBattery`** — SolidJS reactive primitive for observing device battery charge level and charging status.
53
- - **`createWebNotification`** — SolidJS reactive primitive for sending browser desktop notifications and managing permissions.
54
- - **`createWebSocket`** — SolidJS reactive primitive for WebSocket client connections, auto-reconnections, and message passing.
55
- - **`createDocumentTitle`** — SolidJS reactive primitive for managing document title dynamically with unmount restoration.
56
- - **`createFavicon`** — SolidJS reactive primitive for dynamically updating browser favicon link element.
57
- - **`createEventSource`** — SolidJS reactive primitive for subscribing to Server-Sent Events (SSE) streams.
58
- - **Native SolidJS Reactivity** — Zero-dependency, fine-grained reactivity built directly on SolidJS signals.
59
- - **TypeScript First** — Fully typed options, getters, and return tuple interfaces.
60
-
61
- ---
62
-
63
- ## Installation & Usage
64
-
65
- Install the hooks package via your preferred package manager:
3
+ Reactive SolidJS primitives for browser state, events, media APIs, storage, forms, and application behavior.
4
+
5
+ Nikala UI follows a pure copy-paste ownership model. This package is the source distribution used by the registry and documentation tooling. Application developers add hooks with `@nikala-ui/cli`, which copies the source into their own project.
6
+
7
+ Documentation and interactive examples: [nikala.dev](https://nikala.dev)
8
+
9
+ ## Add a hook to an application
10
+
11
+ Initialize Nikala UI if the project has not been configured yet, then add the required hook:
66
12
 
67
13
  ```bash
68
- bun add @nikala-ui/hooks
69
- # or
70
- npm install @nikala-ui/hooks
14
+ bunx @nikala-ui/cli init
15
+ bunx @nikala-ui/cli add --hook create-clipboard
16
+ ```
17
+
18
+ The CLI copies the selected source into `src/hooks/` and rewrites its local imports. The application owns the copied file and does not add `@nikala-ui/hooks` as a runtime dependency.
19
+
20
+ ## Usage
21
+
22
+ Import the copied source from the project's local hooks directory:
23
+
24
+ ```tsx
25
+ import { createControllableSignal } from "@/hooks/create-controllable-signal";
26
+
27
+ const [value, setValue] = createControllableSignal({
28
+ defaultValue: "initial value",
29
+ });
71
30
  ```
72
31
 
73
- Import hooks directly into your SolidJS components:
32
+ The copied primitive uses SolidJS accessors and setters so updates remain fine-grained and reactive.
33
+
34
+ ## Available primitives
35
+
36
+ The package currently exports the following source modules:
37
+
38
+ ### State and interaction
39
+
40
+ - `createControllableSignal`
41
+ - `createDisclosure`
42
+ - `createPrevious`
43
+ - `createUndoRedo`
44
+ - `createPagination`
45
+ - `createForm`
46
+ - `createInputMask`
47
+ - `createDebounce`
48
+ - `createHover`
49
+ - `createLongPress`
50
+ - `createKeybindings`
51
+ - `createGlobalShortcut`
52
+ - `createClickOutside`
53
+ - `createFocusTrap`
54
+ - `createLockScroll`
55
+
56
+ ### Browser and viewport APIs
57
+
58
+ - `createActiveElement`
59
+ - `createWindowSize`
60
+ - `createMousePosition`
61
+ - `createMediaQuery`
62
+ - `createResizeObserver`
63
+ - `createIntersectionObserver`
64
+ - `createScrollPosition`
65
+ - `createScrollIntoView`
66
+ - `createInfiniteScroll`
67
+ - `createOrientation`
68
+ - `createFullscreen`
69
+ - `createNetworkStatus`
70
+ - `createPermission`
71
+ - `createGeolocation`
72
+ - `createBattery`
73
+
74
+ ### Storage, media, and communication
75
+
76
+ - `createStorage`, including local and session storage helpers
77
+ - `createClipboard`
78
+ - `createColorMode`
79
+ - `createDocumentTitle`
80
+ - `createFavicon`
81
+ - `createWebNotification`
82
+ - `createWebSocket`
83
+ - `createEventSource`
84
+ - `createAudioVideo`, which provides audio and video helpers
85
+ - `createTauriWindow`
86
+ - `createAppUpdater`
87
+ - `createChatScroll`
88
+ - `createDocumentTabs`
89
+ - `createTimer`
90
+ - `createIdle`
91
+ - `createFetch`
92
+ - `createDropZone`
93
+
94
+ ### Editor integration
95
+
96
+ - `createTiptapEditor`
97
+
98
+ The public exports are defined in [`src/index.ts`](./src/index.ts). The source filenames use kebab-case and can also be copied individually by the CLI.
99
+
100
+ ## Examples
101
+
102
+ ### Controllable and uncontrolled state
74
103
 
75
104
  ```tsx
76
- import { createControllableSignal } from "@nikala-ui/hooks";
105
+ import { createControllableSignal } from "@/hooks/create-controllable-signal";
77
106
 
78
107
  const [value, setValue] = createControllableSignal({
79
- defaultValue: "Uncontrolled Initial State",
108
+ defaultValue: 0,
109
+ onChange: (nextValue) => console.log(nextValue),
80
110
  });
111
+
112
+ setValue((previousValue) => (previousValue ?? 0) + 1);
81
113
  ```
82
114
 
83
- ### Form validation and submission
115
+ ### Clipboard state
84
116
 
85
- `createForm` supports change-, blur-, and submit-time validation, typed checkbox/select event values, stale async validation protection, and submit error state:
117
+ ```tsx
118
+ import { createClipboard } from "@/hooks/create-clipboard";
119
+
120
+ const clipboard = createClipboard();
121
+
122
+ await clipboard.copy("Copied text");
123
+ ```
124
+
125
+ ### Form state
86
126
 
87
127
  ```tsx
128
+ import { createForm } from "@/hooks/create-form";
129
+
88
130
  const form = createForm({
89
- initialValues: { email: "", marketing: false },
131
+ initialValues: { email: "" },
90
132
  validateOn: "blur",
91
- validate: (values) => ({
92
- ...(!values.email.includes("@") && { email: "Enter a valid email." }),
93
- }),
133
+ validate: (values) =>
134
+ values.email.includes("@") ? {} : { email: "Enter a valid email." },
94
135
  onSubmit: async (values) => saveProfile(values),
95
136
  });
96
-
97
- <Form onSubmit={form.handleSubmit} loading={form.isSubmitting()}>
98
- <Input
99
- value={form.values().email}
100
- onInput={form.handleChange("email")}
101
- onBlur={form.handleBlur("email")}
102
- />
103
- <FormMessage form={form} name="email" />
104
- {form.submitError() && <p>Could not save changes.</p>}
105
- </Form>
106
137
  ```
107
138
 
108
- ---
139
+ `createForm` exposes accessors for values, errors, touched state, submission state, and validity, together with field update and event-handler helpers.
109
140
 
110
- ## Documentation & Links
141
+ ## SSR and browser APIs
111
142
 
112
- - Repository: [github.com/nikala-ui/ui](https://github.com/nikala-ui/ui)
113
- - Hooks Documentation: [nikala.dev/docs/hooks](https://nikala.dev/docs/hooks)
143
+ Primitives that access `window`, `document`, browser events, or browser-only APIs are designed for SolidJS applications and guard browser operations for server-rendered environments. Browser-dependent behavior should still be used from the appropriate client lifecycle in an SSR application.
144
+
145
+ ## Related packages
146
+
147
+ - `@nikala-ui/cli` copies selected hook sources into an application.
148
+ - `@nikala-ui/core` contains the internal registry manifests and source metadata.
149
+ - `@nikala-ui/docs` bundles source snapshots so generated documentation projects can own their local hooks.
150
+
151
+ ## Package development
152
+
153
+ Typecheck the package from the monorepo root or from this directory:
154
+
155
+ ```bash
156
+ cd packages/hooks
157
+ bunx tsc --noEmit
158
+ ```
114
159
 
115
160
  ## License
116
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikala-ui/hooks",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Reactive SolidJS primitives and primitives for Nikala UI",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -23,5 +23,26 @@
23
23
  },
24
24
  "peerDependencies": {
25
25
  "solid-js": "^1.9.0"
26
+ },
27
+ "dependencies": {
28
+ "@tiptap/core": "^3.31.0",
29
+ "@tiptap/extension-character-count": "^3.31.0",
30
+ "@tiptap/extension-highlight": "^3.31.0",
31
+ "@tiptap/extension-image": "^3.31.0",
32
+ "@tiptap/extension-link": "^3.31.0",
33
+ "@tiptap/extension-placeholder": "^3.31.0",
34
+ "@tiptap/extension-subscript": "^3.31.0",
35
+ "@tiptap/extension-superscript": "^3.31.0",
36
+ "@tiptap/extension-table": "^3.31.0",
37
+ "@tiptap/extension-table-cell": "^3.31.0",
38
+ "@tiptap/extension-table-header": "^3.31.0",
39
+ "@tiptap/extension-table-row": "^3.31.0",
40
+ "@tiptap/extension-task-item": "^3.31.0",
41
+ "@tiptap/extension-task-list": "^3.31.0",
42
+ "@tiptap/extension-text-align": "^3.31.0",
43
+ "@tiptap/extension-typography": "^3.31.0",
44
+ "@tiptap/extension-underline": "^3.31.0",
45
+ "@tiptap/starter-kit": "^3.31.0",
46
+ "tiptap-markdown": "^0.9.0"
26
47
  }
27
48
  }
@@ -0,0 +1,272 @@
1
+ import { createSignal, onMount, type Accessor } from "solid-js";
2
+ import { isTauriEnvironment } from "./create-tauri-window";
3
+
4
+ export type UpdaterStatus =
5
+ | "idle"
6
+ | "checking"
7
+ | "available"
8
+ | "up-to-date"
9
+ | "downloading"
10
+ | "downloaded"
11
+ | "error";
12
+
13
+ export interface UpdateManifestInfo {
14
+ version: string;
15
+ currentVersion?: string;
16
+ date?: string;
17
+ body?: string;
18
+ }
19
+
20
+ export interface UpdateProgressInfo {
21
+ downloaded: number;
22
+ total: number;
23
+ percentage: number;
24
+ }
25
+
26
+ export interface CreateAppUpdaterOptions {
27
+ /** Automatically check for updates on mount. Defaults to false. */
28
+ autoCheck?: boolean;
29
+ /** Current application version fallback if not detected. */
30
+ currentVersion?: string;
31
+ /** Callback fired when an update is available. */
32
+ onUpdateAvailable?: (info: UpdateManifestInfo) => void;
33
+ /** Callback fired when download finishes. */
34
+ onDownloadFinished?: () => void;
35
+ /** Callback fired on update check or download error. */
36
+ onError?: (error: Error | string) => void;
37
+ }
38
+
39
+ export interface CreateAppUpdaterReturn {
40
+ /** Current lifecycle status of the updater. */
41
+ status: Accessor<UpdaterStatus>;
42
+ /** Update metadata (new version, release notes, release date). */
43
+ updateInfo: Accessor<UpdateManifestInfo | null>;
44
+ /** Download progress (bytes downloaded, total bytes, percentage). */
45
+ progress: Accessor<UpdateProgressInfo>;
46
+ /** Error message if an operation failed. */
47
+ error: Accessor<string | null>;
48
+ /** Whether an update is currently being checked or downloaded. */
49
+ isLoading: Accessor<boolean>;
50
+ /** Check update endpoint for a new release. */
51
+ checkForUpdates: () => Promise<boolean>;
52
+ /** Download and install the available update. */
53
+ downloadAndInstall: () => Promise<void>;
54
+ /** Restart application to apply the downloaded update. */
55
+ relaunch: () => Promise<void>;
56
+ /** Dismiss current update prompt or clear error state. */
57
+ dismiss: () => void;
58
+ /** Simulate an update cycle in web / playground preview environments. */
59
+ simulateUpdate: (mockInfo?: Partial<UpdateManifestInfo>) => void;
60
+ }
61
+
62
+ /**
63
+ * SolidJS reactive primitive for controlling and observing Tauri v2 application updates.
64
+ * Integrates with @tauri-apps/plugin-updater with full web simulation support.
65
+ */
66
+ export function createAppUpdater(
67
+ options: CreateAppUpdaterOptions = {}
68
+ ): CreateAppUpdaterReturn {
69
+ const [status, setStatus] = createSignal<UpdaterStatus>("idle");
70
+ const [updateInfo, setUpdateInfo] = createSignal<UpdateManifestInfo | null>(null);
71
+ const [progress, setProgress] = createSignal<UpdateProgressInfo>({
72
+ downloaded: 0,
73
+ total: 0,
74
+ percentage: 0,
75
+ });
76
+ const [error, setError] = createSignal<string | null>(null);
77
+
78
+ let activeTauriUpdate: any = null;
79
+ let simulatedTimer: any = null;
80
+
81
+ const getTauriUpdaterPlugin = async () => {
82
+ if (typeof window === "undefined") return null;
83
+ try {
84
+ if ((window as any).__TAURI__?.updater) {
85
+ return (window as any).__TAURI__.updater;
86
+ }
87
+ const moduleName = "@tauri-apps/plugin-updater";
88
+ // @ts-ignore - Optional runtime dependency in Tauri apps
89
+ const plugin = await import(/* @vite-ignore */ moduleName).catch(() => null);
90
+ return plugin;
91
+ } catch {
92
+ return null;
93
+ }
94
+ };
95
+
96
+ const getTauriProcessPlugin = async () => {
97
+ if (typeof window === "undefined") return null;
98
+ try {
99
+ if ((window as any).__TAURI__?.process) {
100
+ return (window as any).__TAURI__.process;
101
+ }
102
+ const moduleName = "@tauri-apps/plugin-process";
103
+ // @ts-ignore - Optional runtime dependency in Tauri apps
104
+ const plugin = await import(/* @vite-ignore */ moduleName).catch(() => null);
105
+ return plugin;
106
+ } catch {
107
+ return null;
108
+ }
109
+ };
110
+
111
+ const checkForUpdates = async (): Promise<boolean> => {
112
+ setStatus("checking");
113
+ setError(null);
114
+
115
+ if (isTauriEnvironment()) {
116
+ try {
117
+ const updaterPlugin = await getTauriUpdaterPlugin();
118
+ if (updaterPlugin?.check) {
119
+ const update = await updaterPlugin.check();
120
+ if (update?.available) {
121
+ activeTauriUpdate = update;
122
+ const info: UpdateManifestInfo = {
123
+ version: update.version,
124
+ currentVersion: update.currentVersion || options.currentVersion || "v1.0.0",
125
+ date: update.date,
126
+ body: update.body || "Bug fixes and performance enhancements.",
127
+ };
128
+ setUpdateInfo(info);
129
+ setStatus("available");
130
+ options.onUpdateAvailable?.(info);
131
+ return true;
132
+ } else {
133
+ setStatus("up-to-date");
134
+ return false;
135
+ }
136
+ }
137
+ } catch (err: any) {
138
+ const errMsg = err?.message || String(err);
139
+ setError(errMsg);
140
+ setStatus("error");
141
+ options.onError?.(errMsg);
142
+ return false;
143
+ }
144
+ }
145
+
146
+ // In web preview / non-tauri mode, simulate checking
147
+ await new Promise((resolve) => setTimeout(resolve, 800));
148
+ setStatus("up-to-date");
149
+ return false;
150
+ };
151
+
152
+ const downloadAndInstall = async (): Promise<void> => {
153
+ if (status() !== "available" && status() !== "error") return;
154
+ setStatus("downloading");
155
+ setError(null);
156
+ setProgress({ downloaded: 0, total: 100, percentage: 0 });
157
+
158
+ if (isTauriEnvironment() && activeTauriUpdate?.downloadAndInstall) {
159
+ try {
160
+ let downloadedBytes = 0;
161
+ let contentLength = 0;
162
+
163
+ await activeTauriUpdate.downloadAndInstall((event: any) => {
164
+ if (event.event === "Started") {
165
+ contentLength = event.data?.contentLength || 100;
166
+ } else if (event.event === "Progress") {
167
+ downloadedBytes += event.data?.chunkLength || 0;
168
+ const pct = contentLength > 0 ? Math.min(100, Math.round((downloadedBytes / contentLength) * 100)) : 50;
169
+ setProgress({
170
+ downloaded: downloadedBytes,
171
+ total: contentLength,
172
+ percentage: pct,
173
+ });
174
+ } else if (event.event === "Finished") {
175
+ setProgress({
176
+ downloaded: contentLength,
177
+ total: contentLength,
178
+ percentage: 100,
179
+ });
180
+ }
181
+ });
182
+
183
+ setStatus("downloaded");
184
+ options.onDownloadFinished?.();
185
+ return;
186
+ } catch (err: any) {
187
+ const errMsg = err?.message || String(err);
188
+ setError(errMsg);
189
+ setStatus("error");
190
+ options.onError?.(errMsg);
191
+ return;
192
+ }
193
+ }
194
+
195
+ // Web simulation mode with smooth progress step
196
+ let currentPct = 0;
197
+ simulatedTimer = setInterval(() => {
198
+ currentPct += 15;
199
+ if (currentPct >= 100) {
200
+ clearInterval(simulatedTimer);
201
+ setProgress({ downloaded: 45.8 * 1024 * 1024, total: 45.8 * 1024 * 1024, percentage: 100 });
202
+ setStatus("downloaded");
203
+ options.onDownloadFinished?.();
204
+ } else {
205
+ const downloaded = (45.8 * 1024 * 1024 * currentPct) / 100;
206
+ setProgress({ downloaded, total: 45.8 * 1024 * 1024, percentage: currentPct });
207
+ }
208
+ }, 250);
209
+ };
210
+
211
+ const relaunch = async (): Promise<void> => {
212
+ if (isTauriEnvironment()) {
213
+ try {
214
+ const processPlugin = await getTauriProcessPlugin();
215
+ if (processPlugin?.relaunch) {
216
+ await processPlugin.relaunch();
217
+ return;
218
+ }
219
+ } catch {
220
+ // Fallback
221
+ }
222
+ }
223
+
224
+ if (typeof window !== "undefined") {
225
+ window.location.reload();
226
+ }
227
+ };
228
+
229
+ const dismiss = (): void => {
230
+ if (simulatedTimer) clearInterval(simulatedTimer);
231
+ setStatus("idle");
232
+ setError(null);
233
+ };
234
+
235
+ const simulateUpdate = (mockInfo?: Partial<UpdateManifestInfo>): void => {
236
+ if (simulatedTimer) clearInterval(simulatedTimer);
237
+ const info: UpdateManifestInfo = {
238
+ version: mockInfo?.version || "v1.2.0",
239
+ currentVersion: mockInfo?.currentVersion || options.currentVersion || "v1.0.0",
240
+ date: mockInfo?.date || new Date().toISOString().split("T")[0],
241
+ body:
242
+ mockInfo?.body ||
243
+ "### What's New in v1.2.0\n- Added native Window Titlebar tabs\n- Enhanced Tauri v2 capability security\n- Optimized SolidJS signal reactivity\n- Fixed titlebar drag region jitter on Linux",
244
+ };
245
+ setUpdateInfo(info);
246
+ setStatus("available");
247
+ setError(null);
248
+ setProgress({ downloaded: 0, total: 100, percentage: 0 });
249
+ options.onUpdateAvailable?.(info);
250
+ };
251
+
252
+ onMount(() => {
253
+ if (options.autoCheck) {
254
+ checkForUpdates();
255
+ }
256
+ });
257
+
258
+ const isLoading = () => status() === "checking" || status() === "downloading";
259
+
260
+ return {
261
+ status,
262
+ updateInfo,
263
+ progress,
264
+ error,
265
+ isLoading,
266
+ checkForUpdates,
267
+ downloadAndInstall,
268
+ relaunch,
269
+ dismiss,
270
+ simulateUpdate,
271
+ };
272
+ }