lutra 0.1.68 → 0.1.70

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 (84) 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/DataList.svelte +111 -0
  8. package/dist/components/DataList.svelte.d.ts +10 -0
  9. package/dist/components/DataListTypes.d.ts +14 -0
  10. package/dist/components/DataListTypes.js +1 -0
  11. package/dist/components/Dialog.svelte +38 -0
  12. package/dist/components/Icon.svelte +2 -2
  13. package/dist/components/IconButton.svelte +10 -22
  14. package/dist/components/Image.svelte +2 -2
  15. package/dist/components/Indicator.svelte +2 -1
  16. package/dist/components/Inset.svelte +13 -0
  17. package/dist/components/Layout.svelte +7 -3
  18. package/dist/components/Layout.svelte.d.ts +3 -2
  19. package/dist/components/MenuDropdown.svelte +12 -2
  20. package/dist/components/MenuItem.svelte +30 -14
  21. package/dist/components/MenuItem.svelte.d.ts +6 -0
  22. package/dist/components/Modal.svelte +36 -20
  23. package/dist/components/Popover.svelte +43 -13
  24. package/dist/components/TabbedContent.svelte +1 -1
  25. package/dist/components/TabbedContentItem.svelte +14 -0
  26. package/dist/components/TabbedContentItem.svelte.d.ts +4 -0
  27. package/dist/components/Table.svelte +69 -0
  28. package/dist/components/Table.svelte.d.ts +7 -0
  29. package/dist/components/Tabs.svelte +44 -36
  30. package/dist/components/Tag.svelte +53 -13
  31. package/dist/components/Tag.svelte.d.ts +4 -0
  32. package/dist/components/Theme.svelte +121 -94
  33. package/dist/components/Theme.svelte.d.ts +7 -6
  34. package/dist/components/Toast.svelte +11 -8
  35. package/dist/components/Tooltip.svelte +17 -10
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.js +2 -0
  38. package/dist/css/1-props.css +197 -163
  39. package/dist/css/2-init.css +519 -0
  40. package/dist/css/{2-base.css → 3-base.css} +42 -131
  41. package/dist/css/{3-typo.css → 4-typo.css} +3 -1
  42. package/dist/css/lutra.css +7 -6
  43. package/dist/css/themes/DefaultTheme.css +26 -4
  44. package/dist/form/Button.svelte +20 -0
  45. package/dist/form/Button.svelte.d.ts +9 -0
  46. package/dist/form/Datepicker.svelte +13 -0
  47. package/dist/form/Datepicker.svelte.d.ts +3 -0
  48. package/dist/form/FieldContent.svelte +20 -11
  49. package/dist/form/FieldError.svelte +1 -1
  50. package/dist/form/FieldGroup.svelte +84 -0
  51. package/dist/form/FieldGroup.svelte.d.ts +20 -0
  52. package/dist/form/Fieldset.svelte +19 -11
  53. package/dist/form/Form.svelte +137 -63
  54. package/dist/form/Form.svelte.d.ts +21 -0
  55. package/dist/form/FormActions.svelte +21 -3
  56. package/dist/form/FormActions.svelte.d.ts +3 -0
  57. package/dist/form/FormSection.svelte +22 -20
  58. package/dist/form/ImageUpload.svelte +50 -30
  59. package/dist/form/ImageUpload.svelte.d.ts +14 -0
  60. package/dist/form/Input.svelte +62 -30
  61. package/dist/form/Input.svelte.d.ts +0 -1
  62. package/dist/form/InputLength.svelte +5 -5
  63. package/dist/form/Label.svelte +6 -6
  64. package/dist/form/LogoUpload.svelte +24 -10
  65. package/dist/form/Select.svelte +23 -10
  66. package/dist/form/Select.svelte.d.ts +6 -6
  67. package/dist/form/Textarea.svelte +11 -1
  68. package/dist/form/Toggle.svelte +162 -0
  69. package/dist/form/Toggle.svelte.d.ts +31 -17
  70. package/dist/form/client.svelte.js +0 -2
  71. package/dist/form/index.d.ts +1 -0
  72. package/dist/form/index.js +1 -0
  73. package/dist/state/Persisted.svelte.d.ts +6 -0
  74. package/dist/state/Persisted.svelte.js +29 -0
  75. package/dist/state/theme.svelte.d.ts +7 -0
  76. package/dist/state/theme.svelte.js +14 -0
  77. package/dist/types.d.ts +6 -23
  78. package/dist/types.js +0 -17
  79. package/dist/util/color.js +2 -2
  80. package/package.json +5 -4
  81. package/dist/config.d.ts +0 -30
  82. package/dist/config.js +0 -18
  83. /package/dist/css/{4-layout.css → 5-layout.css} +0 -0
  84. /package/dist/css/{5-media.css → 6-media.css} +0 -0
