lutra 0.1.0 → 0.1.4

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 (152) hide show
  1. package/dist/components/Avatar.svelte +105 -0
  2. package/dist/components/Avatar.svelte.d.ts +14 -0
  3. package/dist/components/Close.svelte +76 -0
  4. package/dist/components/Close.svelte.d.ts +7 -0
  5. package/dist/components/ContextTip.svelte +41 -0
  6. package/dist/components/ContextTip.svelte.d.ts +7 -0
  7. package/dist/components/Icon.svelte +62 -0
  8. package/dist/components/Icon.svelte.d.ts +8 -0
  9. package/dist/components/IconButton.svelte +120 -0
  10. package/dist/components/IconButton.svelte.d.ts +16 -0
  11. package/dist/components/Image.svelte +172 -0
  12. package/dist/components/Image.svelte.d.ts +56 -0
  13. package/dist/components/Indicator.svelte +387 -0
  14. package/dist/components/Indicator.svelte.d.ts +12 -0
  15. package/dist/components/Inset.svelte +23 -0
  16. package/dist/components/Inset.svelte.d.ts +7 -0
  17. package/dist/components/Layout.svelte +2 -1
  18. package/dist/components/MenuDropdown.svelte +195 -0
  19. package/dist/components/MenuDropdown.svelte.d.ts +16 -0
  20. package/dist/components/MenuItem.svelte +155 -0
  21. package/dist/components/MenuItem.svelte.d.ts +11 -0
  22. package/dist/components/MenuItemContent.svelte +25 -0
  23. package/dist/components/MenuItemContent.svelte.d.ts +10 -0
  24. package/dist/components/MenuTypes.d.ts +72 -0
  25. package/dist/components/MenuTypes.js +1 -0
  26. package/dist/components/Modal.svelte +149 -0
  27. package/dist/components/Modal.svelte.d.ts +16 -0
  28. package/dist/components/Notification.svelte +115 -0
  29. package/dist/components/Notification.svelte.d.ts +12 -0
  30. package/dist/components/Overlay.svelte +31 -0
  31. package/dist/components/Overlay.svelte.d.ts +14 -0
  32. package/dist/components/OverlayContainer.svelte +31 -0
  33. package/dist/components/OverlayContainer.svelte.d.ts +18 -0
  34. package/dist/components/OverlayLayer.svelte +168 -0
  35. package/dist/components/OverlayLayer.svelte.d.ts +8 -0
  36. package/dist/components/TabbedContent.svelte +74 -0
  37. package/dist/components/TabbedContent.svelte.d.ts +11 -0
  38. package/dist/components/TabbedContentItem.svelte +33 -0
  39. package/dist/components/TabbedContentItem.svelte.d.ts +10 -0
  40. package/dist/components/Table.svelte +41 -0
  41. package/dist/components/Table.svelte.d.ts +13 -0
  42. package/dist/components/Tabs.svelte +216 -0
  43. package/dist/components/Tabs.svelte.d.ts +20 -0
  44. package/dist/components/Tag.svelte +120 -0
  45. package/dist/components/Tag.svelte.d.ts +21 -0
  46. package/dist/components/Theme.svelte +32 -14
  47. package/dist/components/Tooltip.svelte +8 -8
  48. package/dist/components/UIContent.svelte +19 -0
  49. package/dist/components/UIContent.svelte.d.ts +7 -0
  50. package/dist/components/index.d.ts +28 -0
  51. package/dist/components/index.js +29 -0
  52. package/dist/components/notifications.svelte.d.ts +21 -0
  53. package/dist/components/notifications.svelte.js +30 -0
  54. package/dist/components/overlays.svelte.d.ts +36 -0
  55. package/dist/components/overlays.svelte.js +44 -0
  56. package/dist/css/1-props.css +389 -724
  57. package/dist/css/2-base.css +257 -123
  58. package/dist/css/3-typo.css +75 -34
  59. package/dist/css/4-layout.css +364 -1
  60. package/dist/css/5-media.css +106 -11
  61. package/dist/css/lutra.css +2 -1
  62. package/dist/css/themes/DefaultTheme.css +209 -0
  63. package/dist/form/Button.svelte +58 -0
  64. package/dist/form/Button.svelte.d.ts +15 -0
  65. package/dist/form/Datepicker.svelte +311 -0
  66. package/dist/form/Datepicker.svelte.d.ts +9 -0
  67. package/dist/form/FieldContent.svelte +178 -0
  68. package/dist/form/FieldContent.svelte.d.ts +21 -0
  69. package/dist/form/FieldError.svelte +24 -0
  70. package/dist/form/FieldError.svelte.d.ts +7 -0
  71. package/dist/form/Fieldset.svelte +103 -0
  72. package/dist/form/Fieldset.svelte.d.ts +20 -0
  73. package/dist/form/Form.svelte +220 -0
  74. package/dist/form/Form.svelte.d.ts +38 -0
  75. package/dist/form/FormActions.svelte +80 -0
  76. package/dist/form/FormActions.svelte.d.ts +9 -0
  77. package/dist/form/FormSection.svelte +96 -0
  78. package/dist/form/FormSection.svelte.d.ts +9 -0
  79. package/dist/form/ImageUpload.svelte +299 -0
  80. package/dist/form/ImageUpload.svelte.d.ts +20 -0
  81. package/dist/form/Input.svelte +444 -0
  82. package/dist/form/Input.svelte.d.ts +108 -0
  83. package/dist/form/InputLength.svelte +42 -0
  84. package/dist/form/InputLength.svelte.d.ts +9 -0
  85. package/dist/form/Label.svelte +88 -0
  86. package/dist/form/Label.svelte.d.ts +16 -0
  87. package/dist/form/LogoUpload.svelte +115 -0
  88. package/dist/form/LogoUpload.svelte.d.ts +18 -0
  89. package/dist/form/Select.svelte +186 -0
  90. package/dist/form/Select.svelte.d.ts +59 -0
  91. package/dist/form/Textarea.svelte +265 -0
  92. package/dist/form/Textarea.svelte.d.ts +95 -0
  93. package/dist/form/Toggle.svelte +4 -0
  94. package/dist/form/Toggle.svelte.d.ts +18 -0
  95. package/dist/form/client.svelte.d.ts +45 -0
  96. package/dist/form/client.svelte.js +102 -0
  97. package/dist/form/form.d.ts +55 -0
  98. package/dist/form/form.js +345 -0
  99. package/dist/form/index.d.ts +17 -0
  100. package/dist/form/index.js +17 -0
  101. package/dist/form/types.d.ts +55 -0
  102. package/dist/form/types.js +1 -0
  103. package/dist/icons/IconAlert.svelte +3 -0
  104. package/dist/icons/IconAlert.svelte.d.ts +26 -0
  105. package/dist/icons/IconCopy.svelte +3 -0
  106. package/dist/icons/IconCopy.svelte.d.ts +26 -0
  107. package/dist/icons/IconDone.svelte +3 -0
  108. package/dist/icons/IconDone.svelte.d.ts +26 -0
  109. package/dist/icons/IconError.svelte +3 -0
  110. package/dist/icons/IconError.svelte.d.ts +26 -0
  111. package/dist/icons/IconHelp.svelte +3 -0
  112. package/dist/icons/IconHelp.svelte.d.ts +26 -0
  113. package/dist/icons/IconHide.svelte +3 -0
  114. package/dist/icons/IconHide.svelte.d.ts +26 -0
  115. package/dist/icons/IconInfo.svelte +3 -0
  116. package/dist/icons/IconInfo.svelte.d.ts +26 -0
  117. package/dist/icons/IconLink.svelte +3 -0
  118. package/dist/icons/IconLink.svelte.d.ts +26 -0
  119. package/dist/icons/IconMenuBurger.svelte +3 -0
  120. package/dist/icons/IconMenuBurger.svelte.d.ts +26 -0
  121. package/dist/icons/IconMenuDots.svelte +3 -0
  122. package/dist/icons/IconMenuDots.svelte.d.ts +26 -0
  123. package/dist/icons/IconSearch.svelte +3 -0
  124. package/dist/icons/IconSearch.svelte.d.ts +26 -0
  125. package/dist/icons/IconShow.svelte +3 -0
  126. package/dist/icons/IconShow.svelte.d.ts +26 -0
  127. package/dist/icons/IconSuccess.svelte +3 -0
  128. package/dist/icons/IconSuccess.svelte.d.ts +26 -0
  129. package/dist/icons/IconWarning.svelte +3 -0
  130. package/dist/icons/IconWarning.svelte.d.ts +26 -0
  131. package/dist/icons/index.d.ts +14 -0
  132. package/dist/icons/index.js +14 -0
  133. package/dist/index.d.ts +3 -5
  134. package/dist/index.js +3 -5
  135. package/dist/util/StringOrComponent.svelte +20 -0
  136. package/dist/util/StringOrComponent.svelte.d.ts +8 -0
  137. package/dist/util/StringOrSnippet.svelte +16 -0
  138. package/dist/util/StringOrSnippet.svelte.d.ts +8 -0
  139. package/dist/util/attr.d.ts +5 -0
  140. package/dist/util/attr.js +21 -0
  141. package/dist/util/color.d.ts +51 -0
  142. package/dist/util/color.js +97 -0
  143. package/dist/util/dom.d.ts +15 -0
  144. package/dist/util/dom.js +73 -0
  145. package/dist/util/keyboard.svelte.d.ts +22 -0
  146. package/dist/util/keyboard.svelte.js +161 -0
  147. package/dist/util/locale.d.ts +1 -0
  148. package/dist/util/locale.js +47 -0
  149. package/dist/util/settings.d.ts +4 -0
  150. package/dist/util/settings.js +1 -0
  151. package/package.json +20 -11
  152. package/dist/css/0-layers.css +0 -1
