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.
- package/dist/components/AspectRatio.svelte +19 -9
- package/dist/components/AspectRatio.svelte.d.ts +2 -1
- package/dist/components/Avatar.svelte +5 -8
- package/dist/components/Close.svelte +24 -27
- package/dist/components/Close.svelte.d.ts +2 -0
- package/dist/components/ContextTip.svelte +3 -2
- package/dist/components/DataList.svelte +111 -0
- package/dist/components/DataList.svelte.d.ts +10 -0
- package/dist/components/DataListTypes.d.ts +14 -0
- package/dist/components/DataListTypes.js +1 -0
- package/dist/components/Dialog.svelte +38 -0
- package/dist/components/Icon.svelte +2 -2
- package/dist/components/IconButton.svelte +10 -22
- package/dist/components/Image.svelte +2 -2
- package/dist/components/Indicator.svelte +2 -1
- package/dist/components/Inset.svelte +13 -0
- package/dist/components/Layout.svelte +7 -3
- package/dist/components/Layout.svelte.d.ts +3 -2
- package/dist/components/MenuDropdown.svelte +12 -2
- package/dist/components/MenuItem.svelte +30 -14
- package/dist/components/MenuItem.svelte.d.ts +6 -0
- package/dist/components/Modal.svelte +36 -20
- package/dist/components/Popover.svelte +43 -13
- package/dist/components/TabbedContent.svelte +1 -1
- package/dist/components/TabbedContentItem.svelte +14 -0
- package/dist/components/TabbedContentItem.svelte.d.ts +4 -0
- package/dist/components/Table.svelte +69 -0
- package/dist/components/Table.svelte.d.ts +7 -0
- package/dist/components/Tabs.svelte +44 -36
- package/dist/components/Tag.svelte +53 -13
- package/dist/components/Tag.svelte.d.ts +4 -0
- package/dist/components/Theme.svelte +121 -94
- package/dist/components/Theme.svelte.d.ts +7 -6
- package/dist/components/Toast.svelte +11 -8
- package/dist/components/Tooltip.svelte +17 -10
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/css/1-props.css +197 -163
- package/dist/css/2-init.css +519 -0
- package/dist/css/{2-base.css → 3-base.css} +42 -131
- package/dist/css/{3-typo.css → 4-typo.css} +3 -1
- package/dist/css/lutra.css +7 -6
- package/dist/css/themes/DefaultTheme.css +26 -4
- package/dist/form/Button.svelte +20 -0
- package/dist/form/Button.svelte.d.ts +9 -0
- package/dist/form/Datepicker.svelte +13 -0
- package/dist/form/Datepicker.svelte.d.ts +3 -0
- package/dist/form/FieldContent.svelte +20 -11
- package/dist/form/FieldError.svelte +1 -1
- package/dist/form/FieldGroup.svelte +84 -0
- package/dist/form/FieldGroup.svelte.d.ts +20 -0
- package/dist/form/Fieldset.svelte +19 -11
- package/dist/form/Form.svelte +137 -63
- package/dist/form/Form.svelte.d.ts +21 -0
- package/dist/form/FormActions.svelte +21 -3
- package/dist/form/FormActions.svelte.d.ts +3 -0
- package/dist/form/FormSection.svelte +22 -20
- package/dist/form/ImageUpload.svelte +50 -30
- package/dist/form/ImageUpload.svelte.d.ts +14 -0
- package/dist/form/Input.svelte +62 -30
- package/dist/form/Input.svelte.d.ts +0 -1
- package/dist/form/InputLength.svelte +5 -5
- package/dist/form/Label.svelte +6 -6
- package/dist/form/LogoUpload.svelte +24 -10
- package/dist/form/Select.svelte +23 -10
- package/dist/form/Select.svelte.d.ts +6 -6
- package/dist/form/Textarea.svelte +11 -1
- package/dist/form/Toggle.svelte +162 -0
- package/dist/form/Toggle.svelte.d.ts +31 -17
- package/dist/form/client.svelte.js +0 -2
- package/dist/form/index.d.ts +1 -0
- package/dist/form/index.js +1 -0
- package/dist/state/Persisted.svelte.d.ts +6 -0
- package/dist/state/Persisted.svelte.js +29 -0
- package/dist/state/theme.svelte.d.ts +7 -0
- package/dist/state/theme.svelte.js +14 -0
- package/dist/types.d.ts +6 -23
- package/dist/types.js +0 -17
- package/dist/util/color.js +2 -2
- package/package.json +5 -4
- package/dist/config.d.ts +0 -30
- package/dist/config.js +0 -18
- /package/dist/css/{4-layout.css → 5-layout.css} +0 -0
- /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, {}, "">;
|
package/dist/form/Input.svelte
CHANGED
|
@@ -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
|
|
356
|
-
<
|
|
357
|
-
<
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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:
|
|
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:
|
|
434
|
+
gap: var(--space-xs);
|
|
411
435
|
align-items: center;
|
|
412
436
|
}
|
|
413
|
-
.
|
|
437
|
+
.RangeTooltipAnchor {
|
|
414
438
|
position: absolute;
|
|
415
|
-
top:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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:
|
|
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:
|
|
31
|
-
font-weight:
|
|
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:
|
|
37
|
+
gap: var(--space-xxs);
|
|
38
38
|
}
|
|
39
39
|
.InputLength .Length.warn {
|
|
40
|
-
color: var(--
|
|
40
|
+
color: var(--field-color-danger);
|
|
41
41
|
}
|
|
42
42
|
</style>
|
package/dist/form/Label.svelte
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
</script>
|
|
35
35
|
|
|
36
36
|
{#if label}
|
|
37
|
-
<label for={id}
|
|
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.
|
|
68
|
-
label.
|
|
67
|
+
label:has(.Help),
|
|
68
|
+
label:has(:global(.ContextTip)) {
|
|
69
69
|
grid-template-columns: 1fr auto;
|
|
70
70
|
}
|
|
71
|
-
label.
|
|
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:
|
|
76
|
-
color: var(--
|
|
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(--
|
|
105
|
+
background: var(--background-main);
|
|
92
106
|
}
|
|
93
107
|
.Info {
|
|
94
|
-
padding:
|
|
108
|
+
padding: var(--space-md) var(--space-lg);
|
|
95
109
|
display: grid;
|
|
96
110
|
grid-template-columns: 1fr;
|
|
97
|
-
gap:
|
|
111
|
+
gap: var(--space-md);
|
|
98
112
|
}
|
|
99
113
|
.Content :global(h5) {
|
|
100
|
-
margin-
|
|
101
|
-
padding:
|
|
114
|
+
margin-block-end: 0;
|
|
115
|
+
padding: var(--space-md);
|
|
102
116
|
}
|
|
103
117
|
.Content :global(.Rows) {
|
|
104
|
-
padding: 0
|
|
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-
|
|
112
|
-
padding:
|
|
125
|
+
margin-block-end: 0;
|
|
126
|
+
padding: var(--space-md) var(--space-lg);
|
|
113
127
|
}
|
|
114
128
|
}
|
|
115
129
|
</style>
|
package/dist/form/Select.svelte
CHANGED
|
@@ -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
|
|
50
|
+
/** Whether the select should be disabled. */
|
|
38
51
|
disabled?: boolean;
|
|
39
|
-
/** Help text to display below the
|
|
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
|
|
56
|
+
/** The label for the select. */
|
|
44
57
|
label?: string | Snippet
|
|
45
|
-
/** Context tooltip for
|
|
58
|
+
/** Context tooltip for the label. Renders with a questionmark using ContextTip. */
|
|
46
59
|
labelTip?: string | Snippet;
|
|
47
|
-
/** The name of the
|
|
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:
|
|
176
|
+
padding-inline-end: 2.5em;
|
|
164
177
|
width: 100%;
|
|
165
178
|
appearance: none;
|
|
166
|
-
border: none
|
|
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:
|
|
190
|
+
padding-inline-end: var(--field-padding-inline);
|
|
178
191
|
}
|
|
179
192
|
svg {
|
|
180
193
|
display: block;
|
|
181
|
-
color: var(--field-
|
|
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
|
|
7
|
+
/** Whether the select should be disabled. */
|
|
8
8
|
disabled?: boolean;
|
|
9
|
-
/** Help text to display below the
|
|
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
|
|
13
|
+
/** The label for the select. */
|
|
14
14
|
label?: string | Snippet;
|
|
15
|
-
/** Context tooltip for
|
|
15
|
+
/** Context tooltip for the label. Renders with a questionmark using ContextTip. */
|
|
16
16
|
labelTip?: string | Snippet;
|
|
17
|
-
/** The name of the
|
|
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
|
|
56
|
+
resize,
|
|
47
57
|
shape = 'rounded',
|
|
48
58
|
step,
|
|
49
59
|
style,
|
package/dist/form/Toggle.svelte
CHANGED
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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];
|
package/dist/form/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/form/index.js
CHANGED
|
@@ -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';
|