@@ -1,18 +1,32 @@
1
1
  import { type Snippet } from 'svelte';
2
2
  type $$ComponentProps = {
3
+ /** Current image source URL. */
3
4
  src?: string | null;
5
+ /** The presigned URL to upload the image to. */
4
6
  uploadUrl: string;
7
+ /** The form field name for the resulting URL value. */
5
8
  name: string;
9
+ /** HTML id for the file input. */
6
10
  id?: string;
11
+ /** Title used in the alt text of the preview image. */
7
12
  title?: string;
13
+ /** Label for the upload button. */
8
14
  button: string;
15
+ /** Maximum file size in bytes before compression is applied. */
9
16
  maxSize?: number;
17
+ /** Maximum compressed file size in megabytes. */
10
18
  compressSize?: number;
19
+ /** Maximum compressed image width in pixels. */
11
20
  compressMaxWidth?: number;
21
+ /** Accepted MIME types for the file input. */
12
22
  accept?: string;
23
+ /** Whether a remove button is shown. */
13
24
  removable?: boolean;
25
+ /** Display shape of the image preview. */
14
26
  shape?: 'square' | 'circle' | 'banner';
27
+ /** Callback invoked with the selected file before upload. */
15
28
  onFile?: (file: File) => void;
29
+ /** Optional additional content rendered below the actions. */
16
30
  children?: Snippet;
17
31
  };
18
32
  declare const ImageUpload: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -14,9 +14,19 @@
14
14
  import { ZodType } from "zod";
15
15
  import FieldContent from "./FieldContent.svelte";
16
16
  import { on } from "svelte/events";
17
- import { fade } from "svelte/transition";
18
- import Theme from "../components/Theme.svelte";
19
17
 
