lutra 0.1.68 → 0.1.69

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 (72) hide show
  1. package/dist/components/AspectRatio.svelte +19 -9
  2. package/dist/components/AspectRatio.svelte.d.ts +2 -1
  3. package/dist/components/Avatar.svelte +5 -8
  4. package/dist/components/Close.svelte +24 -27
  5. package/dist/components/Close.svelte.d.ts +2 -0
  6. package/dist/components/ContextTip.svelte +3 -2
  7. package/dist/components/Dialog.svelte +38 -0
  8. package/dist/components/Icon.svelte +2 -2
  9. package/dist/components/IconButton.svelte +10 -22
  10. package/dist/components/Image.svelte +2 -2
  11. package/dist/components/Indicator.svelte +2 -1
  12. package/dist/components/Inset.svelte +13 -0
  13. package/dist/components/Layout.svelte +7 -3
  14. package/dist/components/Layout.svelte.d.ts +3 -2
  15. package/dist/components/MenuDropdown.svelte +12 -2
  16. package/dist/components/MenuItem.svelte +30 -14
  17. package/dist/components/MenuItem.svelte.d.ts +6 -0
  18. package/dist/components/Modal.svelte +36 -20
  19. package/dist/components/Popover.svelte +39 -12
  20. package/dist/components/TabbedContent.svelte +1 -1
  21. package/dist/components/TabbedContentItem.svelte +14 -0
  22. package/dist/components/TabbedContentItem.svelte.d.ts +4 -0
  23. package/dist/components/Table.svelte +69 -0
  24. package/dist/components/Table.svelte.d.ts +7 -0
  25. package/dist/components/Tabs.svelte +44 -36
  26. package/dist/components/Tag.svelte +53 -13
  27. package/dist/components/Tag.svelte.d.ts +4 -0
  28. package/dist/components/Theme.svelte +121 -94
  29. package/dist/components/Theme.svelte.d.ts +7 -6
  30. package/dist/components/Toast.svelte +11 -8
  31. package/dist/components/Tooltip.svelte +17 -10
  32. package/dist/css/1-props.css +64 -51
  33. package/dist/css/2-init.css +503 -0
  34. package/dist/css/{2-base.css → 3-base.css} +42 -131
  35. package/dist/css/{3-typo.css → 4-typo.css} +3 -1
  36. package/dist/css/lutra.css +7 -6
  37. package/dist/css/themes/DefaultTheme.css +16 -4
  38. package/dist/form/Button.svelte +20 -0
  39. package/dist/form/Button.svelte.d.ts +9 -0
  40. package/dist/form/Datepicker.svelte +13 -0
  41. package/dist/form/Datepicker.svelte.d.ts +3 -0
  42. package/dist/form/FieldContent.svelte +18 -9
  43. package/dist/form/FieldError.svelte +1 -1
  44. package/dist/form/Fieldset.svelte +19 -11
  45. package/dist/form/Form.svelte +137 -63
  46. package/dist/form/Form.svelte.d.ts +21 -0
  47. package/dist/form/FormActions.svelte +21 -3
  48. package/dist/form/FormActions.svelte.d.ts +3 -0
  49. package/dist/form/FormSection.svelte +22 -20
  50. package/dist/form/ImageUpload.svelte +50 -30
  51. package/dist/form/ImageUpload.svelte.d.ts +14 -0
  52. package/dist/form/Input.svelte +62 -30
  53. package/dist/form/Input.svelte.d.ts +0 -1
  54. package/dist/form/InputLength.svelte +5 -5
  55. package/dist/form/Label.svelte +6 -6
  56. package/dist/form/LogoUpload.svelte +24 -10
  57. package/dist/form/Select.svelte +23 -10
  58. package/dist/form/Select.svelte.d.ts +6 -6
  59. package/dist/form/Textarea.svelte +11 -1
  60. package/dist/form/client.svelte.js +0 -2
  61. package/dist/state/Persisted.svelte.d.ts +6 -0
  62. package/dist/state/Persisted.svelte.js +29 -0
  63. package/dist/state/theme.svelte.d.ts +7 -0
  64. package/dist/state/theme.svelte.js +14 -0
  65. package/dist/types.d.ts +6 -23
  66. package/dist/types.js +0 -17
  67. package/dist/util/color.js +2 -2
  68. package/package.json +5 -4
  69. package/dist/config.d.ts +0 -30
  70. package/dist/config.js +0 -18
  71. /package/dist/css/{4-layout.css → 5-layout.css} +0 -0
  72. /package/dist/css/{5-media.css → 6-media.css} +0 -0
