@privaty/ui-forms 0.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.
Files changed (36) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +107 -0
  4. package/dist/components/form-error.svelte +49 -0
  5. package/dist/components/form-error.svelte.d.ts +15 -0
  6. package/dist/components/reset.svelte +49 -0
  7. package/dist/components/reset.svelte.d.ts +19 -0
  8. package/dist/components/submit.svelte +82 -0
  9. package/dist/components/submit.svelte.d.ts +27 -0
  10. package/dist/context.d.ts +23 -0
  11. package/dist/context.js +21 -0
  12. package/dist/form-state.svelte.d.ts +89 -0
  13. package/dist/form-state.svelte.js +139 -0
  14. package/dist/form.svelte +219 -0
  15. package/dist/form.svelte.d.ts +62 -0
  16. package/dist/inputs/checkbox-input.svelte +119 -0
  17. package/dist/inputs/checkbox-input.svelte.d.ts +45 -0
  18. package/dist/inputs/date-input.svelte +132 -0
  19. package/dist/inputs/date-input.svelte.d.ts +61 -0
  20. package/dist/inputs/number-input.svelte +141 -0
  21. package/dist/inputs/number-input.svelte.d.ts +63 -0
  22. package/dist/inputs/select-input.svelte +156 -0
  23. package/dist/inputs/select-input.svelte.d.ts +57 -0
  24. package/dist/inputs/text-input.svelte +126 -0
  25. package/dist/inputs/text-input.svelte.d.ts +59 -0
  26. package/dist/inputs/textarea-input.svelte +135 -0
  27. package/dist/inputs/textarea-input.svelte.d.ts +57 -0
  28. package/dist/inputs/wire-field.d.ts +44 -0
  29. package/dist/inputs/wire-field.js +45 -0
  30. package/dist/testing/fakes.svelte.d.ts +205 -0
  31. package/dist/testing/fakes.svelte.js +346 -0
  32. package/dist/types/field.d.ts +156 -0
  33. package/dist/types/field.js +1 -0
  34. package/dist/types/form.d.ts +27 -0
  35. package/dist/types/form.js +1 -0
  36. package/package.json +52 -0
