lutra 0.0.11 → 0.0.13
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 +4 -3
- package/dist/datatable/DataTableRow.svelte +4 -5
- package/dist/display/ContextTip.svelte +11 -7
- package/dist/display/Icon.svelte +4 -3
- package/dist/display/Tooltip.svelte +7 -4
- package/dist/form/FieldActions.svelte +1 -0
- package/dist/form/FieldContainer.svelte +1 -1
- package/dist/form/FieldContent.svelte +17 -2
- package/dist/form/FieldContent.svelte.d.ts +4 -0
- package/dist/form/FieldSection.svelte +8 -6
- package/dist/form/Form.svelte +29 -24
- package/dist/form/Form.svelte.d.ts +1 -1
- package/dist/form/Input.svelte +17 -15
- package/dist/form/Input.svelte.d.ts +2 -0
- package/dist/form/Select.svelte +78 -13
- package/dist/form/Select.svelte.d.ts +57 -5
- package/dist/form/client.svelte.js +15 -10
- package/dist/form/form.d.ts +4 -4
- package/dist/form/form.js +4 -0
- package/dist/form/types.d.ts +2 -7
- package/package.json +2 -2
@@ -17,14 +17,15 @@ let {
|
|
17
17
|
section {
|
18
18
|
container-type: inline-size;
|
19
19
|
display: grid;
|
20
|
-
grid-template-columns: repeat(
|
20
|
+
grid-template-columns: repeat(var(--dtcols), minmax(min-content, 1fr));
|
21
21
|
border-radius: var(--border-radius);
|
22
|
-
width: 100%;
|
23
22
|
font-size: var(--font-size, 1em);
|
23
|
+
margin-inline: calc((var(--dtc) - 1) * 1em);
|
24
|
+
width: calc(100% + (var(--dtc) - 1 * 2em));
|
25
|
+
overflow-x: auto;
|
24
26
|
}
|
25
27
|
section.contained {
|
26
28
|
border: var(--border);
|
27
29
|
border-radius: var(--border-radius);
|
28
|
-
overflow-x: auto;
|
29
30
|
}
|
30
31
|
</style>
|
@@ -19,22 +19,21 @@ setContext("DataTableRow", { header });
|
|
19
19
|
<style>
|
20
20
|
.DataTableRow {
|
21
21
|
display: grid;
|
22
|
-
grid-column: 1 / calc(var(--dtcols) + 1);
|
23
22
|
grid-template-columns: subgrid;
|
23
|
+
grid-column: 1 / -1;
|
24
24
|
gap: 1.5em;
|
25
25
|
align-items: center;
|
26
26
|
width: 100%;
|
27
|
-
|
28
|
-
padding: 0.75em calc(1.5em * var(--dtc));
|
27
|
+
padding: 0.75em 1em;
|
29
28
|
position: relative;
|
30
29
|
}
|
31
30
|
.DataTableRow:hover {
|
32
|
-
background:
|
31
|
+
background: var(--bg-subtle);
|
33
32
|
}
|
34
33
|
.DataTableRow:has(.Actions) {
|
35
34
|
padding-right: 0;
|
36
35
|
}
|
37
|
-
.DataTableRow :global(*) {
|
36
|
+
.DataTableRow :global(> *) {
|
38
37
|
white-space: nowrap;
|
39
38
|
}
|
40
39
|
.DataTableRow.header {
|
@@ -7,17 +7,21 @@ let {
|
|
7
7
|
</script>
|
8
8
|
|
9
9
|
<Tooltip {tip}>
|
10
|
-
<
|
11
|
-
<Icon icon={Help} />
|
12
|
-
</
|
10
|
+
<a href="#foo" onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
|
11
|
+
<Icon icon={Help} --icon-width="16px" --icon-height="16px" --cursor="help" />
|
12
|
+
</a>
|
13
13
|
</Tooltip>
|
14
14
|
|
15
15
|
<style>
|
16
|
-
|
16
|
+
a {
|
17
17
|
border-radius: 50%;
|
18
|
+
color: var(--text);
|
19
|
+
height: 16px;
|
20
|
+
width: 16px;
|
21
|
+
display: inline-block;
|
18
22
|
}
|
19
|
-
|
20
|
-
|
21
|
-
outline-offset: -
|
23
|
+
a:focus,
|
24
|
+
a:active {
|
25
|
+
outline-offset: -2px;
|
22
26
|
}
|
23
27
|
</style>
|
package/dist/display/Icon.svelte
CHANGED
@@ -17,19 +17,20 @@ let {
|
|
17
17
|
|
18
18
|
<style>
|
19
19
|
.Icon {
|
20
|
-
display:
|
20
|
+
display: flex;
|
21
21
|
align-items: center;
|
22
22
|
justify-content: center;
|
23
23
|
font-size: 1em;
|
24
24
|
width: var(--icon-width, var(--font-size, 1em));
|
25
25
|
height: var(--icon-height, var(--font-size, 1em));
|
26
26
|
overflow: clip;
|
27
|
-
vertical-align: var(--vertical-align,
|
27
|
+
vertical-align: var(--vertical-align, text-bottom);
|
28
28
|
cursor: var(--cursor, default);
|
29
29
|
}
|
30
|
+
img,
|
30
31
|
.Icon :global(svg) {
|
31
32
|
width: 100%;
|
32
33
|
height: 100%;
|
33
|
-
display:
|
34
|
+
display: block;
|
34
35
|
}
|
35
36
|
</style>
|
@@ -32,9 +32,8 @@ const id = `tt-${Math.random().toString(36).substring(2, 15) + Math.random().toS
|
|
32
32
|
<style>
|
33
33
|
.Tooltip {
|
34
34
|
position: relative;
|
35
|
-
height: 100%;
|
36
35
|
display: inline-flex;
|
37
|
-
align
|
36
|
+
vertical-align: var(--vertical-align, text-bottom);
|
38
37
|
}
|
39
38
|
.TooltipContainer {
|
40
39
|
position: absolute;
|
@@ -58,8 +57,8 @@ const id = `tt-${Math.random().toString(36).substring(2, 15) + Math.random().toS
|
|
58
57
|
border-left: var(--border-subtle);
|
59
58
|
border-right: var(--border-subtle);
|
60
59
|
display: block;
|
61
|
-
font-size: max(0.
|
62
|
-
line-height: 1.
|
60
|
+
font-size: max(0.75rem, 11px);
|
61
|
+
line-height: 1.35;
|
63
62
|
font-weight: 500;
|
64
63
|
color: var(--text);
|
65
64
|
max-width: clamp(5ch, 100%, 35ch);
|
@@ -72,6 +71,10 @@ const id = `tt-${Math.random().toString(36).substring(2, 15) + Math.random().toS
|
|
72
71
|
.TooltipContent :global(b) {
|
73
72
|
font-weight: 700;
|
74
73
|
}
|
74
|
+
.TooltipTrigger {
|
75
|
+
display: inline-flex;
|
76
|
+
align-items: center;
|
77
|
+
}
|
75
78
|
.Tooltip:has(.TooltipTrigger:hover):not(.open) .TooltipContainer {
|
76
79
|
animation: fadeIn 0.2s var(--delay, 0.5s) ease-in-out forwards;
|
77
80
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<script>import Label from "./Label.svelte";
|
2
|
+
import FieldError from "./FieldError.svelte";
|
2
3
|
let {
|
3
4
|
id,
|
4
5
|
contained,
|
@@ -7,12 +8,16 @@ let {
|
|
7
8
|
label,
|
8
9
|
labelTip,
|
9
10
|
type = "text",
|
10
|
-
|
11
|
+
direction = "column",
|
12
|
+
// 'row' | 'column'
|
13
|
+
children,
|
14
|
+
field,
|
15
|
+
issue
|
11
16
|
} = $props();
|
12
17
|
</script>
|
13
18
|
|
14
19
|
|
15
|
-
<div class="FieldContent {type}" class:contained>
|
20
|
+
<div class="FieldContent {type} {direction}" class:contained>
|
16
21
|
<Label {label} tip={labelTip} {id} />
|
17
22
|
{#if contained}
|
18
23
|
<div class="Field">
|
@@ -27,6 +32,10 @@ let {
|
|
27
32
|
{:else}
|
28
33
|
{@render children()}
|
29
34
|
{/if}
|
35
|
+
|
36
|
+
{#if field?.tainted && issue?.code}
|
37
|
+
<FieldError code={issue.code} message={issue.message} />
|
38
|
+
{/if}
|
30
39
|
</div>
|
31
40
|
|
32
41
|
<style>
|
@@ -75,4 +84,10 @@ let {
|
|
75
84
|
outline-offset: 3px;
|
76
85
|
border-radius: calc(var(--field-radius) - 2px);
|
77
86
|
}
|
87
|
+
/**
|
88
|
+
* Row
|
89
|
+
*/
|
90
|
+
.FieldContent.row {
|
91
|
+
flex-direction: row;
|
92
|
+
}
|
78
93
|
</style>
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
2
2
|
import type { Snippet } from "svelte";
|
3
|
+
import type { FormField, FormIssue } from "./types.js";
|
3
4
|
declare const __propDef: {
|
4
5
|
props: {
|
5
6
|
id: string;
|
@@ -17,7 +18,10 @@ declare const __propDef: {
|
|
17
18
|
labelTip?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
|
18
19
|
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
19
20
|
}) | undefined;
|
21
|
+
direction?: "row" | "column" | undefined;
|
20
22
|
children: Snippet;
|
23
|
+
field?: FormField | undefined;
|
24
|
+
issue?: FormIssue | undefined;
|
21
25
|
};
|
22
26
|
events: {
|
23
27
|
[evt: string]: CustomEvent<any>;
|
@@ -36,11 +36,12 @@ let {
|
|
36
36
|
padding: 0;
|
37
37
|
display: grid;
|
38
38
|
gap: 0rem;
|
39
|
-
grid-template-columns:
|
39
|
+
grid-template-columns: subgrid;
|
40
40
|
}
|
41
41
|
.FieldSection .FieldSectionTitle {
|
42
42
|
display: flex;
|
43
43
|
flex-direction: column;
|
44
|
+
grid-column: 0 / 1;
|
44
45
|
background-color: var(--base);
|
45
46
|
gap: 0.25rem;
|
46
47
|
padding: 1em 1.5em;
|
@@ -51,20 +52,21 @@ let {
|
|
51
52
|
}
|
52
53
|
.FieldSection .FieldSectionFields {
|
53
54
|
padding: calc(var(--padding, 1.5em) * var(--fcc));
|
55
|
+
grid-column: 1 / -1;
|
54
56
|
display: grid;
|
55
57
|
gap: 1.5rem;
|
56
58
|
}
|
57
59
|
@container (min-width: 600px) {
|
58
60
|
.FieldSection {
|
59
61
|
gap: 3rem;
|
60
|
-
padding: 2rem;
|
61
|
-
grid-template-columns: 1fr
|
62
|
+
padding: calc(2rem * var(--fcc));
|
63
|
+
/*grid-template-columns: 1fr;*/
|
62
64
|
border-bottom: 1px dotted var(--border-light);
|
63
65
|
margin-bottom: 3rem;
|
64
66
|
border-radius: 0;
|
65
67
|
}
|
66
68
|
.FieldSection:has(.FieldSectionTitle) {
|
67
|
-
grid-template-columns: minmax(180px, 1fr) 3fr
|
69
|
+
/*grid-template-columns: minmax(180px, 1fr) 3fr;*/
|
68
70
|
}
|
69
71
|
.FieldSection:last-child {
|
70
72
|
border-bottom: none;
|
@@ -86,8 +88,8 @@ let {
|
|
86
88
|
}
|
87
89
|
@container (min-width: 1024px) {
|
88
90
|
.FieldSection {
|
89
|
-
grid-template-columns: minmax(300px, 1fr) 3fr
|
90
|
-
padding: 3rem;
|
91
|
+
/*grid-template-columns: minmax(300px, 1fr) 3fr;*/
|
92
|
+
padding: calc(3rem * var(--fcc));
|
91
93
|
}
|
92
94
|
.FieldSection:not(:first-child) {
|
93
95
|
border-top: 0;
|
package/dist/form/Form.svelte
CHANGED
@@ -16,26 +16,30 @@ let {
|
|
16
16
|
} = $props();
|
17
17
|
setContext("form", form);
|
18
18
|
setContext("form.validators", getIndividualValidators(form));
|
19
|
-
const schema = dezerialize(form
|
19
|
+
const schema = form?.schema ? dezerialize(form?.schema) : null;
|
20
20
|
const bodyguard = new Bodyguard();
|
21
21
|
let formEl;
|
22
22
|
function setFormIssuesAndFields(issues, fields) {
|
23
|
-
form
|
24
|
-
|
23
|
+
if (form) {
|
24
|
+
form.issues = issues;
|
25
|
+
form.fields = fields;
|
26
|
+
}
|
25
27
|
}
|
26
28
|
async function validate() {
|
27
|
-
form
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
if (form && schema) {
|
30
|
+
form.tainted = true;
|
31
|
+
const req = new Request("localhost", {
|
32
|
+
method: "POST",
|
33
|
+
body: new FormData(formEl)
|
34
|
+
});
|
35
|
+
const result = await bodyguard.softForm(req, schema.parse);
|
36
|
+
if (result.success === true) {
|
37
|
+
form.valid = true;
|
38
|
+
form.issues = [];
|
39
|
+
} else {
|
40
|
+
form.valid = false;
|
41
|
+
form.issues = parseFormIssues(result.error.issues);
|
42
|
+
}
|
39
43
|
}
|
40
44
|
}
|
41
45
|
onMount(() => {
|
@@ -61,19 +65,20 @@ onMount(() => {
|
|
61
65
|
// `result` is an `ActionResult` object
|
62
66
|
// `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
|
63
67
|
console.log('result', result);
|
64
|
-
const resultForm = result?.data?.form;
|
68
|
+
const resultForm = result.type !== "redirect" && result.type !== "error" ? result?.data?.form : null;
|
65
69
|
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);
|
70
|
+
if(resultForm && form) {
|
71
|
+
form.valid = Object.assign({ valid: false }, resultForm).valid ?? false;
|
72
72
|
}
|
73
73
|
} else if(result.type === "failure") {
|
74
|
-
if(resultForm) {
|
75
|
-
setFormIssuesAndFields(
|
74
|
+
if(resultForm && form) {
|
75
|
+
setFormIssuesAndFields(
|
76
|
+
Object.assign({ issues: [] }, resultForm).issues, // Have to assign to avoid type error as we cant use `as` here
|
77
|
+
Object.assign({ fields: [] }, resultForm).fields,
|
78
|
+
);
|
76
79
|
}
|
80
|
+
} else if(result.type === "error") {
|
81
|
+
console.error('[lutra] Error from form enhance call', result.error);
|
77
82
|
} else if(result.type === "redirect") {
|
78
83
|
window.location.href = result.location;
|
79
84
|
}
|
@@ -3,7 +3,7 @@ import type { Snippet } from "svelte";
|
|
3
3
|
import type { Form } from "./types.js";
|
4
4
|
declare const __propDef: {
|
5
5
|
props: {
|
6
|
-
form
|
6
|
+
form?: Form<any> | undefined;
|
7
7
|
action?: string | undefined;
|
8
8
|
enctype?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain" | undefined;
|
9
9
|
method?: "GET" | "POST" | undefined;
|
package/dist/form/Input.svelte
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
<script>import { getContext
|
2
|
-
import Label from "./Label.svelte";
|
1
|
+
<script>import { getContext } from "svelte";
|
3
2
|
import { createId } from "../utils/id.js";
|
4
3
|
import Copy from "../icons/Copy.svelte";
|
5
4
|
import Done from "../icons/Done.svelte";
|
@@ -11,8 +10,10 @@ import { fieldChange, fieldKeydown, ignoreKeys } from "./client.svelte.js";
|
|
11
10
|
import FieldError from "./FieldError.svelte";
|
12
11
|
import { getFromObjWithStringPath } from "./form.js";
|
13
12
|
import { ZodType } from "zod";
|
13
|
+
import FieldContent from "./FieldContent.svelte";
|
14
14
|
let {
|
15
15
|
alt,
|
16
|
+
attrs,
|
16
17
|
autocapitalize,
|
17
18
|
autocomplete,
|
18
19
|
autocorrect,
|
@@ -69,7 +70,7 @@ let copyTooltipOpen = $state(false);
|
|
69
70
|
let copyBtnIcon = $state(Copy);
|
70
71
|
let viewBtnIcon = $state(Show);
|
71
72
|
const form = getContext("form");
|
72
|
-
const field = $derived(form
|
73
|
+
const field = $derived(form?.fields[name]);
|
73
74
|
const issue = $derived(form?.issues?.find((issue2) => issue2.name === name));
|
74
75
|
const validator = getContext("form.validators")?.[name];
|
75
76
|
const data = form?.data;
|
@@ -115,6 +116,7 @@ function copy(e) {
|
|
115
116
|
<input
|
116
117
|
bind:this={el}
|
117
118
|
{alt}
|
119
|
+
{...attrs}
|
118
120
|
autocapitalize={typeof autocapitalize === 'string' ? autocapitalize : (typeof autocapitalize === 'boolean' ? (autocapitalize ? 'on' : 'off') : undefined)}
|
119
121
|
{autocomplete}
|
120
122
|
autocorrect={typeof autocorrect === 'boolean' ? (autocorrect ? 'on' : 'off') : undefined}
|
@@ -136,9 +138,9 @@ function copy(e) {
|
|
136
138
|
{name}
|
137
139
|
{onblur}
|
138
140
|
{onclick}
|
139
|
-
onchange={fieldChange(form, name, () => el, validator)}
|
141
|
+
onchange={fieldChange(form, name, () => el, validator, onchange)}
|
140
142
|
{onfocus}
|
141
|
-
onkeydown={fieldKeydown(form, name, () => el, validator)}
|
143
|
+
onkeydown={fieldKeydown(form, name, () => el, validator, onkeydown)}
|
142
144
|
{onkeyup}
|
143
145
|
{onkeypress}
|
144
146
|
pattern={pattern ? pattern : field?.pattern}
|
@@ -151,16 +153,19 @@ function copy(e) {
|
|
151
153
|
{tabindex}
|
152
154
|
{title}
|
153
155
|
{type}
|
154
|
-
value={value || getFromObjWithStringPath(Object.assign(originalData, data), name) || form?.fields?.[name]?.defaultValue || ''}
|
156
|
+
value={value || getFromObjWithStringPath(Object.assign(originalData ?? {}, data ?? {}), name) || form?.fields?.[name]?.defaultValue || ''}
|
155
157
|
{webkitdirectory}
|
156
158
|
/>
|
157
159
|
{/snippet}
|
158
|
-
<div
|
159
|
-
class="FieldContainer {type}"
|
160
|
-
class:checkOrRadio={type === "checkbox" || type === "radio"}
|
161
|
-
>
|
162
160
|
|
163
|
-
|
161
|
+
<FieldContent
|
162
|
+
{id}
|
163
|
+
{label}
|
164
|
+
{labelTip}
|
165
|
+
direction={(type === "checkbox" || type === "radio") ? 'row' : 'column'}
|
166
|
+
{field}
|
167
|
+
{issue}
|
168
|
+
>
|
164
169
|
|
165
170
|
{#if type === "checkbox" || type === "radio"}
|
166
171
|
{@render input()}
|
@@ -206,11 +211,8 @@ function copy(e) {
|
|
206
211
|
</div>
|
207
212
|
{/if}
|
208
213
|
</div>
|
209
|
-
{#if field?.tainted && issue?.code}
|
210
|
-
<FieldError code={issue.code} message={issue.message} />
|
211
|
-
{/if}
|
212
214
|
{/if}
|
213
|
-
</
|
215
|
+
</FieldContent>
|
214
216
|
|
215
217
|
<style>
|
216
218
|
.FieldContainer {
|
@@ -4,6 +4,8 @@ declare const __propDef: {
|
|
4
4
|
props: {
|
5
5
|
/** alt attribute for the image type. Required for accessibility */
|
6
6
|
alt?: string | undefined;
|
7
|
+
/** Additional attributes to add to the input element. */
|
8
|
+
attrs?: Record<string, string> | undefined;
|
7
9
|
/** Whether the input should be autocapitalized. */
|
8
10
|
autocapitalize?: boolean | "none" | "off" | "on" | "sentences" | "words" | "characters" | undefined;
|
9
11
|
/** Specifies whether autocomplete is enabled for the input. */
|
package/dist/form/Select.svelte
CHANGED
@@ -1,22 +1,87 @@
|
|
1
|
-
<script>import
|
1
|
+
<script>import { getContext } from "svelte";
|
2
|
+
import Label from "./Label.svelte";
|
3
|
+
import { createId } from "../utils/id.js";
|
4
|
+
import Copy from "../icons/Copy.svelte";
|
5
|
+
import Done from "../icons/Done.svelte";
|
6
|
+
import Show from "../icons/Show.svelte";
|
7
|
+
import Hide from "../icons/Hide.svelte";
|
8
|
+
import Tooltip from "../display/Tooltip.svelte";
|
9
|
+
import IconButton from "../display/IconButton.svelte";
|
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";
|
14
|
+
import FieldContent from "./FieldContent.svelte";
|
2
15
|
let {
|
16
|
+
attrs,
|
17
|
+
children,
|
18
|
+
disabled,
|
19
|
+
help,
|
20
|
+
id = $bindable(createId()),
|
3
21
|
label,
|
4
22
|
labelTip,
|
5
|
-
|
6
|
-
|
23
|
+
name,
|
24
|
+
onblur,
|
25
|
+
onchange,
|
26
|
+
onclick,
|
27
|
+
onfocus,
|
28
|
+
onkeydown,
|
29
|
+
onkeyup,
|
30
|
+
onkeypress,
|
31
|
+
options,
|
32
|
+
placeholder,
|
33
|
+
required,
|
34
|
+
results,
|
35
|
+
shape = "default",
|
36
|
+
tabindex,
|
37
|
+
value = $bindable()
|
7
38
|
} = $props();
|
8
|
-
let
|
39
|
+
let el = $state();
|
40
|
+
const form = getContext("form");
|
41
|
+
const field = $derived(form?.fields[name]);
|
42
|
+
const issue = $derived(form?.issues?.find((issue2) => issue2.name === name));
|
43
|
+
const validator = getContext("form.validators")?.[name];
|
44
|
+
const data = form?.data;
|
45
|
+
const originalData = form?.originalData;
|
9
46
|
</script>
|
10
47
|
|
11
|
-
<FieldContent
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
{
|
48
|
+
<FieldContent
|
49
|
+
{id}
|
50
|
+
{label}
|
51
|
+
{labelTip}
|
52
|
+
{field}
|
53
|
+
{issue}
|
54
|
+
>
|
55
|
+
<select
|
56
|
+
bind:this={el}
|
57
|
+
{...attrs}
|
58
|
+
{disabled}
|
59
|
+
{id}
|
60
|
+
{name}
|
61
|
+
{onblur}
|
62
|
+
{onclick}
|
63
|
+
onchange={fieldChange(form, name, () => el, validator, onchange)}
|
64
|
+
{onfocus}
|
65
|
+
onkeydown={fieldKeydown(form, name, () => el, validator, onkeydown)}
|
66
|
+
{onkeyup}
|
67
|
+
{onkeypress}
|
68
|
+
{placeholder}
|
69
|
+
required={required || field?.required}
|
70
|
+
{results}
|
71
|
+
{tabindex}
|
72
|
+
value={value || getFromObjWithStringPath(Object.assign(originalData ?? {}, data ?? {}), name) || form?.fields?.[name]?.defaultValue || ''}
|
73
|
+
>
|
74
|
+
{#if children}
|
75
|
+
{@render children()}
|
76
|
+
{:else if options}
|
77
|
+
{#each options as option}
|
78
|
+
{#if typeof option === 'string'}
|
79
|
+
<option value={option}>{option}</option>
|
80
|
+
{:else}
|
81
|
+
<option value={option.value}>{option.label}</option>
|
82
|
+
{/if}
|
83
|
+
{/each}
|
84
|
+
{/if}
|
20
85
|
</select>
|
21
86
|
</FieldContent>
|
22
87
|
|
@@ -1,19 +1,71 @@
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
2
2
|
declare const __propDef: {
|
3
3
|
props: {
|
4
|
-
|
4
|
+
/** Additional attributes to add to the input element. */
|
5
|
+
attrs?: Record<string, string> | undefined;
|
6
|
+
/** Options for the select element. */
|
7
|
+
children?: ((this: void) => typeof import("svelte").SnippetReturn & {
|
5
8
|
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
6
9
|
}) | undefined;
|
7
|
-
|
10
|
+
/** Whether the input should be disabled. */
|
11
|
+
disabled?: boolean | undefined;
|
12
|
+
/** Help text to display below the input. */
|
13
|
+
help?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
|
8
14
|
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
9
15
|
}) | undefined;
|
10
|
-
|
16
|
+
/** A random id is generated if not provided. */
|
17
|
+
id?: string | undefined;
|
18
|
+
/** The label for the input */
|
19
|
+
label?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
|
20
|
+
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
21
|
+
}) | undefined;
|
22
|
+
/** Context tooltip for a label. Renders with a questionmark using ContextTip. */
|
23
|
+
labelTip?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
|
11
24
|
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
12
25
|
}) | undefined;
|
13
|
-
|
26
|
+
/** The name of the input element. */
|
27
|
+
name: string;
|
28
|
+
/** The onblur event handler */
|
29
|
+
onblur?: ((e: FocusEvent) => void) | undefined;
|
30
|
+
/** Onchange event handler */
|
31
|
+
onchange?: ((e: Event) => void) | undefined;
|
32
|
+
/** Onclick event handler */
|
33
|
+
onclick?: ((e: MouseEvent) => void) | undefined;
|
34
|
+
/** Onfocus event handler */
|
35
|
+
onfocus?: ((e: FocusEvent) => void) | undefined;
|
36
|
+
/** Keyup event handler */
|
37
|
+
onkeyup?: ((e: KeyboardEvent) => void) | undefined;
|
38
|
+
/** Keydown event handler */
|
39
|
+
onkeydown?: ((e: KeyboardEvent) => void) | undefined;
|
40
|
+
/** Keypress event handler */
|
41
|
+
onkeypress?: ((e: KeyboardEvent) => void) | undefined;
|
42
|
+
/** The options for the select element. */
|
43
|
+
options?: string[] | {
|
14
44
|
value: string;
|
15
45
|
label: string;
|
16
|
-
}[];
|
46
|
+
}[] | undefined;
|
47
|
+
/** Placeholder text to display when the input is empty. */
|
48
|
+
placeholder?: string | undefined;
|
49
|
+
/** Suffix content, to display after the input. */
|
50
|
+
suffix?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
|
51
|
+
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
52
|
+
}) | undefined;
|
53
|
+
/** Prefix content, to display before the input. */
|
54
|
+
prefix?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
|
55
|
+
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
56
|
+
}) | undefined;
|
57
|
+
/** Whether the input should be required. */
|
58
|
+
required?: boolean | undefined;
|
59
|
+
/** The maximum number of items that should be displayed in the drop-down list of previous search queries. Safari only. */
|
60
|
+
results?: number | undefined;
|
61
|
+
/** The shape of the input element. */
|
62
|
+
shape?: "default" | "circle" | "rounded" | "pill" | undefined;
|
63
|
+
/** Source URL for the image type. */
|
64
|
+
src?: string | undefined;
|
65
|
+
/** An integer attribute indicating if the element can take input focus (is focusable), if it should participate to sequential keyboard navigation. */
|
66
|
+
tabindex?: number | undefined;
|
67
|
+
/** The value of the input element. */
|
68
|
+
value?: string | undefined;
|
17
69
|
};
|
18
70
|
events: {
|
19
71
|
[evt: string]: CustomEvent<any>;
|
@@ -34,8 +34,8 @@ export function fieldValidate(form, name, el, validator) {
|
|
34
34
|
form.valid = false;
|
35
35
|
form.issues = (form.issues || []).filter((issue) => issue.name !== name);
|
36
36
|
form.issues?.push({
|
37
|
-
name,
|
38
|
-
...result.error.issues[0],
|
37
|
+
name, // Include the path of the field.
|
38
|
+
...((result.error?.issues?.length ? result.error.issues : [])?.[0]), // Include the first issue as we only care about one at a time.
|
39
39
|
});
|
40
40
|
}
|
41
41
|
}
|
@@ -51,11 +51,13 @@ export function fieldValidate(form, name, el, validator) {
|
|
51
51
|
export function fieldKeydown(form, name, el, validator, onkeydown) {
|
52
52
|
return async function (e) {
|
53
53
|
setTimeout(() => {
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
if (form) {
|
55
|
+
const possibleKey = e?.key;
|
56
|
+
if (ignoreKeys.includes(possibleKey))
|
57
|
+
return;
|
58
|
+
form.data[name] = el()?.value || '';
|
59
|
+
fieldValidate(form, name, el(), validator);
|
60
|
+
}
|
59
61
|
}, 0); // Wait for the key to be updated in the input.
|
60
62
|
if (onkeydown)
|
61
63
|
return onkeydown(e);
|
@@ -72,9 +74,12 @@ export function fieldKeydown(form, name, el, validator, onkeydown) {
|
|
72
74
|
*/
|
73
75
|
export function fieldChange(form, name, el, validator, onchange) {
|
74
76
|
return async function (e) {
|
75
|
-
|
76
|
-
|
77
|
-
|
77
|
+
if (form) {
|
78
|
+
console.log('fieldChange', name, el()?.value, validator);
|
79
|
+
form.data[name] = el()?.value || '';
|
80
|
+
form.fields[name].tainted = true;
|
81
|
+
fieldValidate(form, name, el(), validator);
|
82
|
+
}
|
78
83
|
if (onchange)
|
79
84
|
return onchange(e);
|
80
85
|
};
|
package/dist/form/form.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import { Bodyguard, type BodyguardFormConfig, type BodyguardResult, type JSONLike } from "@auth70/bodyguard";
|
1
|
+
import { Bodyguard, type BodyguardFormConfig, type BodyguardResult, type GenericIssue, type JSONLike } from "@auth70/bodyguard";
|
2
2
|
import type { RequestEvent } from "@sveltejs/kit";
|
3
|
-
import type { ZodType, infer as Infer
|
3
|
+
import type { ZodType, infer as Infer } from "zod";
|
4
4
|
import type { Form, FormField, FormIssue, ZodTypes } from "./types.js";
|
5
5
|
export declare const bodyguard: Bodyguard;
|
6
6
|
/**
|
@@ -22,7 +22,7 @@ export declare function loadForm<Z extends ZodTypes>(schema: Z, event: RequestEv
|
|
22
22
|
* @param {ZodIssue[]} issues - The issues to parse.
|
23
23
|
* @returns {FormIssue[]} - The parsed issues.
|
24
24
|
*/
|
25
|
-
export declare function parseFormIssues(issues:
|
25
|
+
export declare function parseFormIssues(issues: GenericIssue[]): FormIssue[];
|
26
26
|
/**
|
27
27
|
* Parse a form using a schema.
|
28
28
|
* @param {ZodType} schema - The schema to parse the form with.
|
@@ -49,4 +49,4 @@ export declare function getFromObjWithStringPath(obj: any, path: string): string
|
|
49
49
|
* @param {Form} form - The form to get the validators from.
|
50
50
|
* @returns {Record<keyof Infer<T>, (value: any) => boolean>} - The validators for each field.
|
51
51
|
*/
|
52
|
-
export declare function getIndividualValidators<T extends ZodTypes>(form
|
52
|
+
export declare function getIndividualValidators<T extends ZodTypes>(form?: Form<T>): Record<keyof Infer<T>, (value: any) => boolean>;
|
package/dist/form/form.js
CHANGED
@@ -246,6 +246,8 @@ export function arrayPathToStringPath(path) {
|
|
246
246
|
* @returns {string | number | Date | boolean | object | undefined} - The value from the object.
|
247
247
|
*/
|
248
248
|
export function getFromObjWithStringPath(obj, path) {
|
249
|
+
if (!obj)
|
250
|
+
return undefined;
|
249
251
|
const parts = path.split('.');
|
250
252
|
let current = obj;
|
251
253
|
for (let i = 0; i < parts.length; i++) {
|
@@ -301,6 +303,8 @@ export function getFromObjWithStringPath(obj, path) {
|
|
301
303
|
* @returns {Record<keyof Infer<T>, (value: any) => boolean>} - The validators for each field.
|
302
304
|
*/
|
303
305
|
export function getIndividualValidators(form) {
|
306
|
+
if (!form)
|
307
|
+
return {};
|
304
308
|
const schema = form.schema;
|
305
309
|
const fields = form.fields;
|
306
310
|
if (!schema || !fields) {
|
package/dist/form/types.d.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import type { GenericIssue } from "@auth70/bodyguard";
|
1
2
|
import type { ZodType, infer as Infer, ZodTuple, ZodBigInt, ZodNaN, ZodSet, ZodVoid, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodRecord, ZodMap, ZodDiscriminatedUnion, ZodUnion, ZodIntersection, ZodNativeEnum, ZodEnum, ZodFunction, ZodLazy, ZodLiteral, ZodCatch, ZodPipeline, ZodBranded, ZodPromise, ZodArray, ZodNullable, ZodOptional, ZodEffects, ZodObject, ZodDefault, ZodString, ZodNumber, ZodBoolean, ZodDate } from "zod";
|
2
3
|
export type Autocomplete = 'on' | 'off' | AutocompleteSingleToken | AutocompleteBilling | AutocompleteShipping;
|
3
4
|
export type AutocompleteBilling = 'billing' | `billing ${AutocompleteSingleToken}`;
|
@@ -12,14 +13,8 @@ export type ADTs = ZodUnion<readonly [ZodTypes, ...ZodTypes[]]> | ZodDiscriminat
|
|
12
13
|
}>[]> | ZodIntersection<ZodTypes, ZodTypes> | ZodNativeEnum<any> | ZodEnum<any>;
|
13
14
|
export type ZodTypes = Modifiers | Primitives | ListCollections | KVCollections | ADTs | ZodFunction<any, ZodTypes> | ZodLazy<ZodTypes> | ZodLiteral<any> | ZodEffects<any, any> | ZodCatch<ZodTypes> | ZodPromise<ZodTypes> | ZodBranded<ZodTypes, any> | ZodPipeline<ZodTypes, ZodTypes>;
|
14
15
|
export type ZTypeName<T extends ZodTypes> = T["_def"]["typeName"];
|
15
|
-
export type FormIssue = {
|
16
|
+
export type FormIssue = GenericIssue & {
|
16
17
|
name: string;
|
17
|
-
code: string;
|
18
|
-
message?: string;
|
19
|
-
minimum?: number;
|
20
|
-
maximum?: number;
|
21
|
-
exact?: boolean;
|
22
|
-
validation?: string;
|
23
18
|
};
|
24
19
|
export type Form<T extends ZodType<any, any>> = {
|
25
20
|
/** Whether the form is valid. */
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "lutra",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.13",
|
4
4
|
"scripts": {
|
5
5
|
"dev": "vite dev --host 0.0.0.0",
|
6
6
|
"props": "node read_props.js",
|
@@ -90,7 +90,7 @@
|
|
90
90
|
"types": "./dist/index.d.ts",
|
91
91
|
"type": "module",
|
92
92
|
"dependencies": {
|
93
|
-
"@auth70/bodyguard": "^1.
|
93
|
+
"@auth70/bodyguard": "^1.6.2",
|
94
94
|
"@auth70/zodex-esm": "^0.7.3",
|
95
95
|
"zod": "^3.22.4"
|
96
96
|
}
|