18
+ /**
19
+ * @description
20
+ * A versatile input component supporting all standard HTML input types. Integrates
21
+ * with the Lutra form system for validation, error display and field state tracking.
22
+ * Supports prefix/suffix content, copy-to-clipboard, password visibility toggle,
23
+ * range slider with value tooltip, and auto-growing file inputs.
24
+ *
25
+ * @example
26
+ * <Input name="email" type="email" label="Email" placeholder="you@example.com" />
27
+ * <Input name="age" type="range" min={0} max={100} unit="years" label="Age" />
28
+ * <Input name="token" type="password" viewable copyable label="API Token" />
29
+ */
20
30
  let {
21
31
  alt,
22
32
  autofocus,
@@ -116,7 +126,6 @@
116
126
  maxlength?: number;
117
127
  /** The minimum number of characters (as UTF-16 code units) the user can enter into the input. Valid for text, search, url, tel, email, and password. */
118
128
  minlength?: number;
119
- /** Allow multiple f
120
129
  /** The maximum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
121
130
  max?: number
122
131
  /** The minimum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
@@ -352,17 +361,12 @@
352
361
  {/if}
353
362
  <div class="RangeInput">
354
363
  {@render input()}
355
- {#if focused && min?.toString().length && max?.toString().length}
356
- <Theme theme="invert">
357
- <div
358
- class="RangeValue"
359
- in:fade={{ duration: 100 }}
360
- out:fade={{ duration: 100 }}
361
- style="left: {rangeValueLeft}%"
362
- >
363
- {value}{unit}
364
- </div>
365
- </Theme>
364
+ {#if min?.toString().length && max?.toString().length}
365
+ <span class="RangeTooltipAnchor" style="left: {rangeValueLeft}%">
366
+ <Tooltip tip="{value}{unit}" open={focused}>
367
+ <span></span>
368
+ </Tooltip>
369
+ </span>
366
370
  {/if}
367
371
  </div>
368
372
  {#if max?.toString().length}
@@ -396,7 +400,8 @@
396
400
  input:not([type="checkbox"]):not([type="radio"]) {
397
401
  border: none;
398
402
  flex-grow: 1;
399
- flex-shrink: 0;
403
+ flex-shrink: 1;
404
+ min-width: 0;
400
405
  }
401
406
  input:not([type="checkbox"]):not([type="radio"]):focus-visible,
402
407
  input:not([type="checkbox"]):not([type="radio"]):active {
@@ -404,29 +409,56 @@
404
409
  }
405
410
  input[type="range"] {
406
411
  display: block;
412
+ appearance: none;
413
+ background: transparent;
414
+ }
415
+ input[type="range"]::-webkit-slider-thumb {
416
+ -webkit-appearance: none;
417
+ appearance: none;
418
+ width: 1rem;
419
+ height: 1rem;
420
+ border-radius: 50%;
421
+ background: var(--focus-ring-color);
422
+ border: none;
423
+ margin-top: calc((0.25rem - 1rem) / 2);
424
+ }
425
+ input[type="range"]::-moz-range-thumb {
426
+ width: 1rem;
427
+ height: 1rem;
428
+ border-radius: 50%;
429
+ background: var(--focus-ring-color);
430
+ border: none;
407
431
  }
408
432
  .Range {
409
433
  display: flex;
410
- gap: 0.5em;
434
+ gap: var(--space-xs);
411
435
  align-items: center;
412
436
  }
413
- .RangeValue {
437
+ .RangeTooltipAnchor {
414
438
  position: absolute;
415
- top: 0;
416
- font-size: max(0.85em, 9px);
417
- color: var(--text-subtle);
418
- background: var(--bg-app);
419
- padding: 0.15em 0.35em;
420
- border-radius: var(--border-radius);
421
- transform: translate(-50%, -125%);
422
- border: var(--border);
423
- min-inline-size: 1em;
424
- box-shadow: 0 0.5rem 1rem var(--shadow);
425
- font-weight: 600;
439
+ top: 50%;
440
+ width: 0;
441
+ height: 0;
442
+ pointer-events: none;
443
+ }
444
+ .RangeTooltipAnchor :global(.TooltipContent) {
445
+ position-area: block-end center;
446
+ margin-block-end: 0;
447
+ margin-block-start: var(--tooltip-offset, var(--space-xxs));
448
+ }
449
+ input[type="range"]::-webkit-slider-runnable-track {
450
+ background: var(--border-color-subtle);
451
+ block-size: 0.25rem;
452
+ border-radius: var(--border-radius-base);
453
+ }
454
+ input[type="range"]::-moz-range-track {
455
+ background: var(--border-color-subtle);
456
+ block-size: 0.25rem;
457
+ border-radius: var(--border-radius-base);
426
458
  }
427
459
  .Range span {
428
- font-size: 0.75em;
429
- color: var(--text-subtle);
460
+ font-size: var(--font-size-xs);
461
+ color: var(--text-color-p-subtle);
430
462
  }
431
463
  .RangeInput {
432
464
  position: relative;
@@ -47,7 +47,6 @@ type $$ComponentProps = {
47
47
  maxlength?: number;
48
48
  /** The minimum number of characters (as UTF-16 code units) the user can enter into the input. Valid for text, search, url, tel, email, and password. */
49
49
  minlength?: number;
50
- /** Allow multiple f
51
50
  /** The maximum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
52
51
  max?: number;
53
52
  /** The minimum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
@@ -27,16 +27,16 @@
27
27
  display: flex;
28
28
  justify-content: flex-end;
29
29
  align-items: center;
30
- font-size: 0.8rem;
31
- font-weight: 500;
32
- color: var(--text-subtle);
30
+ font-size: var(--font-size-xs);
31
+ font-weight: var(--font-weight-normal);
32
+ color: var(--text-color-p-subtle);
33
33
  }
34
34
  .InputLength .Length {
35
35
  display: flex;
36
36
  align-items: center;
37
- gap: 0.25rem;
37
+ gap: var(--space-xxs);
38
38
  }
39
39
  .InputLength .Length.warn {
40
- color: var(--text-color-warn, light-dark(red, red));
40
+ color: var(--field-color-danger);
41
41
  }
42
42
  </style>
@@ -34,7 +34,7 @@
34
34
  </script>
35
35
 
36
36
  {#if label}
37
- <label for={id} class:hasTip={!!tip} class:hasHelp={!!help}>
37
+ <label for={id}>
38
38
  {#if typeof label === 'string'}
39
39
  <span>
40
40
  {label} {#if required}<span aria-hidden="true">*</span>{/if}
@@ -64,16 +64,16 @@
64
64
  gap: var(--form-label-gap, var(--space-md));
65
65
  align-items: center;
66
66
  }
67
- label.hasTip,
68
- label.hasHelp {
67
+ label:has(.Help),
68
+ label:has(:global(.ContextTip)) {
69
69
  grid-template-columns: 1fr auto;
70
70
  }
71
- label.hasHelp.hasTip {
71
+ label:has(.Help):has(:global(.ContextTip)) {
72
72
  grid-template-columns: 1fr auto auto;
73
73
  }
74
74
  label > span > span {
75
- font-weight: 600;
76
- color: var(--text-color-warn, light-dark(red, red));
75
+ font-weight: var(--font-weight-medium);
76
+ color: var(--field-color-danger);
77
77
  padding-inline: 0.175em;
78
78
  }
79
79
  .Help {
@@ -4,6 +4,20 @@
4
4
  import type { Snippet } from "svelte";
5
5
  import StringOrSnippet from "../util/StringOrSnippet.svelte";
6
6
 
7
+ /**
8
+ * @description
9
+ * A dual-theme logo upload component that provides side-by-side upload fields for
10
+ * light and dark theme logos. Each side renders an `ImageUpload` within its
11
+ * respective `Theme` wrapper so the preview matches the actual display context.
12
+ *
13
+ * @example
14
+ * <LogoUpload
15
+ * lightLogoUploadUrl="/api/upload/logo-light"
16
+ * darkLogoUploadUrl="/api/upload/logo-dark"
17
+ * lightLogoSrc={org.logoLightUrl}
18
+ * darkLogoSrc={org.logoDarkUrl}
19
+ * />
20
+ */
7
21
  let {
8
22
  lightLogoInputName = 'logo_light_url',
9
23
  darkLogoInputName = 'logo_dark_url',
@@ -77,9 +91,9 @@
77
91
 
78
92
  <style>
79
93
  .LogoUpload {
80
- border: var(--border);
94
+ border: var(--field-border);
81
95
  overflow: hidden;
82
- border-radius: var(--border-radius);
96
+ border-radius: var(--border-radius-base);
83
97
  margin: 0;
84
98
  padding: 0;
85
99
  list-style: none;
@@ -88,28 +102,28 @@
88
102
  container-type: inline-size;
89
103
  }
90
104
  .Content {
91
- background: var(--bg-app);
105
+ background: var(--background-main);
92
106
  }
93
107
  .Info {
94
- padding: 1rem 1.5rem;
108
+ padding: var(--space-md) var(--space-lg);
95
109
  display: grid;
96
110
  grid-template-columns: 1fr;
97
- gap: 1rem;
111
+ gap: var(--space-md);
98
112
  }
99
113
  .Content :global(h5) {
100
- margin-bottom: 0 !important;
101
- padding: 1rem 1rem 1rem 1rem;
114
+ margin-block-end: 0;
115
+ padding: var(--space-md);
102
116
  }
103
117
  .Content :global(.Rows) {
104
- padding: 0 1rem 1rem 1rem;
118
+ padding: 0 var(--space-md) var(--space-md) var(--space-md);
105
119
  }
106
120
  @container (min-width: 500px) {
107
121
  .LogoUpload {
108
122
  grid-template-columns: 1fr 1fr;
109
123
  }
110
124
  .Content :global(h5) {
111
- margin-bottom: 0 !important;
112
- padding: 1rem 1.5rem;
125
+ margin-block-end: 0;
126
+ padding: var(--space-md) var(--space-lg);
113
127
  }
114
128
  }
115
129
  </style>
@@ -6,6 +6,19 @@
6
6
  import { ZodType } from "zod";
7
7
  import FieldContent from "./FieldContent.svelte";
8
8
 
9
+ /**
10
+ * @description
11
+ * A styled `<select>` element with a custom dropdown arrow, integrated with the
12
+ * Lutra form system for validation and error display. Accepts options via props
13
+ * or as `<option>` children via a snippet.
14
+ *
15
+ * @example
16
+ * <Select name="country" label="Country" options={['US', 'UK', 'DE']} />
17
+ * <Select name="role" label="Role" options={[
18
+ * { value: 'admin', label: 'Administrator' },
19
+ * { value: 'user', label: 'User' },
20
+ * ]} />
21
+ */
9
22
  let {
10
23
  autofocus,
11
24
  children,
@@ -32,19 +45,19 @@
32
45
  }: {
33
46
  /** Whether the input should be autofocused. */
34
47
  autofocus?: boolean;
35
- /** Options for the select element. */
48
+ /** Options for the select element rendered as a snippet. */
36
49
  children?: Snippet;
37
- /** Whether the input should be disabled. */
50
+ /** Whether the select should be disabled. */
38
51
  disabled?: boolean;
39
- /** Help text to display below the input. */
52
+ /** Help text to display below the select. */
40
53
  help?: string | Snippet;
41
54
  /** A random id is generated if not provided. */
42
55
  id?: string;
43
- /** The label for the input */
56
+ /** The label for the select. */
44
57
  label?: string | Snippet
45
- /** Context tooltip for a label. Renders with a questionmark using ContextTip. */
58
+ /** Context tooltip for the label. Renders with a questionmark using ContextTip. */
46
59
  labelTip?: string | Snippet;
47
- /** The name of the input element. */
60
+ /** The name of the select element. */
48
61
  name: string;
49
62
  /** The onblur event handler */
50
63
  onblur?: (e: FocusEvent) => void;
@@ -160,10 +173,10 @@
160
173
  width: 100%;
161
174
  }
162
175
  select {
163
- padding-inline-end: calc(1.5em + 10px);
176
+ padding-inline-end: 2.5em;
164
177
  width: 100%;
165
178
  appearance: none;
166
- border: none !important;
179
+ border: none;
167
180
  border-radius: 0;
168
181
  grid-area: select;
169
182
  background: none;
@@ -174,11 +187,11 @@
174
187
  display: flex;
175
188
  align-items: center;
176
189
  justify-content: end;
177
- padding-inline-end: 0.75em;
190
+ padding-inline-end: var(--field-padding-inline);
178
191
  }
179
192
  svg {
180
193
  display: block;
181
- color: var(--field-text-color, light-dark(black, white));
194
+ color: var(--field-color);
182
195
  }
183
196
  select:focus-visible {
184
197
  outline: none;
@@ -2,19 +2,19 @@ import { type Snippet } from "svelte";
2
2
  type $$ComponentProps = {
3
3
  /** Whether the input should be autofocused. */
4
4
  autofocus?: boolean;
5
- /** Options for the select element. */
5
+ /** Options for the select element rendered as a snippet. */
6
6
  children?: Snippet;
7
- /** Whether the input should be disabled. */
7
+ /** Whether the select should be disabled. */
8
8
  disabled?: boolean;
9
- /** Help text to display below the input. */
9
+ /** Help text to display below the select. */
10
10
  help?: string | Snippet;
11
11
  /** A random id is generated if not provided. */
12
12
  id?: string;
13
- /** The label for the input */
13
+ /** The label for the select. */
14
14
  label?: string | Snippet;
15
- /** Context tooltip for a label. Renders with a questionmark using ContextTip. */
15
+ /** Context tooltip for the label. Renders with a questionmark using ContextTip. */
16
16
  labelTip?: string | Snippet;
17
- /** The name of the input element. */
17
+ /** The name of the select element. */
18
18
  name: string;
19
19
  /** The onblur event handler */
20
20
  onblur?: (e: FocusEvent) => void;
@@ -9,6 +9,16 @@
9
9
  import { getFromObjWithStringPath } from "./form.js";
10
10
  import { ZodType } from "zod";
11
11
 
12
+ /**
13
+ * @description
14
+ * A styled textarea component with optional auto-resize, copy-to-clipboard, and
15
+ * prefix/suffix content. Integrates with the Lutra form system for validation
16
+ * and error display.
17
+ *
18
+ * @example
19
+ * <Textarea name="bio" label="Biography" placeholder="Tell us about yourself..." />
20
+ * <Textarea name="notes" label="Notes" autoresize maxlength={500} />
21
+ */
12
22
  let {
13
23
  autofocus,
14
24
  autocapitalize,
@@ -43,7 +53,7 @@
43
53
  prefix,
44
54
  readonly,
45
55
  required,
46
- resize = true,
56
+ resize,
47
57
  shape = 'rounded',
48
58
  step,
49
59
  style,
@@ -1,4 +1,166 @@
1
1
  <script lang="ts">
2
+ import { getContext, onMount, type Snippet } from "svelte";
3
+ import type { LutraForm } from "./types.js";
4
+ import { fieldChange } from "./client.svelte.js";
5
+ import FieldContent from "./FieldContent.svelte";
6
+ import { getFromObjWithStringPath } from "./form.js";
7
+ import { ZodType } from "zod";
2
8
 
9
+ /**
10
+ * @description
11
+ * A mobile-style toggle switch. Renders as `<input type="checkbox" role="switch">`
12
+ * for full accessibility. Integrates with the Lutra form system for validation,
13
+ * error display and field state tracking.
14
+ *
15
+ * @example
16
+ * <Toggle name="notifications" label="Enable notifications" />
17
+ * <Toggle name="darkMode" label="Dark mode" value={true} />
18
+ */
19
+ let {
20
+ disabled,
21
+ help,
22
+ id = $bindable(crypto.randomUUID()),
23
+ label,
24
+ labelHelp,
25
+ labelTip,
26
+ name,
27
+ onblur,
28
+ onchange,
29
+ onfocus,
30
+ required,
31
+ title,
32
+ value = $bindable(false),
33
+ ...rest
34
+ }: {
35
+ /** Whether the toggle is disabled. */
36
+ disabled?: boolean;
37
+ /** Help text to display below the toggle. */
38
+ help?: string | Snippet;
39
+ /** A random id is generated if not provided. */
40
+ id?: string;
41
+ /** The label for the toggle. */
42
+ label?: string | Snippet;
43
+ /** Help text to display below the label. */
44
+ labelHelp?: string | Snippet;
45
+ /** Context tooltip for the label. Renders with a questionmark using ContextTip. */
46
+ labelTip?: string | Snippet;
47
+ /** The name of the toggle field. */
48
+ name: string;
49
+ /** The onblur event handler. */
50
+ onblur?: (e: FocusEvent) => void;
51
+ /** Onchange event handler. */
52
+ onchange?: (e: Event) => void;
53
+ /** Onfocus event handler. */
54
+ onfocus?: (e: FocusEvent) => void;
55
+ /** Whether the toggle is required. */
56
+ required?: boolean;
57
+ /** A string that defines the title of the toggle. */
58
+ title?: string;
59
+ /** The checked state of the toggle. */
60
+ value?: boolean;
61
+ } = $props();
62
+
63
+ let el: HTMLInputElement | undefined = $state();
64
+
65
+ const form = getContext<LutraForm<any>>('form');
66
+ const field = $derived(form?.fields[name]);
67
+ const issue = $derived(form?.issues?.find((issue) => issue.name === name));
68
+ const validator = getContext<Record<string, ZodType>>('form.validators')?.[name];
69
+ const data = form?.data;
70
+ const originalData = form?.originalData;
71
+
72
+ if(!value) value = form ? (getFromObjWithStringPath(Object.assign(originalData ?? {}, data ?? {}), name) as boolean) : false;
73
+
74
+ onMount(() => {
75
+ if(value) fieldChange(form, name, () => el)({} as any);
76
+ });
3
77
  </script>
4
78
 
79
+ <FieldContent
80
+ {id}
81
+ {label}
82
+ {labelHelp}
83
+ {labelTip}
84
+ contained={false}
85
+ direction="row"
86
+ {field}
87
+ {issue}
88
+ type="toggle"
89
+ {help}
90
+ {required}
91
+ >
92
+ <input
93
+ type="checkbox"
94
+ role="switch"
95
+ class="Toggle"
96
+ bind:this={el}
97
+ {disabled}
98
+ {id}
99
+ {name}
100
+ {title}
101
+ required={required || field?.required}
102
+ onchange={fieldChange(form, name, () => el, validator, onchange)}
103
+ {onblur}
104
+ {onfocus}
105
+ bind:checked={value}
106
+ aria-checked={value}
107
+ {...rest}
108
+ />
109
+ </FieldContent>
110
+
111
+ <style>
112
+ input.Toggle {
113
+ appearance: none;
114
+ width: var(--toggle-width);
115
+ height: var(--toggle-height);
116
+ border-radius: calc(infinity * 1px);
117
+ background: var(--toggle-background);
118
+ border: var(--field-border-size) var(--field-border-style) var(--toggle-border-color);
119
+ cursor: pointer;
120
+ position: relative;
121
+ flex-shrink: 0;
122
+ padding: 0;
123
+ margin: 0;
124
+ transition:
125
+ background var(--transition-duration-fast) var(--transition-timing-function),
126
+ border-color var(--transition-duration-fast) var(--transition-timing-function);
127
+ }
128
+
129
+ input.Toggle::before {
130
+ content: "";
131
+ position: absolute;
132
+ top: 50%;
133
+ inset-inline-start: var(--toggle-thumb-offset);
134
+ width: var(--toggle-thumb-size);
135
+ height: var(--toggle-thumb-size);
136
+ border-radius: 50%;
137
+ background: var(--toggle-thumb-color);
138
+ translate: 0 -50%;
139
+ transition: translate var(--transition-duration-fast) var(--transition-timing-function);
140
+ box-shadow: var(--toggle-thumb-shadow);
141
+ }
142
+
143
+ input.Toggle:checked {
144
+ background: var(--toggle-background-checked);
145
+ border-color: var(--toggle-border-color-checked);
146
+ }
147
+
148
+ input.Toggle:checked::before {
149
+ translate: calc(var(--toggle-width) - var(--toggle-height)) -50%;
150
+ }
151
+
152
+ input.Toggle:checked::after {
153
+ content: none;
154
+ display: none;
155
+ }
156
+
157
+ input.Toggle:focus-visible {
158
+ outline: var(--focus-ring-size) solid var(--focus-ring-color);
159
+ outline-offset: var(--focus-ring-offset);
160
+ }
161
+
162
+ input.Toggle:disabled {
163
+ opacity: 0.5;
164
+ cursor: not-allowed;
165
+ }
166
+ </style>
@@ -1,18 +1,32 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const Toggle: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {}, {}, string>;
17
- type Toggle = InstanceType<typeof Toggle>;
1
+ import { type Snippet } from "svelte";
2
+ type $$ComponentProps = {
3
+ /** Whether the toggle is disabled. */
4
+ disabled?: boolean;
5
+ /** Help text to display below the toggle. */
6
+ help?: string | Snippet;
7
+ /** A random id is generated if not provided. */
8
+ id?: string;
9
+ /** The label for the toggle. */
10
+ label?: string | Snippet;
11
+ /** Help text to display below the label. */
12
+ labelHelp?: string | Snippet;
13
+ /** Context tooltip for the label. Renders with a questionmark using ContextTip. */
14
+ labelTip?: string | Snippet;
15
+ /** The name of the toggle field. */
16
+ name: string;
17
+ /** The onblur event handler. */
18
+ onblur?: (e: FocusEvent) => void;
19
+ /** Onchange event handler. */
20
+ onchange?: (e: Event) => void;
21
+ /** Onfocus event handler. */
22
+ onfocus?: (e: FocusEvent) => void;
23
+ /** Whether the toggle is required. */
24
+ required?: boolean;
25
+ /** A string that defines the title of the toggle. */
26
+ title?: string;
27
+ /** The checked state of the toggle. */
28
+ value?: boolean;
29
+ };
30
+ declare const Toggle: import("svelte").Component<$$ComponentProps, {}, "id" | "value">;
31
+ type Toggle = ReturnType<typeof Toggle>;
18
32
  export default Toggle;
@@ -85,8 +85,6 @@ function getTypedValue(el) {
85
85
  export function fieldChange(form, name, el, validator, onchange) {
86
86
  return async function (e) {
87
87
  if (form && form.fields[name]) {
88
- console.log('fieldChange', name, el()?.value, form.data[name]);
89
- // Update the form data with the new value.
90
88
  form.data[name] = getTypedValue(el());
91
89
  // if the value is the same as the original value, then the field is not tainted
92
90
  form.fields[name].tainted = form.data[name] !== form.originalData[name];
@@ -1,6 +1,7 @@
1
1
  export { default as Button } from './Button.svelte';
2
2
  export { default as FormActions } from './FormActions.svelte';
3
3
  export { default as FieldError } from './FieldError.svelte';
4
+ export { default as FieldGroup } from './FieldGroup.svelte';
4
5
  export { default as FormSection } from './FormSection.svelte';
5
6
  export { default as Fieldset } from './Fieldset.svelte';
6
7
  export { default as Form } from './Form.svelte';
@@ -1,6 +1,7 @@
1
1
  export { default as Button } from './Button.svelte';
2
2
  export { default as FormActions } from './FormActions.svelte';
3
3
  export { default as FieldError } from './FieldError.svelte';
4
+ export { default as FieldGroup } from './FieldGroup.svelte';
4
5
  export { default as FormSection } from './FormSection.svelte';
5
6
  export { default as Fieldset } from './Fieldset.svelte';
6
7
  export { default as Form } from './Form.svelte';