@@ -0,0 +1,126 @@
1
+ <!-- @component
2
+ Text-family input (text, email, password, search, url, tel) wired to a
3
+ SvelteKit remote form string field — must render inside a <Form>, whose
4
+ context it registers with. Shows resolver-translated validation errors once
5
+ the form state reveals them, plus a majority-aware required/optional marker,
6
+ and turns readonly while the form submits.
7
+ -->
8
+ <script lang="ts">
9
+ import { cn } from "@privaty/ui/cn.js";
10
+ import Input from "@privaty/ui/components/input.svelte";
11
+ import type { LabelStyle } from "@privaty/ui/components/types.js";
12
+ import type { HTMLInputAttributes } from "svelte/elements";
13
+ import type { TextField, TextFieldType } from "../types/field";
14
+ import { wireField } from "./wire-field";
15
+
16
+ interface Props {
17
+ /** The SvelteKit remote form field to bind. Structural — anything
18
+ * satisfying the TextField slice works, e.g. a fake from the public
19
+ * testing subpath. */
20
+ field: TextField;
21
+ /** Visible label text. In the floating label style it doubles as the
22
+ * placeholder. */
23
+ label: string;
24
+ /** Native input type within the text family. Defaults to "text". */
25
+ type?: TextFieldType;
26
+
27
+ /** Label placement: "top" (default), "left", "floating", or "hidden"
28
+ * (visually hidden, still read by screen readers). */
29
+ labelStyle?: LabelStyle;
30
+
31
+ /** Marks the field required: feeds the majority-aware required/optional
32
+ * marker (required markers styled red) — forms where at least half the
33
+ * fields are required mark the optional ones instead. Validation itself
34
+ * comes from the field's schema, not this flag, and it is not forwarded
35
+ * as a native `required` attribute. */
36
+ required?: boolean;
37
+ /** Disables the control. Disabled controls are excluded from FormData —
38
+ * never disable to lock the form during submit; the input already turns
39
+ * readonly while submitting. */
40
+ disabled?: boolean;
41
+ /** Renders the input readonly. Also forced on while the form is
42
+ * submitting, independent of this prop. */
43
+ readonly?: boolean;
44
+
45
+ /** Seed handed to Kit's `as()` (defaults to "") — the value native form
46
+ * reset restores and the baseline for dirty tracking. Assumed stable for
47
+ * the component's lifetime. */
48
+ initialValue?: string;
49
+
50
+ /** Placeholder text — ignored in the floating label style, where the
51
+ * label plays that role. */
52
+ placeholder?: string;
53
+ /** Forwarded to the native input. */
54
+ autocomplete?: HTMLInputAttributes["autocomplete"];
55
+
56
+ /** Extra classes for the outer field wrapper. */
57
+ class?: string;
58
+ /** Extra classes for the <label> element. */
59
+ labelClass?: string;
60
+ /** Extra classes for the <input> element. */
61
+ inputClass?: string;
62
+ /** Extra classes for the required/optional marker. */
63
+ markerClass?: string;
64
+ /** Extra classes for the error <ul>. */
65
+ errorClass?: string;
66
+ }
67
+
68
+ const {
69
+ field,
70
+ label,
71
+ type = "text",
72
+
73
+ labelStyle,
74
+
75
+ required = false,
76
+ disabled = false,
77
+ readonly = false,
78
+
79
+ initialValue = "",
80
+
81
+ placeholder,
82
+ autocomplete,
83
+
84
+ class: classes,
85
+ labelClass,
86
+ inputClass,
87
+ markerClass,
88
+ errorClass,
89
+ }: Props = $props();
90
+
91
+ const attributes = $derived(field.as(type, initialValue));
92
+
93
+ // The field, type, and initialValue are stable for the component's lifetime,
94
+ // so capturing the initial name and registration is intentional.
95
+ // svelte-ignore state_referenced_locally
96
+ const name = attributes.name;
97
+
98
+ // svelte-ignore state_referenced_locally
99
+ const wired = wireField({
100
+ name,
101
+ initialValue,
102
+ required,
103
+ issues: () => field.issues(),
104
+ getValue: () => field.value(),
105
+ setValue: (value) => field.set(value as string),
106
+ normalize: (value) => (value == null ? "" : String(value)),
107
+ });
108
+ </script>
109
+
110
+ <Input
111
+ {...attributes}
112
+ {label}
113
+ {labelStyle}
114
+ errors={wired.errors}
115
+ marker={wired.marker}
116
+ aria-invalid={wired.errors.length > 0 ? true : undefined}
117
+ {disabled}
118
+ readonly={readonly || wired.state.isSubmitting}
119
+ {placeholder}
120
+ {autocomplete}
121
+ class={classes}
122
+ {labelClass}
123
+ {inputClass}
124
+ markerClass={cn(required && "text-red-700 dark:text-red-500", markerClass)}
125
+ {errorClass}
126
+ />
@@ -0,0 +1,59 @@
1
+ import type { LabelStyle } from "@privaty/ui/components/types.js";
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+ import type { TextField, TextFieldType } from "../types/field";
4
+ interface Props {
5
+ /** The SvelteKit remote form field to bind. Structural — anything
6
+ * satisfying the TextField slice works, e.g. a fake from the public
7
+ * testing subpath. */
8
+ field: TextField;
9
+ /** Visible label text. In the floating label style it doubles as the
10
+ * placeholder. */
11
+ label: string;
12
+ /** Native input type within the text family. Defaults to "text". */
13
+ type?: TextFieldType;
14
+ /** Label placement: "top" (default), "left", "floating", or "hidden"
15
+ * (visually hidden, still read by screen readers). */
16
+ labelStyle?: LabelStyle;
17
+ /** Marks the field required: feeds the majority-aware required/optional
18
+ * marker (required markers styled red) — forms where at least half the
19
+ * fields are required mark the optional ones instead. Validation itself
20
+ * comes from the field's schema, not this flag, and it is not forwarded
21
+ * as a native `required` attribute. */
22
+ required?: boolean;
23
+ /** Disables the control. Disabled controls are excluded from FormData —
24
+ * never disable to lock the form during submit; the input already turns
25
+ * readonly while submitting. */
26
+ disabled?: boolean;
27
+ /** Renders the input readonly. Also forced on while the form is
28
+ * submitting, independent of this prop. */
29
+ readonly?: boolean;
30
+ /** Seed handed to Kit's `as()` (defaults to "") — the value native form
31
+ * reset restores and the baseline for dirty tracking. Assumed stable for
32
+ * the component's lifetime. */
33
+ initialValue?: string;
34
+ /** Placeholder text — ignored in the floating label style, where the
35
+ * label plays that role. */
36
+ placeholder?: string;
37
+ /** Forwarded to the native input. */
38
+ autocomplete?: HTMLInputAttributes["autocomplete"];
39
+ /** Extra classes for the outer field wrapper. */
40
+ class?: string;
41
+ /** Extra classes for the <label> element. */
42
+ labelClass?: string;
43
+ /** Extra classes for the <input> element. */
44
+ inputClass?: string;
45
+ /** Extra classes for the required/optional marker. */
46
+ markerClass?: string;
47
+ /** Extra classes for the error <ul>. */
48
+ errorClass?: string;
49
+ }
50
+ /**
51
+ * Text-family input (text, email, password, search, url, tel) wired to a
52
+ * SvelteKit remote form string field — must render inside a <Form>, whose
53
+ * context it registers with. Shows resolver-translated validation errors once
54
+ * the form state reveals them, plus a majority-aware required/optional marker,
55
+ * and turns readonly while the form submits.
56
+ */
57
+ declare const TextInput: import("svelte").Component<Props, {}, "">;
58
+ type TextInput = ReturnType<typeof TextInput>;
59
+ export default TextInput;
@@ -0,0 +1,135 @@
1
+ <!-- @component
2
+ Multi-line text input wired to a SvelteKit remote form string field, rendered
3
+ as a textarea — must render inside a <Form>, whose context it registers with.
4
+ Shows resolver-translated validation errors once the form state reveals them,
5
+ plus a majority-aware required/optional marker, and turns readonly while the
6
+ form submits. No floating label style — that is tuned to single-line inputs.
7
+ -->
8
+ <script lang="ts">
9
+ import { cn } from "@privaty/ui/cn.js";
10
+ import Textarea from "@privaty/ui/components/textarea.svelte";
11
+ import type { LabelStyle } from "@privaty/ui/components/types.js";
12
+ import type { HTMLTextareaAttributes } from "svelte/elements";
13
+ import type { TextField } from "../types/field";
14
+ import { wireField } from "./wire-field";
15
+
16
+ interface Props {
17
+ /** The SvelteKit remote form field to bind. Structural — anything
18
+ * satisfying the TextField slice works, e.g. a fake from the public
19
+ * testing subpath. */
20
+ field: TextField;
21
+ /** Visible label text for the control. */
22
+ label: string;
23
+
24
+ /** Label placement: "top" (default), "left", or "hidden" (visually
25
+ * hidden, still read by screen readers). */
26
+ labelStyle?: Exclude<LabelStyle, "floating">;
27
+
28
+ /** Marks the field required: feeds the majority-aware required/optional
29
+ * marker (required markers styled red) — forms where at least half the
30
+ * fields are required mark the optional ones instead. Validation itself
31
+ * comes from the field's schema, not this flag, and it is not forwarded
32
+ * as a native `required` attribute. */
33
+ required?: boolean;
34
+ /** Disables the control. Disabled controls are excluded from FormData —
35
+ * never disable to lock the form during submit; the textarea already
36
+ * turns readonly while submitting. */
37
+ disabled?: boolean;
38
+ /** Renders the textarea readonly. Also forced on while the form is
39
+ * submitting, independent of this prop. */
40
+ readonly?: boolean;
41
+
42
+ /** Seed handed to Kit's `as()` (defaults to "") — the value native form
43
+ * reset restores and the baseline for dirty tracking. Assumed stable for
44
+ * the component's lifetime. */
45
+ initialValue?: string;
46
+
47
+ /** Forwarded to the native textarea. */
48
+ placeholder?: string;
49
+ /** Forwarded to the native textarea. */
50
+ autocomplete?: HTMLTextareaAttributes["autocomplete"];
51
+ /** Visible line count (native `rows`). Defaults to 3. */
52
+ rows?: number;
53
+
54
+ /** Extra classes for the outer field wrapper. */
55
+ class?: string;
56
+ /** Extra classes for the <label> element. */
57
+ labelClass?: string;
58
+ /** Extra classes for the <textarea> element. */
59
+ textareaClass?: string;
60
+ /** Extra classes for the required/optional marker. */
61
+ markerClass?: string;
62
+ /** Extra classes for the error <ul>. */
63
+ errorClass?: string;
64
+ }
65
+
66
+ const {
67
+ field,
68
+ label,
69
+
70
+ labelStyle,
71
+
72
+ required = false,
73
+ disabled = false,
74
+ readonly = false,
75
+
76
+ initialValue = "",
77
+
78
+ placeholder,
79
+ autocomplete,
80
+ rows = 3,
81
+
82
+ class: classes,
83
+ labelClass,
84
+ textareaClass,
85
+ markerClass,
86
+ errorClass,
87
+ }: Props = $props();
88
+
89
+ // Kit has no textarea `as()` type — but `as("text")` returns exactly what
90
+ // a textarea needs (name, aria-invalid, the value accessors) and its text
91
+ // branch carries no `type` attribute to clash with the element.
92
+ const attributes = $derived(field.as("text", initialValue));
93
+
94
+ // The slice TYPES the return broadly as input attributes, whose event-
95
+ // handler element types clash with a textarea — at runtime the object only
96
+ // holds the textarea-compatible members above, so the spread is cast.
97
+ const textareaAttributes = $derived(
98
+ attributes as unknown as Omit<HTMLTextareaAttributes, "class">,
99
+ );
100
+
101
+ // The field and initialValue are stable for the component's lifetime, so
102
+ // capturing the initial name and registration is intentional.
103
+ // svelte-ignore state_referenced_locally
104
+ const name = attributes.name;
105
+
106
+ // svelte-ignore state_referenced_locally
107
+ const wired = wireField({
108
+ name,
109
+ initialValue,
110
+ required,
111
+ issues: () => field.issues(),
112
+ getValue: () => field.value(),
113
+ setValue: (value) => field.set(value as string),
114
+ normalize: (value) => (value == null ? "" : String(value)),
115
+ });
116
+ </script>
117
+
118
+ <Textarea
119
+ {...textareaAttributes}
120
+ {label}
121
+ {labelStyle}
122
+ errors={wired.errors}
123
+ marker={wired.marker}
124
+ aria-invalid={wired.errors.length > 0 ? true : undefined}
125
+ {disabled}
126
+ readonly={readonly || wired.state.isSubmitting}
127
+ {placeholder}
128
+ {autocomplete}
129
+ {rows}
130
+ class={classes}
131
+ {labelClass}
132
+ {textareaClass}
133
+ markerClass={cn(required && "text-red-700 dark:text-red-500", markerClass)}
134
+ {errorClass}
135
+ />
@@ -0,0 +1,57 @@
1
+ import type { LabelStyle } from "@privaty/ui/components/types.js";
2
+ import type { HTMLTextareaAttributes } from "svelte/elements";
3
+ import type { TextField } from "../types/field";
4
+ interface Props {
5
+ /** The SvelteKit remote form field to bind. Structural — anything
6
+ * satisfying the TextField slice works, e.g. a fake from the public
7
+ * testing subpath. */
8
+ field: TextField;
9
+ /** Visible label text for the control. */
10
+ label: string;
11
+ /** Label placement: "top" (default), "left", or "hidden" (visually
12
+ * hidden, still read by screen readers). */
13
+ labelStyle?: Exclude<LabelStyle, "floating">;
14
+ /** Marks the field required: feeds the majority-aware required/optional
15
+ * marker (required markers styled red) — forms where at least half the
16
+ * fields are required mark the optional ones instead. Validation itself
17
+ * comes from the field's schema, not this flag, and it is not forwarded
18
+ * as a native `required` attribute. */
19
+ required?: boolean;
20
+ /** Disables the control. Disabled controls are excluded from FormData —
21
+ * never disable to lock the form during submit; the textarea already
22
+ * turns readonly while submitting. */
23
+ disabled?: boolean;
24
+ /** Renders the textarea readonly. Also forced on while the form is
25
+ * submitting, independent of this prop. */
26
+ readonly?: boolean;
27
+ /** Seed handed to Kit's `as()` (defaults to "") — the value native form
28
+ * reset restores and the baseline for dirty tracking. Assumed stable for
29
+ * the component's lifetime. */
30
+ initialValue?: string;
31
+ /** Forwarded to the native textarea. */
32
+ placeholder?: string;
33
+ /** Forwarded to the native textarea. */
34
+ autocomplete?: HTMLTextareaAttributes["autocomplete"];
35
+ /** Visible line count (native `rows`). Defaults to 3. */
36
+ rows?: number;
37
+ /** Extra classes for the outer field wrapper. */
38
+ class?: string;
39
+ /** Extra classes for the <label> element. */
40
+ labelClass?: string;
41
+ /** Extra classes for the <textarea> element. */
42
+ textareaClass?: string;
43
+ /** Extra classes for the required/optional marker. */
44
+ markerClass?: string;
45
+ /** Extra classes for the error <ul>. */
46
+ errorClass?: string;
47
+ }
48
+ /**
49
+ * Multi-line text input wired to a SvelteKit remote form string field, rendered
50
+ * as a textarea — must render inside a <Form>, whose context it registers with.
51
+ * Shows resolver-translated validation errors once the form state reveals them,
52
+ * plus a majority-aware required/optional marker, and turns readonly while the
53
+ * form submits. No floating label style — that is tuned to single-line inputs.
54
+ */
55
+ declare const TextareaInput: import("svelte").Component<Props, {}, "">;
56
+ type TextareaInput = ReturnType<typeof TextareaInput>;
57
+ export default TextareaInput;
@@ -0,0 +1,44 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import type { FormState } from "../form-state.svelte";
3
+ interface WireFieldOptions {
4
+ /** The field's form name (from the attributes Kit's `as(...)` returned).
5
+ * Must be unique within the form. */
6
+ name: string;
7
+ /** The typed seed value — the baseline for dirty comparison and what
8
+ * reset restores. */
9
+ initialValue: unknown;
10
+ /** Whether the field is required — feeds the majority-aware marker. */
11
+ required: boolean;
12
+ /** Returns the field's current issues (the field's `issues()`). */
13
+ issues: () => readonly StandardSchemaV1.Issue[] | undefined;
14
+ /** Reads the field's current value — raw DOM strings can appear mid-edit. */
15
+ getValue: () => unknown;
16
+ /** Writes a value back to the field — used by the form-level reset. */
17
+ setValue: (value: unknown) => void;
18
+ /** Maps raw field values onto the initialValue's domain before dirty
19
+ * comparison (e.g. "5" vs 5, "on" vs true). */
20
+ normalize: (value: unknown) => unknown;
21
+ }
22
+ interface WiredField {
23
+ /** The owning form's FormState — inputs read e.g. isSubmitting from it. */
24
+ state: FormState;
25
+ /** Resolver-translated issue messages, or empty while the field's issues
26
+ * are gated (not yet touched and no submit attempted). Recomputed on
27
+ * access, so template reads stay reactive. */
28
+ readonly errors: string[];
29
+ /** The required/optional label text for the field's marker, or undefined —
30
+ * only the form's minority kind is marked, and nothing renders until every
31
+ * field has registered. */
32
+ readonly marker: string | undefined;
33
+ }
34
+ /**
35
+ * The wiring every form input shares: registers the field with the FormState
36
+ * (unregistering on destroy), and exposes the gated, resolver-translated
37
+ * errors plus the majority-aware required/optional marker.
38
+ *
39
+ * Must be called during component init (it uses context and onDestroy). The
40
+ * getters recompute on access, so reads from a template stay reactive.
41
+ */
42
+ declare function wireField(options: WireFieldOptions): WiredField;
43
+ export { wireField };
44
+ export type { WiredField, WireFieldOptions };
@@ -0,0 +1,45 @@
1
+ import { getUiConfig } from "@privaty/ui/config/context.js";
2
+ import { onDestroy } from "svelte";
3
+ import { getFormContext } from "../context";
4
+ /**
5
+ * The wiring every form input shares: registers the field with the FormState
6
+ * (unregistering on destroy), and exposes the gated, resolver-translated
7
+ * errors plus the majority-aware required/optional marker.
8
+ *
9
+ * Must be called during component init (it uses context and onDestroy). The
10
+ * getters recompute on access, so reads from a template stay reactive.
11
+ */
12
+ function wireField(options) {
13
+ const { state } = getFormContext();
14
+ const config = getUiConfig();
15
+ onDestroy(state.register({
16
+ name: options.name,
17
+ initialValue: options.initialValue,
18
+ required: options.required,
19
+ getValue: options.getValue,
20
+ setValue: options.setValue,
21
+ normalize: options.normalize,
22
+ }));
23
+ return {
24
+ state,
25
+ get errors() {
26
+ return state.shouldShowIssues(options.name)
27
+ ? (options.issues() ?? []).map((issue) => config.resolveMessage(issue))
28
+ : [];
29
+ },
30
+ get marker() {
31
+ // Markers wait until every field has registered — a partial majority
32
+ // would render the wrong marker first (visible as an SSR/load flash).
33
+ if (!state.settled)
34
+ return undefined;
35
+ return state.majorityRequired
36
+ ? options.required
37
+ ? undefined
38
+ : config.labels.form.optional
39
+ : options.required
40
+ ? config.labels.form.required
41
+ : undefined;
42
+ },
43
+ };
44
+ }
45
+ export { wireField };
@@ -0,0 +1,205 @@
1
+ import type { CheckboxField, DateField, NumberField, SelectField, TextField } from "../types/field";
2
+ import type { ValidatableForm } from "../types/form";
3
+ /**
4
+ * Test doubles for the structural form interfaces. For use in specs and
5
+ * consumers' own tests — never imported by library runtime code.
6
+ */
7
+ /**
8
+ * The minimal issue shape the library reads back from a form — a Standard
9
+ * Schema issue narrowed to what tests need to construct.
10
+ */
11
+ interface FakeIssue {
12
+ /** Human-readable validation message. */
13
+ message: string;
14
+ /** Path segments locating the field the issue belongs to (Standard Schema
15
+ * shape). Omit for form-level issues. */
16
+ path?: readonly (string | number)[];
17
+ }
18
+ /**
19
+ * Minimal `ValidatableForm` fake for FormState-level tests. `computeIssues`
20
+ * runs on every `validate()` call and its result becomes the issue set that
21
+ * `fields.allIssues()` exposes (default: always valid). State is reactive —
22
+ * effects re-run on `setIssues`/`setPending`.
23
+ */
24
+ declare function fakeForm(computeIssues?: () => readonly FakeIssue[] | undefined): {
25
+ /** The fake form — pass it where a `ValidatableForm` is expected. */
26
+ form: ValidatableForm;
27
+ /** Arguments of every `validate()` call, in order. */
28
+ validateCalls: unknown[];
29
+ /** Replaces the issue set directly, bypassing `validate()`. */
30
+ setIssues: (next: readonly FakeIssue[] | undefined) => void;
31
+ /** Sets the form's `pending` submission count. */
32
+ setPending: (next: number) => void;
33
+ };
34
+ /**
35
+ * Structural `TextField` fake for TextInput/TextareaInput tests. `as()`
36
+ * returns plain spreadable attributes (no attachment); value and issues are
37
+ * reactive state. Pass `options.issues` to seed the field with issues.
38
+ */
39
+ declare function fakeTextField(name: string, options?: {
40
+ issues?: readonly {
41
+ message: string;
42
+ }[];
43
+ }): {
44
+ /** The fake field — pass it as the input component's `field` prop. */
45
+ field: TextField;
46
+ /** Simulates USER typing (string fields store the same value `set()`
47
+ * would — no raw/typed split here). */
48
+ edit: (next: string) => void;
49
+ /** Replaces the field's issue set. */
50
+ setIssues: (next: readonly {
51
+ message: string;
52
+ }[] | undefined) => void;
53
+ };
54
+ /**
55
+ * Structural `DateField` fake for DateInput tests. The whole date family
56
+ * (date, month, week, time, datetime-local) carries ISO-style string values,
57
+ * so one fake covers all five. Same shape and reactivity as `fakeTextField`.
58
+ */
59
+ declare function fakeDateField(name: string, options?: {
60
+ issues?: readonly {
61
+ message: string;
62
+ }[];
63
+ }): {
64
+ /** The fake field — pass it as the input component's `field` prop. */
65
+ field: DateField;
66
+ /** Simulates USER input — an ISO-style string, same as `set()` would
67
+ * store. */
68
+ edit: (next: string) => void;
69
+ /** Replaces the field's issue set. */
70
+ setIssues: (next: readonly {
71
+ message: string;
72
+ }[] | undefined) => void;
73
+ };
74
+ /**
75
+ * Structural `NumberField` fake for NumberInput tests. Mirrors Kit's mid-edit
76
+ * behavior: `edit()` stores the raw DOM string a user's typing would produce,
77
+ * while `set()` stores the typed number — the distinction is what makes
78
+ * dirty-tracking tests meaningful.
79
+ */
80
+ declare function fakeNumberField(name: string, options?: {
81
+ issues?: readonly {
82
+ message: string;
83
+ }[];
84
+ }): {
85
+ /** The fake field — pass it as the input component's `field` prop. */
86
+ field: NumberField;
87
+ /** Simulates USER typing: stores the raw DOM string, like Kit does. */
88
+ edit: (next: number | undefined) => void;
89
+ /** Replaces the field's issue set. */
90
+ setIssues: (next: readonly {
91
+ message: string;
92
+ }[] | undefined) => void;
93
+ };
94
+ /**
95
+ * Structural `SelectField` fake for SelectInput tests. Same shape and
96
+ * reactivity as `fakeTextField` — select values are plain strings on both
97
+ * the DOM and typed sides.
98
+ */
99
+ declare function fakeSelectField(name: string, options?: {
100
+ issues?: readonly {
101
+ message: string;
102
+ }[];
103
+ }): {
104
+ /** The fake field — pass it as the input component's `field` prop. */
105
+ field: SelectField;
106
+ /** Simulates the USER choosing an option: stores its string value. */
107
+ edit: (next: string) => void;
108
+ /** Replaces the field's issue set. */
109
+ setIssues: (next: readonly {
110
+ message: string;
111
+ }[] | undefined) => void;
112
+ };
113
+ /**
114
+ * Structural `CheckboxField` fake for CheckboxInput tests. Like Kit's
115
+ * `as("checkbox", seed)`, the returned attributes expose `checked` and
116
+ * `defaultChecked` getters (the latter is what makes native reset restore
117
+ * the seed). `edit()` stores the raw DOM value ("on"/null) while `set()`
118
+ * stores the typed boolean — mirroring Kit's mid-edit behavior.
119
+ */
120
+ declare function fakeCheckboxField(name: string, options?: {
121
+ issues?: readonly {
122
+ message: string;
123
+ }[];
124
+ }): {
125
+ /** The fake field — pass it as the input component's `field` prop. */
126
+ field: CheckboxField;
127
+ /** Simulates a USER toggle: raw DOM value, like Kit's input listener. */
128
+ edit: (next: boolean) => void;
129
+ /** Replaces the field's issue set. */
130
+ setIssues: (next: readonly {
131
+ message: string;
132
+ }[] | undefined) => void;
133
+ };
134
+ /** The options object Kit's `validate()` accepts, as the fakes model it. */
135
+ interface FakeValidateOptions {
136
+ /** Kit's flag to also surface issues on fields not yet edited and blurred
137
+ * (ignored after first submission). The fake attaches no behavior to it —
138
+ * it is only recorded in `validateCalls` and forwarded to `onValidate`. */
139
+ all?: boolean;
140
+ /** True for client-schema-only validation with no server round-trip. In
141
+ * the fake it decides issue origin (preflight issues are client-flagged,
142
+ * full validations server-flagged) and the merge-vs-replace semantics of
143
+ * the resulting issue set. */
144
+ preflightOnly?: boolean;
145
+ }
146
+ /** Behavior knobs for `fakeRemoteForm`. */
147
+ interface FakeRemoteFormOptions {
148
+ /** Issues the next validation resolves with (default: none — valid). */
149
+ onValidate?: (options: FakeValidateOptions | undefined) => readonly FakeIssue[] | undefined;
150
+ /** Submission outcome: return false for "rejected by validation", throw for
151
+ * a failed request (default: true — success). May return a promise. */
152
+ onSubmit?: () => boolean | Promise<boolean>;
153
+ /** Issues installed — server-flagged, replacing the whole set — when
154
+ * onSubmit rejects, like Kit's rejected submissions do. Left unset, a
155
+ * rejection leaves the issue set untouched. */
156
+ serverIssues?: readonly FakeIssue[];
157
+ /** Hold every validate() unresolved until releaseValidate() is called. */
158
+ gateValidate?: boolean;
159
+ /** The value exposed as `form.result` after submission. */
160
+ result?: unknown;
161
+ }
162
+ /**
163
+ * A full-surface fake of a SvelteKit remote form for testing the Form
164
+ * component: `enhance` returns spreadable attributes whose attachment
165
+ * intercepts native submits (like Kit's does), so tests drive real buttons.
166
+ */
167
+ declare function fakeRemoteForm(options?: FakeRemoteFormOptions): {
168
+ /** The fake remote form — pass it as the Form component's form. */
169
+ form: {
170
+ method: "POST";
171
+ action: string;
172
+ preflight: (schema: unknown) => /*elided*/ any;
173
+ enhance: (callback: (instance: ReturnType<(node: HTMLFormElement) => {
174
+ element: HTMLFormElement;
175
+ submit: () => Promise<boolean>;
176
+ }>) => unknown) => {
177
+ [x: symbol]: (node: HTMLFormElement) => () => void;
178
+ method: "POST";
179
+ action: string;
180
+ };
181
+ validate: (validateOptions?: FakeValidateOptions) => Promise<void>;
182
+ readonly result: unknown;
183
+ readonly pending: number;
184
+ fields: {
185
+ allIssues: () => {
186
+ message: string;
187
+ path: readonly (string | number)[] | undefined;
188
+ }[] | undefined;
189
+ };
190
+ };
191
+ /** Arguments of every `validate()` call, in order. */
192
+ validateCalls: (FakeValidateOptions | undefined)[];
193
+ /** Schemas passed to `preflight()`, in order. */
194
+ preflightCalls: unknown[];
195
+ /** Number of enhance submissions started so far. */
196
+ submitCount: () => number;
197
+ /** Resolves the oldest still-gated `validate()` — pairs with the
198
+ * `gateValidate` option. */
199
+ releaseValidate: () => void | undefined;
200
+ /** Installs server-flagged issues directly — models the SSR-restored
201
+ * issue set of a rejected no-JS submission. */
202
+ setServerIssues: (next: readonly FakeIssue[]) => void;
203
+ };
204
+ export { fakeCheckboxField, fakeDateField, fakeForm, fakeNumberField, fakeRemoteForm, fakeSelectField, fakeTextField, };
205
+ export type { FakeIssue, FakeRemoteFormOptions, FakeValidateOptions };