@samuel-charpentier/sform 0.0.2 → 0.0.4
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 +281 -76
- package/dist/Sform/SIssues.svelte +49 -0
- package/dist/Sform/SIssues.svelte.d.ts +14 -0
- package/dist/Sform/SResult.svelte +36 -0
- package/dist/Sform/SResult.svelte.d.ts +39 -0
- package/dist/Sform/Sfield.svelte +7 -1
- package/dist/Sform/Sform.svelte +2 -1
- package/dist/Sform/context.svelte.d.ts +11 -2
- package/dist/Sform/context.svelte.js +62 -2
- package/dist/Sform/index.d.ts +3 -1
- package/dist/Sform/index.js +2 -0
- package/dist/Sform/inputs/ButtonInput.svelte +26 -45
- package/dist/Sform/inputs/ButtonInput.svelte.d.ts +43 -29
- package/dist/Sform/inputs/CheckboxGroupInput.svelte +3 -16
- package/dist/Sform/inputs/HiddenInput.svelte +10 -0
- package/dist/Sform/inputs/HiddenInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/RadioInput.svelte +3 -5
- package/dist/Sform/inputs/TextInput.svelte +26 -30
- package/dist/Sform/sform.css +14 -0
- package/dist/Sform/types.d.ts +95 -15
- package/dist/Sform/utils/Fieldset.svelte +24 -0
- package/dist/Sform/utils/Fieldset.svelte.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/Sform/Sfield.svelte
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import ToggleOptionsInput from './inputs/ToggleOptionsInput.svelte';
|
|
19
19
|
import MaskedInput from './inputs/MaskedInput.svelte';
|
|
20
20
|
import PasswordInput from './inputs/PasswordInput.svelte';
|
|
21
|
+
import HiddenInput from './inputs/HiddenInput.svelte';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Sfield - Type-safe form field component.
|
|
@@ -41,6 +42,10 @@
|
|
|
41
42
|
// Register this field with the context on mount
|
|
42
43
|
$effect(() => {
|
|
43
44
|
context.registerField(name);
|
|
45
|
+
// Register for issue display tracking (all types except hidden display their issues)
|
|
46
|
+
if (props.type !== 'hidden') {
|
|
47
|
+
context.registerFieldWithIssueDisplay(name);
|
|
48
|
+
}
|
|
44
49
|
});
|
|
45
50
|
|
|
46
51
|
const classes: SfieldClasses = $derived(
|
|
@@ -112,7 +117,6 @@
|
|
|
112
117
|
'month',
|
|
113
118
|
'week',
|
|
114
119
|
'color',
|
|
115
|
-
'hidden',
|
|
116
120
|
'file'
|
|
117
121
|
].includes(props.type)
|
|
118
122
|
);
|
|
@@ -147,6 +151,8 @@
|
|
|
147
151
|
<ToggleOptionsInput {...passthroughProps()} {...internalProps} />
|
|
148
152
|
{:else if props.type === 'masked'}
|
|
149
153
|
<MaskedInput {...passthroughProps()} {...internalProps} />
|
|
154
|
+
{:else if props.type === 'hidden'}
|
|
155
|
+
<HiddenInput {...passthroughProps()} {...internalProps} />
|
|
150
156
|
{/if}
|
|
151
157
|
|
|
152
158
|
{#if props.hint}
|
package/dist/Sform/Sform.svelte
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
import type { SformContext, ValidateOn } from './types.js';
|
|
2
|
-
|
|
1
|
+
import type { RemoteFormIssue, SformContext, ValidateOn } from './types.js';
|
|
2
|
+
interface FormLike {
|
|
3
|
+
pending?: number;
|
|
4
|
+
result?: unknown;
|
|
5
|
+
fields: {
|
|
6
|
+
allIssues?: () => RemoteFormIssue[] | undefined;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function createSformContext(getValidateOn: () => ValidateOn, getFieldNames: () => string[], triggerValidation: () => void, submitForm: () => void, getForm: () => FormLike): SformContext;
|
|
3
11
|
export declare function getSformContext(): SformContext;
|
|
12
|
+
export {};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
2
|
import { SvelteSet } from 'svelte/reactivity';
|
|
3
3
|
const SFORM_CONTEXT_KEY = Symbol('sform-context');
|
|
4
|
-
export function createSformContext(getValidateOn, getFieldNames, triggerValidation, submitForm) {
|
|
4
|
+
export function createSformContext(getValidateOn, getFieldNames, triggerValidation, submitForm, getForm) {
|
|
5
5
|
const touched = new SvelteSet();
|
|
6
6
|
const dirty = new SvelteSet();
|
|
7
7
|
const registeredFields = new SvelteSet();
|
|
8
|
+
const fieldsWithIssueDisplay = new SvelteSet();
|
|
8
9
|
let submitted = $state(false);
|
|
9
10
|
const context = {
|
|
10
11
|
get validateOn() {
|
|
@@ -51,12 +52,71 @@ export function createSformContext(getValidateOn, getFieldNames, triggerValidati
|
|
|
51
52
|
registerField: (name) => {
|
|
52
53
|
registeredFields.add(name);
|
|
53
54
|
},
|
|
55
|
+
registerFieldWithIssueDisplay: (name) => {
|
|
56
|
+
fieldsWithIssueDisplay.add(name);
|
|
57
|
+
},
|
|
54
58
|
resetFieldStates: () => {
|
|
55
59
|
touched.clear();
|
|
56
60
|
dirty.clear();
|
|
57
61
|
submitted = false;
|
|
58
62
|
},
|
|
59
|
-
submitForm
|
|
63
|
+
submitForm,
|
|
64
|
+
getFormState: () => {
|
|
65
|
+
const form = getForm();
|
|
66
|
+
const pending = (form.pending ?? 0) !== 0;
|
|
67
|
+
const hasResult = form.result !== undefined;
|
|
68
|
+
const issues = form.fields.allIssues?.() ?? [];
|
|
69
|
+
const hasIssues = issues.length > 0;
|
|
70
|
+
if (pending) {
|
|
71
|
+
return {
|
|
72
|
+
state: 'pending',
|
|
73
|
+
pending: true,
|
|
74
|
+
success: false,
|
|
75
|
+
hasIssues: false,
|
|
76
|
+
result: undefined
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (hasResult) {
|
|
80
|
+
// If there's a result, it's success (issues come via invalid() which throws, no result)
|
|
81
|
+
return {
|
|
82
|
+
state: 'success',
|
|
83
|
+
pending: false,
|
|
84
|
+
success: true,
|
|
85
|
+
hasIssues: false,
|
|
86
|
+
result: form.result
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (hasIssues) {
|
|
90
|
+
return {
|
|
91
|
+
state: 'hasIssues',
|
|
92
|
+
pending: false,
|
|
93
|
+
success: false,
|
|
94
|
+
hasIssues: true,
|
|
95
|
+
result: undefined
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
state: 'default',
|
|
100
|
+
pending: false,
|
|
101
|
+
success: false,
|
|
102
|
+
hasIssues: false,
|
|
103
|
+
result: undefined
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
getUnhandledIssues: () => {
|
|
107
|
+
const form = getForm();
|
|
108
|
+
const allIssues = form.fields.allIssues?.() ?? [];
|
|
109
|
+
// Filter out issues that are linked to fields with issue display
|
|
110
|
+
return allIssues.filter((issue) => {
|
|
111
|
+
// If issue has no path, it's a form-level issue (from invalid("message"))
|
|
112
|
+
if (!issue.path || issue.path.length === 0) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
// Check if the first path segment is a field that displays issues
|
|
116
|
+
const fieldName = String(issue.path[0]);
|
|
117
|
+
return !fieldsWithIssueDisplay.has(fieldName);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
60
120
|
};
|
|
61
121
|
setContext(SFORM_CONTEXT_KEY, context);
|
|
62
122
|
return context;
|
package/dist/Sform/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { default as Sform } from './Sform.svelte';
|
|
2
2
|
export { default as Sfield } from './Sfield.svelte';
|
|
3
3
|
export { default as Sbutton } from './inputs/ButtonInput.svelte';
|
|
4
|
+
export { default as SIssues } from './SIssues.svelte';
|
|
5
|
+
export { default as SResult } from './SResult.svelte';
|
|
4
6
|
export { applyMask, unmask, MASK_PATTERNS, DEFAULT_TOKENS, type MaskOptions, type MaskResult, type MaskToken, type MaskPattern } from './utils/mask.js';
|
|
5
|
-
export type { ValidateOn, FieldState, SfieldClasses, InputType, SelectOption, SformContext, SformProps, ButtonFormState, ButtonInputProps, InputAffixProps, RangeInputProps, ToggleInputProps, ToggleOption, ToggleOptionsInputProps, SfieldTypeMap, AllowedSfieldType, TypedSfieldProps, SfieldBaseProps, TypedBaseSfieldProps, SfieldTextProps, SfieldPasswordProps, SfieldNumberProps, SfieldTextareaProps, SfieldSelectProps, SfieldCheckboxProps, SfieldCheckboxGroupProps, SfieldRadioProps, SfieldRangeProps, SfieldToggleProps, SfieldToggleOptionsProps, SfieldMaskedProps, RemoteForm, RemoteFormField, RemoteFormFields, RemoteFormFieldValue, RemoteFormInput, RemoteFormIssue } from './types.js';
|
|
7
|
+
export type { ValidateOn, FieldState, SfieldClasses, InputType, SelectOption, SformContext, SformProps, ButtonState, ButtonFormState, ButtonInputProps, SIssuesProps, InputAffixProps, RangeInputProps, ToggleInputProps, ToggleOption, ToggleOptionsInputProps, SfieldTypeMap, AllowedSfieldType, TypedSfieldProps, SfieldBaseProps, TypedBaseSfieldProps, SfieldTextProps, SfieldPasswordProps, SfieldNumberProps, SfieldTextareaProps, SfieldSelectProps, SfieldCheckboxProps, SfieldCheckboxGroupProps, SfieldRadioProps, SfieldRangeProps, SfieldToggleProps, SfieldToggleOptionsProps, SfieldMaskedProps, RemoteForm, RemoteFormField, RemoteFormFields, RemoteFormFieldValue, RemoteFormInput, RemoteFormIssue } from './types.js';
|
package/dist/Sform/index.js
CHANGED
|
@@ -3,5 +3,7 @@ export { default as Sform } from './Sform.svelte';
|
|
|
3
3
|
export { default as Sfield } from './Sfield.svelte';
|
|
4
4
|
// Standalone components (not routed through Sfield)
|
|
5
5
|
export { default as Sbutton } from './inputs/ButtonInput.svelte';
|
|
6
|
+
export { default as SIssues } from './SIssues.svelte';
|
|
7
|
+
export { default as SResult } from './SResult.svelte';
|
|
6
8
|
// Utilities
|
|
7
9
|
export { applyMask, unmask, MASK_PATTERNS, DEFAULT_TOKENS } from './utils/mask.js';
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="T = unknown">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ButtonState, RemoteFormIssue } from '../types.js';
|
|
4
4
|
import { getSformContext } from '../context.svelte.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Minimal form shape needed for type inference.
|
|
8
|
+
* This allows the component to infer T from the form's result type.
|
|
9
|
+
*/
|
|
10
|
+
interface FormLike<Output> {
|
|
11
|
+
result?: Output;
|
|
7
12
|
pending?: number;
|
|
8
|
-
result?: unknown;
|
|
9
13
|
fields: {
|
|
10
14
|
allIssues?: () => RemoteFormIssue[] | undefined;
|
|
15
|
+
[key: string]: unknown;
|
|
11
16
|
};
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
interface Props {
|
|
15
|
-
/** The remote form -
|
|
16
|
-
form: FormLike
|
|
17
|
-
/** Button text (used if no
|
|
20
|
+
/** The remote form - used to infer the result type T */
|
|
21
|
+
form: FormLike<T>;
|
|
22
|
+
/** Button text (used if no children snippet provided) */
|
|
18
23
|
label?: string;
|
|
19
24
|
/** Button type */
|
|
20
25
|
buttonType?: 'submit' | 'reset' | 'button';
|
|
@@ -22,55 +27,37 @@
|
|
|
22
27
|
class?: string;
|
|
23
28
|
/** Whether button is disabled */
|
|
24
29
|
disabled?: boolean;
|
|
30
|
+
/** Children snippet receives ButtonState<T> for custom rendering with typed result */
|
|
31
|
+
children?: Snippet<[ButtonState<T>]>;
|
|
25
32
|
/** Callback that runs before validation/submission (can be async) */
|
|
26
33
|
onsubmit?: () => void | Promise<void>;
|
|
27
|
-
/** Snippet for default state */
|
|
28
|
-
defaultState?: Snippet<[ButtonFormState]>;
|
|
29
|
-
/** Snippet for pending state */
|
|
30
|
-
pendingState?: Snippet<[ButtonFormState]>;
|
|
31
|
-
/** Snippet for success state */
|
|
32
|
-
successState?: Snippet<[ButtonFormState]>;
|
|
33
|
-
/** Snippet for error/issues state */
|
|
34
|
-
errorState?: Snippet<[ButtonFormState]>;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
let {
|
|
38
|
-
form,
|
|
39
37
|
label = 'Submit',
|
|
40
38
|
buttonType = 'submit',
|
|
41
39
|
class: className,
|
|
42
40
|
disabled = false,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
pendingState,
|
|
46
|
-
successState,
|
|
47
|
-
errorState
|
|
41
|
+
children,
|
|
42
|
+
onsubmit
|
|
48
43
|
}: Props = $props();
|
|
49
44
|
|
|
50
|
-
// Get Sform context
|
|
45
|
+
// Get Sform context for form state and actions
|
|
51
46
|
const sformContext = getSformContext();
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const hasResult = form.result !== undefined;
|
|
56
|
-
const issues = form.fields.allIssues?.() ?? [];
|
|
57
|
-
const hasIssues = issues.length > 0;
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
pending,
|
|
61
|
-
success: hasResult && !hasIssues && !pending,
|
|
62
|
-
hasIssues,
|
|
63
|
-
result: form.result
|
|
64
|
-
};
|
|
65
|
-
});
|
|
48
|
+
// Get form state from context with the generic type
|
|
49
|
+
const formState = $derived.by(sformContext.getFormState<T>);
|
|
66
50
|
|
|
67
51
|
const isDisabled = $derived(disabled || formState.pending);
|
|
68
52
|
|
|
69
53
|
async function handleClick(event: MouseEvent) {
|
|
70
|
-
if (buttonType !== 'submit'
|
|
54
|
+
if (buttonType !== 'submit') return;
|
|
71
55
|
|
|
72
56
|
event.preventDefault();
|
|
73
|
-
|
|
57
|
+
|
|
58
|
+
if (onsubmit) {
|
|
59
|
+
await onsubmit();
|
|
60
|
+
}
|
|
74
61
|
|
|
75
62
|
// Mark form as submitted and all fields dirty so issues display when server responds
|
|
76
63
|
sformContext.markSubmitted();
|
|
@@ -82,14 +69,8 @@
|
|
|
82
69
|
</script>
|
|
83
70
|
|
|
84
71
|
<button type={buttonType} class={className} disabled={isDisabled} onclick={handleClick}>
|
|
85
|
-
{#if
|
|
86
|
-
{@render
|
|
87
|
-
{:else if formState.success && successState}
|
|
88
|
-
{@render successState(formState)}
|
|
89
|
-
{:else if formState.hasIssues && errorState}
|
|
90
|
-
{@render errorState(formState)}
|
|
91
|
-
{:else if defaultState}
|
|
92
|
-
{@render defaultState(formState)}
|
|
72
|
+
{#if children}
|
|
73
|
+
{@render children(formState)}
|
|
93
74
|
{:else}
|
|
94
75
|
{label}
|
|
95
76
|
{/if}
|
|
@@ -1,34 +1,48 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import type { ButtonState, RemoteFormIssue } from '../types.js';
|
|
3
|
+
declare function $$render<T = unknown>(): {
|
|
4
|
+
props: {
|
|
5
|
+
/** The remote form - used to infer the result type T */
|
|
6
|
+
form: {
|
|
7
|
+
result?: T | undefined;
|
|
8
|
+
pending?: number;
|
|
9
|
+
fields: {
|
|
10
|
+
allIssues?: () => RemoteFormIssue[] | undefined;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/** Button text (used if no children snippet provided) */
|
|
15
|
+
label?: string;
|
|
16
|
+
/** Button type */
|
|
17
|
+
buttonType?: "submit" | "reset" | "button";
|
|
18
|
+
/** Button class */
|
|
19
|
+
class?: string;
|
|
20
|
+
/** Whether button is disabled */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Children snippet receives ButtonState<T> for custom rendering with typed result */
|
|
23
|
+
children?: Snippet<[ButtonState<T>]>;
|
|
24
|
+
/** Callback that runs before validation/submission (can be async) */
|
|
25
|
+
onsubmit?: () => void | Promise<void>;
|
|
8
26
|
};
|
|
27
|
+
exports: {};
|
|
28
|
+
bindings: "";
|
|
29
|
+
slots: {};
|
|
30
|
+
events: {};
|
|
31
|
+
};
|
|
32
|
+
declare class __sveltets_Render<T = unknown> {
|
|
33
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
34
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
35
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
36
|
+
bindings(): "";
|
|
37
|
+
exports(): {};
|
|
9
38
|
}
|
|
10
|
-
interface
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
buttonType?: 'submit' | 'reset' | 'button';
|
|
17
|
-
/** Button class */
|
|
18
|
-
class?: string;
|
|
19
|
-
/** Whether button is disabled */
|
|
20
|
-
disabled?: boolean;
|
|
21
|
-
/** Callback that runs before validation/submission (can be async) */
|
|
22
|
-
onsubmit?: () => void | Promise<void>;
|
|
23
|
-
/** Snippet for default state */
|
|
24
|
-
defaultState?: Snippet<[ButtonFormState]>;
|
|
25
|
-
/** Snippet for pending state */
|
|
26
|
-
pendingState?: Snippet<[ButtonFormState]>;
|
|
27
|
-
/** Snippet for success state */
|
|
28
|
-
successState?: Snippet<[ButtonFormState]>;
|
|
29
|
-
/** Snippet for error/issues state */
|
|
30
|
-
errorState?: Snippet<[ButtonFormState]>;
|
|
39
|
+
interface $$IsomorphicComponent {
|
|
40
|
+
new <T = unknown>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
41
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
42
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
43
|
+
<T = unknown>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
44
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
31
45
|
}
|
|
32
|
-
declare const ButtonInput:
|
|
33
|
-
type ButtonInput =
|
|
46
|
+
declare const ButtonInput: $$IsomorphicComponent;
|
|
47
|
+
type ButtonInput<T = unknown> = InstanceType<typeof ButtonInput<T>>;
|
|
34
48
|
export default ButtonInput;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { CheckboxGroupInputProps, SelectOption } from '../types.js';
|
|
3
|
+
import Fieldset from '../utils/Fieldset.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -29,10 +30,7 @@
|
|
|
29
30
|
}
|
|
30
31
|
</script>
|
|
31
32
|
|
|
32
|
-
<
|
|
33
|
-
{#if label}
|
|
34
|
-
<legend class={labelClass}>{label}</legend>
|
|
35
|
-
{/if}
|
|
33
|
+
<Fieldset {label} {labelClass} {className} {disabled}>
|
|
36
34
|
{#each normalizedOptions as option}
|
|
37
35
|
{@const fieldAttrs = getFieldAttrs(option.value)}
|
|
38
36
|
{@const uniqueId = `${name}-${option.value}`}
|
|
@@ -48,20 +46,9 @@
|
|
|
48
46
|
<span class="sform-checkbox-label">{option.label}</span>
|
|
49
47
|
</label>
|
|
50
48
|
{/each}
|
|
51
|
-
</
|
|
49
|
+
</Fieldset>
|
|
52
50
|
|
|
53
51
|
<style>
|
|
54
|
-
.sform-checkbox-group {
|
|
55
|
-
border: none;
|
|
56
|
-
padding: 0;
|
|
57
|
-
margin: 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.sform-checkbox-group legend {
|
|
61
|
-
padding: 0;
|
|
62
|
-
margin-bottom: 0.5rem;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
52
|
.sform-checkbox-option {
|
|
66
53
|
display: flex;
|
|
67
54
|
align-items: center;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HiddenInputProps } from '../types.js';
|
|
3
|
+
|
|
4
|
+
let { field, name, value }: HiddenInputProps = $props();
|
|
5
|
+
|
|
6
|
+
// Hidden fields require the value to be passed to field.as('hidden', value)
|
|
7
|
+
const fieldAttrs = $derived(field.as('hidden', value ?? ''));
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<input {...fieldAttrs} type="hidden" id={name} />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { RadioInputProps, SelectOption } from '../types.js';
|
|
3
|
+
import Fieldset from '../utils/Fieldset.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -29,10 +30,7 @@
|
|
|
29
30
|
}
|
|
30
31
|
</script>
|
|
31
32
|
|
|
32
|
-
<
|
|
33
|
-
{#if label}
|
|
34
|
-
<legend class={labelClass}>{label}</legend>
|
|
35
|
-
{/if}
|
|
33
|
+
<Fieldset {label} {labelClass} {className} {disabled}>
|
|
36
34
|
{#each normalizedOptions as option}
|
|
37
35
|
{@const fieldAttrs = getFieldAttrs(option.value)}
|
|
38
36
|
{@const uniqueId = `${name}-${option.value}`}
|
|
@@ -48,7 +46,7 @@
|
|
|
48
46
|
<span class="sform-radio-label">{option.label}</span>
|
|
49
47
|
</label>
|
|
50
48
|
{/each}
|
|
51
|
-
</
|
|
49
|
+
</Fieldset>
|
|
52
50
|
|
|
53
51
|
<style>
|
|
54
52
|
.sform-radio-group {
|
|
@@ -28,34 +28,30 @@
|
|
|
28
28
|
let inputElement: HTMLInputElement | undefined = $state();
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
|
-
{#if
|
|
32
|
-
{
|
|
33
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
34
|
-
{/if}
|
|
35
|
-
<div class="sform-input-wrapper {wrapperClass ?? ''}">
|
|
36
|
-
{#if prefix}
|
|
37
|
-
<div class="sform-prefix" onclick={() => inputElement?.focus()} role="presentation">
|
|
38
|
-
{#if typeof prefix === 'function'}{@render prefix()}{:else}{prefix}{/if}
|
|
39
|
-
</div>
|
|
40
|
-
{/if}
|
|
41
|
-
<input
|
|
42
|
-
bind:this={inputElement}
|
|
43
|
-
{...fieldAttrs}
|
|
44
|
-
id={name}
|
|
45
|
-
class={className}
|
|
46
|
-
{placeholder}
|
|
47
|
-
{disabled}
|
|
48
|
-
{readonly}
|
|
49
|
-
{autocomplete}
|
|
50
|
-
{onblur}
|
|
51
|
-
{oninput}
|
|
52
|
-
/>
|
|
53
|
-
{#if suffix}
|
|
54
|
-
<div class="sform-suffix" onclick={() => inputElement?.focus()} role="presentation">
|
|
55
|
-
{#if typeof suffix === 'function'}{@render suffix()}{:else}{suffix}{/if}
|
|
56
|
-
</div>
|
|
57
|
-
{/if}
|
|
58
|
-
</div>
|
|
59
|
-
{:else}
|
|
60
|
-
<input {...fieldAttrs} type="hidden" id={name} />
|
|
31
|
+
{#if label}
|
|
32
|
+
<label class={labelClass} for={name}>{label}</label>
|
|
61
33
|
{/if}
|
|
34
|
+
<div class="sform-input-wrapper {wrapperClass ?? ''}">
|
|
35
|
+
{#if prefix}
|
|
36
|
+
<div class="sform-prefix" onclick={() => inputElement?.focus()} role="presentation">
|
|
37
|
+
{#if typeof prefix === 'function'}{@render prefix()}{:else}{prefix}{/if}
|
|
38
|
+
</div>
|
|
39
|
+
{/if}
|
|
40
|
+
<input
|
|
41
|
+
bind:this={inputElement}
|
|
42
|
+
{...fieldAttrs}
|
|
43
|
+
id={name}
|
|
44
|
+
class={className}
|
|
45
|
+
{placeholder}
|
|
46
|
+
{disabled}
|
|
47
|
+
{readonly}
|
|
48
|
+
{autocomplete}
|
|
49
|
+
{onblur}
|
|
50
|
+
{oninput}
|
|
51
|
+
/>
|
|
52
|
+
{#if suffix}
|
|
53
|
+
<div class="sform-suffix" onclick={() => inputElement?.focus()} role="presentation">
|
|
54
|
+
{#if typeof suffix === 'function'}{@render suffix()}{:else}{suffix}{/if}
|
|
55
|
+
</div>
|
|
56
|
+
{/if}
|
|
57
|
+
</div>
|
package/dist/Sform/sform.css
CHANGED
|
@@ -600,6 +600,7 @@ select.sform-input {
|
|
|
600
600
|
.sform-result-success {
|
|
601
601
|
color: #065f46;
|
|
602
602
|
background: var(--sform-success-bg);
|
|
603
|
+
margin-block-start: 1rem;
|
|
603
604
|
}
|
|
604
605
|
|
|
605
606
|
.sform-result-error {
|
|
@@ -612,6 +613,19 @@ select.sform-input {
|
|
|
612
613
|
background: var(--sform-warning-bg);
|
|
613
614
|
}
|
|
614
615
|
|
|
616
|
+
/* ===========================================
|
|
617
|
+
Issues List
|
|
618
|
+
=========================================== */
|
|
619
|
+
|
|
620
|
+
.sform-issues-message {
|
|
621
|
+
font-weight: bold;
|
|
622
|
+
margin-bottom: 0.25rem;
|
|
623
|
+
}
|
|
624
|
+
.sform-issues-list {
|
|
625
|
+
margin: 0;
|
|
626
|
+
padding-inline-start: 1.25rem;
|
|
627
|
+
}
|
|
628
|
+
|
|
615
629
|
/* ===========================================
|
|
616
630
|
Utilities
|
|
617
631
|
=========================================== */
|