@samuel-charpentier/sform 0.0.12 → 1.1.0
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 +697 -621
- package/dist/Sform/SIssues.svelte +1 -1
- package/dist/Sform/Sfield.svelte +226 -59
- package/dist/Sform/Sform.svelte +79 -11
- package/dist/Sform/Sform.svelte.d.ts +6 -1
- package/dist/Sform/context.svelte.d.ts +1 -1
- package/dist/Sform/context.svelte.js +41 -1
- package/dist/Sform/index.d.ts +1 -1
- package/dist/Sform/inputs/ButtonInput.svelte +25 -36
- package/dist/Sform/inputs/ButtonInput.svelte.d.ts +2 -25
- package/dist/Sform/inputs/CheckboxGroupInput.svelte +3 -31
- package/dist/Sform/inputs/CheckboxInput.svelte +5 -1
- package/dist/Sform/inputs/HiddenInput.svelte +10 -10
- package/dist/Sform/inputs/MaskedInput.svelte +3 -4
- package/dist/Sform/inputs/NumberInput.svelte +2 -3
- package/dist/Sform/inputs/PasswordInput.svelte +2 -3
- package/dist/Sform/inputs/RadioCheckboxLabel.svelte +50 -0
- package/dist/Sform/inputs/RadioCheckboxLabel.svelte.d.ts +39 -0
- package/dist/Sform/inputs/RadioInput.svelte +3 -42
- package/dist/Sform/inputs/RangeInput.svelte +2 -3
- package/dist/Sform/inputs/SelectInput.svelte +2 -3
- package/dist/Sform/inputs/TextInput.svelte +2 -3
- package/dist/Sform/inputs/TextareaInput.svelte +2 -3
- package/dist/Sform/inputs/ToggleInput.svelte +5 -1
- package/dist/Sform/inputs/TopLabel.svelte +14 -0
- package/dist/Sform/inputs/TopLabel.svelte.d.ts +4 -0
- package/dist/Sform/sform.css +0 -41
- package/dist/Sform/types.d.ts +68 -44
- package/dist/Sform/utils/Fieldset.svelte +8 -3
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/Sform/inputs/ToggleOptionsInput.svelte +0 -118
- package/dist/Sform/inputs/ToggleOptionsInput.svelte.d.ts +0 -4
package/dist/Sform/types.d.ts
CHANGED
|
@@ -18,6 +18,39 @@ export type { RemoteFormField, RemoteFormFields, RemoteFormFieldValue, RemoteFor
|
|
|
18
18
|
* - 'submit': Validate and show all issues only after submit
|
|
19
19
|
*/
|
|
20
20
|
export type ValidateOn = 'blur' | 'change' | 'submit';
|
|
21
|
+
/**
|
|
22
|
+
* Lifecycle event names emitted by Sform.
|
|
23
|
+
*/
|
|
24
|
+
export type SformLifecycleEvent = 'beforeSubmit' | 'afterSubmitTriggered' | 'afterSubmitResponse' | 'beforeValidate' | 'afterValidateCalled' | 'afterValidateSettled';
|
|
25
|
+
/**
|
|
26
|
+
* Function signature for lifecycle hooks.
|
|
27
|
+
*/
|
|
28
|
+
export type SformLifecycleHook = () => void | Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Lifecycle hooks that can be registered by Sform and Sfield components.
|
|
31
|
+
*/
|
|
32
|
+
export interface SformLifecycleHooks {
|
|
33
|
+
/** Runs before submit is triggered by Sbutton. */
|
|
34
|
+
beforeSubmit?: SformLifecycleHook;
|
|
35
|
+
/** Runs right after submit is triggered by Sbutton. */
|
|
36
|
+
afterSubmitTriggered?: SformLifecycleHook;
|
|
37
|
+
/** Runs after the submit cycle receives a response (pending -> idle). */
|
|
38
|
+
afterSubmitResponse?: SformLifecycleHook;
|
|
39
|
+
/** Runs immediately before form.validate() is called. */
|
|
40
|
+
beforeValidate?: SformLifecycleHook;
|
|
41
|
+
/** Runs immediately after form.validate() is called. */
|
|
42
|
+
afterValidateCalled?: SformLifecycleHook;
|
|
43
|
+
/** Runs when form.validate() settles (resolves or rejects). */
|
|
44
|
+
afterValidateSettled?: SformLifecycleHook;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Where a field's issues should be displayed.
|
|
48
|
+
* - auto: visible fields display their own issues; hidden fields leave issues to SIssues
|
|
49
|
+
* - field: this field displays its own issues
|
|
50
|
+
* - form: this field leaves issues to SIssues
|
|
51
|
+
* - none: this field marks issues as handled without rendering them
|
|
52
|
+
*/
|
|
53
|
+
export type IssueDisplay = 'auto' | 'field' | 'form' | 'none';
|
|
21
54
|
/**
|
|
22
55
|
* Field state tracking
|
|
23
56
|
*/
|
|
@@ -72,7 +105,6 @@ export interface SfieldTypeMap {
|
|
|
72
105
|
toggle: boolean;
|
|
73
106
|
select: string;
|
|
74
107
|
radio: string;
|
|
75
|
-
'toggle-options': string;
|
|
76
108
|
'checkbox-group': string[];
|
|
77
109
|
file: File;
|
|
78
110
|
}
|
|
@@ -86,7 +118,7 @@ export type AllowedSfieldType<T> = {
|
|
|
86
118
|
/**
|
|
87
119
|
* Supported input types for Sfield
|
|
88
120
|
*/
|
|
89
|
-
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' | '
|
|
121
|
+
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' | 'masked';
|
|
90
122
|
/** Text-like input types */
|
|
91
123
|
export type TextInputType = 'text' | 'email' | 'tel' | 'url' | 'search' | 'date' | 'datetime-local' | 'time' | 'month' | 'week' | 'color' | 'file';
|
|
92
124
|
/** Numeric input types */
|
|
@@ -96,7 +128,7 @@ export type NumericInputType = 'number';
|
|
|
96
128
|
*/
|
|
97
129
|
export interface SelectOption {
|
|
98
130
|
value: string;
|
|
99
|
-
label: string
|
|
131
|
+
label: string | Snippet<[SelectOption]>;
|
|
100
132
|
disabled?: boolean;
|
|
101
133
|
}
|
|
102
134
|
/**
|
|
@@ -158,9 +190,11 @@ export interface SformContext {
|
|
|
158
190
|
/** Form-level validateOn mode */
|
|
159
191
|
validateOn: ValidateOn;
|
|
160
192
|
/** Trigger validation (called on blur/input based on mode) */
|
|
161
|
-
triggerValidation: () => void
|
|
193
|
+
triggerValidation: () => void | Promise<void>;
|
|
162
194
|
/** Whether form has been submitted */
|
|
163
195
|
submitted: boolean;
|
|
196
|
+
/** Whether the whole form is disabled */
|
|
197
|
+
disabled: boolean;
|
|
164
198
|
/** Mark form as submitted */
|
|
165
199
|
markSubmitted: () => void;
|
|
166
200
|
/** Mark all fields as touched and dirty to show all issues */
|
|
@@ -173,6 +207,10 @@ export interface SformContext {
|
|
|
173
207
|
registerFieldWithIssueDisplay: (name: string) => void;
|
|
174
208
|
/** Programmatically submit the form */
|
|
175
209
|
submitForm: () => void;
|
|
210
|
+
/** Register lifecycle hooks. Returns an unregister callback. */
|
|
211
|
+
registerLifecycleHooks: (hooks: SformLifecycleHooks) => () => void;
|
|
212
|
+
/** Run all hooks for a lifecycle event. */
|
|
213
|
+
runLifecycleHooks: (event: SformLifecycleEvent) => Promise<void>;
|
|
176
214
|
/** Get the current form state (for buttons and state-aware components) */
|
|
177
215
|
getFormState: <T = unknown>() => ButtonState<T>;
|
|
178
216
|
/** Get issues that are not displayed by any Sfield component */
|
|
@@ -211,6 +249,14 @@ export interface SformProps<Input extends RemoteFormInput = RemoteFormInput, Out
|
|
|
211
249
|
validateOn?: ValidateOn;
|
|
212
250
|
/** Form element class */
|
|
213
251
|
class?: string;
|
|
252
|
+
/** If true, only run preflight validation (no submission) */
|
|
253
|
+
preflightOnly?: boolean;
|
|
254
|
+
/** If true, reset touched/dirty/submitted state after successful submit response */
|
|
255
|
+
resetOnSuccess?: boolean;
|
|
256
|
+
/** Lifecycle hooks for submit/validate phases */
|
|
257
|
+
lifecycle?: SformLifecycleHooks;
|
|
258
|
+
/** If true, disables the entire form: no validation, no submission, all fields disabled */
|
|
259
|
+
disabled?: boolean;
|
|
214
260
|
/** Children content */
|
|
215
261
|
children: Snippet;
|
|
216
262
|
}
|
|
@@ -221,7 +267,7 @@ export interface BaseSfieldProps {
|
|
|
221
267
|
/** Field name - corresponds to form.fields[name] */
|
|
222
268
|
name: string;
|
|
223
269
|
/** Field label */
|
|
224
|
-
label?: string;
|
|
270
|
+
label?: string | Snippet;
|
|
225
271
|
/** Placeholder text */
|
|
226
272
|
placeholder?: string;
|
|
227
273
|
/** Field-level validateOn override */
|
|
@@ -319,24 +365,14 @@ export interface RangeSfieldProps extends BaseSfieldProps {
|
|
|
319
365
|
export interface ToggleSfieldProps extends BaseSfieldProps {
|
|
320
366
|
type: 'toggle';
|
|
321
367
|
/** Label when toggle is on */
|
|
322
|
-
onLabel?: string;
|
|
368
|
+
onLabel?: string | Snippet;
|
|
323
369
|
/** Label when toggle is off */
|
|
324
|
-
offLabel?: string;
|
|
370
|
+
offLabel?: string | Snippet;
|
|
325
371
|
/** Value when checked */
|
|
326
372
|
checkedValue?: string;
|
|
327
373
|
/** Value when unchecked */
|
|
328
374
|
uncheckedValue?: string;
|
|
329
375
|
}
|
|
330
|
-
/**
|
|
331
|
-
* Props for toggle-options input (button group)
|
|
332
|
-
*/
|
|
333
|
-
export interface ToggleOptionsSfieldProps extends BaseSfieldProps {
|
|
334
|
-
type: 'toggle-options';
|
|
335
|
-
/** Options for toggle buttons */
|
|
336
|
-
options: ToggleOption[] | string[];
|
|
337
|
-
/** Allow multiple selections */
|
|
338
|
-
multiple?: boolean;
|
|
339
|
-
}
|
|
340
376
|
/**
|
|
341
377
|
* Props for masked input
|
|
342
378
|
*/
|
|
@@ -354,7 +390,7 @@ export interface MaskedSfieldProps extends BaseSfieldProps {
|
|
|
354
390
|
/**
|
|
355
391
|
* Discriminated union of all Sfield prop types
|
|
356
392
|
*/
|
|
357
|
-
export type SfieldProps = TextSfieldProps | PasswordSfieldProps | NumberSfieldProps | TextareaSfieldProps | SelectSfieldProps | CheckboxSfieldProps | CheckboxGroupSfieldProps | RadioSfieldProps | RangeSfieldProps | ToggleSfieldProps |
|
|
393
|
+
export type SfieldProps = TextSfieldProps | PasswordSfieldProps | NumberSfieldProps | TextareaSfieldProps | SelectSfieldProps | CheckboxSfieldProps | CheckboxGroupSfieldProps | RadioSfieldProps | RangeSfieldProps | ToggleSfieldProps | MaskedSfieldProps;
|
|
358
394
|
/**
|
|
359
395
|
* Props that Sfield manages internally and should NOT be passed from parent.
|
|
360
396
|
* These are set by Sfield itself based on context and internal state.
|
|
@@ -372,10 +408,14 @@ interface SfieldExtraProps<T extends RemoteFormFieldValue> {
|
|
|
372
408
|
field: RemoteFormField<T>;
|
|
373
409
|
/** Field-level validateOn override */
|
|
374
410
|
validateOn?: ValidateOn;
|
|
411
|
+
/** Where this field's issues should be displayed */
|
|
412
|
+
issueDisplay?: IssueDisplay;
|
|
375
413
|
/** CSS classes for sub-elements (wrapper, label, input, messages) */
|
|
376
414
|
class?: SfieldClasses | string;
|
|
377
415
|
/** Hint text displayed below the input, above validation messages */
|
|
378
416
|
hint?: string | Snippet;
|
|
417
|
+
/** Lifecycle hooks registered while this field is mounted */
|
|
418
|
+
lifecycle?: SformLifecycleHooks;
|
|
379
419
|
}
|
|
380
420
|
/**
|
|
381
421
|
* Helper type: Create Sfield props from component props
|
|
@@ -404,14 +444,12 @@ export type SfieldRadioProps = SfieldPropsFrom<RadioInputProps, string, 'radio'>
|
|
|
404
444
|
export type SfieldRangeProps = SfieldPropsFrom<RangeInputProps, number, 'range'>;
|
|
405
445
|
/** Props for toggle input */
|
|
406
446
|
export type SfieldToggleProps = SfieldPropsFrom<ToggleInputProps, boolean, 'toggle'>;
|
|
407
|
-
/** Props for toggle-options input */
|
|
408
|
-
export type SfieldToggleOptionsProps = SfieldPropsFrom<ToggleOptionsInputProps, string, 'toggle-options'>;
|
|
409
447
|
/** Props for masked input */
|
|
410
448
|
export type SfieldMaskedProps = SfieldPropsFrom<MaskedInputProps, string, 'masked'>;
|
|
411
449
|
/** Props for hidden input */
|
|
412
450
|
export type SfieldHiddenProps = SfieldPropsFrom<HiddenInputProps, string, 'hidden'>;
|
|
413
451
|
/** All possible Sfield props as a discriminated union */
|
|
414
|
-
type AllSfieldProps = SfieldTextProps | SfieldPasswordProps | SfieldNumberProps | SfieldTextareaProps | SfieldSelectProps | SfieldCheckboxProps | SfieldCheckboxGroupProps | SfieldRadioProps | SfieldRangeProps | SfieldToggleProps |
|
|
452
|
+
type AllSfieldProps = SfieldTextProps | SfieldPasswordProps | SfieldNumberProps | SfieldTextareaProps | SfieldSelectProps | SfieldCheckboxProps | SfieldCheckboxGroupProps | SfieldRadioProps | SfieldRangeProps | SfieldToggleProps | SfieldMaskedProps | SfieldHiddenProps;
|
|
415
453
|
/**
|
|
416
454
|
* Extract props that match a specific field value type.
|
|
417
455
|
* This ensures type="number" is only valid for number fields, etc.
|
|
@@ -453,7 +491,7 @@ export interface BaseInputComponentProps {
|
|
|
453
491
|
/** Field name */
|
|
454
492
|
name: string;
|
|
455
493
|
/** Field label */
|
|
456
|
-
label?: string;
|
|
494
|
+
label?: string | Snippet;
|
|
457
495
|
/** Placeholder text */
|
|
458
496
|
placeholder?: string;
|
|
459
497
|
/** Input class */
|
|
@@ -575,6 +613,8 @@ export interface HiddenInputProps {
|
|
|
575
613
|
name: string;
|
|
576
614
|
/** The value for the hidden field - required for hidden inputs */
|
|
577
615
|
value?: string;
|
|
616
|
+
/** Whether the hidden input is disabled */
|
|
617
|
+
disabled?: boolean;
|
|
578
618
|
}
|
|
579
619
|
/**
|
|
580
620
|
* @deprecated Use ButtonState instead
|
|
@@ -608,8 +648,8 @@ export interface ButtonFormLike<T = unknown> {
|
|
|
608
648
|
export interface ButtonInputProps<T = unknown> {
|
|
609
649
|
/** The remote form - used to infer the result type T */
|
|
610
650
|
form: ButtonFormLike<T>;
|
|
611
|
-
/** Button text (used if no children snippet provided) */
|
|
612
|
-
label?: string
|
|
651
|
+
/** Button text (used if no children snippet provided). Can also be a snippet receiving ButtonState<T>. */
|
|
652
|
+
label?: string | Snippet<[ButtonState<T>]>;
|
|
613
653
|
/** Button type */
|
|
614
654
|
buttonType?: 'submit' | 'reset' | 'button';
|
|
615
655
|
/** Button class */
|
|
@@ -658,31 +698,14 @@ export interface PasswordInputProps extends BaseInputComponentProps {
|
|
|
658
698
|
*/
|
|
659
699
|
export interface ToggleInputProps extends BaseInputComponentProps {
|
|
660
700
|
/** Label when toggle is on */
|
|
661
|
-
onLabel?: string;
|
|
701
|
+
onLabel?: string | Snippet;
|
|
662
702
|
/** Label when toggle is off */
|
|
663
|
-
offLabel?: string;
|
|
703
|
+
offLabel?: string | Snippet;
|
|
664
704
|
/** Value when checked */
|
|
665
705
|
checkedValue?: string;
|
|
666
706
|
/** Value when unchecked */
|
|
667
707
|
uncheckedValue?: string;
|
|
668
708
|
}
|
|
669
|
-
/**
|
|
670
|
-
* Toggle option for ToggleOptions component
|
|
671
|
-
*/
|
|
672
|
-
export interface ToggleOption {
|
|
673
|
-
value: string;
|
|
674
|
-
label: string;
|
|
675
|
-
disabled?: boolean;
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Props for toggle options (button group) component
|
|
679
|
-
*/
|
|
680
|
-
export interface ToggleOptionsInputProps extends BaseInputComponentProps {
|
|
681
|
-
/** Options to display as toggle buttons */
|
|
682
|
-
options: ToggleOption[] | string[];
|
|
683
|
-
/** Allow multiple selections */
|
|
684
|
-
multiple?: boolean;
|
|
685
|
-
}
|
|
686
709
|
/**
|
|
687
710
|
* Props for masked input component
|
|
688
711
|
*/
|
|
@@ -704,9 +727,10 @@ export interface MaskedInputProps extends BaseInputComponentProps, InputAffixPro
|
|
|
704
727
|
* Props for Fieldset utils component
|
|
705
728
|
*/
|
|
706
729
|
export type FieldsetProps = {
|
|
707
|
-
label?: string;
|
|
730
|
+
label?: string | Snippet;
|
|
708
731
|
labelClass?: string;
|
|
709
732
|
className?: string;
|
|
710
733
|
disabled?: boolean;
|
|
711
734
|
children: Snippet;
|
|
735
|
+
type: 'radio' | 'checkbox';
|
|
712
736
|
};
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { FieldsetProps } from '../types';
|
|
4
4
|
|
|
5
|
-
let { label, labelClass, className, disabled, children }: FieldsetProps = $props();
|
|
5
|
+
let { label, labelClass, className, disabled, children, type }: FieldsetProps = $props();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
|
-
<fieldset class="sform-
|
|
8
|
+
<fieldset class="{`sform-${type}-group`} {className ?? ''}" {disabled}>
|
|
9
9
|
{#if label}
|
|
10
10
|
<legend class={labelClass}>{label}</legend>
|
|
11
11
|
{/if}
|
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
</fieldset>
|
|
14
14
|
|
|
15
15
|
<style>
|
|
16
|
-
.sform-checkbox-group
|
|
16
|
+
:is(.sform-checkbox-group, .sform-radio-group) {
|
|
17
|
+
border: none;
|
|
18
|
+
padding: 0;
|
|
19
|
+
margin: 0;
|
|
20
|
+
}
|
|
21
|
+
:is(.sform-checkbox-group, .sform-radio-group) legend {
|
|
17
22
|
padding: 0.25rem 0.5rem;
|
|
18
23
|
margin: 0;
|
|
19
24
|
border: var(--sform-border-width) solid var(--sform-border);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Sform, Sfield, Sbutton, SIssues, SResult } from './Sform/index.js';
|
|
2
|
-
export type { ValidateOn, FieldState, SfieldClasses, InputType, SelectOption, SformContext, SformProps, ButtonState, ButtonInputProps, InputAffixProps, SfieldTypeMap, AllowedSfieldType, TypedSfieldProps, SfieldBaseProps, TypedBaseSfieldProps, SfieldTextProps, SfieldPasswordProps, SfieldNumberProps, SfieldTextareaProps, SfieldSelectProps, SfieldCheckboxProps, SfieldCheckboxGroupProps, SfieldRadioProps, SfieldRangeProps, SfieldToggleProps,
|
|
2
|
+
export type { ValidateOn, FieldState, SfieldClasses, InputType, SelectOption, SformContext, SformProps, ButtonState, ButtonInputProps, InputAffixProps, SfieldTypeMap, AllowedSfieldType, TypedSfieldProps, SfieldBaseProps, TypedBaseSfieldProps, SfieldTextProps, SfieldPasswordProps, SfieldNumberProps, SfieldTextareaProps, SfieldSelectProps, SfieldCheckboxProps, SfieldCheckboxGroupProps, SfieldRadioProps, SfieldRangeProps, SfieldToggleProps, SfieldMaskedProps, RemoteForm, RemoteFormField, RemoteFormFields, RemoteFormFieldValue, RemoteFormInput, RemoteFormIssue } from './Sform/index.js';
|
package/package.json
CHANGED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ToggleOptionsInputProps, ToggleOption } from '../types.js';
|
|
3
|
-
|
|
4
|
-
let {
|
|
5
|
-
field,
|
|
6
|
-
name,
|
|
7
|
-
label,
|
|
8
|
-
class: className,
|
|
9
|
-
labelClass,
|
|
10
|
-
disabled,
|
|
11
|
-
options,
|
|
12
|
-
multiple = false,
|
|
13
|
-
showIssues,
|
|
14
|
-
onblur,
|
|
15
|
-
oninput
|
|
16
|
-
}: ToggleOptionsInputProps = $props();
|
|
17
|
-
|
|
18
|
-
// Normalize options to ToggleOption format
|
|
19
|
-
const normalizedOptions: ToggleOption[] = $derived(
|
|
20
|
-
options.map((opt) => (typeof opt === 'string' ? { value: opt, label: opt } : opt))
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
// Helper to get field attrs with controlled aria-invalid
|
|
24
|
-
function getFieldAttrs(optionValue: string) {
|
|
25
|
-
const inputType = multiple ? 'checkbox' : 'radio';
|
|
26
|
-
const attrs = field.as(inputType, optionValue);
|
|
27
|
-
return {
|
|
28
|
-
...attrs,
|
|
29
|
-
'aria-invalid': showIssues ? attrs['aria-invalid'] : undefined
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
{#if label}
|
|
35
|
-
<span id="{name}-label" class={labelClass}>{label}</span>
|
|
36
|
-
{/if}
|
|
37
|
-
<div
|
|
38
|
-
class="sform-toggle-options {className ?? ''}"
|
|
39
|
-
role={multiple ? 'group' : 'radiogroup'}
|
|
40
|
-
aria-labelledby={label ? `${name}-label` : undefined}
|
|
41
|
-
aria-label={!label ? name : undefined}
|
|
42
|
-
>
|
|
43
|
-
{#each normalizedOptions as option (option.value)}
|
|
44
|
-
{@const optionDisabled = disabled || option.disabled}
|
|
45
|
-
{@const fieldAttrs = getFieldAttrs(option.value)}
|
|
46
|
-
{@const inputId = `${name}-${option.value}`}
|
|
47
|
-
<label class="sform-toggle-option" class:disabled={optionDisabled} for={inputId}>
|
|
48
|
-
<input
|
|
49
|
-
{...fieldAttrs}
|
|
50
|
-
type={multiple ? 'checkbox' : 'radio'}
|
|
51
|
-
id={inputId}
|
|
52
|
-
disabled={optionDisabled}
|
|
53
|
-
{onblur}
|
|
54
|
-
{oninput}
|
|
55
|
-
/>
|
|
56
|
-
<span>{option.label}</span>
|
|
57
|
-
</label>
|
|
58
|
-
{/each}
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<style>
|
|
62
|
-
.sform-toggle-options {
|
|
63
|
-
display: inline-flex;
|
|
64
|
-
border-radius: 0.375rem;
|
|
65
|
-
overflow: hidden;
|
|
66
|
-
border: 1px solid #ccc;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.sform-toggle-option {
|
|
70
|
-
display: flex;
|
|
71
|
-
align-items: center;
|
|
72
|
-
justify-content: center;
|
|
73
|
-
padding: 0.5rem 1rem;
|
|
74
|
-
background: white;
|
|
75
|
-
cursor: pointer;
|
|
76
|
-
transition:
|
|
77
|
-
background-color 0.15s,
|
|
78
|
-
color 0.15s;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.sform-toggle-option:not(:last-child) {
|
|
82
|
-
border-right: 1px solid #ccc;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.sform-toggle-option:hover:not(.disabled) {
|
|
86
|
-
background-color: #f5f5f5;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/* Hide the actual input but keep it functional */
|
|
90
|
-
.sform-toggle-option input {
|
|
91
|
-
position: absolute;
|
|
92
|
-
opacity: 0;
|
|
93
|
-
width: 0;
|
|
94
|
-
height: 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/* Style when checked */
|
|
98
|
-
.sform-toggle-option:has(input:checked) {
|
|
99
|
-
background-color: #2196f3;
|
|
100
|
-
color: white;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.sform-toggle-option:has(input:checked):hover {
|
|
104
|
-
background-color: #1976d2;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.sform-toggle-option.disabled {
|
|
108
|
-
opacity: 0.5;
|
|
109
|
-
cursor: not-allowed;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/* Focus styles */
|
|
113
|
-
.sform-toggle-option:has(input:focus-visible) {
|
|
114
|
-
outline: 2px solid #2196f3;
|
|
115
|
-
outline-offset: -2px;
|
|
116
|
-
z-index: 1;
|
|
117
|
-
}
|
|
118
|
-
</style>
|