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.
@@ -17,14 +17,15 @@ let {
17
17
  section {
18
18
  container-type: inline-size;
19
19
  display: grid;
20
- grid-template-columns: repeat(calc(var(--dtcols) - 1), auto) 1fr;
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
- overflow: none;
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: color-mix(in srgb, var(--bg-subtle) calc(var(--dtc) * 100%), transparent);
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
- <button onclick={(e) => e.preventDefault()}>
11
- <Icon icon={Help} />
12
- </button>
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
- button {
16
+ a {
17
17
  border-radius: 50%;
18
+ color: var(--text);
19
+ height: 16px;
20
+ width: 16px;
21
+ display: inline-block;
18
22
  }
19
- button:focus,
20
- button:active {
21
- outline-offset: -3px;
23
+ a:focus,
24
+ a:active {
25
+ outline-offset: -2px;
22
26
  }
23
27
  </style>
@@ -17,19 +17,20 @@ let {
17
17
 
18
18
  <style>
19
19
  .Icon {
20
- display: inline-flex;
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, baseline);
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: inline-block;
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-items: center;
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.85rem, 12px);
62
- line-height: 1.4;
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
  }
@@ -16,6 +16,7 @@
16
16
  gap: 1.35em;
17
17
  background: color-mix(in srgb, var(--bg-subtle) calc(var(--fcc) * 100%), transparent);
18
18
  padding: calc(1.5em * var(--fcc)) calc(1.5em * var(--fcc));
19
+ grid-column: 1 / -1;
19
20
  }
20
21
  @container (max-width: 400px) {
21
22
  .FieldActions {
@@ -13,7 +13,7 @@
13
13
  container-type: inline-size;
14
14
  display: grid;
15
15
  gap: 1.5em;
16
- grid-template-columns: 1fr;
16
+ grid-template-columns: 250px 1fr;
17
17
  border-radius: var(--border-radius);
18
18
  }
19
19
  .FieldContainer.contained {
@@ -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
- children
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: 1fr;
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;
@@ -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.schema);
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.issues = issues;
24
- form.fields = fields;
23
+ if (form) {
24
+ form.issues = issues;
25
+ form.fields = fields;
26
+ }
25
27
  }
26
28
  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);
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(resultForm.issues, resultForm.fields);
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: Form<any>;
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;
@@ -1,5 +1,4 @@
1
- <script>import { getContext, unstate } from "svelte";
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.fields[name]);
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
- <Label {id} {label} tip={labelTip} />
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
- </div>
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. */
@@ -1,22 +1,87 @@
1
- <script>import FieldContent from "./FieldContent.svelte";
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
- help,
6
- options
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 id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
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 {id} {label} {labelTip}>
12
- <select>
13
- {#each options as option}
14
- {#if typeof option === "string"}
15
- <option value={option}>{option}</option>
16
- {:else}
17
- <option value={option.value}>{option.label}</option>
18
- {/if}
19
- {/each}
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
- label?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
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
- labelTip?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
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
- help?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
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
- options: string[] | {
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
- const possibleKey = e?.key;
55
- if (ignoreKeys.includes(possibleKey))
56
- return;
57
- form.data[name] = el()?.value || '';
58
- fieldValidate(form, name, el(), validator);
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
- form.data[name] = el()?.value || '';
76
- form.fields[name].tainted = true;
77
- fieldValidate(form, name, el(), validator);
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
  };
@@ -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, ZodIssue, ZodIssueBase } from "zod";
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: (ZodIssue & ZodIssueBase)[]): FormIssue[];
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: Form<T>): Record<keyof Infer<T>, (value: any) => boolean>;
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) {
@@ -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.11",
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.5.2",
93
+ "@auth70/bodyguard": "^1.6.2",
94
94
  "@auth70/zodex-esm": "^0.7.3",
95
95
  "zod": "^3.22.4"
96
96
  }