@@ -0,0 +1,444 @@
1
+ <script lang="ts">
2
+ import { getContext, onMount, type Snippet } from "svelte";
3
+ import type { Autocomplete } from "./types.js";
4
+ import Copy from "../icons/IconCopy.svelte";
5
+ import Done from "../icons/IconDone.svelte";
6
+ import Show from "../icons/IconShow.svelte";
7
+ import Hide from "../icons/IconHide.svelte";
8
+ import Tooltip from "../components/Tooltip.svelte";
9
+ import IconButton from "../components/IconButton.svelte";
10
+ import type { LutraForm } from "./types.js";
11
+ import { fieldChange, fieldKeydown, ignoreKeys } from "./client.svelte.js";
12
+ import FieldError from "./FieldError.svelte";
13
+ import { getFromObjWithStringPath } from "./form.js";
14
+ import { ZodType } from "zod";
15
+ import FieldContent from "./FieldContent.svelte";
16
+ import { on } from "svelte/events";
17
+ import { fade } from "svelte/transition";
18
+ import Theme from "../components/Theme.svelte";
19
+
20
+ let {
21
+ alt,
22
+ autofocus,
23
+ autocapitalize,
24
+ autocomplete,
25
+ autocorrect,
26
+ capture,
27
+ checked,
28
+ contained,
29
+ copyable,
30
+ defaultValue,
31
+ disabled,
32
+ enterkeyhint,
33
+ height,
34
+ help,
35
+ id = $bindable(crypto.randomUUID()),
36
+ indeterminate,
37
+ inputmode,
38
+ label,
39
+ labelHelp,
40
+ labelTip,
41
+ list,
42
+ maxlength,
43
+ minlength,
44
+ max,
45
+ min,
46
+ multiple,
47
+ name,
48
+ pattern,
49
+ placeholder,
50
+ suffix,
51
+ onblur,
52
+ onchange,
53
+ onclick,
54
+ onfocus,
55
+ onkeydown,
56
+ onkeyup,
57
+ onkeypress,
58
+ prefix,
59
+ readonly,
60
+ required,
61
+ shape = 'rounded',
62
+ size,
63
+ src,
64
+ step,
65
+ title,
66
+ type,
67
+ unit,
68
+ value = $bindable(''),
69
+ viewable,
70
+ webkitdirectory,
71
+ ...rest
72
+ }: {
73
+ /** alt attribute for the image type. Required for accessibility */
74
+ alt?: string;
75
+ /** Whether the input should be autofocused. */
76
+ autofocus?: boolean;
77
+ /** Whether the input should be autocapitalized. */
78
+ autocapitalize?: boolean | 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';
79
+ /** Specifies whether autocomplete is enabled for the input. */
80
+ autocomplete?: Autocomplete;
81
+ /** Whether the input should be autocorrected. (Safari only) */
82
+ autocorrect?: boolean;
83
+ /** A hint to the browser for which capture method to use. */
84
+ capture?: 'user' | 'environment';
85
+ /** Whether the input should be checked. */
86
+ checked?: boolean;
87
+ /** Whether the input should be contained. */
88
+ contained?: boolean;
89
+ /** Whether the input should be copyable. */
90
+ copyable?: boolean;
91
+ /** The default value of the input element. */
92
+ defaultValue?: string;
93
+ /** Whether the input should be disabled. */
94
+ disabled?: boolean;
95
+ /** A hint to the browser for which enter key to display for the input. */
96
+ enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';
97
+ /** The height of the input element. Valid for image inputs. */
98
+ height?: number;
99
+ /** Help text to display below the input. */
100
+ help?: string | Snippet;
101
+ /** A random id is generated if not provided. */
102
+ id?: string;
103
+ /** Whether the input should be indeterminate. */
104
+ indeterminate?: boolean;
105
+ /** A hint to the browser for which keyboard to display. This is only used for type="text" inputs. Strongly consider using type="email" or type="url" etc. instead */
106
+ inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
107
+ /** The label for the input */
108
+ label?: string | Snippet
109
+ /** Help text to display below the input. */
110
+ labelHelp?: string | Snippet;
111
+ /** Context tooltip for a label. Renders with a questionmark using ContextTip. */
112
+ labelTip?: string | Snippet;
113
+ /** The id of a datalist element that contains pre-defined options for the input element. */
114
+ list?: string;
115
+ /** The maximum number of characters (as UTF-16 code units) the user can enter into the input. Valid for text, search, url, tel, email, and password. */
116
+ maxlength?: number;
117
+ /** 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
+ minlength?: number;
119
+ /** Allow multiple f
120
+ /** The maximum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
121
+ max?: number
122
+ /** The minimum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
123
+ min?: number;
124
+ /** Whether the input should allow multiple values. Valid for email and file inputs. */
125
+ multiple?: boolean;
126
+ /** The name of the input element. */
127
+ name: string;
128
+ /** The onblur event handler */
129
+ onblur?: (e: FocusEvent) => void;
130
+ /** Onchange event handler */
131
+ onchange?: (e: Event) => void;
132
+ /** Onclick event handler */
133
+ onclick?: (e: MouseEvent) => void;
134
+ /** Onfocus event handler */
135
+ onfocus?: (e: FocusEvent) => void;
136
+ /** Keyup event handler */
137
+ onkeyup?: (e: KeyboardEvent) => void;
138
+ /** Keydown event handler */
139
+ onkeydown?: (e: KeyboardEvent) => void;
140
+ /** Keypress event handler */
141
+ onkeypress?: (e: KeyboardEvent) => void;
142
+ /** A regular expression that the input's value is checked against. Valid for text, search, url, tel, email, and password. */
143
+ pattern?: string;
144
+ /** Placeholder text to display when the input is empty. */
145
+ placeholder?: string;
146
+ /** Suffix content, to display after the input. */
147
+ suffix?: string | Snippet;
148
+ /** Prefix content, to display before the input. */
149
+ prefix?: string | Snippet;
150
+ /** Whether the input should be read-only. */
151
+ readonly?: boolean;
152
+ /** Whether the input should be required. */
153
+ required?: boolean;
154
+ /** The shape of the input element. */
155
+ shape?: 'default' | 'rounded' | 'pill' | 'circle';
156
+ /** The size of the input element. */
157
+ size?: number;
158
+ /** Source URL for the image type. */
159
+ src?: string;
160
+ /** A number that specifies the granularity that the value must adhere to. Valid for date, month, week, time, datetime-local, number, and range. */
161
+ step?: number;
162
+ /** A string that defines the title of the input element. */
163
+ title?: string;
164
+ /** The type of input to display. */
165
+ type?: 'button' | 'checkbox' | 'color' | 'date' | 'datetime-local' | 'email' | 'file' | 'hidden' | 'image' | 'month' | 'number' | 'password' | 'radio' | 'range' | 'search' | 'submit' | 'tel' | 'text' | 'time' | 'url' | 'week';
166
+ /** Unit of measurement for range inputs. */
167
+ unit?: string;
168
+ /** The value of the input element. */
169
+ value?: string | number | boolean | null;
170
+ /** Whether the input should be viewable. Valid for password inputs. */
171
+ viewable?: boolean;
172
+ /** Whether the input should only accept directory uploads. */
173
+ webkitdirectory?: boolean;
174
+ } = $props();
175
+
176
+ if(contained === undefined) { contained = getContext('lutra.form.input.contained') ?? getContext('lutra.form.contained') ?? getContext('lutra.contained') ?? false; }
177
+
178
+ let el: HTMLInputElement | undefined = $state();
179
+ let copyTitle = $state('Copy');
180
+ let viewTitle = $state('Show');
181
+ let copyTooltipOpen = $state(false);
182
+ let copyBtnIcon = $state(Copy);
183
+ let viewBtnIcon = $state(Show);
184
+
185
+ // Set defaults from form context
186
+ const form = getContext<LutraForm<any>>('form');
187
+ const field = $derived(form?.fields[name]);
188
+ const issue = $derived(form?.issues?.find((issue) => issue.name === name));
189
+ const validator = getContext<Record<string, ZodType>>('form.validators')?.[name];
190
+ const data = form?.data;
191
+ const originalData = form?.originalData;
192
+
193
+ function view(e: MouseEvent) {
194
+ e.preventDefault();
195
+ if(!el) return;
196
+ if(el.type === 'password') {
197
+ el.type = 'text';
198
+ viewBtnIcon = Hide;
199
+ viewTitle = 'Hide';
200
+ } else {
201
+ el.type = 'password';
202
+ viewBtnIcon = Show;
203
+ viewTitle = 'Show';
204
+ }
205
+ el.focus();
206
+ }
207
+
208
+ function copy(e: MouseEvent) {
209
+ e.preventDefault();
210
+ if(!el) return;
211
+ // try with navigator.clipboard
212
+ if(navigator.clipboard) {
213
+ navigator.clipboard.writeText(value!.toString());
214
+ } else {
215
+ el.focus();
216
+ el.select();
217
+ document.execCommand('copy');
218
+ }
219
+ copyBtnIcon = Done;
220
+ copyTitle = 'Copied!';
221
+ copyTooltipOpen = true;
222
+ setTimeout(() => {
223
+ copyBtnIcon = Copy;
224
+ copyTitle = 'Copy';
225
+ copyTooltipOpen = false;
226
+ }, 1500);
227
+ el!.focus();
228
+ }
229
+
230
+ if(!value) value = form ? (getFromObjWithStringPath(Object.assign(originalData ?? {}, data ?? {}), name) as string) || form?.fields?.[name]?.defaultValue as string : '';
231
+
232
+ onMount(() => {
233
+ if(value) fieldChange(form, name, () => el)({} as any);
234
+ });
235
+
236
+ let focused = $state(false);
237
+ let rangeValueLeft = $derived.by(() => {
238
+ if(!el) return 0;
239
+ value;
240
+ return (el.valueAsNumber - min!) / (max! - min!) * 100;
241
+ });
242
+
243
+ function focus(e: FocusEvent) {
244
+ focused = true;
245
+ if(onfocus) return onfocus(e);
246
+ }
247
+
248
+ function blur(e: FocusEvent) {
249
+ focused = false;
250
+ if(onblur) return onblur(e);
251
+ }
252
+
253
+ </script>
254
+
255
+ {#snippet input()}
256
+ {#if type === 'checkbox'}
257
+ <input
258
+ type="checkbox"
259
+ bind:this={el}
260
+ {alt}
261
+ {autofocus}
262
+ {disabled}
263
+ {enterkeyhint}
264
+ {height}
265
+ {id}
266
+ {indeterminate}
267
+ {inputmode}
268
+ {name}
269
+ onblur={blur}
270
+ {onclick}
271
+ onchange={fieldChange(form, name, () => el, validator, onchange)}
272
+ onfocus={focus}
273
+ onkeydown={fieldKeydown(form, name, () => el, validator, onkeydown)}
274
+ {onkeyup}
275
+ {onkeypress}
276
+ {readonly}
277
+ required={required || field?.required}
278
+ {title}
279
+ bind:checked={value as boolean}
280
+ {...rest}
281
+ />
282
+ {:else}
283
+ <input
284
+ bind:this={el}
285
+ {alt}
286
+ {autofocus}
287
+ autocapitalize={typeof autocapitalize === 'string' ? autocapitalize : (typeof autocapitalize === 'boolean' ? (autocapitalize ? 'on' : 'off') : undefined)}
288
+ {autocomplete}
289
+ autocorrect={typeof autocorrect === 'boolean' ? (autocorrect ? 'on' : 'off') : undefined}
290
+ {capture}
291
+ {checked}
292
+ {disabled}
293
+ {enterkeyhint}
294
+ {height}
295
+ {id}
296
+ {inputmode}
297
+ {list}
298
+ maxlength={maxlength ? maxlength : field?.maxlength}
299
+ minlength={minlength ? minlength : field?.minlength}
300
+ max={max ? max : field?.max}
301
+ min={min ? min : field?.min}
302
+ {multiple}
303
+ {name}
304
+ onblur={blur}
305
+ {onclick}
306
+ onchange={fieldChange(form, name, () => el, validator, onchange)}
307
+ onfocus={focus}
308
+ onkeydown={fieldKeydown(form, name, () => el, validator, onkeydown)}
309
+ {onkeyup}
310
+ {onkeypress}
311
+ pattern={pattern ? pattern : field?.pattern}
312
+ {placeholder}
313
+ {readonly}
314
+ required={required || field?.required}
315
+ {size}
316
+ {src}
317
+ {step}
318
+ {title}
319
+ {type}
320
+ bind:value={value}
321
+ {webkitdirectory}
322
+ {...rest}
323
+ />
324
+ {/if}
325
+ {/snippet}
326
+
327
+ {#if type === 'hidden'}
328
+ {@render input()}
329
+ {:else}
330
+ <FieldContent
331
+ {id}
332
+ {label}
333
+ {labelHelp}
334
+ {labelTip}
335
+ contained={type === "checkbox" || type === "radio" || type === "color" || type === "range" ? false : true}
336
+ direction={(type === "checkbox" || type === "radio") ? 'row' : 'column'}
337
+ {field}
338
+ {issue}
339
+ {type}
340
+ {help}
341
+ {prefix}
342
+ {suffix}
343
+ {required}
344
+ >
345
+
346
+ {#if type === "checkbox" || type === "radio"}
347
+ {@render input()}
348
+ {:else if type === "range"}
349
+ <div class="Range">
350
+ {#if min?.toString().length}
351
+ <span>{min}{unit}</span>
352
+ {/if}
353
+ <div class="RangeInput">
354
+ {@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>
366
+ {/if}
367
+ </div>
368
+ {#if max?.toString().length}
369
+ <span>{max}{unit}</span>
370
+ {/if}
371
+ </div>
372
+ {:else}
373
+
374
+ {@render input()}
375
+
376
+ {#if copyable}
377
+ <Tooltip tip={copyTitle} open={copyTooltipOpen}>
378
+ <IconButton icon={copyBtnIcon} onclick={copy} disabled={copyTooltipOpen} />
379
+ </Tooltip>
380
+ {/if}
381
+
382
+ {#if type === "password" && viewable}
383
+ <Tooltip tip={viewTitle}>
384
+ <IconButton icon={viewBtnIcon} onclick={view} />
385
+ </Tooltip>
386
+ {/if}
387
+
388
+ {/if}
389
+ </FieldContent>
390
+ {/if}
391
+
392
+ <style>
393
+ /**
394
+ * Input element
395
+ */
396
+ input:not([type="checkbox"]):not([type="radio"]) {
397
+ border: none;
398
+ flex-grow: 1;
399
+ flex-shrink: 0;
400
+ }
401
+ input:not([type="checkbox"]):not([type="radio"]):focus-visible,
402
+ input:not([type="checkbox"]):not([type="radio"]):active {
403
+ outline: none;
404
+ }
405
+ input[type="range"] {
406
+ display: block;
407
+ }
408
+ /**
409
+ * Checkbox and radio
410
+ */
411
+ input[type="checkbox"] {
412
+ border-radius: 6px;
413
+ }
414
+ .Range {
415
+ display: flex;
416
+ gap: 0.5em;
417
+ align-items: center;
418
+ }
419
+ .RangeValue {
420
+ position: absolute;
421
+ top: 0;
422
+ font-size: max(0.85em, 9px);
423
+ color: var(--text-subtle);
424
+ background: var(--bg-app);
425
+ padding: 0.15em 0.35em;
426
+ border-radius: var(--border-radius);
427
+ transform: translate(-50%, -125%);
428
+ border: var(--border);
429
+ min-inline-size: 1em;
430
+ box-shadow: 0 0.5rem 1rem var(--shadow);
431
+ font-weight: 600;
432
+ }
433
+ .Range span {
434
+ font-size: 0.75em;
435
+ color: var(--text-subtle);
436
+ }
437
+ .RangeInput {
438
+ position: relative;
439
+ flex-grow: 1;
440
+ }
441
+ .RangeInput :global(input) {
442
+ width: 100%;
443
+ }
444
+ </style>
@@ -0,0 +1,108 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { Autocomplete } from "./types.js";
3
+ type $$ComponentProps = {
4
+ /** alt attribute for the image type. Required for accessibility */
5
+ alt?: string;
6
+ /** Whether the input should be autofocused. */
7
+ autofocus?: boolean;
8
+ /** Whether the input should be autocapitalized. */
9
+ autocapitalize?: boolean | 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';
10
+ /** Specifies whether autocomplete is enabled for the input. */
11
+ autocomplete?: Autocomplete;
12
+ /** Whether the input should be autocorrected. (Safari only) */
13
+ autocorrect?: boolean;
14
+ /** A hint to the browser for which capture method to use. */
15
+ capture?: 'user' | 'environment';
16
+ /** Whether the input should be checked. */
17
+ checked?: boolean;
18
+ /** Whether the input should be contained. */
19
+ contained?: boolean;
20
+ /** Whether the input should be copyable. */
21
+ copyable?: boolean;
22
+ /** The default value of the input element. */
23
+ defaultValue?: string;
24
+ /** Whether the input should be disabled. */
25
+ disabled?: boolean;
26
+ /** A hint to the browser for which enter key to display for the input. */
27
+ enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';
28
+ /** The height of the input element. Valid for image inputs. */
29
+ height?: number;
30
+ /** Help text to display below the input. */
31
+ help?: string | Snippet;
32
+ /** A random id is generated if not provided. */
33
+ id?: string;
34
+ /** Whether the input should be indeterminate. */
35
+ indeterminate?: boolean;
36
+ /** A hint to the browser for which keyboard to display. This is only used for type="text" inputs. Strongly consider using type="email" or type="url" etc. instead */
37
+ inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
38
+ /** The label for the input */
39
+ label?: string | Snippet;
40
+ /** Help text to display below the input. */
41
+ labelHelp?: string | Snippet;
42
+ /** Context tooltip for a label. Renders with a questionmark using ContextTip. */
43
+ labelTip?: string | Snippet;
44
+ /** The id of a datalist element that contains pre-defined options for the input element. */
45
+ list?: string;
46
+ /** The maximum number of characters (as UTF-16 code units) the user can enter into the input. Valid for text, search, url, tel, email, and password. */
47
+ maxlength?: number;
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
+ minlength?: number;
50
+ /** Allow multiple f
51
+ /** The maximum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
52
+ max?: number;
53
+ /** The minimum value of the input element. Valid for date, month, week, time, datetime-local, number, and range. */
54
+ min?: number;
55
+ /** Whether the input should allow multiple values. Valid for email and file inputs. */
56
+ multiple?: boolean;
57
+ /** The name of the input element. */
58
+ name: string;
59
+ /** The onblur event handler */
60
+ onblur?: (e: FocusEvent) => void;
61
+ /** Onchange event handler */
62
+ onchange?: (e: Event) => void;
63
+ /** Onclick event handler */
64
+ onclick?: (e: MouseEvent) => void;
65
+ /** Onfocus event handler */
66
+ onfocus?: (e: FocusEvent) => void;
67
+ /** Keyup event handler */
68
+ onkeyup?: (e: KeyboardEvent) => void;
69
+ /** Keydown event handler */
70
+ onkeydown?: (e: KeyboardEvent) => void;
71
+ /** Keypress event handler */
72
+ onkeypress?: (e: KeyboardEvent) => void;
73
+ /** A regular expression that the input's value is checked against. Valid for text, search, url, tel, email, and password. */
74
+ pattern?: string;
75
+ /** Placeholder text to display when the input is empty. */
76
+ placeholder?: string;
77
+ /** Suffix content, to display after the input. */
78
+ suffix?: string | Snippet;
79
+ /** Prefix content, to display before the input. */
80
+ prefix?: string | Snippet;
81
+ /** Whether the input should be read-only. */
82
+ readonly?: boolean;
83
+ /** Whether the input should be required. */
84
+ required?: boolean;
85
+ /** The shape of the input element. */
86
+ shape?: 'default' | 'rounded' | 'pill' | 'circle';
87
+ /** The size of the input element. */
88
+ size?: number;
89
+ /** Source URL for the image type. */
90
+ src?: string;
91
+ /** A number that specifies the granularity that the value must adhere to. Valid for date, month, week, time, datetime-local, number, and range. */
92
+ step?: number;
93
+ /** A string that defines the title of the input element. */
94
+ title?: string;
95
+ /** The type of input to display. */
96
+ type?: 'button' | 'checkbox' | 'color' | 'date' | 'datetime-local' | 'email' | 'file' | 'hidden' | 'image' | 'month' | 'number' | 'password' | 'radio' | 'range' | 'search' | 'submit' | 'tel' | 'text' | 'time' | 'url' | 'week';
97
+ /** Unit of measurement for range inputs. */
98
+ unit?: string;
99
+ /** The value of the input element. */
100
+ value?: string | number | boolean | null;
101
+ /** Whether the input should be viewable. Valid for password inputs. */
102
+ viewable?: boolean;
103
+ /** Whether the input should only accept directory uploads. */
104
+ webkitdirectory?: boolean;
105
+ };
106
+ declare const Input: import("svelte").Component<$$ComponentProps, {}, "id" | "value">;
107
+ type Input = ReturnType<typeof Input>;
108
+ export default Input;
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ /**
3
+ * @description
4
+ * Internal: A component that displays the length of an input field. Used by the Input and Textarea components.
5
+ */
6
+ let {
7
+ value,
8
+ maxlength,
9
+ }: {
10
+ /** The value of the input. */
11
+ value: string;
12
+ /** The maximum length of the input. */
13
+ maxlength: number;
14
+ } = $props();
15
+ </script>
16
+
17
+ {#if maxlength}
18
+ <div class="InputLength">
19
+ <div class="Length" class:warn={(value?.length || 0) > maxlength - 5}>
20
+ {value?.length || 0}/{maxlength}
21
+ </div>
22
+ </div>
23
+ {/if}
24
+
25
+ <style>
26
+ .InputLength {
27
+ display: flex;
28
+ justify-content: flex-end;
29
+ align-items: center;
30
+ font-size: 0.8rem;
31
+ font-weight: 500;
32
+ color: var(--text-subtle);
33
+ }
34
+ .InputLength .Length {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 0.25rem;
38
+ }
39
+ .InputLength .Length.warn {
40
+ color: var(--text-color-warn, light-dark(red, red));
41
+ }
42
+ </style>
@@ -0,0 +1,9 @@
1
+ type $$ComponentProps = {
2
+ /** The value of the input. */
3
+ value: string;
4
+ /** The maximum length of the input. */
5
+ maxlength: number;
6
+ };
7
+ declare const InputLength: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type InputLength = ReturnType<typeof InputLength>;
9
+ export default InputLength;
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ import ContextTip from "../components/ContextTip.svelte";
3
+ import StringOrSnippet from "../util/StringOrSnippet.svelte";
4
+ import type { Snippet } from "svelte";
5
+
6
+ /**
7
+ * @description
8
+ * The label component is used to create a label for an input element.
9
+ * @example
10
+ * <Label label="Name" />
11
+ * {#snippet l()}
12
+ * <Label label={l} />
13
+ * {/snippet}
14
+ */
15
+ let {
16
+ id,
17
+ label,
18
+ help,
19
+ tip,
20
+ required,
21
+ }: {
22
+ /** The id of the input element */
23
+ id?: string;
24
+ /** The label for the input */
25
+ label?: string | Snippet;
26
+ /** The help text for the input */
27
+ tip?: string | Snippet;
28
+ /** The help text for the input */
29
+ help?: string | Snippet;
30
+ /** Whether the input is required */
31
+ required?: boolean;
32
+ } = $props();
33
+
34
+ </script>
35
+
36
+ {#if label}
37
+ <label for={id} class:hasTip={!!tip} class:hasHelp={!!help}>
38
+ {#if typeof label === 'string'}
39
+ <span>
40
+ {label} {#if required}<span aria-hidden="true">*</span>{/if}
41
+ </span>
42
+ {:else}
43
+ <span>
44
+ {@render label()}
45
+ </span>
46
+ {/if}
47
+ {#if help}
48
+ <span class="Help">
49
+ <StringOrSnippet content={help} />
50
+ </span>
51
+ {/if}
52
+ {#if tip}
53
+ <ContextTip {tip} />
54
+ {/if}
55
+ </label>
56
+ {/if}
57
+
58
+ <style>
59
+ label {
60
+ cursor: var(--cursor, default);
61
+ text-wrap: balance;
62
+ display: inline-grid;
63
+ grid-template-columns: 1fr;
64
+ gap: var(--form-label-gap, var(--space-md));
65
+ align-items: center;
66
+ }
67
+ label.hasTip,
68
+ label.hasHelp {
69
+ grid-template-columns: 1fr auto;
70
+ }
71
+ label.hasHelp.hasTip {
72
+ grid-template-columns: 1fr auto auto;
73
+ }
74
+ label > span > span {
75
+ font-weight: 600;
76
+ color: var(--text-color-warn, light-dark(red, red));
77
+ padding-inline: 0.175em;
78
+ }
79
+ .Help {
80
+ font-weight: var(--font-weight-normal);
81
+ }
82
+ .Help :global(a) {
83
+ color: var(--link-color);
84
+ }
85
+ .Help :global(a:hover) {
86
+ color: var(--link-color-hover);
87
+ }
88
+ </style>