@samuel-charpentier/sform 0.0.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 +21 -0
- package/README.md +405 -0
- package/dist/Sform/Sfield.svelte +164 -0
- package/dist/Sform/Sfield.svelte.d.ts +4 -0
- package/dist/Sform/Sform.svelte +101 -0
- package/dist/Sform/Sform.svelte.d.ts +51 -0
- package/dist/Sform/context.svelte.d.ts +3 -0
- package/dist/Sform/context.svelte.js +65 -0
- package/dist/Sform/index.d.ts +5 -0
- package/dist/Sform/index.js +7 -0
- package/dist/Sform/inputs/ButtonInput.svelte +75 -0
- package/dist/Sform/inputs/ButtonInput.svelte.d.ts +32 -0
- package/dist/Sform/inputs/CheckboxGroupInput.svelte +81 -0
- package/dist/Sform/inputs/CheckboxGroupInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/CheckboxInput.svelte +47 -0
- package/dist/Sform/inputs/CheckboxInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/MaskedInput.svelte +220 -0
- package/dist/Sform/inputs/MaskedInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/NumberInput.svelte +116 -0
- package/dist/Sform/inputs/NumberInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/PasswordInput.svelte +142 -0
- package/dist/Sform/inputs/PasswordInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/RadioInput.svelte +81 -0
- package/dist/Sform/inputs/RadioInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/RangeInput.svelte +67 -0
- package/dist/Sform/inputs/RangeInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/SelectInput.svelte +36 -0
- package/dist/Sform/inputs/SelectInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/TextInput.svelte +61 -0
- package/dist/Sform/inputs/TextInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/TextareaInput.svelte +56 -0
- package/dist/Sform/inputs/TextareaInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/ToggleInput.svelte +101 -0
- package/dist/Sform/inputs/ToggleInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/ToggleOptionsInput.svelte +118 -0
- package/dist/Sform/inputs/ToggleOptionsInput.svelte.d.ts +4 -0
- package/dist/Sform/sform.css +628 -0
- package/dist/Sform/types.d.ts +610 -0
- package/dist/Sform/types.js +5 -0
- package/dist/Sform/utils/mask.d.ts +68 -0
- package/dist/Sform/utils/mask.js +155 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sform TypeScript Types
|
|
3
|
+
* Defines all types for the Sform library
|
|
4
|
+
*/
|
|
5
|
+
import type { Snippet } from 'svelte';
|
|
6
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
7
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
8
|
+
import type { MaskPattern, MaskToken } from './utils/mask.js';
|
|
9
|
+
import type { RemoteForm as SvelteKitRemoteForm, RemoteFormField, RemoteFormFields, RemoteFormFieldValue, RemoteFormInput, RemoteFormIssue, RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit';
|
|
10
|
+
/**
|
|
11
|
+
* Re-export SvelteKit's remote form types for library consumers
|
|
12
|
+
*/
|
|
13
|
+
export type { RemoteFormField, RemoteFormFields, RemoteFormFieldValue, RemoteFormInput, RemoteFormIssue };
|
|
14
|
+
/**
|
|
15
|
+
* When to trigger validation and show issues
|
|
16
|
+
* - 'blur': Validate and show issues after leaving field (default)
|
|
17
|
+
* - 'change': Validate and show issues on every keystroke
|
|
18
|
+
* - 'submit': Validate and show all issues only after submit
|
|
19
|
+
*/
|
|
20
|
+
export type ValidateOn = 'blur' | 'change' | 'submit';
|
|
21
|
+
/**
|
|
22
|
+
* Field state tracking
|
|
23
|
+
*/
|
|
24
|
+
export interface FieldState {
|
|
25
|
+
/** Field has been focused and blurred */
|
|
26
|
+
touched: boolean;
|
|
27
|
+
/** Field value has changed from initial */
|
|
28
|
+
dirty: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Class names for Sfield customization
|
|
32
|
+
*/
|
|
33
|
+
export interface SfieldClasses {
|
|
34
|
+
/** Wrapper element class */
|
|
35
|
+
wrapper?: string;
|
|
36
|
+
/** Label element class */
|
|
37
|
+
label?: string;
|
|
38
|
+
/** Input element class */
|
|
39
|
+
input?: string;
|
|
40
|
+
/** Messages/errors container class */
|
|
41
|
+
messages?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Maps Sfield input types to the value types they handle.
|
|
45
|
+
* Used for type-safe field-to-input matching.
|
|
46
|
+
*/
|
|
47
|
+
export interface SfieldTypeMap {
|
|
48
|
+
text: string;
|
|
49
|
+
email: string;
|
|
50
|
+
password: string;
|
|
51
|
+
tel: string;
|
|
52
|
+
url: string;
|
|
53
|
+
search: string;
|
|
54
|
+
date: string;
|
|
55
|
+
'datetime-local': string;
|
|
56
|
+
time: string;
|
|
57
|
+
month: string;
|
|
58
|
+
week: string;
|
|
59
|
+
color: string;
|
|
60
|
+
textarea: string;
|
|
61
|
+
hidden: string;
|
|
62
|
+
masked: string;
|
|
63
|
+
number: number;
|
|
64
|
+
range: number;
|
|
65
|
+
checkbox: boolean;
|
|
66
|
+
toggle: boolean;
|
|
67
|
+
select: string;
|
|
68
|
+
radio: string;
|
|
69
|
+
'toggle-options': string;
|
|
70
|
+
'checkbox-group': string[];
|
|
71
|
+
file: File;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Given a field value type T, returns the Sfield types that can handle it.
|
|
75
|
+
* This is the inverse of SfieldTypeMap - we find which keys produce T.
|
|
76
|
+
*/
|
|
77
|
+
export type AllowedSfieldType<T> = {
|
|
78
|
+
[K in keyof SfieldTypeMap]: T extends SfieldTypeMap[K] ? K : never;
|
|
79
|
+
}[keyof SfieldTypeMap];
|
|
80
|
+
/**
|
|
81
|
+
* Supported input types for Sfield
|
|
82
|
+
*/
|
|
83
|
+
export type InputType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search' | 'date' | 'datetime-local' | 'time' | 'month' | 'week' | 'color' | 'textarea' | 'select' | 'checkbox' | 'checkbox-group' | 'radio' | 'file' | 'hidden' | 'range' | 'toggle' | 'toggle-options' | 'masked';
|
|
84
|
+
/** Text-like input types */
|
|
85
|
+
export type TextInputType = 'text' | 'email' | 'tel' | 'url' | 'search' | 'date' | 'datetime-local' | 'time' | 'month' | 'week' | 'color' | 'file' | 'hidden';
|
|
86
|
+
/** Numeric input types */
|
|
87
|
+
export type NumericInputType = 'number';
|
|
88
|
+
/**
|
|
89
|
+
* Select option type
|
|
90
|
+
*/
|
|
91
|
+
export interface SelectOption {
|
|
92
|
+
value: string;
|
|
93
|
+
label: string;
|
|
94
|
+
disabled?: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Remote form type alias using SvelteKit's official type.
|
|
98
|
+
* Generic parameters allow type-safe form handling:
|
|
99
|
+
* - Input: The schema type for form data (defaults to RemoteFormInput)
|
|
100
|
+
* - Output: The return type from form submission (defaults to unknown)
|
|
101
|
+
*/
|
|
102
|
+
export type RemoteForm<Input extends RemoteFormInput | void = RemoteFormInput, Output = unknown> = SvelteKitRemoteForm<Input, Output>;
|
|
103
|
+
/**
|
|
104
|
+
* A remote form instance - either a full RemoteForm or the result of .for(id).
|
|
105
|
+
* The .for(id) method returns Omit<RemoteForm, 'for'>, so this type accepts both.
|
|
106
|
+
*/
|
|
107
|
+
export type RemoteFormInstance<Input extends RemoteFormInput | void = RemoteFormInput, Output = unknown> = SvelteKitRemoteForm<Input, Output> | Omit<SvelteKitRemoteForm<Input, Output>, 'for'>;
|
|
108
|
+
/**
|
|
109
|
+
* Form context provided by Sform to children.
|
|
110
|
+
* Manages field state (touched, dirty, submitted) for validation display.
|
|
111
|
+
*/
|
|
112
|
+
export interface SformContext {
|
|
113
|
+
/** Get field state */
|
|
114
|
+
getFieldState: (name: string) => FieldState;
|
|
115
|
+
/** Mark field as touched */
|
|
116
|
+
markTouched: (name: string) => void;
|
|
117
|
+
/** Mark field as dirty */
|
|
118
|
+
markDirty: (name: string) => void;
|
|
119
|
+
/** Check if field should display issues */
|
|
120
|
+
shouldDisplayIssues: (name: string, fieldValidateOn?: ValidateOn) => boolean;
|
|
121
|
+
/** Form-level validateOn mode */
|
|
122
|
+
validateOn: ValidateOn;
|
|
123
|
+
/** Trigger validation (called on blur/input based on mode) */
|
|
124
|
+
triggerValidation: () => void;
|
|
125
|
+
/** Whether form has been submitted */
|
|
126
|
+
submitted: boolean;
|
|
127
|
+
/** Mark form as submitted */
|
|
128
|
+
markSubmitted: () => void;
|
|
129
|
+
/** Mark all fields as touched and dirty to show all issues */
|
|
130
|
+
markAllFieldsDirty: () => void;
|
|
131
|
+
/** Reset all field states (touched, dirty, submitted) */
|
|
132
|
+
resetFieldStates: () => void;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Enhance callback options - typed based on form Input type.
|
|
136
|
+
* Matches SvelteKit's RemoteForm.enhance() callback signature exactly.
|
|
137
|
+
*/
|
|
138
|
+
export interface EnhanceCallbackOptions<Input extends RemoteFormInput> {
|
|
139
|
+
/** The HTML form element */
|
|
140
|
+
form: HTMLFormElement;
|
|
141
|
+
/** The typed form data */
|
|
142
|
+
data: Input;
|
|
143
|
+
/** Submit the form and optionally update queries */
|
|
144
|
+
submit: () => Promise<void> & {
|
|
145
|
+
updates: (...queries: Array<RemoteQuery<unknown> | RemoteQueryOverride>) => Promise<void>;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Enhance callback function type
|
|
150
|
+
*/
|
|
151
|
+
export type EnhanceCallback<Input extends RemoteFormInput> = (opts: EnhanceCallbackOptions<Input>) => void | Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Props for Sform component.
|
|
154
|
+
* Generic over the form's input and output types.
|
|
155
|
+
*/
|
|
156
|
+
export interface SformProps<Input extends RemoteFormInput = RemoteFormInput, Output = unknown> {
|
|
157
|
+
/** Remote form object from form() API, or the result of form.for(id) */
|
|
158
|
+
form: SvelteKitRemoteForm<Input, Output> | Omit<SvelteKitRemoteForm<Input, Output>, 'for'>;
|
|
159
|
+
/** Preflight validation schema (Valibot, Zod, or any StandardSchema) */
|
|
160
|
+
schema?: StandardSchemaV1<Input, unknown>;
|
|
161
|
+
/** Enhance callback for custom form submission handling */
|
|
162
|
+
enhance?: EnhanceCallback<Input>;
|
|
163
|
+
/** When to validate and show issues: 'blur' (default), 'change', or 'submit' */
|
|
164
|
+
validateOn?: ValidateOn;
|
|
165
|
+
/** Form element class */
|
|
166
|
+
class?: string;
|
|
167
|
+
/** Children content */
|
|
168
|
+
children: Snippet;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Base props shared by all Sfield types
|
|
172
|
+
*/
|
|
173
|
+
export interface BaseSfieldProps {
|
|
174
|
+
/** Field name - corresponds to form.fields[name] */
|
|
175
|
+
name: string;
|
|
176
|
+
/** Field label */
|
|
177
|
+
label?: string;
|
|
178
|
+
/** Placeholder text */
|
|
179
|
+
placeholder?: string;
|
|
180
|
+
/** Field-level validateOn override */
|
|
181
|
+
validateOn?: ValidateOn;
|
|
182
|
+
/** CSS classes for sub-elements */
|
|
183
|
+
class?: SfieldClasses | string;
|
|
184
|
+
/** Whether field is disabled */
|
|
185
|
+
disabled?: boolean;
|
|
186
|
+
/** Whether field is readonly */
|
|
187
|
+
readonly?: boolean;
|
|
188
|
+
/** Autocomplete attribute */
|
|
189
|
+
autocomplete?: HTMLInputAttributes['autocomplete'];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Props for text-like inputs (text, email, tel, url, search, date, etc.)
|
|
193
|
+
*/
|
|
194
|
+
export interface TextSfieldProps extends BaseSfieldProps {
|
|
195
|
+
type: TextInputType;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Props for password input
|
|
199
|
+
*/
|
|
200
|
+
export interface PasswordSfieldProps extends BaseSfieldProps {
|
|
201
|
+
type: 'password';
|
|
202
|
+
/** Whether to show the password visibility toggle (default: true) */
|
|
203
|
+
showToggle?: boolean;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Props for number input
|
|
207
|
+
*/
|
|
208
|
+
export interface NumberSfieldProps extends BaseSfieldProps {
|
|
209
|
+
type: 'number';
|
|
210
|
+
/** Minimum value */
|
|
211
|
+
min?: number | string;
|
|
212
|
+
/** Maximum value */
|
|
213
|
+
max?: number | string;
|
|
214
|
+
/** Step value */
|
|
215
|
+
step?: number | string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Props for textarea input
|
|
219
|
+
*/
|
|
220
|
+
export interface TextareaSfieldProps extends BaseSfieldProps {
|
|
221
|
+
type: 'textarea';
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Props for select input
|
|
225
|
+
*/
|
|
226
|
+
export interface SelectSfieldProps extends BaseSfieldProps {
|
|
227
|
+
type: 'select';
|
|
228
|
+
/** Options for select */
|
|
229
|
+
options: SelectOption[] | string[];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Props for checkbox input (single yes/no)
|
|
233
|
+
*/
|
|
234
|
+
export interface CheckboxSfieldProps extends BaseSfieldProps {
|
|
235
|
+
type: 'checkbox';
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Props for checkbox group input (multi-select)
|
|
239
|
+
*/
|
|
240
|
+
export interface CheckboxGroupSfieldProps extends BaseSfieldProps {
|
|
241
|
+
type: 'checkbox-group';
|
|
242
|
+
/** Options for checkbox group */
|
|
243
|
+
options: SelectOption[] | string[];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Props for radio input
|
|
247
|
+
*/
|
|
248
|
+
export interface RadioSfieldProps extends BaseSfieldProps {
|
|
249
|
+
type: 'radio';
|
|
250
|
+
/** Options for radio button group */
|
|
251
|
+
options: SelectOption[] | string[];
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Props for range input
|
|
255
|
+
*/
|
|
256
|
+
export interface RangeSfieldProps extends BaseSfieldProps {
|
|
257
|
+
type: 'range';
|
|
258
|
+
/** Minimum value */
|
|
259
|
+
min?: number | string;
|
|
260
|
+
/** Maximum value */
|
|
261
|
+
max?: number | string;
|
|
262
|
+
/** Step value */
|
|
263
|
+
step?: number | string;
|
|
264
|
+
/** Show value display */
|
|
265
|
+
showValue?: boolean;
|
|
266
|
+
/** Format function for value display */
|
|
267
|
+
formatValue?: (value: number) => string;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Props for toggle input
|
|
271
|
+
*/
|
|
272
|
+
export interface ToggleSfieldProps extends BaseSfieldProps {
|
|
273
|
+
type: 'toggle';
|
|
274
|
+
/** Label when toggle is on */
|
|
275
|
+
onLabel?: string;
|
|
276
|
+
/** Label when toggle is off */
|
|
277
|
+
offLabel?: string;
|
|
278
|
+
/** Value when checked */
|
|
279
|
+
checkedValue?: string;
|
|
280
|
+
/** Value when unchecked */
|
|
281
|
+
uncheckedValue?: string;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Props for toggle-options input (button group)
|
|
285
|
+
*/
|
|
286
|
+
export interface ToggleOptionsSfieldProps extends BaseSfieldProps {
|
|
287
|
+
type: 'toggle-options';
|
|
288
|
+
/** Options for toggle buttons */
|
|
289
|
+
options: ToggleOption[] | string[];
|
|
290
|
+
/** Allow multiple selections */
|
|
291
|
+
multiple?: boolean;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Props for masked input
|
|
295
|
+
*/
|
|
296
|
+
export interface MaskedSfieldProps extends BaseSfieldProps {
|
|
297
|
+
type: 'masked';
|
|
298
|
+
/** Mask pattern (e.g., '(999) 999-9999') or preset name */
|
|
299
|
+
mask: string;
|
|
300
|
+
/** Placeholder character for masked inputs */
|
|
301
|
+
maskPlaceholder?: string;
|
|
302
|
+
/** Whether to show the full mask with placeholders */
|
|
303
|
+
showMaskPlaceholder?: boolean;
|
|
304
|
+
/** Whether to store the unmasked (raw) value. If true, stores '1234567890'. If false, stores '(123) 456-7890'. Default: true */
|
|
305
|
+
unmaskValue?: boolean;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Discriminated union of all Sfield prop types
|
|
309
|
+
*/
|
|
310
|
+
export type SfieldProps = TextSfieldProps | PasswordSfieldProps | NumberSfieldProps | TextareaSfieldProps | SelectSfieldProps | CheckboxSfieldProps | CheckboxGroupSfieldProps | RadioSfieldProps | RangeSfieldProps | ToggleSfieldProps | ToggleOptionsSfieldProps | MaskedSfieldProps;
|
|
311
|
+
/**
|
|
312
|
+
* Props that Sfield manages internally and should NOT be passed from parent.
|
|
313
|
+
* These are set by Sfield itself based on context and internal state.
|
|
314
|
+
*/
|
|
315
|
+
type SfieldInternalProps = 'field' | 'name' | 'showIssues' | 'onblur' | 'oninput' | 'labelClass';
|
|
316
|
+
/**
|
|
317
|
+
* Props that Sfield adds on top of component props.
|
|
318
|
+
* - field: The typed remote form field
|
|
319
|
+
* - validateOn: Override for when validation triggers
|
|
320
|
+
* - class: Can be string or SfieldClasses object
|
|
321
|
+
* - hint: Help text shown below input
|
|
322
|
+
*/
|
|
323
|
+
interface SfieldExtraProps<T extends RemoteFormFieldValue> {
|
|
324
|
+
/** The remote form field - passed directly for type safety */
|
|
325
|
+
field: RemoteFormField<T>;
|
|
326
|
+
/** Field-level validateOn override */
|
|
327
|
+
validateOn?: ValidateOn;
|
|
328
|
+
/** CSS classes for sub-elements (wrapper, label, input, messages) */
|
|
329
|
+
class?: SfieldClasses | string;
|
|
330
|
+
/** Hint text displayed below the input, above validation messages */
|
|
331
|
+
hint?: string | Snippet;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Helper type: Create Sfield props from component props
|
|
335
|
+
* Omits internal props and adds Sfield-specific extras
|
|
336
|
+
*/
|
|
337
|
+
type SfieldPropsFrom<ComponentProps, T extends RemoteFormFieldValue, TypeValue extends string> = Omit<ComponentProps, SfieldInternalProps | 'class'> & SfieldExtraProps<T> & {
|
|
338
|
+
type: TypeValue;
|
|
339
|
+
};
|
|
340
|
+
/** Props for text-like inputs (text, email, tel, url, search, date, etc.) */
|
|
341
|
+
export type SfieldTextProps = SfieldPropsFrom<TextInputComponentProps, string, TextInputType>;
|
|
342
|
+
/** Props for password input */
|
|
343
|
+
export type SfieldPasswordProps = SfieldPropsFrom<PasswordInputProps, string, 'password'>;
|
|
344
|
+
/** Props for number input */
|
|
345
|
+
export type SfieldNumberProps = SfieldPropsFrom<NumberInputComponentProps, number, 'number'>;
|
|
346
|
+
/** Props for textarea input */
|
|
347
|
+
export type SfieldTextareaProps = SfieldPropsFrom<TextareaInputProps, string, 'textarea'>;
|
|
348
|
+
/** Props for select input */
|
|
349
|
+
export type SfieldSelectProps = SfieldPropsFrom<SelectInputProps, string, 'select'>;
|
|
350
|
+
/** Props for checkbox input */
|
|
351
|
+
export type SfieldCheckboxProps = SfieldPropsFrom<CheckboxRadioInputProps, boolean, 'checkbox'>;
|
|
352
|
+
/** Props for checkbox-group input */
|
|
353
|
+
export type SfieldCheckboxGroupProps = SfieldPropsFrom<CheckboxGroupInputProps, string[], 'checkbox-group'>;
|
|
354
|
+
/** Props for radio input */
|
|
355
|
+
export type SfieldRadioProps = SfieldPropsFrom<RadioInputProps, string, 'radio'>;
|
|
356
|
+
/** Props for range input */
|
|
357
|
+
export type SfieldRangeProps = SfieldPropsFrom<RangeInputProps, number, 'range'>;
|
|
358
|
+
/** Props for toggle input */
|
|
359
|
+
export type SfieldToggleProps = SfieldPropsFrom<ToggleInputProps, boolean, 'toggle'>;
|
|
360
|
+
/** Props for toggle-options input */
|
|
361
|
+
export type SfieldToggleOptionsProps = SfieldPropsFrom<ToggleOptionsInputProps, string, 'toggle-options'>;
|
|
362
|
+
/** Props for masked input */
|
|
363
|
+
export type SfieldMaskedProps = SfieldPropsFrom<MaskedInputProps, string, 'masked'>;
|
|
364
|
+
/** All possible Sfield props as a discriminated union */
|
|
365
|
+
type AllSfieldProps = SfieldTextProps | SfieldPasswordProps | SfieldNumberProps | SfieldTextareaProps | SfieldSelectProps | SfieldCheckboxProps | SfieldCheckboxGroupProps | SfieldRadioProps | SfieldRangeProps | SfieldToggleProps | SfieldToggleOptionsProps | SfieldMaskedProps;
|
|
366
|
+
/**
|
|
367
|
+
* Extract props that match a specific field value type.
|
|
368
|
+
* This ensures type="number" is only valid for number fields, etc.
|
|
369
|
+
*/
|
|
370
|
+
type PropsForFieldType<T extends RemoteFormFieldValue> = Extract<AllSfieldProps, {
|
|
371
|
+
field: RemoteFormField<T>;
|
|
372
|
+
}>;
|
|
373
|
+
/**
|
|
374
|
+
* Typed Sfield props - constrains the 'type' based on the field's value type.
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* // If fields.name is RemoteFormField<string>, only string-compatible types are allowed
|
|
379
|
+
* <Sfield field={fields.name} type="text" /> // ✓ OK
|
|
380
|
+
* <Sfield field={fields.name} type="number" /> // ✗ Error: number not compatible with string
|
|
381
|
+
*
|
|
382
|
+
* // If fields.age is RemoteFormField<number>, only number-compatible types are allowed
|
|
383
|
+
* <Sfield field={fields.age} type="number" /> // ✓ OK
|
|
384
|
+
* <Sfield field={fields.age} type="text" /> // ✗ Error: text not compatible with number
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
export type TypedSfieldProps<T extends RemoteFormFieldValue> = PropsForFieldType<T>;
|
|
388
|
+
/**
|
|
389
|
+
* Base props shared by all Sfield types (for reference/extension)
|
|
390
|
+
* @deprecated Sfield props are now derived from component props. Use the individual Sfield*Props types.
|
|
391
|
+
*/
|
|
392
|
+
export type SfieldBaseProps<T extends RemoteFormFieldValue> = SfieldExtraProps<T> & Omit<BaseInputComponentProps, SfieldInternalProps | 'class'>;
|
|
393
|
+
/**
|
|
394
|
+
* Legacy type alias for backwards compatibility
|
|
395
|
+
* @deprecated Use SfieldBaseProps instead
|
|
396
|
+
*/
|
|
397
|
+
export type TypedBaseSfieldProps<T extends RemoteFormFieldValue> = SfieldBaseProps<T>;
|
|
398
|
+
/**
|
|
399
|
+
* Base props for internal input components (without type - for components with fixed types)
|
|
400
|
+
*/
|
|
401
|
+
export interface BaseInputComponentProps {
|
|
402
|
+
/** Remote field from form */
|
|
403
|
+
field: RemoteFormField<RemoteFormFieldValue>;
|
|
404
|
+
/** Field name */
|
|
405
|
+
name: string;
|
|
406
|
+
/** Field label */
|
|
407
|
+
label?: string;
|
|
408
|
+
/** Placeholder text */
|
|
409
|
+
placeholder?: string;
|
|
410
|
+
/** Input class */
|
|
411
|
+
class?: string;
|
|
412
|
+
/** Label class */
|
|
413
|
+
labelClass?: string;
|
|
414
|
+
/** Whether input is disabled */
|
|
415
|
+
disabled?: boolean;
|
|
416
|
+
/** Whether input is readonly */
|
|
417
|
+
readonly?: boolean;
|
|
418
|
+
/** Autocomplete attribute */
|
|
419
|
+
autocomplete?: HTMLInputAttributes['autocomplete'];
|
|
420
|
+
/** Whether to show validation state (controls aria-invalid) */
|
|
421
|
+
showIssues?: boolean;
|
|
422
|
+
/** Blur handler */
|
|
423
|
+
onblur?: () => void;
|
|
424
|
+
/** Input handler */
|
|
425
|
+
oninput?: () => void;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Props for prefix/suffix support on input components
|
|
429
|
+
*/
|
|
430
|
+
export interface InputAffixProps {
|
|
431
|
+
/** Text/content rendered before the input (inside wrapper) */
|
|
432
|
+
prefix?: Snippet | string;
|
|
433
|
+
/** Text/content rendered after the input (inside wrapper) */
|
|
434
|
+
suffix?: Snippet | string;
|
|
435
|
+
/** Class for the input wrapper (contains prefix, input, suffix) */
|
|
436
|
+
wrapperClass?: string;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Props for internal input components that need a dynamic type
|
|
440
|
+
*/
|
|
441
|
+
export interface InputComponentProps extends BaseInputComponentProps {
|
|
442
|
+
/** Input type */
|
|
443
|
+
type: InputType;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Props for numeric input components (number, range) - uses base props since type is fixed
|
|
447
|
+
*/
|
|
448
|
+
export interface NumericInputComponentProps extends BaseInputComponentProps {
|
|
449
|
+
/** Minimum value */
|
|
450
|
+
min?: number | string;
|
|
451
|
+
/** Maximum value */
|
|
452
|
+
max?: number | string;
|
|
453
|
+
/** Step value */
|
|
454
|
+
step?: number | string;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Props for number input component
|
|
458
|
+
*/
|
|
459
|
+
export interface NumberInputComponentProps extends BaseInputComponentProps, InputAffixProps {
|
|
460
|
+
/** Minimum value */
|
|
461
|
+
min?: number | string;
|
|
462
|
+
/** Maximum value */
|
|
463
|
+
max?: number | string;
|
|
464
|
+
/** Step value */
|
|
465
|
+
step?: number | string;
|
|
466
|
+
/** Show spinner controls (default: true) */
|
|
467
|
+
showControls?: boolean;
|
|
468
|
+
/** Text alignment - adapts to text direction (default: 'start') */
|
|
469
|
+
align?: 'start' | 'end';
|
|
470
|
+
/** Maximum decimal places allowed (0 = integers only, undefined = no limit) */
|
|
471
|
+
maxDecimals?: number;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Props for text input component (text, email, tel, url, search, date, etc.)
|
|
475
|
+
*/
|
|
476
|
+
export interface TextInputComponentProps extends BaseInputComponentProps, InputAffixProps {
|
|
477
|
+
/** Input type - text-like types only */
|
|
478
|
+
type: TextInputType;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Props for textarea input component
|
|
482
|
+
*/
|
|
483
|
+
export interface TextareaInputProps extends BaseInputComponentProps, InputAffixProps {
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Props for select input component
|
|
487
|
+
*/
|
|
488
|
+
export interface SelectInputProps extends BaseInputComponentProps {
|
|
489
|
+
/** Options for select */
|
|
490
|
+
options: SelectOption[] | string[];
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Props for checkbox input component (single yes/no)
|
|
494
|
+
*/
|
|
495
|
+
export interface CheckboxRadioInputProps extends BaseInputComponentProps {
|
|
496
|
+
/** Value for the input (unused for single checkbox) */
|
|
497
|
+
value?: string;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Props for checkbox group input component
|
|
501
|
+
*/
|
|
502
|
+
export interface CheckboxGroupInputProps extends BaseInputComponentProps {
|
|
503
|
+
/** Options for the checkbox group */
|
|
504
|
+
options: SelectOption[] | string[];
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Props for radio input component
|
|
508
|
+
*/
|
|
509
|
+
export interface RadioInputProps extends BaseInputComponentProps {
|
|
510
|
+
/** Options for the radio group */
|
|
511
|
+
options: SelectOption[] | string[];
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Form state for button snippets
|
|
515
|
+
*/
|
|
516
|
+
export interface ButtonFormState {
|
|
517
|
+
/** Whether form is submitting */
|
|
518
|
+
pending: boolean;
|
|
519
|
+
/** Whether form submission was successful (has result, no issues) */
|
|
520
|
+
success: boolean;
|
|
521
|
+
/** Whether form has validation issues */
|
|
522
|
+
hasIssues: boolean;
|
|
523
|
+
/** The form result if available */
|
|
524
|
+
result: unknown;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Props for button input component
|
|
528
|
+
*/
|
|
529
|
+
export interface ButtonInputProps {
|
|
530
|
+
/** Button text (used if no snippets provided) */
|
|
531
|
+
label?: string;
|
|
532
|
+
/** Button type */
|
|
533
|
+
buttonType?: 'submit' | 'reset' | 'button';
|
|
534
|
+
/** Button class */
|
|
535
|
+
class?: string;
|
|
536
|
+
/** Whether button is disabled */
|
|
537
|
+
disabled?: boolean;
|
|
538
|
+
/** Snippet for default state */
|
|
539
|
+
defaultState?: Snippet;
|
|
540
|
+
/** Snippet for pending state */
|
|
541
|
+
pendingState?: Snippet;
|
|
542
|
+
/** Snippet for success state */
|
|
543
|
+
successState?: Snippet;
|
|
544
|
+
/** Snippet for error/issues state */
|
|
545
|
+
errorState?: Snippet;
|
|
546
|
+
/** Access to form state for custom rendering */
|
|
547
|
+
formState?: ButtonFormState;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Props for range input component
|
|
551
|
+
*/
|
|
552
|
+
export interface RangeInputProps extends NumericInputComponentProps {
|
|
553
|
+
/** Show value display */
|
|
554
|
+
showValue?: boolean;
|
|
555
|
+
/** Format function for value display */
|
|
556
|
+
formatValue?: (value: number) => string;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Props for password input component
|
|
560
|
+
*/
|
|
561
|
+
export interface PasswordInputProps extends BaseInputComponentProps {
|
|
562
|
+
/** Whether to show the password visibility toggle */
|
|
563
|
+
showToggle?: boolean;
|
|
564
|
+
showToggleIcon?: Snippet<[boolean]>;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Props for toggle input component
|
|
568
|
+
*/
|
|
569
|
+
export interface ToggleInputProps extends BaseInputComponentProps {
|
|
570
|
+
/** Label when toggle is on */
|
|
571
|
+
onLabel?: string;
|
|
572
|
+
/** Label when toggle is off */
|
|
573
|
+
offLabel?: string;
|
|
574
|
+
/** Value when checked */
|
|
575
|
+
checkedValue?: string;
|
|
576
|
+
/** Value when unchecked */
|
|
577
|
+
uncheckedValue?: string;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Toggle option for ToggleOptions component
|
|
581
|
+
*/
|
|
582
|
+
export interface ToggleOption {
|
|
583
|
+
value: string;
|
|
584
|
+
label: string;
|
|
585
|
+
disabled?: boolean;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Props for toggle options (button group) component
|
|
589
|
+
*/
|
|
590
|
+
export interface ToggleOptionsInputProps extends BaseInputComponentProps {
|
|
591
|
+
/** Options to display as toggle buttons */
|
|
592
|
+
options: ToggleOption[] | string[];
|
|
593
|
+
/** Allow multiple selections */
|
|
594
|
+
multiple?: boolean;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Props for masked input component
|
|
598
|
+
*/
|
|
599
|
+
export interface MaskedInputProps extends BaseInputComponentProps, InputAffixProps {
|
|
600
|
+
/** The mask pattern or a preset name */
|
|
601
|
+
mask: string | MaskPattern;
|
|
602
|
+
/** Custom token definitions */
|
|
603
|
+
tokens?: Record<string, MaskToken>;
|
|
604
|
+
/** Placeholder character for unfilled positions */
|
|
605
|
+
maskPlaceholder?: string;
|
|
606
|
+
/** Whether to show the full mask with placeholders */
|
|
607
|
+
showMaskPlaceholder?: boolean;
|
|
608
|
+
/** Whether to store the unmasked (raw) value. If true, stores '1234567890'. If false, stores '(123) 456-7890'. Default: true */
|
|
609
|
+
unmaskValue?: boolean;
|
|
610
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask Utility for Sform
|
|
3
|
+
* Native implementation for input masking without external dependencies
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Mask token definitions
|
|
7
|
+
* - 9 or #: Numeric (0-9)
|
|
8
|
+
* - a: Alphabetic (a-z, A-Z)
|
|
9
|
+
* - A: Alphabetic uppercase (a-z, A-Z) -> transforms to uppercase
|
|
10
|
+
* - *: Alphanumeric (0-9, a-z, A-Z)
|
|
11
|
+
* - Other characters are literals
|
|
12
|
+
*/
|
|
13
|
+
export interface MaskToken {
|
|
14
|
+
pattern: RegExp;
|
|
15
|
+
transform?: (char: string) => string;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_TOKENS: Record<string, MaskToken>;
|
|
18
|
+
export interface MaskOptions {
|
|
19
|
+
/** The mask pattern (e.g., '(999) 999-9999') */
|
|
20
|
+
mask: string;
|
|
21
|
+
/** Custom token definitions */
|
|
22
|
+
tokens?: Record<string, MaskToken>;
|
|
23
|
+
/** Placeholder character for unfilled positions */
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/** Whether to show the full mask with placeholders */
|
|
26
|
+
showPlaceholder?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface MaskResult {
|
|
29
|
+
/** The masked display value */
|
|
30
|
+
masked: string;
|
|
31
|
+
/** The raw unmasked value (only user input, no literals) */
|
|
32
|
+
raw: string;
|
|
33
|
+
/** Whether the input is complete (all required positions filled) */
|
|
34
|
+
complete: boolean;
|
|
35
|
+
/** Cursor position after masking */
|
|
36
|
+
cursorPosition: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Apply mask to a value
|
|
40
|
+
*
|
|
41
|
+
* Algorithm:
|
|
42
|
+
* 1. Walk through the mask and input simultaneously
|
|
43
|
+
* 2. For each mask position:
|
|
44
|
+
* - If it's a wildcard, try to find a matching input character
|
|
45
|
+
* - If it's a literal, add it only if we have more input to process
|
|
46
|
+
* 3. Stop when input is exhausted
|
|
47
|
+
*/
|
|
48
|
+
export declare function applyMask(value: string, options: MaskOptions, cursorPosition?: number): MaskResult;
|
|
49
|
+
/**
|
|
50
|
+
* Get the raw value from a masked value
|
|
51
|
+
*/
|
|
52
|
+
export declare function unmask(maskedValue: string, options: MaskOptions): string;
|
|
53
|
+
/**
|
|
54
|
+
* Common mask patterns
|
|
55
|
+
*/
|
|
56
|
+
export declare const MASK_PATTERNS: {
|
|
57
|
+
readonly phone: "(999) 999-9999";
|
|
58
|
+
readonly phoneIntl: "+9 (999) 999-9999";
|
|
59
|
+
readonly ssn: "999-99-9999";
|
|
60
|
+
readonly zip: "99999";
|
|
61
|
+
readonly zipPlus4: "99999-9999";
|
|
62
|
+
readonly creditCard: "9999 9999 9999 9999";
|
|
63
|
+
readonly date: "99/99/9999";
|
|
64
|
+
readonly time: "99:99";
|
|
65
|
+
readonly time24: "99:99";
|
|
66
|
+
readonly currency: "$9,999.99";
|
|
67
|
+
};
|
|
68
|
+
export type MaskPattern = keyof typeof MASK_PATTERNS;
|