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.
- 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/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 +39 -12
- 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/css/1-props.css +64 -51
- package/dist/css/2-init.css +503 -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 +16 -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 +18 -9
- package/dist/form/FieldError.svelte +1 -1
- 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/client.svelte.js +0 -2
- 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
|
@@ -2,20 +2,38 @@
|
|
|
2
2
|
import StringOrSnippet from "../util/StringOrSnippet.svelte";
|
|
3
3
|
import { setContext, type Snippet } from "svelte";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* Action bar for form buttons. Renders at the bottom of a form with configurable
|
|
8
|
+
* alignment. When the form is contained, inherits background and padding from
|
|
9
|
+
* `--form-background-actions` via the `--fcc` multiplier pattern.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <FormActions>
|
|
13
|
+
* <Button type="submit">Save</Button>
|
|
14
|
+
* </FormActions>
|
|
15
|
+
* <FormActions align="justified" info="Changes are saved automatically.">
|
|
16
|
+
* <Button type="submit">Save</Button>
|
|
17
|
+
* <Button kind="secondary" href="/cancel">Cancel</Button>
|
|
18
|
+
* </FormActions>
|
|
19
|
+
*/
|
|
5
20
|
let {
|
|
6
21
|
align = 'end',
|
|
7
22
|
children,
|
|
8
23
|
info,
|
|
9
24
|
}: {
|
|
25
|
+
/** Alignment of the action buttons. */
|
|
10
26
|
align?: 'justified' | 'start' | 'center' | 'end' | 'full';
|
|
27
|
+
/** Action buttons and other content. */
|
|
11
28
|
children: Snippet;
|
|
29
|
+
/** Optional info text displayed alongside the action buttons. */
|
|
12
30
|
info?: string | Snippet;
|
|
13
31
|
} = $props();
|
|
14
32
|
|
|
15
33
|
setContext('form.actions.align', align);
|
|
16
34
|
</script>
|
|
17
35
|
|
|
18
|
-
<div class="FormActions {align}"
|
|
36
|
+
<div class="FormActions {align}">
|
|
19
37
|
{#if info}
|
|
20
38
|
<div class="Info">
|
|
21
39
|
<StringOrSnippet content={info} />
|
|
@@ -29,7 +47,7 @@
|
|
|
29
47
|
<style>
|
|
30
48
|
.FormActions {
|
|
31
49
|
display: grid;
|
|
32
|
-
background: color-mix(in
|
|
50
|
+
background: color-mix(in oklch, var(--form-background-actions) calc(var(--fcc) * 100%), transparent);
|
|
33
51
|
padding: calc(var(--space-md) * var(--fcc)) calc(var(--space-xl) * var(--fcc));
|
|
34
52
|
grid-column: 1 / -1;
|
|
35
53
|
grid-template-columns: subgrid;
|
|
@@ -57,7 +75,7 @@
|
|
|
57
75
|
justify-content: end;
|
|
58
76
|
grid-column: 1 / -1;
|
|
59
77
|
}
|
|
60
|
-
.FormActions.
|
|
78
|
+
.FormActions:has(.Info) .Actions {
|
|
61
79
|
grid-column: 2 / -1;
|
|
62
80
|
}
|
|
63
81
|
.FormActions.full .Actions {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type Snippet } from "svelte";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
+
/** Alignment of the action buttons. */
|
|
3
4
|
align?: 'justified' | 'start' | 'center' | 'end' | 'full';
|
|
5
|
+
/** Action buttons and other content. */
|
|
4
6
|
children: Snippet;
|
|
7
|
+
/** Optional info text displayed alongside the action buttons. */
|
|
5
8
|
info?: string | Snippet;
|
|
6
9
|
};
|
|
7
10
|
declare const FormActions: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -3,7 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @description
|
|
6
|
-
* A
|
|
6
|
+
* A section within a form, grouping related fields under an optional title and
|
|
7
|
+
* description. Uses CSS subgrid for alignment with the parent form layout and
|
|
8
|
+
* responds to `@container` queries to collapse into a single column at narrow widths.
|
|
9
|
+
*
|
|
10
|
+
* @cssprop --form-section-gap -- Gap between the title and content areas.
|
|
11
|
+
* @cssprop --form-title-gap -- Gap within the section title (title + description).
|
|
12
|
+
* @cssprop --form-field-gap -- Gap between fields in the content area.
|
|
13
|
+
* @cssprop --form-padding-block -- Block padding when the form is contained.
|
|
14
|
+
* @cssprop --form-padding-inline -- Inline padding when the form is contained.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <FormSection title="Personal Information" description="Enter your details.">
|
|
18
|
+
* <Input name="name" label="Name" />
|
|
19
|
+
* <Input name="email" type="email" label="Email" />
|
|
20
|
+
* </FormSection>
|
|
7
21
|
*/
|
|
8
22
|
let {
|
|
9
23
|
title,
|
|
@@ -53,12 +67,11 @@
|
|
|
53
67
|
.FormSectionTitle {
|
|
54
68
|
display: flex;
|
|
55
69
|
flex-direction: column;
|
|
56
|
-
background-color: var(--base);
|
|
57
70
|
gap: var(--form-title-gap, var(--space-md));
|
|
58
71
|
text-wrap: balance;
|
|
59
72
|
}
|
|
60
73
|
.FormSection:not(:first-child) {
|
|
61
|
-
border-top: calc(var(--fcc) * var(--border-size)) var(--border-style) var(--border-color);
|
|
74
|
+
border-top: calc(var(--fcc) * var(--form-border-size)) var(--form-border-style) var(--form-border-color);
|
|
62
75
|
}
|
|
63
76
|
.FormSectionContent {
|
|
64
77
|
display: grid;
|
|
@@ -67,27 +80,16 @@
|
|
|
67
80
|
.FormSection.noTitle .FormSectionContent {
|
|
68
81
|
grid-column: 1 / -1;
|
|
69
82
|
}
|
|
70
|
-
@
|
|
71
|
-
.FormSection {
|
|
72
|
-
padding: var(--space-xl);
|
|
73
|
-
gap: var(--space-md);
|
|
74
|
-
}
|
|
75
|
-
.FormSectionTitle {
|
|
76
|
-
gap: var(--space-xs);
|
|
77
|
-
padding-block-end: var(--space-md);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
@media(max-width: 640px) {
|
|
83
|
+
@container (max-width: 800px) {
|
|
81
84
|
.FormSection {
|
|
82
|
-
padding: var(--
|
|
83
|
-
|
|
85
|
+
padding-block: calc(var(--fcc) * var(--form-padding-block));
|
|
86
|
+
padding-inline: calc(var(--fcc) * var(--form-padding-inline));
|
|
87
|
+
gap: var(--form-section-gap, var(--space-md));
|
|
84
88
|
}
|
|
85
89
|
.FormSectionTitle {
|
|
86
|
-
gap: var(--
|
|
87
|
-
padding-block-end: var(--
|
|
90
|
+
gap: var(--form-title-gap);
|
|
91
|
+
padding-block-end: var(--form-title-padding-block-end);
|
|
88
92
|
}
|
|
89
|
-
}
|
|
90
|
-
@container (max-width: 800px) {
|
|
91
93
|
.FormSectionTitle,
|
|
92
94
|
.FormSectionContent {
|
|
93
95
|
grid-column: 1 / -1;
|
|
@@ -9,6 +9,23 @@
|
|
|
9
9
|
const imageCompression: ImageCompressionFn = (imageCompressionModule as unknown as { default?: ImageCompressionFn }).default ?? imageCompressionModule as unknown as ImageCompressionFn;
|
|
10
10
|
import { addToast } from "../components/toasts.svelte.js";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @description
|
|
14
|
+
* An image upload component with client-side compression, progress indicator and
|
|
15
|
+
* optional remove button. Supports square, circle and banner display shapes.
|
|
16
|
+
* Uploads to a presigned URL via `PUT`.
|
|
17
|
+
*
|
|
18
|
+
* @cssprop --max-image-width -- Maximum width of the image preview (used in the grid layout).
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <ImageUpload
|
|
22
|
+
* uploadUrl="/api/upload"
|
|
23
|
+
* name="avatar"
|
|
24
|
+
* button="Upload"
|
|
25
|
+
* shape="circle"
|
|
26
|
+
* src={user.avatarUrl}
|
|
27
|
+
* />
|
|
28
|
+
*/
|
|
12
29
|
let {
|
|
13
30
|
src,
|
|
14
31
|
uploadUrl,
|
|
@@ -25,19 +42,33 @@
|
|
|
25
42
|
onFile,
|
|
26
43
|
children,
|
|
27
44
|
}: {
|
|
45
|
+
/** Current image source URL. */
|
|
28
46
|
src?: string | null;
|
|
47
|
+
/** The presigned URL to upload the image to. */
|
|
29
48
|
uploadUrl: string;
|
|
49
|
+
/** The form field name for the resulting URL value. */
|
|
30
50
|
name: string;
|
|
51
|
+
/** HTML id for the file input. */
|
|
31
52
|
id?: string;
|
|
53
|
+
/** Title used in the alt text of the preview image. */
|
|
32
54
|
title?: string;
|
|
55
|
+
/** Label for the upload button. */
|
|
33
56
|
button: string;
|
|
57
|
+
/** Maximum file size in bytes before compression is applied. */
|
|
34
58
|
maxSize?: number;
|
|
59
|
+
/** Maximum compressed file size in megabytes. */
|
|
35
60
|
compressSize?: number;
|
|
61
|
+
/** Maximum compressed image width in pixels. */
|
|
36
62
|
compressMaxWidth?: number;
|
|
63
|
+
/** Accepted MIME types for the file input. */
|
|
37
64
|
accept?: string;
|
|
65
|
+
/** Whether a remove button is shown. */
|
|
38
66
|
removable?: boolean;
|
|
67
|
+
/** Display shape of the image preview. */
|
|
39
68
|
shape?: 'square' | 'circle' | 'banner';
|
|
69
|
+
/** Callback invoked with the selected file before upload. */
|
|
40
70
|
onFile?: (file: File) => void;
|
|
71
|
+
/** Optional additional content rendered below the actions. */
|
|
41
72
|
children?: Snippet;
|
|
42
73
|
} = $props();
|
|
43
74
|
|
|
@@ -63,8 +94,6 @@
|
|
|
63
94
|
useWebWorker: true,
|
|
64
95
|
onProgress: (progress: number) => resizeProgress = progress,
|
|
65
96
|
});
|
|
66
|
-
console.log('compressedFile instanceof Blob', compressedFile instanceof Blob); // true
|
|
67
|
-
console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB
|
|
68
97
|
uploadState = 'uploading';
|
|
69
98
|
|
|
70
99
|
const response = await fetch(uploadUrl, {
|
|
@@ -79,15 +108,10 @@
|
|
|
79
108
|
addToast({ content: 'Something went wrong. Please try again.', autoClose: 3000 })
|
|
80
109
|
cancel();
|
|
81
110
|
}
|
|
82
|
-
//src = new URL(response.url).href.split('?')[0];
|
|
83
|
-
console.log('setting', name, new URL(response.url).href.split('?')[0]);
|
|
84
111
|
const imgUrl = new URL(response.url).href.split('?')[0];
|
|
85
112
|
data.set(name, imgUrl);
|
|
86
113
|
src = imgUrl;
|
|
87
|
-
console.log('response', response, response.url);
|
|
88
|
-
console.log('data', data.forEach((v, k) => console.log(k, v)));
|
|
89
114
|
} catch(err) {
|
|
90
|
-
console.error('error', err);
|
|
91
115
|
addToast({ content: 'Something went wrong. Please try again.', autoClose: 3000 })
|
|
92
116
|
cancel();
|
|
93
117
|
}
|
|
@@ -141,7 +165,7 @@
|
|
|
141
165
|
</script>
|
|
142
166
|
|
|
143
167
|
<div class="ImageUpload {shape}">
|
|
144
|
-
<div class="Image"
|
|
168
|
+
<div class="Image">
|
|
145
169
|
{#if src}
|
|
146
170
|
<img src={src} alt="{title} logo" />
|
|
147
171
|
{/if}
|
|
@@ -194,16 +218,16 @@
|
|
|
194
218
|
display: grid;
|
|
195
219
|
grid-template-columns: 1fr;
|
|
196
220
|
align-items: center;
|
|
197
|
-
gap:
|
|
221
|
+
gap: var(--space-md);
|
|
198
222
|
container-type: inline-size;
|
|
199
223
|
}
|
|
200
224
|
.Image {
|
|
201
|
-
border:
|
|
225
|
+
border: var(--border-size-thin) solid var(--border-color-subtle);
|
|
202
226
|
pointer-events: none;
|
|
203
227
|
position: relative;
|
|
204
228
|
display: none;
|
|
205
229
|
}
|
|
206
|
-
.Image
|
|
230
|
+
.Image:has(img) {
|
|
207
231
|
display: block;
|
|
208
232
|
}
|
|
209
233
|
.Image img {
|
|
@@ -229,32 +253,29 @@
|
|
|
229
253
|
height: 100%;
|
|
230
254
|
width: 100%;
|
|
231
255
|
object-fit: contain;
|
|
232
|
-
padding:
|
|
256
|
+
padding: var(--space-md);
|
|
233
257
|
text-align: center;
|
|
234
|
-
border:
|
|
235
|
-
border-radius: var(--border-radius);
|
|
258
|
+
border: var(--border-size-thin) dashed var(--border-color);
|
|
259
|
+
border-radius: var(--border-radius-base);
|
|
236
260
|
}
|
|
237
261
|
.Loading {
|
|
238
262
|
position: absolute;
|
|
239
|
-
|
|
240
|
-
left: 0;
|
|
241
|
-
right: 0;
|
|
242
|
-
bottom: 0;
|
|
263
|
+
inset: 0;
|
|
243
264
|
z-index: 5;
|
|
244
|
-
backdrop-filter:
|
|
245
|
-
background: var(--
|
|
265
|
+
backdrop-filter: var(--scrim-backdrop-filter);
|
|
266
|
+
background: var(--scrim-background);
|
|
246
267
|
display: flex;
|
|
247
268
|
align-items: center;
|
|
248
269
|
justify-content: center;
|
|
249
270
|
flex-direction: column;
|
|
250
|
-
gap:
|
|
251
|
-
padding:
|
|
271
|
+
gap: var(--space-md);
|
|
272
|
+
padding: var(--space-md);
|
|
252
273
|
}
|
|
253
274
|
input {
|
|
254
275
|
display: none;
|
|
255
276
|
}
|
|
256
277
|
.error {
|
|
257
|
-
margin-
|
|
278
|
+
margin-block-start: var(--space-sm);
|
|
258
279
|
}
|
|
259
280
|
.ImageUpload.banner {
|
|
260
281
|
grid-template-areas: "image";
|
|
@@ -266,7 +287,6 @@
|
|
|
266
287
|
overflow: hidden;
|
|
267
288
|
object-fit: cover;
|
|
268
289
|
height: 15rem;
|
|
269
|
-
aspect-ratio: initial !important;
|
|
270
290
|
}
|
|
271
291
|
.ImageUpload.banner .Image img {
|
|
272
292
|
object-fit: cover;
|
|
@@ -276,28 +296,28 @@
|
|
|
276
296
|
grid-area: image;
|
|
277
297
|
flex-direction: column;
|
|
278
298
|
justify-content: center;
|
|
279
|
-
padding:
|
|
299
|
+
padding: var(--space-md);
|
|
280
300
|
}
|
|
281
301
|
.ImageUpload.banner:hover .ImageUploadActions {
|
|
282
|
-
background: var(--
|
|
302
|
+
background: var(--scrim-background);
|
|
283
303
|
}
|
|
284
304
|
.ImageUploadActions {
|
|
285
305
|
display: grid;
|
|
286
306
|
grid-template-columns: 1fr 1fr;
|
|
287
|
-
gap:
|
|
307
|
+
gap: var(--space-sm);
|
|
288
308
|
align-items: start;
|
|
289
309
|
}
|
|
290
310
|
@container (min-width: 320px) {
|
|
291
311
|
.ImageUpload:not(.banner) {
|
|
292
312
|
grid-template-columns: minmax(auto, var(--max-image-width)) 1fr;
|
|
293
|
-
gap:
|
|
313
|
+
gap: var(--space-xl);
|
|
294
314
|
}
|
|
295
315
|
.ImageUpload.banner .ImageUploadActions {
|
|
296
|
-
padding:
|
|
316
|
+
padding: var(--space-xxl);
|
|
297
317
|
}
|
|
298
318
|
.ImageUploadActions {
|
|
299
319
|
grid-template-columns: 1fr;
|
|
300
|
-
gap:
|
|
320
|
+
gap: var(--space-md);
|
|
301
321
|
}
|
|
302
322
|
}
|
|
303
323
|
</style>
|
|
@@ -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>
|