lutra 0.0.5 → 0.0.7
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/dist/datatable/DataTable.svelte +2 -8
- package/dist/datatable/DataTable.svelte.d.ts +1 -0
- package/dist/datatable/DataTableRow.svelte +32 -0
- package/dist/datatable/DataTableRow.svelte.d.ts +3 -0
- package/dist/form/FieldError.svelte +14 -0
- package/dist/form/FieldError.svelte.d.ts +17 -0
- package/dist/form/Form.svelte +73 -2
- package/dist/form/Form.svelte.d.ts +5 -0
- package/dist/form/Input.svelte +52 -14
- package/dist/form/Input.svelte.d.ts +17 -3
- package/dist/form/client.svelte.d.ts +45 -0
- package/dist/form/client.svelte.js +85 -0
- package/dist/form/form.d.ts +50 -32
- package/dist/form/form.js +319 -28
- package/dist/form/index.d.ts +4 -0
- package/dist/form/index.js +4 -0
- package/dist/form/server.d.ts +21 -0
- package/dist/form/server.js +48 -0
- package/dist/form/types.d.ts +53 -0
- package/dist/layout/Theme.svelte +6 -1
- package/dist/style.css +19 -3
- package/dist/typo/H2.svelte +1 -1
- package/dist/typo/H4.svelte +1 -1
- package/dist/typo/H5.svelte +1 -1
- package/dist/typo/H6.svelte +1 -1
- package/package.json +9 -3
@@ -1,16 +1,10 @@
|
|
1
1
|
<script>import UiContent from "../layout/UIContent.svelte";
|
2
2
|
import { setContext } from "svelte";
|
3
3
|
let {
|
4
|
+
columns,
|
4
5
|
contained,
|
5
6
|
children
|
6
7
|
} = $props();
|
7
|
-
let columns = $state(0);
|
8
|
-
setContext("DataTableColumns", {
|
9
|
-
addColumn: () => {
|
10
|
-
console.log("addColumn");
|
11
|
-
columns++;
|
12
|
-
}
|
13
|
-
});
|
14
8
|
</script>
|
15
9
|
|
16
10
|
<UiContent>
|
@@ -31,6 +25,6 @@ setContext("DataTableColumns", {
|
|
31
25
|
section.contained {
|
32
26
|
border: var(--border);
|
33
27
|
border-radius: var(--border-radius);
|
34
|
-
overflow:
|
28
|
+
overflow-x: auto;
|
35
29
|
}
|
36
30
|
</style>
|
@@ -1,6 +1,7 @@
|
|
1
1
|
<script>import { setContext } from "svelte";
|
2
2
|
let {
|
3
3
|
children,
|
4
|
+
actions,
|
4
5
|
header = false
|
5
6
|
} = $props();
|
6
7
|
setContext("DataTableRow", { header });
|
@@ -8,6 +9,11 @@ setContext("DataTableRow", { header });
|
|
8
9
|
|
9
10
|
<svelte:element this={header ? "header" : "section"} class="DataTableRow" class:header>
|
10
11
|
{@render children()}
|
12
|
+
{#if actions}
|
13
|
+
<div class="Actions">
|
14
|
+
{@render actions()}
|
15
|
+
</div>
|
16
|
+
{/if}
|
11
17
|
</svelte:element>
|
12
18
|
|
13
19
|
<style>
|
@@ -17,7 +23,19 @@ setContext("DataTableRow", { header });
|
|
17
23
|
grid-template-columns: subgrid;
|
18
24
|
gap: 1.5em;
|
19
25
|
align-items: center;
|
26
|
+
width: 100%;
|
27
|
+
overflow: none;
|
20
28
|
padding: 0.75em calc(1.5em * var(--dtc));
|
29
|
+
position: relative;
|
30
|
+
}
|
31
|
+
.DataTableRow:hover {
|
32
|
+
background: color-mix(in srgb, var(--bg-subtle) calc(var(--dtc) * 100%), transparent);
|
33
|
+
}
|
34
|
+
.DataTableRow:has(.Actions) {
|
35
|
+
padding-right: 0;
|
36
|
+
}
|
37
|
+
.DataTableRow :global(*) {
|
38
|
+
white-space: nowrap;
|
21
39
|
}
|
22
40
|
.DataTableRow.header {
|
23
41
|
background: color-mix(in srgb, var(--bg-subtle) calc(var(--dtc) * 100%), transparent);
|
@@ -25,4 +43,18 @@ setContext("DataTableRow", { header });
|
|
25
43
|
section:not(:first-child) {
|
26
44
|
border-top: var(--border);
|
27
45
|
}
|
46
|
+
.Actions {
|
47
|
+
display: flex;
|
48
|
+
gap: 0.75em;
|
49
|
+
justify-self: end;
|
50
|
+
position: sticky;
|
51
|
+
top: 0;
|
52
|
+
right: 0;
|
53
|
+
padding-left: 0.75em;
|
54
|
+
padding-right: calc(1em * var(--dtc));
|
55
|
+
background: var(--bg-app);
|
56
|
+
}
|
57
|
+
.DataTableRow:hover .Actions {
|
58
|
+
background: color-mix(in srgb, var(--bg-subtle) calc(var(--dtc) * 100%), transparent);
|
59
|
+
}
|
28
60
|
</style>
|
@@ -3,6 +3,9 @@ import { type Snippet } from "svelte";
|
|
3
3
|
declare const __propDef: {
|
4
4
|
props: {
|
5
5
|
header?: boolean | undefined;
|
6
|
+
actions?: ((this: void) => typeof import("svelte").SnippetReturn & {
|
7
|
+
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
8
|
+
}) | undefined;
|
6
9
|
children: Snippet;
|
7
10
|
};
|
8
11
|
events: {
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
2
|
+
declare const __propDef: {
|
3
|
+
props: {
|
4
|
+
code: string;
|
5
|
+
message?: string | undefined;
|
6
|
+
};
|
7
|
+
events: {
|
8
|
+
[evt: string]: CustomEvent<any>;
|
9
|
+
};
|
10
|
+
slots: {};
|
11
|
+
};
|
12
|
+
export type FieldErrorProps = typeof __propDef.props;
|
13
|
+
export type FieldErrorEvents = typeof __propDef.events;
|
14
|
+
export type FieldErrorSlots = typeof __propDef.slots;
|
15
|
+
export default class FieldError extends SvelteComponent<FieldErrorProps, FieldErrorEvents, FieldErrorSlots> {
|
16
|
+
}
|
17
|
+
export {};
|
package/dist/form/Form.svelte
CHANGED
@@ -1,14 +1,85 @@
|
|
1
|
-
<script>import { enhance
|
1
|
+
<script>import { enhance } from "$app/forms";
|
2
2
|
import UiContent from "../layout/UIContent.svelte";
|
3
|
+
import { Bodyguard } from "@auth70/bodyguard";
|
3
4
|
import { onMount, setContext } from "svelte";
|
5
|
+
import { get } from "svelte/store";
|
6
|
+
import { dezerialize } from "@auth70/zodex-esm";
|
7
|
+
import { array } from "zod";
|
8
|
+
import { arrayPathToStringPath, getIndividualValidators, parseFormIssues } from "./form.js";
|
4
9
|
let {
|
10
|
+
form,
|
11
|
+
action,
|
12
|
+
enctype = "multipart/form-data",
|
13
|
+
method = "POST",
|
5
14
|
fullWidth = false,
|
6
15
|
children
|
7
16
|
} = $props();
|
17
|
+
setContext("form", form);
|
18
|
+
setContext("form.validators", getIndividualValidators(form));
|
19
|
+
const schema = dezerialize(form.schema);
|
20
|
+
const bodyguard = new Bodyguard();
|
21
|
+
let formEl;
|
22
|
+
function setFormIssuesAndFields(issues, fields) {
|
23
|
+
form.issues = issues;
|
24
|
+
form.fields = fields;
|
25
|
+
}
|
26
|
+
async function validate() {
|
27
|
+
form.tainted = true;
|
28
|
+
const req = new Request("localhost", {
|
29
|
+
method: "POST",
|
30
|
+
body: new FormData(formEl)
|
31
|
+
});
|
32
|
+
const result = await bodyguard.softForm(req, schema.parse);
|
33
|
+
if (result.success === true) {
|
34
|
+
form.valid = true;
|
35
|
+
form.issues = [];
|
36
|
+
} else {
|
37
|
+
form.valid = false;
|
38
|
+
form.issues = parseFormIssues(result.error.issues);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
onMount(() => {
|
42
|
+
validate();
|
43
|
+
});
|
8
44
|
</script>
|
9
45
|
|
10
46
|
<UiContent>
|
11
|
-
<form
|
47
|
+
<form
|
48
|
+
{method}
|
49
|
+
{action}
|
50
|
+
{enctype}
|
51
|
+
bind:this={formEl}
|
52
|
+
onchange={validate}
|
53
|
+
use:enhance={({ formElement, formData, action, cancel, submitter }) => {
|
54
|
+
// `formElement` is this `<form>` element
|
55
|
+
// `formData` is its `FormData` object that's about to be submitted
|
56
|
+
// `action` is the URL to which the form is posted
|
57
|
+
// calling `cancel()` will prevent the submission
|
58
|
+
// `submitter` is the `HTMLElement` that caused the form to be submitted
|
59
|
+
|
60
|
+
return async ({ result, update }) => {
|
61
|
+
// `result` is an `ActionResult` object
|
62
|
+
// `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
|
63
|
+
console.log('result', result);
|
64
|
+
const resultForm = (result as any).data.form;
|
65
|
+
if(result.type === "success") {
|
66
|
+
if(resultForm) {
|
67
|
+
form.valid = resultForm.valid;
|
68
|
+
}
|
69
|
+
} else if(result.type === "error") {
|
70
|
+
if(resultForm) {
|
71
|
+
setFormIssuesAndFields(resultForm.issues, resultForm.fields);
|
72
|
+
}
|
73
|
+
} else if(result.type === "failure") {
|
74
|
+
if(resultForm) {
|
75
|
+
setFormIssuesAndFields(resultForm.issues, resultForm.fields);
|
76
|
+
}
|
77
|
+
} else if(result.type === "redirect") {
|
78
|
+
window.location.href = result.location;
|
79
|
+
}
|
80
|
+
};
|
81
|
+
}}
|
82
|
+
>
|
12
83
|
{@render children()}
|
13
84
|
</form>
|
14
85
|
</UiContent>
|
@@ -1,7 +1,12 @@
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
2
2
|
import type { Snippet } from "svelte";
|
3
|
+
import type { Form } from "./types.js";
|
3
4
|
declare const __propDef: {
|
4
5
|
props: {
|
6
|
+
form: Form<any>;
|
7
|
+
action?: string | undefined;
|
8
|
+
enctype?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain" | undefined;
|
9
|
+
method?: "GET" | "POST" | undefined;
|
5
10
|
fullWidth?: boolean | undefined;
|
6
11
|
children: Snippet;
|
7
12
|
};
|
package/dist/form/Input.svelte
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
<script>import
|
1
|
+
<script>import { getContext, unstate } from "svelte";
|
2
|
+
import Label from "./Label.svelte";
|
2
3
|
import { createId } from "../utils/id.js";
|
3
4
|
import Copy from "../icons/Copy.svelte";
|
4
5
|
import Done from "../icons/Done.svelte";
|
@@ -6,7 +7,10 @@ import Show from "../icons/Show.svelte";
|
|
6
7
|
import Hide from "../icons/Hide.svelte";
|
7
8
|
import Tooltip from "../display/Tooltip.svelte";
|
8
9
|
import IconButton from "../display/IconButton.svelte";
|
9
|
-
import
|
10
|
+
import { fieldChange, fieldKeydown, ignoreKeys } from "./client.svelte.js";
|
11
|
+
import FieldError from "./FieldError.svelte";
|
12
|
+
import { getFromObjWithStringPath } from "./form.js";
|
13
|
+
import { ZodType } from "zod";
|
10
14
|
let {
|
11
15
|
alt,
|
12
16
|
autocapitalize,
|
@@ -37,6 +41,13 @@ let {
|
|
37
41
|
pattern,
|
38
42
|
placeholder,
|
39
43
|
suffix,
|
44
|
+
onblur,
|
45
|
+
onchange,
|
46
|
+
onclick,
|
47
|
+
onfocus,
|
48
|
+
onkeydown,
|
49
|
+
onkeyup,
|
50
|
+
onkeypress,
|
40
51
|
prefix,
|
41
52
|
readonly,
|
42
53
|
required,
|
@@ -46,8 +57,8 @@ let {
|
|
46
57
|
step,
|
47
58
|
tabindex,
|
48
59
|
title,
|
49
|
-
type
|
50
|
-
value = $bindable(
|
60
|
+
type,
|
61
|
+
value = $bindable(),
|
51
62
|
viewable,
|
52
63
|
webkitdirectory
|
53
64
|
} = $props();
|
@@ -57,6 +68,12 @@ let viewTitle = $state("Show");
|
|
57
68
|
let copyTooltipOpen = $state(false);
|
58
69
|
let copyBtnIcon = $state(Copy);
|
59
70
|
let viewBtnIcon = $state(Show);
|
71
|
+
const form = getContext("form");
|
72
|
+
const field = $derived(form.fields[name]);
|
73
|
+
const issue = $derived(form?.issues?.find((issue2) => issue2.name === name));
|
74
|
+
const validator = getContext("form.validators")?.[name];
|
75
|
+
const data = form?.data;
|
76
|
+
const originalData = form?.originalData;
|
60
77
|
function view(e) {
|
61
78
|
e.preventDefault();
|
62
79
|
if (!el)
|
@@ -111,28 +128,37 @@ function copy(e) {
|
|
111
128
|
{indeterminate}
|
112
129
|
{inputmode}
|
113
130
|
{list}
|
114
|
-
{maxlength}
|
115
|
-
{minlength}
|
116
|
-
{max}
|
117
|
-
{min}
|
131
|
+
maxlength={maxlength ? maxlength : field?.maxlength}
|
132
|
+
minlength={minlength ? minlength : field?.minlength}
|
133
|
+
max={max ? max : field?.max}
|
134
|
+
min={min ? min : field?.min}
|
118
135
|
{multiple}
|
119
136
|
{name}
|
120
|
-
{
|
137
|
+
{onblur}
|
138
|
+
{onclick}
|
139
|
+
onchange={fieldChange(form, name, () => el, validator)}
|
140
|
+
{onfocus}
|
141
|
+
onkeydown={fieldKeydown(form, name, () => el, validator)}
|
142
|
+
{onkeyup}
|
143
|
+
{onkeypress}
|
144
|
+
pattern={pattern ? pattern : field?.pattern}
|
121
145
|
{placeholder}
|
122
146
|
{readonly}
|
123
|
-
{required}
|
147
|
+
required={required || field?.required}
|
124
148
|
{results}
|
125
149
|
{src}
|
126
150
|
{step}
|
127
151
|
{tabindex}
|
128
152
|
{title}
|
129
153
|
{type}
|
130
|
-
{value}
|
154
|
+
value={value || getFromObjWithStringPath(Object.assign(originalData, data), name) || form?.fields?.[name]?.defaultValue || ''}
|
131
155
|
{webkitdirectory}
|
132
156
|
/>
|
133
157
|
{/snippet}
|
134
|
-
|
135
|
-
|
158
|
+
<div
|
159
|
+
class="FieldContainer {type}"
|
160
|
+
class:checkOrRadio={type === "checkbox" || type === "radio"}
|
161
|
+
>
|
136
162
|
|
137
163
|
<Label {id} {label} tip={labelTip} />
|
138
164
|
|
@@ -144,6 +170,7 @@ function copy(e) {
|
|
144
170
|
class="Field {type}"
|
145
171
|
class:hasPrefix={!!prefix}
|
146
172
|
class:hasSuffix={!!suffix}
|
173
|
+
class:invalid={field?.tainted && issue?.code}
|
147
174
|
>
|
148
175
|
{#if prefix}
|
149
176
|
<div class="Fix Prefix">
|
@@ -179,6 +206,9 @@ function copy(e) {
|
|
179
206
|
</div>
|
180
207
|
{/if}
|
181
208
|
</div>
|
209
|
+
{#if field?.tainted && issue?.code}
|
210
|
+
<FieldError code={issue.code} message={issue.message} />
|
211
|
+
{/if}
|
182
212
|
{/if}
|
183
213
|
</div>
|
184
214
|
|
@@ -191,11 +221,15 @@ function copy(e) {
|
|
191
221
|
}
|
192
222
|
.Field {
|
193
223
|
background-color: var(--field-bg);
|
194
|
-
border: var(--border-size) var(--border-style) var(--border-color);
|
224
|
+
border: var(--field-border-size) var(--field-border-style) var(--field-border-color);
|
195
225
|
border-radius: var(--field-radius);
|
196
226
|
display: flex;
|
197
227
|
padding-inline: 0.15em;
|
198
228
|
}
|
229
|
+
.Field:has(input:user-invalid),
|
230
|
+
.Field.invalid {
|
231
|
+
border-color: var(--field-border-color-error);
|
232
|
+
}
|
199
233
|
.Field > *:not(input) {
|
200
234
|
flex-grow: 0;
|
201
235
|
flex-shrink: 0;
|
@@ -217,6 +251,10 @@ function copy(e) {
|
|
217
251
|
.Field:has(input:focus-visible) {
|
218
252
|
outline: var(--focus-outline);
|
219
253
|
}
|
254
|
+
.Field:has(input:focus-visible:user-invalid),
|
255
|
+
.Field.invalid:has(input:focus-visible) {
|
256
|
+
outline-color: var(--focus-color-error);
|
257
|
+
}
|
220
258
|
/**
|
221
259
|
* Input element
|
222
260
|
*/
|
@@ -25,7 +25,7 @@ declare const __propDef: {
|
|
25
25
|
/** Whether the input should be disabled. */
|
26
26
|
disabled?: boolean | undefined;
|
27
27
|
/** A hint to the browser for which enter key to display for the input. */
|
28
|
-
enterkeyhint?: "search" | "
|
28
|
+
enterkeyhint?: "search" | "done" | "enter" | "go" | "next" | "previous" | "send" | undefined;
|
29
29
|
/** The height of the input element. Valid for image inputs. */
|
30
30
|
height?: number | undefined;
|
31
31
|
/** Help text to display below the input. */
|
@@ -60,7 +60,21 @@ declare const __propDef: {
|
|
60
60
|
/** Whether the input should allow multiple values. Valid for email and file inputs. */
|
61
61
|
multiple?: boolean | undefined;
|
62
62
|
/** The name of the input element. */
|
63
|
-
name
|
63
|
+
name: string;
|
64
|
+
/** The onblur event handler */
|
65
|
+
onblur?: ((e: FocusEvent) => void) | undefined;
|
66
|
+
/** Onchange event handler */
|
67
|
+
onchange?: ((e: Event) => void) | undefined;
|
68
|
+
/** Onclick event handler */
|
69
|
+
onclick?: ((e: MouseEvent) => void) | undefined;
|
70
|
+
/** Onfocus event handler */
|
71
|
+
onfocus?: ((e: FocusEvent) => void) | undefined;
|
72
|
+
/** Keyup event handler */
|
73
|
+
onkeyup?: ((e: KeyboardEvent) => void) | undefined;
|
74
|
+
/** Keydown event handler */
|
75
|
+
onkeydown?: ((e: KeyboardEvent) => void) | undefined;
|
76
|
+
/** Keypress event handler */
|
77
|
+
onkeypress?: ((e: KeyboardEvent) => void) | undefined;
|
64
78
|
/** A regular expression that the input's value is checked against. Valid for text, search, url, tel, email, and password. */
|
65
79
|
pattern?: string | undefined;
|
66
80
|
/** Placeholder text to display when the input is empty. */
|
@@ -92,7 +106,7 @@ declare const __propDef: {
|
|
92
106
|
/** A string that defines the title of the input element. */
|
93
107
|
title?: string | undefined;
|
94
108
|
/** The type of input to display. */
|
95
|
-
type?: "number" | "color" | "button" | "search" | "time" | "image" | "text" | "submit" | "email" | "tel" | "url" | "checkbox" | "radio" | "hidden" | "password" | "file" | "range" | "
|
109
|
+
type?: "number" | "color" | "button" | "search" | "time" | "image" | "text" | "submit" | "email" | "tel" | "url" | "date" | "checkbox" | "radio" | "hidden" | "password" | "file" | "range" | "datetime-local" | "month" | "week" | undefined;
|
96
110
|
/** The value of the input element. */
|
97
111
|
value?: string | undefined;
|
98
112
|
/** Whether the input should be viewable. Valid for password inputs. */
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import type { ZodType } from "zod";
|
2
|
+
import type { Form } from "./types.js";
|
3
|
+
/**
|
4
|
+
* Use a form in a Svelte component.
|
5
|
+
* @param {Form} form - The form to use.
|
6
|
+
* @returns
|
7
|
+
*/
|
8
|
+
export declare function useForm(form: Form<any>): {
|
9
|
+
form: {
|
10
|
+
originalData: any;
|
11
|
+
data: any;
|
12
|
+
} & Form<any>;
|
13
|
+
};
|
14
|
+
/**
|
15
|
+
* Validate a single field.
|
16
|
+
* @param {Form} form - The form to validate.
|
17
|
+
* @param {string} name - The name of the field to validate.
|
18
|
+
* @param {HTMLInputElement | HTMLSelectElement} el - The element to validate.
|
19
|
+
* @param {ZodType} validator - The validator to use.
|
20
|
+
*/
|
21
|
+
export declare function fieldValidate(form: Form<any>, name: string, el?: HTMLInputElement | HTMLSelectElement, validator?: ZodType<any, any>): void;
|
22
|
+
/**
|
23
|
+
* Keydown event handler for a field.
|
24
|
+
* @param {KeyboardEvent} e - The event.
|
25
|
+
* @param {Form} form - The form to use.
|
26
|
+
* @param {string} name - The name of the field.
|
27
|
+
* @param {HTMLInputElement | HTMLSelectElement} el - The element to validate.
|
28
|
+
* @param {ZodType} validator - The validator to use.
|
29
|
+
* @param {(e: KeyboardEvent) => void} onkeydown - The onkeydown event handler.
|
30
|
+
*/
|
31
|
+
export declare function fieldKeydown(form: Form<any>, name: string, el: () => HTMLInputElement | HTMLSelectElement | undefined, validator?: ZodType<any, any>, onkeydown?: (e: KeyboardEvent) => void): (e: KeyboardEvent) => Promise<void>;
|
32
|
+
/**
|
33
|
+
* Change event handler for a field.
|
34
|
+
* @param {Event} e - The event.
|
35
|
+
* @param {Form} form - The form to use.
|
36
|
+
* @param {string} name - The name of the field.
|
37
|
+
* @param {HTMLInputElement | HTMLSelectElement} el - The element to validate.
|
38
|
+
* @param {ZodType} validator - The validator to use.
|
39
|
+
* @param {(e: Event) => void} onchange - The onchange event handler.
|
40
|
+
*/
|
41
|
+
export declare function fieldChange(form: Form<any>, name: string, el: () => HTMLInputElement | HTMLSelectElement | undefined, validator?: ZodType<any, any>, onchange?: (e: Event) => void): (e: Event) => Promise<void>;
|
42
|
+
/**
|
43
|
+
* Keys that should be ignored when marking a field as tainted.
|
44
|
+
*/
|
45
|
+
export declare const ignoreKeys: string[];
|
@@ -0,0 +1,85 @@
|
|
1
|
+
/**
|
2
|
+
* Use a form in a Svelte component.
|
3
|
+
* @param {Form} form - The form to use.
|
4
|
+
* @returns
|
5
|
+
*/
|
6
|
+
export function useForm(form) {
|
7
|
+
let _form = Object.assign({
|
8
|
+
originalData: JSON.parse(JSON.stringify(form.data ?? {})),
|
9
|
+
data: form.data ?? {},
|
10
|
+
}, form);
|
11
|
+
// Have to declare a variable separately to use in the return object
|
12
|
+
// as the compiler is looking for a declaration.
|
13
|
+
let __form = $state(_form);
|
14
|
+
return {
|
15
|
+
form: __form,
|
16
|
+
};
|
17
|
+
}
|
18
|
+
/**
|
19
|
+
* Validate a single field.
|
20
|
+
* @param {Form} form - The form to validate.
|
21
|
+
* @param {string} name - The name of the field to validate.
|
22
|
+
* @param {HTMLInputElement | HTMLSelectElement} el - The element to validate.
|
23
|
+
* @param {ZodType} validator - The validator to use.
|
24
|
+
*/
|
25
|
+
export function fieldValidate(form, name, el, validator) {
|
26
|
+
if (!validator)
|
27
|
+
return;
|
28
|
+
const result = validator.safeParse(el?.value);
|
29
|
+
if (result.success) {
|
30
|
+
form.issues = (form.issues || []).filter((issue) => issue.name !== name);
|
31
|
+
form.valid = form.issues.length === 0;
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
form.valid = false;
|
35
|
+
form.issues = (form.issues || []).filter((issue) => issue.name !== name);
|
36
|
+
form.issues?.push({
|
37
|
+
name,
|
38
|
+
...result.error.issues[0],
|
39
|
+
});
|
40
|
+
}
|
41
|
+
}
|
42
|
+
/**
|
43
|
+
* Keydown event handler for a field.
|
44
|
+
* @param {KeyboardEvent} e - The event.
|
45
|
+
* @param {Form} form - The form to use.
|
46
|
+
* @param {string} name - The name of the field.
|
47
|
+
* @param {HTMLInputElement | HTMLSelectElement} el - The element to validate.
|
48
|
+
* @param {ZodType} validator - The validator to use.
|
49
|
+
* @param {(e: KeyboardEvent) => void} onkeydown - The onkeydown event handler.
|
50
|
+
*/
|
51
|
+
export function fieldKeydown(form, name, el, validator, onkeydown) {
|
52
|
+
return async function (e) {
|
53
|
+
setTimeout(() => {
|
54
|
+
const possibleKey = e?.key;
|
55
|
+
if (ignoreKeys.includes(possibleKey))
|
56
|
+
return;
|
57
|
+
form.data[name] = el()?.value || '';
|
58
|
+
fieldValidate(form, name, el(), validator);
|
59
|
+
}, 0); // Wait for the key to be updated in the input.
|
60
|
+
if (onkeydown)
|
61
|
+
return onkeydown(e);
|
62
|
+
};
|
63
|
+
}
|
64
|
+
/**
|
65
|
+
* Change event handler for a field.
|
66
|
+
* @param {Event} e - The event.
|
67
|
+
* @param {Form} form - The form to use.
|
68
|
+
* @param {string} name - The name of the field.
|
69
|
+
* @param {HTMLInputElement | HTMLSelectElement} el - The element to validate.
|
70
|
+
* @param {ZodType} validator - The validator to use.
|
71
|
+
* @param {(e: Event) => void} onchange - The onchange event handler.
|
72
|
+
*/
|
73
|
+
export function fieldChange(form, name, el, validator, onchange) {
|
74
|
+
return async function (e) {
|
75
|
+
form.data[name] = el()?.value || '';
|
76
|
+
form.fields[name].tainted = true;
|
77
|
+
fieldValidate(form, name, el(), validator);
|
78
|
+
if (onchange)
|
79
|
+
return onchange(e);
|
80
|
+
};
|
81
|
+
}
|
82
|
+
/**
|
83
|
+
* Keys that should be ignored when marking a field as tainted.
|
84
|
+
*/
|
85
|
+
export const ignoreKeys = ['Tab', 'Shift', 'Control', 'Alt', 'Meta', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Escape', 'CapsLock', 'NumLock', 'ScrollLock', 'Pause', 'ContextMenu', 'PrintScreen', 'Help', 'Clear', 'OS', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F12'];
|
package/dist/form/form.d.ts
CHANGED
@@ -1,34 +1,52 @@
|
|
1
|
-
|
2
|
-
import {
|
3
|
-
|
4
|
-
|
5
|
-
export
|
6
|
-
success: true;
|
7
|
-
value: any;
|
8
|
-
};
|
9
|
-
export type ErrorType = {
|
10
|
-
success: false;
|
11
|
-
error: string;
|
12
|
-
};
|
13
|
-
export type FormRegistry = {
|
14
|
-
items: Record<string, FormElement | FormElement[]>;
|
15
|
-
getValues(): Record<string, string>;
|
16
|
-
register(name: string, el: FormElement | FormElement[]): void;
|
17
|
-
updateFormValues(): void;
|
18
|
-
validate(): boolean;
|
19
|
-
validateField(nameOrElement: string): SuccessType | ErrorType;
|
20
|
-
validateField(nameOrElement: FormElement): SuccessType | ErrorType;
|
21
|
-
validateField(nameOrElement: FormElement[]): SuccessType | ErrorType;
|
22
|
-
validateSingleField(nameOrElement: string | FormElement): SuccessType | ErrorType;
|
23
|
-
};
|
24
|
-
export type BeforeSubmitFn = (opts: {
|
25
|
-
form: HTMLFormElement;
|
26
|
-
data: FormData;
|
27
|
-
cancel: () => void;
|
28
|
-
}) => void | Promise<void>;
|
29
|
-
export type AddBeforeSubmitFn = (id: string, fn: BeforeSubmitFn) => void;
|
30
|
-
export declare const getPropertyPaths: (schema: ZodType) => string[];
|
1
|
+
import { Bodyguard, type BodyguardFormConfig, type BodyguardResult, type JSONLike } from "@auth70/bodyguard";
|
2
|
+
import type { RequestEvent } from "@sveltejs/kit";
|
3
|
+
import type { ZodType, infer as Infer, ZodIssue, ZodIssueBase } from "zod";
|
4
|
+
import type { Form, FormField, FormIssue, ZodTypes } from "./types.js";
|
5
|
+
export declare const bodyguard: Bodyguard;
|
31
6
|
/**
|
32
|
-
*
|
7
|
+
* Get fields from a Zod schema.
|
8
|
+
* @param {ZodObject<any>} schema - The schema to get fields from.
|
9
|
+
* @returns {Record<string, any>} - The fields from the schema.
|
33
10
|
*/
|
34
|
-
export declare
|
11
|
+
export declare function getFieldsFromSchema(schema: ZodType, data?: BodyguardResult<any>, parents?: string[]): Record<string, FormField>;
|
12
|
+
/**
|
13
|
+
* Server page load call. Optionally with a default object to populate the form with from locals.
|
14
|
+
* @param {ZodType} schema - The schema to parse the form with.
|
15
|
+
* @param {RequestEvent} event - The event to load the (possible) form from.
|
16
|
+
* @param {JSONLike} obj - The default object to populate the form with.
|
17
|
+
* @returns {Promise<Form<Z>>} - The form to use.
|
18
|
+
*/
|
19
|
+
export declare function loadForm<Z extends ZodTypes>(schema: Z, event: RequestEvent, obj?: JSONLike): Promise<Form<Z>>;
|
20
|
+
/**
|
21
|
+
* Parse zod issues into form issues (with a path string).
|
22
|
+
* @param {ZodIssue[]} issues - The issues to parse.
|
23
|
+
* @returns {FormIssue[]} - The parsed issues.
|
24
|
+
*/
|
25
|
+
export declare function parseFormIssues(issues: (ZodIssue & ZodIssueBase)[]): FormIssue[];
|
26
|
+
/**
|
27
|
+
* Parse a form using a schema.
|
28
|
+
* @param {ZodType} schema - The schema to parse the form with.
|
29
|
+
* @param {Request} request - The request to parse.
|
30
|
+
* @param {BodyguardFormConfig} opts - The options for the form validation.
|
31
|
+
* @returns {Promise<{ valid: false } | { valid: true, data: Infer<Z> }>} - The result of the form validation.
|
32
|
+
*/
|
33
|
+
export declare function parseForm<Z extends ZodType>(schema: Z, event: RequestEvent, opts?: BodyguardFormConfig): Promise<Form<Z>>;
|
34
|
+
/**
|
35
|
+
* Convert an array path from a Zod issue to a string path for a form field.
|
36
|
+
* @param {(string | number)[]} path - The path to convert.
|
37
|
+
* @returns {string} - The string path.
|
38
|
+
*/
|
39
|
+
export declare function arrayPathToStringPath(path: (string | number)[]): string;
|
40
|
+
/**
|
41
|
+
* Get a value from an object using a string path.
|
42
|
+
* @param {any} obj - The object to get the value from.
|
43
|
+
* @param {string} path - The path to get the value from.
|
44
|
+
* @returns {string | number | Date | boolean | object | undefined} - The value from the object.
|
45
|
+
*/
|
46
|
+
export declare function getFromObjWithStringPath(obj: any, path: string): string | number | Date | boolean | object | undefined;
|
47
|
+
/**
|
48
|
+
* Get individual validators for each field in a form.
|
49
|
+
* @param {Form} form - The form to get the validators from.
|
50
|
+
* @returns {Record<keyof Infer<T>, (value: any) => boolean>} - The validators for each field.
|
51
|
+
*/
|
52
|
+
export declare function getIndividualValidators<T extends ZodTypes>(form: Form<T>): Record<keyof Infer<T>, (value: any) => boolean>;
|