@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
|
@@ -1,46 +1,26 @@
|
|
|
1
1
|
<script lang="ts" generics="T = unknown">
|
|
2
|
-
import { tick
|
|
3
|
-
import type {
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import type { ButtonInputProps } from '../types.js';
|
|
4
4
|
import { getSformContext } from '../context.svelte.js';
|
|
5
5
|
|
|
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;
|
|
12
|
-
pending?: number;
|
|
13
|
-
fields: {
|
|
14
|
-
allIssues?: () => RemoteFormIssue[] | undefined;
|
|
15
|
-
[key: string]: unknown;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface Props {
|
|
20
|
-
/** The remote form - used to infer the result type T */
|
|
21
|
-
form: FormLike<T>;
|
|
22
|
-
/** Button text (used if no children snippet provided) */
|
|
23
|
-
label?: string;
|
|
24
|
-
/** Button type */
|
|
25
|
-
buttonType?: 'submit' | 'reset' | 'button';
|
|
26
|
-
/** Button class */
|
|
27
|
-
class?: string;
|
|
28
|
-
/** Whether button is disabled */
|
|
29
|
-
disabled?: boolean;
|
|
30
|
-
/** Children snippet receives ButtonState<T> for custom rendering with typed result */
|
|
31
|
-
children?: Snippet<[ButtonState<T>]>;
|
|
32
|
-
/** Callback that runs before validation/submission (can be async) */
|
|
33
|
-
onsubmit?: () => void | Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
6
|
let {
|
|
7
|
+
form,
|
|
37
8
|
label = 'Submit',
|
|
38
9
|
buttonType = 'submit',
|
|
39
10
|
class: className,
|
|
40
11
|
disabled = false,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}:
|
|
12
|
+
onsubmit,
|
|
13
|
+
children
|
|
14
|
+
}: ButtonInputProps<T> = $props();
|
|
15
|
+
|
|
16
|
+
$effect(() => {
|
|
17
|
+
// Keep the prop "form" observably consumed for strict compiler checks.
|
|
18
|
+
void form.result;
|
|
19
|
+
void form.pending;
|
|
20
|
+
void form.fields;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const hasForm = $derived(form !== undefined);
|
|
44
24
|
|
|
45
25
|
// Get Sform context for form state and actions
|
|
46
26
|
const sformContext = getSformContext();
|
|
@@ -48,13 +28,17 @@
|
|
|
48
28
|
// Get form state from context with the generic type
|
|
49
29
|
const formState = $derived.by(sformContext.getFormState<T>);
|
|
50
30
|
|
|
51
|
-
const isDisabled = $derived(
|
|
31
|
+
const isDisabled = $derived(
|
|
32
|
+
disabled || formState.pending || !hasForm || sformContext.disabled
|
|
33
|
+
);
|
|
52
34
|
|
|
53
35
|
async function handleClick(event: MouseEvent) {
|
|
54
36
|
if (buttonType !== 'submit') return;
|
|
55
37
|
|
|
56
38
|
event.preventDefault();
|
|
57
39
|
|
|
40
|
+
if (sformContext.disabled) return;
|
|
41
|
+
|
|
58
42
|
// Focus the button to trigger blur on any focused input before submission
|
|
59
43
|
// This ensures blur validation runs with valid form data, not stale data
|
|
60
44
|
buttonElement.focus();
|
|
@@ -65,12 +49,15 @@
|
|
|
65
49
|
await onsubmit();
|
|
66
50
|
}
|
|
67
51
|
|
|
52
|
+
await sformContext.runLifecycleHooks('beforeSubmit');
|
|
53
|
+
|
|
68
54
|
// Mark form as submitted and all fields dirty so issues display when server responds
|
|
69
55
|
sformContext.markSubmitted();
|
|
70
56
|
sformContext.markAllFieldsDirty();
|
|
71
57
|
|
|
72
58
|
// Submit the form via context
|
|
73
59
|
sformContext.submitForm();
|
|
60
|
+
await sformContext.runLifecycleHooks('afterSubmitTriggered');
|
|
74
61
|
}
|
|
75
62
|
let buttonElement: HTMLButtonElement;
|
|
76
63
|
</script>
|
|
@@ -84,6 +71,8 @@
|
|
|
84
71
|
>
|
|
85
72
|
{#if children}
|
|
86
73
|
{@render children(formState)}
|
|
74
|
+
{:else if typeof label === 'function'}
|
|
75
|
+
{@render label(formState)}
|
|
87
76
|
{:else}
|
|
88
77
|
{label}
|
|
89
78
|
{/if}
|
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ButtonState, RemoteFormIssue } from '../types.js';
|
|
1
|
+
import type { ButtonInputProps } from '../types.js';
|
|
3
2
|
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>;
|
|
26
|
-
};
|
|
3
|
+
props: ButtonInputProps<T>;
|
|
27
4
|
exports: {};
|
|
28
5
|
bindings: "";
|
|
29
6
|
slots: {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { CheckboxGroupInputProps, SelectOption } from '../types.js';
|
|
3
3
|
import Fieldset from '../utils/Fieldset.svelte';
|
|
4
|
+
import RadioCheckboxLabel from './RadioCheckboxLabel.svelte';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
field,
|
|
@@ -30,39 +31,10 @@
|
|
|
30
31
|
}
|
|
31
32
|
</script>
|
|
32
33
|
|
|
33
|
-
<Fieldset {label} {labelClass} {className} {disabled}>
|
|
34
|
+
<Fieldset {label} {labelClass} {className} {disabled} type="checkbox">
|
|
34
35
|
{#each normalizedOptions as option}
|
|
35
36
|
{@const fieldAttrs = getFieldAttrs(option.value)}
|
|
36
37
|
{@const uniqueId = `${name}-${option.value}`}
|
|
37
|
-
<
|
|
38
|
-
<input
|
|
39
|
-
{...fieldAttrs}
|
|
40
|
-
type="checkbox"
|
|
41
|
-
id={uniqueId}
|
|
42
|
-
disabled={disabled || option.disabled}
|
|
43
|
-
{onblur}
|
|
44
|
-
{oninput}
|
|
45
|
-
/>
|
|
46
|
-
<span class="sform-checkbox-label">{option.label}</span>
|
|
47
|
-
</label>
|
|
38
|
+
<RadioCheckboxLabel {fieldAttrs} {uniqueId} {option} {disabled} {onblur} {oninput} />
|
|
48
39
|
{/each}
|
|
49
40
|
</Fieldset>
|
|
50
|
-
|
|
51
|
-
<style>
|
|
52
|
-
.sform-checkbox-option {
|
|
53
|
-
display: flex;
|
|
54
|
-
align-items: center;
|
|
55
|
-
gap: 0.5rem;
|
|
56
|
-
cursor: pointer;
|
|
57
|
-
padding: 0.25rem 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.sform-checkbox-option.disabled {
|
|
61
|
-
opacity: 0.6;
|
|
62
|
-
cursor: not-allowed;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.sform-checkbox-option input {
|
|
66
|
-
margin: 0;
|
|
67
|
-
}
|
|
68
|
-
</style>
|
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
<label class="sform-checkbox {className ?? ''}" class:disabled>
|
|
25
25
|
<input {...fieldAttrs} type="checkbox" id={name} {disabled} {onblur} {oninput} />
|
|
26
26
|
{#if label}
|
|
27
|
-
|
|
27
|
+
{#if typeof label === 'function'}
|
|
28
|
+
{@render label()}
|
|
29
|
+
{:else}
|
|
30
|
+
<span class={labelClass}>{label}</span>
|
|
31
|
+
{/if}
|
|
28
32
|
{/if}
|
|
29
33
|
</label>
|
|
30
34
|
|
|
@@ -1,10 +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
|
+
<script lang="ts">
|
|
2
|
+
import type { HiddenInputProps } from '../types.js';
|
|
3
|
+
|
|
4
|
+
let { field, name, value, disabled }: 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} {disabled} />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { MaskedInputProps } from '../types.js';
|
|
3
3
|
import { applyMask, MASK_PATTERNS, type MaskPattern, type MaskToken } from '../utils/mask.js';
|
|
4
|
+
import TopLabel from './TopLabel.svelte';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
field,
|
|
@@ -182,11 +183,9 @@
|
|
|
182
183
|
}
|
|
183
184
|
</script>
|
|
184
185
|
|
|
185
|
-
{
|
|
186
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
187
|
-
{/if}
|
|
186
|
+
<TopLabel {label} {labelClass} {name} />
|
|
188
187
|
<!-- Hidden input holds the actual value for form submission -->
|
|
189
|
-
<input type="hidden" {name} value={field.value() ?? ''} />
|
|
188
|
+
<input type="hidden" {name} value={field.value() ?? ''} {disabled} />
|
|
190
189
|
<!-- Visible input shows masked display value but doesn't submit (no name) -->
|
|
191
190
|
<div class="sform-input-wrapper {wrapperClass ?? ''}">
|
|
192
191
|
{#if prefix}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { NumberInputComponentProps } from '../types.js';
|
|
3
|
+
import TopLabel from './TopLabel.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -73,9 +74,7 @@
|
|
|
73
74
|
}
|
|
74
75
|
</script>
|
|
75
76
|
|
|
76
|
-
{
|
|
77
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
78
|
-
{/if}
|
|
77
|
+
<TopLabel {label} {labelClass} {name} />
|
|
79
78
|
<div class="sform-input-wrapper {wrapperClass ?? ''}">
|
|
80
79
|
{#if prefix}
|
|
81
80
|
<div class="sform-prefix" onclick={() => inputElement?.focus()} role="presentation">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { PasswordInputProps } from '../types.js';
|
|
3
|
+
import TopLabel from './TopLabel.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -32,9 +33,7 @@
|
|
|
32
33
|
}
|
|
33
34
|
</script>
|
|
34
35
|
|
|
35
|
-
{
|
|
36
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
37
|
-
{/if}
|
|
36
|
+
<TopLabel {label} {labelClass} {name} />
|
|
38
37
|
<div class="sform-password-wrapper">
|
|
39
38
|
<input
|
|
40
39
|
{...fieldAttrs}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts" generics="InputType extends HTMLInputTypeAttribute & ('checkbox' | 'radio')">
|
|
2
|
+
import type { HTMLInputTypeAttribute } from 'svelte/elements';
|
|
3
|
+
import type { SelectOption } from '../types';
|
|
4
|
+
|
|
5
|
+
type FieldAttrs<InputType> = {
|
|
6
|
+
'aria-invalid'?: boolean | 'false' | 'true';
|
|
7
|
+
name: string;
|
|
8
|
+
type: InputType;
|
|
9
|
+
value?: string;
|
|
10
|
+
checked: boolean;
|
|
11
|
+
};
|
|
12
|
+
type Props = {
|
|
13
|
+
fieldAttrs: FieldAttrs<InputType>;
|
|
14
|
+
uniqueId: string;
|
|
15
|
+
option: SelectOption;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
onblur?: (event: FocusEvent) => void;
|
|
18
|
+
oninput?: (event: Event) => void;
|
|
19
|
+
};
|
|
20
|
+
let { fieldAttrs, uniqueId, option, disabled, onblur, oninput }: Props = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<label class={`sform-${fieldAttrs.type}-option`} class:disabled={option.disabled}>
|
|
24
|
+
<input {...fieldAttrs} id={uniqueId} disabled={disabled || option.disabled} {onblur} {oninput} />
|
|
25
|
+
{#if typeof option.label === 'function'}
|
|
26
|
+
{@render option.label(option)}
|
|
27
|
+
{:else}
|
|
28
|
+
<span class={`sform-${fieldAttrs.type}-label`}>{option.label}</span>
|
|
29
|
+
{/if}
|
|
30
|
+
</label>
|
|
31
|
+
|
|
32
|
+
<style>
|
|
33
|
+
.sform-checkbox-option,
|
|
34
|
+
.sform-radio-option {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 0.5rem;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
padding: 0.25rem 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:is(.sform-checkbox-option, .sform-radio-option).disabled {
|
|
43
|
+
opacity: 0.6;
|
|
44
|
+
cursor: not-allowed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:is(.sform-checkbox-option, .sform-radio-option) input {
|
|
48
|
+
margin: 0;
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HTMLInputTypeAttribute } from 'svelte/elements';
|
|
2
|
+
import type { SelectOption } from '../types';
|
|
3
|
+
declare function $$render<InputType extends HTMLInputTypeAttribute & ('checkbox' | 'radio')>(): {
|
|
4
|
+
props: {
|
|
5
|
+
fieldAttrs: {
|
|
6
|
+
'aria-invalid'?: boolean | "false" | "true";
|
|
7
|
+
name: string;
|
|
8
|
+
type: InputType;
|
|
9
|
+
value?: string;
|
|
10
|
+
checked: boolean;
|
|
11
|
+
};
|
|
12
|
+
uniqueId: string;
|
|
13
|
+
option: SelectOption;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
onblur?: (event: FocusEvent) => void;
|
|
16
|
+
oninput?: (event: Event) => void;
|
|
17
|
+
};
|
|
18
|
+
exports: {};
|
|
19
|
+
bindings: "";
|
|
20
|
+
slots: {};
|
|
21
|
+
events: {};
|
|
22
|
+
};
|
|
23
|
+
declare class __sveltets_Render<InputType extends HTMLInputTypeAttribute & ('checkbox' | 'radio')> {
|
|
24
|
+
props(): ReturnType<typeof $$render<InputType>>['props'];
|
|
25
|
+
events(): ReturnType<typeof $$render<InputType>>['events'];
|
|
26
|
+
slots(): ReturnType<typeof $$render<InputType>>['slots'];
|
|
27
|
+
bindings(): "";
|
|
28
|
+
exports(): {};
|
|
29
|
+
}
|
|
30
|
+
interface $$IsomorphicComponent {
|
|
31
|
+
new <InputType extends HTMLInputTypeAttribute & ('checkbox' | 'radio')>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<InputType>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<InputType>['props']>, ReturnType<__sveltets_Render<InputType>['events']>, ReturnType<__sveltets_Render<InputType>['slots']>> & {
|
|
32
|
+
$$bindings?: ReturnType<__sveltets_Render<InputType>['bindings']>;
|
|
33
|
+
} & ReturnType<__sveltets_Render<InputType>['exports']>;
|
|
34
|
+
<InputType extends HTMLInputTypeAttribute & ('checkbox' | 'radio')>(internal: unknown, props: ReturnType<__sveltets_Render<InputType>['props']> & {}): ReturnType<__sveltets_Render<InputType>['exports']>;
|
|
35
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
36
|
+
}
|
|
37
|
+
declare const RadioCheckboxLabel: $$IsomorphicComponent;
|
|
38
|
+
type RadioCheckboxLabel<InputType extends HTMLInputTypeAttribute & ('checkbox' | 'radio')> = InstanceType<typeof RadioCheckboxLabel<InputType>>;
|
|
39
|
+
export default RadioCheckboxLabel;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { RadioInputProps, SelectOption } from '../types.js';
|
|
3
3
|
import Fieldset from '../utils/Fieldset.svelte';
|
|
4
|
+
import RadioCheckboxLabel from './RadioCheckboxLabel.svelte';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
field,
|
|
@@ -30,50 +31,10 @@
|
|
|
30
31
|
}
|
|
31
32
|
</script>
|
|
32
33
|
|
|
33
|
-
<Fieldset {label} {labelClass} {className} {disabled}>
|
|
34
|
+
<Fieldset {label} {labelClass} {className} {disabled} type="radio">
|
|
34
35
|
{#each normalizedOptions as option}
|
|
35
36
|
{@const fieldAttrs = getFieldAttrs(option.value)}
|
|
36
37
|
{@const uniqueId = `${name}-${option.value}`}
|
|
37
|
-
<
|
|
38
|
-
<input
|
|
39
|
-
{...fieldAttrs}
|
|
40
|
-
type="radio"
|
|
41
|
-
id={uniqueId}
|
|
42
|
-
disabled={disabled || option.disabled}
|
|
43
|
-
{onblur}
|
|
44
|
-
{oninput}
|
|
45
|
-
/>
|
|
46
|
-
<span class="sform-radio-label">{option.label}</span>
|
|
47
|
-
</label>
|
|
38
|
+
<RadioCheckboxLabel {fieldAttrs} {uniqueId} {option} {disabled} {onblur} {oninput} />
|
|
48
39
|
{/each}
|
|
49
40
|
</Fieldset>
|
|
50
|
-
|
|
51
|
-
<style>
|
|
52
|
-
.sform-radio-group {
|
|
53
|
-
border: none;
|
|
54
|
-
padding: 0;
|
|
55
|
-
margin: 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.sform-radio-group legend {
|
|
59
|
-
padding: 0;
|
|
60
|
-
margin-bottom: 0.5rem;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.sform-radio-option {
|
|
64
|
-
display: flex;
|
|
65
|
-
align-items: center;
|
|
66
|
-
gap: 0.5rem;
|
|
67
|
-
cursor: pointer;
|
|
68
|
-
padding: 0.25rem 0;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.sform-radio-option.disabled {
|
|
72
|
-
opacity: 0.6;
|
|
73
|
-
cursor: not-allowed;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.sform-radio-option input {
|
|
77
|
-
margin: 0;
|
|
78
|
-
}
|
|
79
|
-
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { RangeInputProps } from '../types.js';
|
|
3
|
+
import TopLabel from './TopLabel.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -30,9 +31,7 @@
|
|
|
30
31
|
});
|
|
31
32
|
</script>
|
|
32
33
|
|
|
33
|
-
{
|
|
34
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
35
|
-
{/if}
|
|
34
|
+
<TopLabel {label} {labelClass} {name} />
|
|
36
35
|
<div class="sform-range-wrapper">
|
|
37
36
|
<input
|
|
38
37
|
{...fieldAttrs}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { SelectInputProps, SelectOption } from '../types.js';
|
|
3
|
+
import TopLabel from './TopLabel.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -24,9 +25,7 @@
|
|
|
24
25
|
);
|
|
25
26
|
</script>
|
|
26
27
|
|
|
27
|
-
{
|
|
28
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
29
|
-
{/if}
|
|
28
|
+
<TopLabel {label} {labelClass} {name} />
|
|
30
29
|
<select {...fieldAttrs} id={name} class={className} {disabled} {onblur} {oninput}>
|
|
31
30
|
{#each normalizedOptions as option (option.value)}
|
|
32
31
|
<option value={option.value} disabled={option.disabled}>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TextInputComponentProps } from '../types.js';
|
|
3
|
+
import TopLabel from './TopLabel.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -28,9 +29,7 @@
|
|
|
28
29
|
let inputElement: HTMLInputElement | undefined = $state();
|
|
29
30
|
</script>
|
|
30
31
|
|
|
31
|
-
{
|
|
32
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
33
|
-
{/if}
|
|
32
|
+
<TopLabel {label} {labelClass} {name} />
|
|
34
33
|
<div class="sform-input-wrapper {wrapperClass ?? ''}">
|
|
35
34
|
{#if prefix}
|
|
36
35
|
<div class="sform-prefix" onclick={() => inputElement?.focus()} role="presentation">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TextareaInputProps } from '../types.js';
|
|
3
|
+
import TopLabel from './TopLabel.svelte';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
field,
|
|
@@ -27,9 +28,7 @@
|
|
|
27
28
|
let textareaElement: HTMLTextAreaElement | undefined = $state();
|
|
28
29
|
</script>
|
|
29
30
|
|
|
30
|
-
{
|
|
31
|
-
<label class={labelClass} for={name}>{label}</label>
|
|
32
|
-
{/if}
|
|
31
|
+
<TopLabel {label} {labelClass} {name} />
|
|
33
32
|
<div class="sform-input-wrapper sform-textarea-wrapper {wrapperClass ?? ''}">
|
|
34
33
|
{#if prefix}
|
|
35
34
|
<div class="sform-prefix" onclick={() => textareaElement?.focus()} role="presentation">
|
|
@@ -25,7 +25,11 @@
|
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
{#if label}
|
|
28
|
-
|
|
28
|
+
{#if typeof label === 'function'}
|
|
29
|
+
{@render label()}
|
|
30
|
+
{:else}
|
|
31
|
+
<span id="{name}-label" class={labelClass}>{label}</span>
|
|
32
|
+
{/if}
|
|
29
33
|
{/if}
|
|
30
34
|
<label class="sform-toggle {className ?? ''}" class:disabled>
|
|
31
35
|
<input
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { BaseInputComponentProps } from '../types';
|
|
3
|
+
|
|
4
|
+
let { label, labelClass, name }: Pick<BaseInputComponentProps, 'label' | 'labelClass' | 'name'> =
|
|
5
|
+
$props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
{#if label}
|
|
9
|
+
{#if typeof label === 'function'}
|
|
10
|
+
{@render label()}
|
|
11
|
+
{:else}
|
|
12
|
+
<label class={labelClass} for={name}>{label}</label>
|
|
13
|
+
{/if}
|
|
14
|
+
{/if}
|
package/dist/Sform/sform.css
CHANGED
|
@@ -417,47 +417,6 @@ select.sform-input {
|
|
|
417
417
|
user-select: none;
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
-
/* ===========================================
|
|
421
|
-
Toggle Options (Segmented Control)
|
|
422
|
-
=========================================== */
|
|
423
|
-
.sform-toggle-options {
|
|
424
|
-
display: inline-flex;
|
|
425
|
-
background: var(--sform-bg-muted);
|
|
426
|
-
border-radius: var(--sform-radius-lg);
|
|
427
|
-
padding: 0.25rem;
|
|
428
|
-
gap: 0.25rem;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
.sform-toggle-option {
|
|
432
|
-
padding: var(--sform-padding-sm);
|
|
433
|
-
font-size: var(--sform-font-size-sm);
|
|
434
|
-
font-weight: 500;
|
|
435
|
-
color: var(--sform-text-muted);
|
|
436
|
-
background: transparent;
|
|
437
|
-
border: none;
|
|
438
|
-
border-radius: var(--sform-radius);
|
|
439
|
-
cursor: pointer;
|
|
440
|
-
transition:
|
|
441
|
-
background-color var(--sform-transition),
|
|
442
|
-
color var(--sform-transition);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
.sform-toggle-option:hover:not(:disabled):not(.selected) {
|
|
446
|
-
color: var(--sform-text);
|
|
447
|
-
background: rgba(0, 0, 0, 0.05);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
.sform-toggle-option.selected {
|
|
451
|
-
color: var(--sform-primary);
|
|
452
|
-
background: var(--sform-bg);
|
|
453
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
.sform-toggle-option:disabled {
|
|
457
|
-
opacity: 0.5;
|
|
458
|
-
cursor: not-allowed;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
420
|
/* ===========================================
|
|
462
421
|
Password Input with Toggle
|
|
463
422
|
=========================================== */
|