@@ -16,13 +16,15 @@ small {
16
16
  line-height: var(--font-line-height-tight);
17
17
  }
18
18
 
19
- small.smaller {
19
+ small.smaller,
20
+ small small {
20
21
  font-size: var(--font-size-xs);
21
22
  }
22
23
 
23
24
  /* Form elements inherit base typography */
24
25
  input, textarea, select, button {
25
26
  font-family: var(--font-family);
27
+ font-size: inherit;
26
28
  line-height: var(--font-line-height);
27
29
  }
28
30
 
@@ -1,7 +1,8 @@
1
- @layer l-base, base, l-typo, typo, l-layout, layout, theme, l-media, media, misc;
1
+ @layer l-props, l-init, l-base, l-typo, typo, l-layout, l-media, l-default, theme, media, misc;
2
2
 
3
- @import "./1-props.css";
4
- @import "./2-base.css" layer(l-base);
5
- @import "./3-typo.css" layer(l-typo);
6
- @import "./4-layout.css" layer(l-layout);
7
- @import "./5-media.css" layer(l-media);
3
+ @import "./1-props.css" layer(l-props);
4
+ @import "./2-init.css" layer(l-init);
5
+ @import "./3-base.css" layer(l-base);
6
+ @import "./4-typo.css" layer(l-typo);
7
+ @import "./5-layout.css" layer(l-layout);
8
+ @import "./6-media.css" layer(l-media);
@@ -1,4 +1,6 @@
1
- @layer theme {
1
+ @layer l-props, l-init, l-base, l-typo, typo, l-layout, l-media, l-default, theme, media, misc;
2
+
3
+ @layer l-default {
2
4
  :root {
3
5
  --hue: 240deg;
4
6
  --chroma: 0.5;
@@ -172,7 +174,8 @@
172
174
 
173
175
  --field-label-color: var(--text-color-p);
174
176
 
175
- --form-background-actions: color-mix(in srgb, var(--theme-surface-interactive) 35%, transparent);
177
+ --form-background: var(--background-body);
178
+ --form-background-actions: color-mix(in oklch, var(--theme-surface-interactive) 35%, transparent);
176
179
 
177
180
  /**
178
181
  * Borders
@@ -201,12 +204,20 @@
201
204
  --link-color-hover: var(--button-submit-base-color-hover);
202
205
  --link-color-active: var(--button-submit-base-color-hover);
203
206
 
207
+ /**
208
+ * Status Colors
209
+ */
210
+
211
+ --status-default-color: light-dark(#111111, #e0e0e0);
212
+
204
213
  /**
205
214
  * Menus
206
215
  */
207
216
 
208
217
  --menu-background-color: var(--surface-background);
209
- --menu-background-color-hover: color-mix(in srgb, var(--theme-surface-interactive) 35%, transparent);
218
+ --menu-background-color-hover: color-mix(in oklch, var(--theme-surface-interactive) 35%, transparent);
219
+ --menu-text-color: var(--text-color-p);
220
+ --menu-text-color-subtle: var(--text-color-p-subtle);
210
221
  --menu-border-color: var(--border-color-subtle);
211
222
  --menu-border-size: var(--border-size-thin);
212
223
  --menu-border-style: var(--border-style);
@@ -221,7 +232,7 @@
221
232
  --table-header-color: var(--text-color-heading);
222
233
  --table-row-background: transparent;
223
234
  --table-row-background-even: transparent;
224
- --table-row-background-hover: color-mix(in srgb, var(--theme-surface-interactive) 60%, transparent);
235
+ --table-row-background-hover: color-mix(in oklch, var(--theme-surface-interactive) 60%, transparent);
225
236
  --table-cell-color: var(--text-color-p);
226
237
 
227
238
  /**
@@ -270,6 +281,7 @@
270
281
  --toast-border: var(--surface-border);
271
282
  --toast-border-radius: var(--surface-border-radius);
272
283
  --toast-shadow: var(--surface-shadow);
284
+ --toast-title-color: var(--text-color-heading);
273
285
 
274
286
  /**
275
287
  * Scrim/Backdrop
@@ -3,6 +3,17 @@
3
3
  import { getContext, type Component, type Snippet } from 'svelte';
4
4
  import type { LutraForm } from './types.js';
5
5
 
6
+ /**
7
+ * @description
8
+ * A polymorphic form button that renders as a `<button>` or `<a>` depending on whether `href` is set.
9
+ * Automatically disables itself while its parent form is in a loading state.
10
+ * Inherits the alignment context set by a parent `FormActions` component.
11
+ *
12
+ * @example
13
+ * <Button type="submit" kind="default">Save</Button>
14
+ * <Button href="/cancel" kind="secondary">Cancel</Button>
15
+ * <Button kind="warn" icon={TrashIcon}>Delete</Button>
16
+ */
6
17
  let {
7
18
  href,
8
19
  type = 'button',
@@ -14,14 +25,23 @@
14
25
  onclick,
15
26
  children,
16
27
  }: {
28
+ /** When set, renders as an `<a>` link instead of a `<button>`. */
17
29
  href?: string;
30
+ /** The button type attribute. */
18
31
  type?: 'button' | 'submit' | 'reset';
32
+ /** Visual style variant. */
19
33
  kind?: 'default' | 'outlined' | 'secondary' | 'warn';
34
+ /** Size variant. */
20
35
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
36
+ /** Additional CSS class names. */
21
37
  class?: string;
38
+ /** Whether the button is disabled. Also disabled automatically when the parent form is loading. */
22
39
  disabled?: boolean;
40
+ /** Icon component or string to render before the label. */
23
41
  icon?: string | Component;
42
+ /** Click event handler. */
24
43
  onclick?: (event: MouseEvent) => void;
44
+ /** Button label content. */
25
45
  children: Snippet;
26
46
  } = $props();
27
47
 
@@ -1,13 +1,22 @@
1
1
  import { type Component, type Snippet } from 'svelte';
2
2
  type $$ComponentProps = {
3
+ /** When set, renders as an `<a>` link instead of a `<button>`. */
3
4
  href?: string;
5
+ /** The button type attribute. */
4
6
  type?: 'button' | 'submit' | 'reset';
7
+ /** Visual style variant. */
5
8
  kind?: 'default' | 'outlined' | 'secondary' | 'warn';
9
+ /** Size variant. */
6
10
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
11
+ /** Additional CSS class names. */
7
12
  class?: string;
13
+ /** Whether the button is disabled. Also disabled automatically when the parent form is loading. */
8
14
  disabled?: boolean;
15
+ /** Icon component or string to render before the label. */
9
16
  icon?: string | Component;
17
+ /** Click event handler. */
10
18
  onclick?: (event: MouseEvent) => void;
19
+ /** Button label content. */
11
20
  children: Snippet;
12
21
  };
13
22
  declare const Button: Component<$$ComponentProps, {}, "">;
@@ -4,11 +4,24 @@
4
4
  import UiContent from "../components/UIContent.svelte";
5
5
  import { getLocaleFirstDayOfWeek } from "../util/locale.js";
6
6
 
7
+ /**
8
+ * @description
9
+ * A custom date/time range picker with year, month and day grids.
10
+ * Renders start and end `datetime-local` inputs alongside visual selection components
11
+ * for year, month and day. Respects the user's locale for first day of week.
12
+ *
13
+ * @example
14
+ * <Datepicker />
15
+ * <Datepicker range={{ min: new Date('2024-01-01'), max: new Date('2025-12-31') }} />
16
+ */
7
17
  let {
8
18
  range
9
19
  }: {
20
+ /** Optional date range constraints. */
10
21
  range?: {
22
+ /** Minimum selectable date. */
11
23
  min?: Date;
24
+ /** Maximum selectable date. */
12
25
  max?: Date;
13
26
  }
14
27
  } = $props();
@@ -1,6 +1,9 @@
1
1
  type $$ComponentProps = {
2
+ /** Optional date range constraints. */
2
3
  range?: {
4
+ /** Minimum selectable date. */
3
5
  min?: Date;
6
+ /** Maximum selectable date. */
4
7
  max?: Date;
5
8
  };
6
9
  };
@@ -7,7 +7,14 @@
7
7
 
8
8
  /**
9
9
  * @description
10
- * This component is used internally by the Input, Select, etc. components to render the label and the input field in a consistent way.
10
+ * Internal wrapper used by Input, Select and Textarea to render the label, field
11
+ * border, prefix/suffix decorations, help text and error messages in a consistent layout.
12
+ *
13
+ * @cssprop --field-background -- Background color of the field container.
14
+ * @cssprop --field-border-size -- Border width of the field container.
15
+ * @cssprop --field-border-style -- Border style of the field container.
16
+ * @cssprop --field-border-color -- Border color of the field container.
17
+ * @cssprop --field-border-radius -- Border radius of the field container.
11
18
  */
12
19
  let {
13
20
  id,
@@ -62,8 +69,7 @@
62
69
  {#if contained}
63
70
  <div
64
71
  class="Field"
65
- class:hasPrefix={prefix}
66
- class:hasSuffix={suffix}
72
+
67
73
  class:invalid={field?.tainted && issue?.code}
68
74
  >
69
75
  {#if prefix}
@@ -105,13 +111,15 @@
105
111
  .FieldContentContainer {
106
112
  display: flex;
107
113
  flex-direction: column;
108
- gap: var(--form-field-gap, var(--space-xs));
114
+ gap: var(--form-field-inside-gap);
115
+ min-width: 0;
109
116
  }
110
117
  .FieldContent {
111
118
  display: flex;
112
119
  gap: var(--form-label-gap, var(--space-xs));
113
120
  flex-direction: column;
114
121
  font-size: 1em;
122
+ min-width: 0;
115
123
  }
116
124
  .FieldContent.row {
117
125
  flex-direction: row-reverse;
@@ -124,6 +132,7 @@
124
132
  border: var(--field-border-size) var(--field-border-style) var(--field-border-color);
125
133
  border-radius: var(--field-border-radius);
126
134
  display: flex;
135
+ min-width: 0;
127
136
  }
128
137
  .Field.invalid {
129
138
  border-color: var(--field-border-color-invalid);
@@ -138,7 +147,7 @@
138
147
  padding-inline: var(--form-field-gap, var(--space-md));
139
148
  font-size: 1em;
140
149
  text-box: trim-both cap alphabetic;
141
- color: var(--text-subtle);
150
+ color: var(--text-color-p-subtle);
142
151
  }
143
152
  .Suffix {
144
153
  padding-inline-start: 0;
@@ -158,16 +167,16 @@
158
167
  outline-color: var(--focus-ring-color-invalid);
159
168
  border-color: var(--focus-ring-color-invalid);
160
169
  }
161
- .Field.hasPrefix :global(input) {
170
+ .Field:has(.Prefix) :global(input) {
162
171
  padding-inline-start: var(--space-xxs);
163
172
  }
164
173
  .Field :global(button) {
165
- margin-right: var(--space-xxs);
174
+ margin-inline-end: var(--space-xxs);
166
175
  }
167
176
  .Field :global(button:focus-visible) {
168
- outline: var(--focus-outline);
177
+ outline: var(--focus-ring);
169
178
  outline-offset: 3px;
170
- border-radius: calc(var(--field-radius) - 2px);
179
+ border-radius: calc(var(--field-border-radius) - 2px);
171
180
  }
172
181
  .Help {
173
182
  font-size: min(11px, var(--font-size-075));
@@ -16,7 +16,7 @@
16
16
 
17
17
  <style>
18
18
  p.Error {
19
- color: var(--field-color-danger, light-dark(red, red));
19
+ color: var(--field-color-danger);
20
20
  font-size: max(0.85em, 11px);
21
21
  font-weight: var(--font-weight-normal);
22
22
  text-wrap: balance;
@@ -3,14 +3,19 @@
3
3
 
4
4
  /**
5
5
  * @description
6
- * A fieldset is a group of related form elements that can be disabled or enabled.
6
+ * A fieldset is a group of related form elements. Supports responsive column
7
+ * layouts via container queries, optional contained styling with padding,
8
+ * and rounded borders.
9
+ *
10
+ * @cssprop --form-field-gap -- Gap between fields within the fieldset.
11
+ * @cssprop --form-padding-block -- Block padding when contained.
12
+ * @cssprop --form-padding-inline -- Inline padding when contained.
13
+ * @cssprop --field-label-font-size -- Font size for the legend.
14
+ *
7
15
  * @example
8
- * <script>
9
- * import Input from "lutra/Input.svelte";
10
- * <\/script>
11
- * <Fieldset legend="Personal Information">
12
- * <Input label="First Name" />
13
- * <Input label="Last Name" />
16
+ * <Fieldset legend="Personal Information" columns={2}>
17
+ * <Input label="First Name" name="first" />
18
+ * <Input label="Last Name" name="last" />
14
19
  * </Fieldset>
15
20
  */
16
21
 
@@ -76,16 +81,19 @@
76
81
  }
77
82
  fieldset {
78
83
  display: grid;
84
+ border: none;
85
+ margin: 0;
86
+ padding: 0;
79
87
  width: var(--width, fit-content);
80
88
  grid-template-columns: repeat(var(--lg-cols), 1fr);
81
- gap: var(--gap, 1.5em);
89
+ gap: var(--form-field-gap);
82
90
  }
83
91
  legend {
84
- font-weight: 500;
85
- font-size: var(--font-size, 1em);
92
+ font-weight: var(--font-weight-normal);
93
+ font-size: var(--field-label-font-size);
86
94
  }
87
95
  fieldset.contained {
88
- padding: 1.5em;
96
+ padding: var(--form-padding-block) var(--form-padding-inline);
89
97
  }
90
98
  fieldset.fullWidth {
91
99
  width: 100%;
@@ -12,6 +12,30 @@
12
12
  import { getIndividualValidators, parseFormIssues } from "./form.js";
13
13
  import { useForm } from "./client.svelte.js";
14
14
 
15
+ /**
16
+ * @description
17
+ * The root form component. Wraps SvelteKit's `enhance` for progressive enhancement,
18
+ * integrates with Zod-based validation via Bodyguard, and provides layout token
19
+ * overrides for all child form sections, fields and actions.
20
+ *
21
+ * @cssprop --form-action-gap -- Gap between action buttons.
22
+ * @cssprop --form-field-gap -- Gap between fields in a section.
23
+ * @cssprop --form-label-gap -- Gap between label and input.
24
+ * @cssprop --form-title-gap -- Gap within a section title.
25
+ * @cssprop --form-section-gap -- Gap between sections.
26
+ * @cssprop --form-padding-block -- Block padding for contained sections.
27
+ * @cssprop --form-padding-inline -- Inline padding for contained sections.
28
+ *
29
+ * @example
30
+ * <Form form={data.form} action="?/save">
31
+ * <FormSection title="Profile">
32
+ * <Input name="name" label="Name" />
33
+ * </FormSection>
34
+ * <FormActions>
35
+ * <Button type="submit">Save</Button>
36
+ * </FormActions>
37
+ * </Form>
38
+ */
15
39
  let {
16
40
  name = 'form',
17
41
  form: _form,
@@ -35,15 +59,25 @@
35
59
  paddingBlock,
36
60
  paddingInline,
37
61
  }: {
62
+ /** The key under which the form data is returned in the action result. */
38
63
  name?: string;
64
+ /** The Lutra form object from the server load function. */
39
65
  form?: LutraForm<any>;
66
+ /** Bindable reference to the underlying `<form>` element. */
40
67
  formEl?: HTMLFormElement | null;
68
+ /** The form action URL. */
41
69
  action?: string;
70
+ /** The form encoding type. */
42
71
  enctype?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain";
72
+ /** The HTTP method. */
43
73
  method?: "GET" | "POST";
74
+ /** Callback invoked before the form is submitted. Can cancel submission. */
44
75
  beforesubmit?: BeforeSubmitFn;
76
+ /** Whether the form should span the full width. */
45
77
  fullWidth?: boolean;
78
+ /** Whether to reset fields after a successful update. */
46
79
  resetOnUpdate?: boolean;
80
+ /** Callback invoked with the action result after submission. */
47
81
  onresult?: (args: {
48
82
  formData: FormData;
49
83
  formElement: HTMLFormElement;
@@ -54,16 +88,27 @@
54
88
  invalidateAll?: boolean | undefined;
55
89
  } | undefined) => void;
56
90
  }) => void;
91
+ /** Form content. */
57
92
  children: Snippet;
93
+ /** Whether the form sections should be contained (bordered). */
58
94
  contained?: boolean;
95
+ /** General spacing override. */
59
96
  spacing?: string;
97
+ /** Override for `--form-action-gap`. */
60
98
  actionGap?: string;
99
+ /** Override for `--form-field-gap`. */
61
100
  fieldGap?: string;
101
+ /** Override for `--form-label-gap`. */
62
102
  labelGap?: string;
103
+ /** Override for `--form-title-gap`. */
63
104
  titleGap?: string;
105
+ /** Override for `--form-section-gap`. */
64
106
  sectionGap?: string;
107
+ /** Shorthand override for both `--form-padding-block` and `--form-padding-inline`. */
65
108
  padding?: string;
109
+ /** Override for `--form-padding-block`. */
66
110
  paddingBlock?: string;
111
+ /** Override for `--form-padding-inline`. */
67
112
  paddingInline?: string;
68
113
  } = $props();
69
114
 
@@ -85,7 +130,6 @@
85
130
  const bodyguard = new Bodyguard();
86
131
 
87
132
  function setFormIssuesAndFields(issues: any, fields: any) {
88
- console.log('setFormIssuesAndFields', issues, fields)
89
133
  if(form) {
90
134
  form.issues = issues;
91
135
  form.fields = fields;
@@ -126,6 +170,7 @@
126
170
  };
127
171
 
128
172
  const style = `
173
+ --fcc: ${contained ? 1 : 0};
129
174
  ${actionGap ? `--form-action-gap: ${actionGap};` : ''}
130
175
  ${fieldGap ? `--form-field-gap: ${fieldGap};` : ''}
131
176
  ${labelGap ? `--form-label-gap: ${labelGap};` : ''}
@@ -146,75 +191,104 @@
146
191
  </script>
147
192
 
148
193
  <UiContent>
149
- <form
150
- {method}
151
- {action}
152
- {enctype}
153
- style={style}
154
- bind:this={formEl}
155
- onchange={validate}
156
- use:enhance={async ({ formElement, formData, action, cancel, submitter }) => {
157
- // `formElement` is this `<form>` element
158
- // `formData` is its `FormData` object that's about to be submitted
159
- // `action` is the URL to which the form is posted
160
- // calling `cancel()` will prevent the submission
161
- // `submitter` is the `HTMLElement` that caused the form to be submitted
162
- if(form) form.state = 'loading';
163
- //await Promise.resolve(beforesubmit(form));
164
- if(beforesubmit) await Promise.resolve(beforesubmit({ form: formElement, data: formData, cancel: () => {
165
- if(form) form.state = 'error';
166
- cancel();
167
- }}));
168
- for(const { id, fn } of beforesubmitFunctions) {
169
- await Promise.resolve(fn({ form: formElement, data: formData, cancel: () => {
194
+ <div class="FormContainer">
195
+ <form
196
+ {method}
197
+ {action}
198
+ {enctype}
199
+ style={style}
200
+ bind:this={formEl}
201
+ onchange={validate}
202
+ use:enhance={async ({ formElement, formData, action, cancel, submitter }) => {
203
+ // `formElement` is this `<form>` element
204
+ // `formData` is its `FormData` object that's about to be submitted
205
+ // `action` is the URL to which the form is posted
206
+ // calling `cancel()` will prevent the submission
207
+ // `submitter` is the `HTMLElement` that caused the form to be submitted
208
+ if(form) form.state = 'loading';
209
+ //await Promise.resolve(beforesubmit(form));
210
+ if(beforesubmit) await Promise.resolve(beforesubmit({ form: formElement, data: formData, cancel: () => {
170
211
  if(form) form.state = 'error';
171
212
  cancel();
172
213
  }}));
173
- }
174
- console.log('form state', form?.state);
175
- return async (opts) => {
176
- const { result, update } = opts;
177
- // `result` is an `ActionResult` object
178
- // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
179
- console.log('form result', opts);
180
- if(onresult) {
181
- console.log('calling onresult', opts);
182
- onresult(opts);
214
+ for(const { id, fn } of beforesubmitFunctions) {
215
+ await Promise.resolve(fn({ form: formElement, data: formData, cancel: () => {
216
+ if(form) form.state = 'error';
217
+ cancel();
218
+ }}));
183
219
  }
184
- const resultForm = result.type !== "redirect" && result.type !== "error" ? result?.data![name] : null;
185
- console.log('resultForm', resultForm, form);
186
- if(result.type === "success") {
187
- if(resultForm && form) {
188
- form.valid = Object.assign({ valid: false }, resultForm).valid ?? false;
220
+ return async (opts) => {
221
+ const { result, update } = opts;
222
+ if(onresult) {
223
+ onresult(opts);
189
224
  }
190
- if(form) form.state = 'success';
191
- console.log('form state', form?.state, resetOnUpdate, opts);
192
- update({ reset: !!resetOnUpdate });
193
- } else if(result.type === "failure") {
194
- console.log('FAILURE', opts);
195
- if(resultForm && form) {
196
- console.log('setting form issues and fields')
197
- setFormIssuesAndFields(
198
- Object.assign({ issues: [] }, resultForm).issues, // Have to assign to avoid type error as we cant use `as` here
199
- Object.assign({ fields: [] }, resultForm).fields,
200
- );
225
+ const resultForm = result.type !== "redirect" && result.type !== "error" ? result?.data![name] : null;
226
+ if(result.type === "success") {
227
+ if(resultForm && form) {
228
+ form.valid = Object.assign({ valid: false }, resultForm).valid ?? false;
229
+ }
230
+ if(form) form.state = 'success';
231
+ update({ reset: !!resetOnUpdate });
232
+ } else if(result.type === "failure") {
233
+ if(resultForm && form) {
234
+ setFormIssuesAndFields(
235
+ Object.assign({ issues: [] }, resultForm).issues, // Have to assign to avoid type error as we cant use `as` here
236
+ Object.assign({ fields: [] }, resultForm).fields,
237
+ );
238
+ }
239
+ if(form) form.state = 'error';
240
+ } else if(result.type === "error") {
241
+ console.error('[lutra] Error from form enhance call', result.error, opts);
242
+ if(form) form.state = 'error';
243
+ } else if(result.type === "redirect") {
244
+ if(form) form.state = 'success';
245
+ await goto(result.location);
201
246
  }
202
- if(form) form.state = 'error';
203
- } else if(result.type === "error") {
204
- console.error('[lutra] Error from form enhance call', result.error, opts);
205
- if(form) form.state = 'error';
206
- } else if(result.type === "redirect") {
207
- console.log('redirect', opts);
208
- //window.location.href = result.location;
209
- if(form) form.state = 'success';
210
- await goto(result.location);
211
- }
212
- };
213
- }}
214
- >
215
- {@render children()}
216
- </form>
247
+ };
248
+ }}
249
+ >
250
+ {@render children()}
251
+ </form>
252
+ </div>
217
253
  </UiContent>
218
254
 
219
255
  <style>
256
+ .FormContainer {
257
+ container-type: inline-size;
258
+ }
259
+ form {
260
+ display: grid;
261
+ grid-template-columns: fit-content(20%) 1fr;
262
+ border: calc(var(--fcc) * var(--form-border-size)) var(--form-border-style) var(--form-border-color);
263
+ border-radius: calc(var(--fcc) * var(--form-border-radius));
264
+ --form-padding-block: var(--space-md);
265
+ --form-padding-inline: var(--space-md);
266
+ --form-section-gap: var(--space-md);
267
+ }
268
+ @container (min-width: 600px) {
269
+ form {
270
+ --form-padding-block: var(--space-lg);
271
+ --form-padding-inline: var(--space-lg);
272
+ --form-section-gap: var(--space-xl);
273
+ }
274
+ }
275
+ @container (min-width: 1000px) {
276
+ form {
277
+ --form-padding-block: var(--space-xl);
278
+ --form-padding-inline: var(--space-xl);
279
+ --form-section-gap: var(--space-xxl);
280
+ }
281
+ }
282
+ @container (min-width: 1400px) {
283
+ form {
284
+ --form-padding-block: var(--space-xxl);
285
+ --form-padding-inline: var(--space-xxl);
286
+ --form-section-gap: var(--space-xxxl);
287
+ }
288
+ }
289
+ @container (max-width: 800px) {
290
+ form {
291
+ grid-template-columns: 1fr;
292
+ }
293
+ }
220
294
  </style>
@@ -2,15 +2,25 @@ import type { ActionResult } from "@sveltejs/kit";
2
2
  import type { Snippet } from "svelte";
3
3
  import type { BeforeSubmitFn, LutraForm } from "./types.js";
4
4
  type $$ComponentProps = {
5
+ /** The key under which the form data is returned in the action result. */
5
6
  name?: string;
7
+ /** The Lutra form object from the server load function. */
6
8
  form?: LutraForm<any>;
9
+ /** Bindable reference to the underlying `<form>` element. */
7
10
  formEl?: HTMLFormElement | null;
11
+ /** The form action URL. */
8
12
  action?: string;
13
+ /** The form encoding type. */
9
14
  enctype?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain";
15
+ /** The HTTP method. */
10
16
  method?: "GET" | "POST";
17
+ /** Callback invoked before the form is submitted. Can cancel submission. */
11
18
  beforesubmit?: BeforeSubmitFn;
19
+ /** Whether the form should span the full width. */
12
20
  fullWidth?: boolean;
21
+ /** Whether to reset fields after a successful update. */
13
22
  resetOnUpdate?: boolean;
23
+ /** Callback invoked with the action result after submission. */
14
24
  onresult?: (args: {
15
25
  formData: FormData;
16
26
  formElement: HTMLFormElement;
@@ -21,16 +31,27 @@ type $$ComponentProps = {
21
31
  invalidateAll?: boolean | undefined;
22
32
  } | undefined) => void;
23
33
  }) => void;
34
+ /** Form content. */
24
35
  children: Snippet;
36
+ /** Whether the form sections should be contained (bordered). */
25
37
  contained?: boolean;
38
+ /** General spacing override. */
26
39
  spacing?: string;
40
+ /** Override for `--form-action-gap`. */
27
41
  actionGap?: string;
42
+ /** Override for `--form-field-gap`. */
28
43
  fieldGap?: string;
44
+ /** Override for `--form-label-gap`. */
29
45
  labelGap?: string;
46
+ /** Override for `--form-title-gap`. */
30
47
  titleGap?: string;
48
+ /** Override for `--form-section-gap`. */
31
49
  sectionGap?: string;
50
+ /** Shorthand override for both `--form-padding-block` and `--form-padding-inline`. */
32
51
  padding?: string;
52
+ /** Override for `--form-padding-block`. */
33
53
  paddingBlock?: string;
54
+ /** Override for `--form-padding-inline`. */
34
55
  paddingInline?: string;
35
56
  };
36
57
  declare const Form: import("svelte").Component<$$ComponentProps, {}, "formEl">;