pulse-js-framework 1.7.5 → 1.7.8
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/README.md +78 -392
- package/cli/dev.js +14 -0
- package/cli/docs-test.js +633 -0
- package/cli/index.js +313 -31
- package/cli/lint.js +13 -4
- package/cli/logger.js +32 -4
- package/cli/release.js +50 -20
- package/compiler/parser.js +1 -1
- package/package.json +11 -4
- package/runtime/dom-advanced.js +357 -0
- package/runtime/dom-binding.js +230 -0
- package/runtime/dom-conditional.js +133 -0
- package/runtime/dom-element.js +142 -0
- package/runtime/dom-lifecycle.js +178 -0
- package/runtime/dom-list.js +267 -0
- package/runtime/dom-selector.js +267 -0
- package/runtime/dom.js +119 -1279
- package/runtime/form.js +417 -22
- package/runtime/native.js +398 -52
- package/runtime/pulse.js +1 -1
- package/runtime/router.js +6 -5
- package/runtime/store.js +81 -6
- package/types/async.d.ts +310 -0
- package/types/form.d.ts +378 -0
- package/types/index.d.ts +44 -0
- /package/{core → runtime}/errors.js +0 -0
package/types/async.d.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Framework - Async Primitives Type Definitions
|
|
3
|
+
* @module pulse-js-framework/runtime/async
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Pulse } from './pulse';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Versioned Async - Race Condition Handling
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/** Context returned by begin() for tracking async operation validity */
|
|
13
|
+
export interface VersionedContext {
|
|
14
|
+
/** Check if this operation is still valid */
|
|
15
|
+
isCurrent(): boolean;
|
|
16
|
+
|
|
17
|
+
/** Check if this operation has been superseded */
|
|
18
|
+
isStale(): boolean;
|
|
19
|
+
|
|
20
|
+
/** Execute callback only if this operation is still current */
|
|
21
|
+
ifCurrent<T>(fn: () => T): T | undefined;
|
|
22
|
+
|
|
23
|
+
/** Set a timeout that auto-clears when operation becomes stale */
|
|
24
|
+
setTimeout(fn: () => void, ms: number): number;
|
|
25
|
+
|
|
26
|
+
/** Set an interval that auto-clears when operation becomes stale */
|
|
27
|
+
setInterval(fn: () => void, ms: number): number;
|
|
28
|
+
|
|
29
|
+
/** Clear a registered timeout */
|
|
30
|
+
clearTimeout(id: number): void;
|
|
31
|
+
|
|
32
|
+
/** Clear a registered interval */
|
|
33
|
+
clearInterval(id: number): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Controller for managing versioned async operations */
|
|
37
|
+
export interface VersionedAsyncController {
|
|
38
|
+
/** Start a new versioned operation, invalidating previous ones */
|
|
39
|
+
begin(): VersionedContext;
|
|
40
|
+
|
|
41
|
+
/** Abort the current operation */
|
|
42
|
+
abort(): void;
|
|
43
|
+
|
|
44
|
+
/** Get current version number */
|
|
45
|
+
getVersion(): number;
|
|
46
|
+
|
|
47
|
+
/** Clean up all timers without aborting */
|
|
48
|
+
cleanup(): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Options for createVersionedAsync */
|
|
52
|
+
export interface VersionedAsyncOptions {
|
|
53
|
+
/** Callback invoked when operation is aborted */
|
|
54
|
+
onAbort?: () => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a versioned async controller for race condition handling.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const controller = createVersionedAsync();
|
|
62
|
+
*
|
|
63
|
+
* async function fetchData() {
|
|
64
|
+
* const ctx = controller.begin();
|
|
65
|
+
* const data = await fetch('/api/data').then(r => r.json());
|
|
66
|
+
* ctx.ifCurrent(() => setState(data));
|
|
67
|
+
* }
|
|
68
|
+
*/
|
|
69
|
+
export declare function createVersionedAsync(
|
|
70
|
+
options?: VersionedAsyncOptions
|
|
71
|
+
): VersionedAsyncController;
|
|
72
|
+
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// useAsync - Reactive Async Operation Handler
|
|
75
|
+
// ============================================================================
|
|
76
|
+
|
|
77
|
+
/** Status of an async operation */
|
|
78
|
+
export type AsyncStatus = 'idle' | 'loading' | 'success' | 'error';
|
|
79
|
+
|
|
80
|
+
/** Options for useAsync */
|
|
81
|
+
export interface UseAsyncOptions<T> {
|
|
82
|
+
/** Execute immediately on creation (default: true) */
|
|
83
|
+
immediate?: boolean;
|
|
84
|
+
|
|
85
|
+
/** Initial data value (default: null) */
|
|
86
|
+
initialData?: T | null;
|
|
87
|
+
|
|
88
|
+
/** Callback invoked on error */
|
|
89
|
+
onError?: (error: Error) => void;
|
|
90
|
+
|
|
91
|
+
/** Callback invoked on success */
|
|
92
|
+
onSuccess?: (data: T) => void;
|
|
93
|
+
|
|
94
|
+
/** Number of retry attempts on failure (default: 0) */
|
|
95
|
+
retries?: number;
|
|
96
|
+
|
|
97
|
+
/** Delay between retries in ms (default: 1000) */
|
|
98
|
+
retryDelay?: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Return type of useAsync */
|
|
102
|
+
export interface UseAsyncReturn<T> {
|
|
103
|
+
/** Reactive data value */
|
|
104
|
+
data: Pulse<T | null>;
|
|
105
|
+
|
|
106
|
+
/** Reactive error state */
|
|
107
|
+
error: Pulse<Error | null>;
|
|
108
|
+
|
|
109
|
+
/** Reactive loading state */
|
|
110
|
+
loading: Pulse<boolean>;
|
|
111
|
+
|
|
112
|
+
/** Reactive status ('idle' | 'loading' | 'success' | 'error') */
|
|
113
|
+
status: Pulse<AsyncStatus>;
|
|
114
|
+
|
|
115
|
+
/** Execute the async function */
|
|
116
|
+
execute(...args: unknown[]): Promise<T | null>;
|
|
117
|
+
|
|
118
|
+
/** Reset state to initial values */
|
|
119
|
+
reset(): void;
|
|
120
|
+
|
|
121
|
+
/** Abort current execution */
|
|
122
|
+
abort(): void;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create a reactive async operation handler.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* const { data, loading, error, execute } = useAsync(
|
|
130
|
+
* () => fetch('/api/users').then(r => r.json()),
|
|
131
|
+
* { retries: 3, onSuccess: (data) => console.log('Got:', data) }
|
|
132
|
+
* );
|
|
133
|
+
*/
|
|
134
|
+
export declare function useAsync<T>(
|
|
135
|
+
asyncFn: (...args: unknown[]) => Promise<T>,
|
|
136
|
+
options?: UseAsyncOptions<T>
|
|
137
|
+
): UseAsyncReturn<T>;
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// useResource - SWR-style Resource Fetching
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
/** Options for useResource */
|
|
144
|
+
export interface ResourceOptions<T> {
|
|
145
|
+
/** Auto-refresh interval in ms */
|
|
146
|
+
refreshInterval?: number;
|
|
147
|
+
|
|
148
|
+
/** Refresh when window regains focus (default: false) */
|
|
149
|
+
refreshOnFocus?: boolean;
|
|
150
|
+
|
|
151
|
+
/** Refresh when network reconnects (default: false) */
|
|
152
|
+
refreshOnReconnect?: boolean;
|
|
153
|
+
|
|
154
|
+
/** Initial data value (default: null) */
|
|
155
|
+
initialData?: T | null;
|
|
156
|
+
|
|
157
|
+
/** Callback invoked on error */
|
|
158
|
+
onError?: (error: Error) => void;
|
|
159
|
+
|
|
160
|
+
/** Time in ms before data is considered stale (default: 0) */
|
|
161
|
+
staleTime?: number;
|
|
162
|
+
|
|
163
|
+
/** Time in ms to keep data in cache (default: 300000 = 5 min) */
|
|
164
|
+
cacheTime?: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Return type of useResource */
|
|
168
|
+
export interface UseResourceReturn<T> {
|
|
169
|
+
/** Reactive data value */
|
|
170
|
+
data: Pulse<T | null>;
|
|
171
|
+
|
|
172
|
+
/** Reactive error state */
|
|
173
|
+
error: Pulse<Error | null>;
|
|
174
|
+
|
|
175
|
+
/** Reactive loading state (initial fetch) */
|
|
176
|
+
loading: Pulse<boolean>;
|
|
177
|
+
|
|
178
|
+
/** Reactive stale state */
|
|
179
|
+
isStale: Pulse<boolean>;
|
|
180
|
+
|
|
181
|
+
/** Reactive validating state (background refresh) */
|
|
182
|
+
isValidating: Pulse<boolean>;
|
|
183
|
+
|
|
184
|
+
/** Last fetch timestamp */
|
|
185
|
+
lastFetchTime: Pulse<number>;
|
|
186
|
+
|
|
187
|
+
/** Fetch data (uses cache if available and fresh) */
|
|
188
|
+
fetch(): Promise<T | null>;
|
|
189
|
+
|
|
190
|
+
/** Force refresh, ignoring cache */
|
|
191
|
+
refresh(): Promise<T | null>;
|
|
192
|
+
|
|
193
|
+
/** Mutate data optimistically */
|
|
194
|
+
mutate(newData: T | ((current: T | null) => T), shouldRevalidate?: boolean): void;
|
|
195
|
+
|
|
196
|
+
/** Clear cache for this resource */
|
|
197
|
+
invalidate(): void;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a reactive resource with caching and stale-while-revalidate.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* // Static key
|
|
205
|
+
* const users = useResource('users', () => fetch('/api/users').then(r => r.json()));
|
|
206
|
+
*
|
|
207
|
+
* // Dynamic key (re-fetches when userId changes)
|
|
208
|
+
* const userId = pulse(1);
|
|
209
|
+
* const user = useResource(
|
|
210
|
+
* () => `user-${userId.get()}`,
|
|
211
|
+
* () => fetch(`/api/users/${userId.get()}`).then(r => r.json())
|
|
212
|
+
* );
|
|
213
|
+
*/
|
|
214
|
+
export declare function useResource<T>(
|
|
215
|
+
key: string | (() => string),
|
|
216
|
+
fetcher: () => Promise<T>,
|
|
217
|
+
options?: ResourceOptions<T>
|
|
218
|
+
): UseResourceReturn<T>;
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// usePolling - Repeated Async Operations
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
/** Options for usePolling */
|
|
225
|
+
export interface PollingOptions {
|
|
226
|
+
/** Polling interval in ms (required) */
|
|
227
|
+
interval: number;
|
|
228
|
+
|
|
229
|
+
/** Execute immediately on start (default: true) */
|
|
230
|
+
immediate?: boolean;
|
|
231
|
+
|
|
232
|
+
/** Pause when page is hidden (default: true) */
|
|
233
|
+
pauseOnHidden?: boolean;
|
|
234
|
+
|
|
235
|
+
/** Pause when offline (default: true) */
|
|
236
|
+
pauseOnOffline?: boolean;
|
|
237
|
+
|
|
238
|
+
/** Max consecutive errors before stopping (default: 3) */
|
|
239
|
+
maxErrors?: number;
|
|
240
|
+
|
|
241
|
+
/** Callback invoked on error */
|
|
242
|
+
onError?: (error: Error) => void;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Return type of usePolling */
|
|
246
|
+
export interface UsePollingReturn<T> {
|
|
247
|
+
/** Reactive data value */
|
|
248
|
+
data: Pulse<T | null>;
|
|
249
|
+
|
|
250
|
+
/** Reactive error state */
|
|
251
|
+
error: Pulse<Error | null>;
|
|
252
|
+
|
|
253
|
+
/** Reactive polling state */
|
|
254
|
+
isPolling: Pulse<boolean>;
|
|
255
|
+
|
|
256
|
+
/** Consecutive error count */
|
|
257
|
+
errorCount: Pulse<number>;
|
|
258
|
+
|
|
259
|
+
/** Start polling */
|
|
260
|
+
start(): void;
|
|
261
|
+
|
|
262
|
+
/** Stop polling */
|
|
263
|
+
stop(): void;
|
|
264
|
+
|
|
265
|
+
/** Pause polling (without resetting state) */
|
|
266
|
+
pause(): void;
|
|
267
|
+
|
|
268
|
+
/** Resume polling */
|
|
269
|
+
resume(): void;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Create a polling mechanism for repeated async operations.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* const { data, start, stop, isPolling } = usePolling(
|
|
277
|
+
* () => fetch('/api/status').then(r => r.json()),
|
|
278
|
+
* { interval: 5000, pauseOnHidden: true }
|
|
279
|
+
* );
|
|
280
|
+
*
|
|
281
|
+
* start(); // Begin polling
|
|
282
|
+
* stop(); // Stop when done
|
|
283
|
+
*/
|
|
284
|
+
export declare function usePolling<T>(
|
|
285
|
+
asyncFn: () => Promise<T>,
|
|
286
|
+
options: PollingOptions
|
|
287
|
+
): UsePollingReturn<T>;
|
|
288
|
+
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// Cache Utilities
|
|
291
|
+
// ============================================================================
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Clear the entire resource cache
|
|
295
|
+
*/
|
|
296
|
+
export declare function clearResourceCache(): void;
|
|
297
|
+
|
|
298
|
+
/** Cache statistics */
|
|
299
|
+
export interface ResourceCacheStats {
|
|
300
|
+
/** Number of cached entries */
|
|
301
|
+
size: number;
|
|
302
|
+
|
|
303
|
+
/** List of cached keys */
|
|
304
|
+
keys: string[];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get resource cache statistics
|
|
309
|
+
*/
|
|
310
|
+
export declare function getResourceCacheStats(): ResourceCacheStats;
|
package/types/form.d.ts
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Framework - Form Management Type Definitions
|
|
3
|
+
* @module pulse-js-framework/runtime/form
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Pulse } from './pulse';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Validation Rules
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/** Sync validation result - true for valid, string for error message */
|
|
13
|
+
export type ValidationResult = true | string;
|
|
14
|
+
|
|
15
|
+
/** Async validation result */
|
|
16
|
+
export type AsyncValidationResult = Promise<ValidationResult>;
|
|
17
|
+
|
|
18
|
+
/** Base validation rule */
|
|
19
|
+
export interface ValidationRule {
|
|
20
|
+
/** Validation function */
|
|
21
|
+
validate: (value: unknown, allValues: Record<string, unknown>) => ValidationResult;
|
|
22
|
+
|
|
23
|
+
/** Default error message */
|
|
24
|
+
message?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Async validation rule */
|
|
28
|
+
export interface AsyncValidationRule {
|
|
29
|
+
/** Mark as async validator */
|
|
30
|
+
async: true;
|
|
31
|
+
|
|
32
|
+
/** Debounce delay in ms */
|
|
33
|
+
debounce?: number;
|
|
34
|
+
|
|
35
|
+
/** Async validation function */
|
|
36
|
+
validate: (value: unknown, allValues: Record<string, unknown>) => AsyncValidationResult;
|
|
37
|
+
|
|
38
|
+
/** Default error message */
|
|
39
|
+
message?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Any validation rule (sync or async) */
|
|
43
|
+
export type AnyValidationRule = ValidationRule | AsyncValidationRule;
|
|
44
|
+
|
|
45
|
+
/** Async validator options */
|
|
46
|
+
export interface AsyncValidatorOptions {
|
|
47
|
+
/** Debounce delay in ms (default: 300) */
|
|
48
|
+
debounce?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Built-in validators */
|
|
52
|
+
export interface Validators {
|
|
53
|
+
/** Required field validation */
|
|
54
|
+
required(message?: string): ValidationRule;
|
|
55
|
+
|
|
56
|
+
/** Minimum length validation */
|
|
57
|
+
minLength(length: number, message?: string): ValidationRule;
|
|
58
|
+
|
|
59
|
+
/** Maximum length validation */
|
|
60
|
+
maxLength(length: number, message?: string): ValidationRule;
|
|
61
|
+
|
|
62
|
+
/** Email format validation */
|
|
63
|
+
email(message?: string): ValidationRule;
|
|
64
|
+
|
|
65
|
+
/** URL format validation */
|
|
66
|
+
url(message?: string): ValidationRule;
|
|
67
|
+
|
|
68
|
+
/** Regex pattern validation */
|
|
69
|
+
pattern(pattern: RegExp, message?: string): ValidationRule;
|
|
70
|
+
|
|
71
|
+
/** Minimum value validation (for numbers) */
|
|
72
|
+
min(value: number, message?: string): ValidationRule;
|
|
73
|
+
|
|
74
|
+
/** Maximum value validation (for numbers) */
|
|
75
|
+
max(value: number, message?: string): ValidationRule;
|
|
76
|
+
|
|
77
|
+
/** Custom validation function */
|
|
78
|
+
custom(
|
|
79
|
+
fn: (value: unknown, allValues: Record<string, unknown>) => ValidationResult
|
|
80
|
+
): ValidationRule;
|
|
81
|
+
|
|
82
|
+
/** Match another field value */
|
|
83
|
+
matches(fieldName: string, message?: string): ValidationRule;
|
|
84
|
+
|
|
85
|
+
/** Async custom validation */
|
|
86
|
+
asyncCustom(
|
|
87
|
+
fn: (value: unknown, allValues: Record<string, unknown>) => AsyncValidationResult,
|
|
88
|
+
options?: AsyncValidatorOptions
|
|
89
|
+
): AsyncValidationRule;
|
|
90
|
+
|
|
91
|
+
/** Async email validation (check availability via API) */
|
|
92
|
+
asyncEmail(
|
|
93
|
+
checkFn: (email: string, allValues: Record<string, unknown>) => Promise<boolean>,
|
|
94
|
+
message?: string,
|
|
95
|
+
options?: AsyncValidatorOptions
|
|
96
|
+
): AsyncValidationRule;
|
|
97
|
+
|
|
98
|
+
/** Async unique validation (check uniqueness via API) */
|
|
99
|
+
asyncUnique(
|
|
100
|
+
checkFn: (value: unknown, allValues: Record<string, unknown>) => Promise<boolean>,
|
|
101
|
+
message?: string,
|
|
102
|
+
options?: AsyncValidatorOptions
|
|
103
|
+
): AsyncValidationRule;
|
|
104
|
+
|
|
105
|
+
/** Async server-side validation */
|
|
106
|
+
asyncServer(
|
|
107
|
+
validateFn: (value: unknown, allValues: Record<string, unknown>) => Promise<string | null>,
|
|
108
|
+
options?: AsyncValidatorOptions
|
|
109
|
+
): AsyncValidationRule;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Exported validators object */
|
|
113
|
+
export declare const validators: Validators;
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Field Types
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
/** Field state and controls */
|
|
120
|
+
export interface Field<T = unknown> {
|
|
121
|
+
/** Reactive field value */
|
|
122
|
+
value: Pulse<T>;
|
|
123
|
+
|
|
124
|
+
/** Reactive error message (null if valid) */
|
|
125
|
+
error: Pulse<string | null>;
|
|
126
|
+
|
|
127
|
+
/** Reactive touched state (true after blur) */
|
|
128
|
+
touched: Pulse<boolean>;
|
|
129
|
+
|
|
130
|
+
/** Reactive dirty state (true if value changed from initial) */
|
|
131
|
+
dirty: Pulse<boolean>;
|
|
132
|
+
|
|
133
|
+
/** Reactive valid state (no error and not validating) */
|
|
134
|
+
valid: Pulse<boolean>;
|
|
135
|
+
|
|
136
|
+
/** Reactive validating state (true during async validation) */
|
|
137
|
+
validating: Pulse<boolean>;
|
|
138
|
+
|
|
139
|
+
/** Validate field (sync + async) */
|
|
140
|
+
validate(): Promise<boolean | null>;
|
|
141
|
+
|
|
142
|
+
/** Validate field (sync only) */
|
|
143
|
+
validateSync(): boolean;
|
|
144
|
+
|
|
145
|
+
/** Handle input change (works with events or raw values) */
|
|
146
|
+
onChange(eventOrValue: Event | T): void;
|
|
147
|
+
|
|
148
|
+
/** Handle blur event */
|
|
149
|
+
onBlur(): void;
|
|
150
|
+
|
|
151
|
+
/** Handle focus event */
|
|
152
|
+
onFocus?(): void;
|
|
153
|
+
|
|
154
|
+
/** Reset field to initial value */
|
|
155
|
+
reset(): void;
|
|
156
|
+
|
|
157
|
+
/** Set error message manually */
|
|
158
|
+
setError(message: string): void;
|
|
159
|
+
|
|
160
|
+
/** Clear error message */
|
|
161
|
+
clearError(): void;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// useForm
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
/** Validation schema - maps field names to validation rules */
|
|
169
|
+
export type ValidationSchema<T> = {
|
|
170
|
+
[K in keyof T]?: AnyValidationRule[];
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** Form options */
|
|
174
|
+
export interface FormOptions<T> {
|
|
175
|
+
/** Validate on value change (default: true) */
|
|
176
|
+
validateOnChange?: boolean;
|
|
177
|
+
|
|
178
|
+
/** Validate on blur (default: true) */
|
|
179
|
+
validateOnBlur?: boolean;
|
|
180
|
+
|
|
181
|
+
/** Validate on submit (default: true) */
|
|
182
|
+
validateOnSubmit?: boolean;
|
|
183
|
+
|
|
184
|
+
/** Submit handler */
|
|
185
|
+
onSubmit?: (values: T) => void | Promise<void>;
|
|
186
|
+
|
|
187
|
+
/** Error handler */
|
|
188
|
+
onError?: (errors: Record<string, string>) => void;
|
|
189
|
+
|
|
190
|
+
/** Validation mode: when to start validating (default: 'onChange') */
|
|
191
|
+
mode?: 'onChange' | 'onBlur' | 'onSubmit';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Form fields - maps field names to Field objects */
|
|
195
|
+
export type FormFields<T> = {
|
|
196
|
+
[K in keyof T]: Field<T[K]>;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/** Form errors - maps field names to error messages */
|
|
200
|
+
export type FormErrors<T> = {
|
|
201
|
+
[K in keyof T]?: string;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** Return type of useForm */
|
|
205
|
+
export interface UseFormReturn<T extends Record<string, unknown>> {
|
|
206
|
+
/** Field states and controls */
|
|
207
|
+
fields: FormFields<T>;
|
|
208
|
+
|
|
209
|
+
/** Reactive overall validity state */
|
|
210
|
+
isValid: Pulse<boolean>;
|
|
211
|
+
|
|
212
|
+
/** Reactive validating state (any field is validating) */
|
|
213
|
+
isValidating: Pulse<boolean>;
|
|
214
|
+
|
|
215
|
+
/** Reactive dirty state (any field is dirty) */
|
|
216
|
+
isDirty: Pulse<boolean>;
|
|
217
|
+
|
|
218
|
+
/** Reactive touched state (any field is touched) */
|
|
219
|
+
isTouched: Pulse<boolean>;
|
|
220
|
+
|
|
221
|
+
/** Reactive submitting state */
|
|
222
|
+
isSubmitting: Pulse<boolean>;
|
|
223
|
+
|
|
224
|
+
/** Number of submit attempts */
|
|
225
|
+
submitCount: Pulse<number>;
|
|
226
|
+
|
|
227
|
+
/** Reactive errors object */
|
|
228
|
+
errors: Pulse<FormErrors<T>>;
|
|
229
|
+
|
|
230
|
+
/** Get current form values */
|
|
231
|
+
getValues(): T;
|
|
232
|
+
|
|
233
|
+
/** Set multiple values at once */
|
|
234
|
+
setValues(values: Partial<T>, shouldValidate?: boolean): void;
|
|
235
|
+
|
|
236
|
+
/** Set a single field value */
|
|
237
|
+
setValue<K extends keyof T>(name: K, value: T[K], shouldValidate?: boolean): void;
|
|
238
|
+
|
|
239
|
+
/** Validate all fields (sync + async) */
|
|
240
|
+
validateAll(): Promise<boolean>;
|
|
241
|
+
|
|
242
|
+
/** Validate all fields (sync only) */
|
|
243
|
+
validateAllSync(): boolean;
|
|
244
|
+
|
|
245
|
+
/** Reset form to initial values */
|
|
246
|
+
reset(newValues?: Partial<T>): void;
|
|
247
|
+
|
|
248
|
+
/** Handle form submission */
|
|
249
|
+
handleSubmit(event?: Event): Promise<boolean>;
|
|
250
|
+
|
|
251
|
+
/** Set field errors manually */
|
|
252
|
+
setErrors(errors: FormErrors<T>): void;
|
|
253
|
+
|
|
254
|
+
/** Clear all errors */
|
|
255
|
+
clearErrors(): void;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Create a reactive form with validation.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* const { fields, handleSubmit, isValid, reset } = useForm(
|
|
263
|
+
* { email: '', password: '' },
|
|
264
|
+
* {
|
|
265
|
+
* email: [validators.required(), validators.email()],
|
|
266
|
+
* password: [validators.required(), validators.minLength(8)]
|
|
267
|
+
* },
|
|
268
|
+
* {
|
|
269
|
+
* onSubmit: (values) => console.log('Submit:', values)
|
|
270
|
+
* }
|
|
271
|
+
* );
|
|
272
|
+
*/
|
|
273
|
+
export declare function useForm<T extends Record<string, unknown>>(
|
|
274
|
+
initialValues: T,
|
|
275
|
+
validationSchema?: ValidationSchema<T>,
|
|
276
|
+
options?: FormOptions<T>
|
|
277
|
+
): UseFormReturn<T>;
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// useField
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
/** Options for useField */
|
|
284
|
+
export interface UseFieldOptions {
|
|
285
|
+
/** Validate on change after touched (default: true) */
|
|
286
|
+
validateOnChange?: boolean;
|
|
287
|
+
|
|
288
|
+
/** Validate on blur (default: true) */
|
|
289
|
+
validateOnBlur?: boolean;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Create a single reactive field (for use outside of useForm).
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* const email = useField('', [validators.required(), validators.email()]);
|
|
297
|
+
*
|
|
298
|
+
* // With async validation
|
|
299
|
+
* const username = useField('', [
|
|
300
|
+
* validators.required(),
|
|
301
|
+
* validators.asyncUnique(async (value) => checkUsernameAvailable(value))
|
|
302
|
+
* ]);
|
|
303
|
+
*/
|
|
304
|
+
export declare function useField<T = unknown>(
|
|
305
|
+
initialValue: T,
|
|
306
|
+
rules?: AnyValidationRule[],
|
|
307
|
+
options?: UseFieldOptions
|
|
308
|
+
): Field<T>;
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// useFieldArray
|
|
312
|
+
// ============================================================================
|
|
313
|
+
|
|
314
|
+
/** Field in a field array */
|
|
315
|
+
export interface FieldArrayItem<T> extends Field<T> {}
|
|
316
|
+
|
|
317
|
+
/** Return type of useFieldArray */
|
|
318
|
+
export interface UseFieldArrayReturn<T> {
|
|
319
|
+
/** Reactive array of fields */
|
|
320
|
+
fields: Pulse<FieldArrayItem<T>[]>;
|
|
321
|
+
|
|
322
|
+
/** Reactive array of current values */
|
|
323
|
+
values: Pulse<T[]>;
|
|
324
|
+
|
|
325
|
+
/** Reactive array of errors */
|
|
326
|
+
errors: Pulse<(string | null)[]>;
|
|
327
|
+
|
|
328
|
+
/** Reactive overall validity */
|
|
329
|
+
isValid: Pulse<boolean>;
|
|
330
|
+
|
|
331
|
+
/** Append a new field at the end */
|
|
332
|
+
append(value: T): void;
|
|
333
|
+
|
|
334
|
+
/** Prepend a new field at the beginning */
|
|
335
|
+
prepend(value: T): void;
|
|
336
|
+
|
|
337
|
+
/** Insert a new field at specific index */
|
|
338
|
+
insert(index: number, value: T): void;
|
|
339
|
+
|
|
340
|
+
/** Remove field at index */
|
|
341
|
+
remove(index: number): void;
|
|
342
|
+
|
|
343
|
+
/** Move field from one index to another */
|
|
344
|
+
move(from: number, to: number): void;
|
|
345
|
+
|
|
346
|
+
/** Swap two fields */
|
|
347
|
+
swap(indexA: number, indexB: number): void;
|
|
348
|
+
|
|
349
|
+
/** Replace field at index with new value */
|
|
350
|
+
replace(index: number, value: T): void;
|
|
351
|
+
|
|
352
|
+
/** Reset to initial values */
|
|
353
|
+
reset(newValues?: T[]): void;
|
|
354
|
+
|
|
355
|
+
/** Validate all fields (sync + async) */
|
|
356
|
+
validateAll(): Promise<boolean>;
|
|
357
|
+
|
|
358
|
+
/** Validate all fields (sync only) */
|
|
359
|
+
validateAllSync(): boolean;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Create a field array for dynamic lists of fields.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* const tags = useFieldArray(['tag1', 'tag2'], [validators.required()]);
|
|
367
|
+
*
|
|
368
|
+
* tags.append('tag3');
|
|
369
|
+
* tags.remove(0);
|
|
370
|
+
*
|
|
371
|
+
* tags.fields.get().forEach((field, index) => {
|
|
372
|
+
* el('input', { value: field.value.get(), onInput: field.onChange });
|
|
373
|
+
* });
|
|
374
|
+
*/
|
|
375
|
+
export declare function useFieldArray<T>(
|
|
376
|
+
initialValues?: T[],
|
|
377
|
+
itemRules?: AnyValidationRule[]
|
|
378
|
+
): UseFieldArrayReturn<T>;
|