noph-ui 0.26.12 → 0.26.14
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/autocomplete/AutoComplete.svelte +0 -4
- package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
- package/dist/menu/Menu.svelte +1 -1
- package/dist/select/NativeSelect.svelte +30 -40
- package/dist/select/NativeSelect.svelte.d.ts +3 -2
- package/dist/select/Select.svelte +22 -92
- package/dist/select/Select.svelte.d.ts +1 -1
- package/dist/select/types.d.ts +3 -4
- package/dist/text-field/TextField.svelte +64 -153
- package/dist/text-field/TextField.svelte.d.ts +1 -1
- package/dist/text-field/types.d.ts +3 -4
- package/package.json +8 -8
- package/dist/text-field/report-validity.d.ts +0 -1
- package/dist/text-field/report-validity.js +0 -6
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
variant = 'outlined',
|
|
12
12
|
element = $bindable(),
|
|
13
13
|
populated,
|
|
14
|
-
reportValidity = $bindable(),
|
|
15
|
-
checkValidity = $bindable(),
|
|
16
14
|
clampMenuWidth = false,
|
|
17
15
|
children,
|
|
18
16
|
optionsFilter,
|
|
@@ -195,8 +193,6 @@
|
|
|
195
193
|
}
|
|
196
194
|
onkeydown?.(event)
|
|
197
195
|
}}
|
|
198
|
-
bind:reportValidity
|
|
199
|
-
bind:checkValidity
|
|
200
196
|
bind:element
|
|
201
197
|
>{@render children?.()}
|
|
202
198
|
</TextField>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AutoCompleteProps } from './types.ts';
|
|
2
|
-
declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value"
|
|
2
|
+
declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value">;
|
|
3
3
|
type AutoComplete = ReturnType<typeof AutoComplete>;
|
|
4
4
|
export default AutoComplete;
|
package/dist/menu/Menu.svelte
CHANGED
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
label?: string
|
|
5
5
|
noAsterisk?: boolean
|
|
6
6
|
supportingText?: string
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
issues?:
|
|
8
|
+
| {
|
|
9
|
+
message: string
|
|
10
|
+
}[]
|
|
11
|
+
| undefined
|
|
9
12
|
variant?: 'outlined' | 'filled'
|
|
10
13
|
element?: HTMLSpanElement
|
|
11
14
|
}
|
|
12
15
|
let {
|
|
13
16
|
id,
|
|
14
17
|
supportingText,
|
|
15
|
-
|
|
16
|
-
errorText,
|
|
18
|
+
issues,
|
|
17
19
|
variant = 'outlined',
|
|
18
20
|
value = $bindable(),
|
|
19
21
|
label,
|
|
@@ -21,30 +23,26 @@
|
|
|
21
23
|
required,
|
|
22
24
|
noAsterisk,
|
|
23
25
|
children,
|
|
24
|
-
|
|
25
|
-
oninvalid,
|
|
26
|
+
oninput,
|
|
26
27
|
...attributes
|
|
27
28
|
}: SelectProps = $props()
|
|
28
29
|
const uid = $props.id()
|
|
29
30
|
const selectId = id ?? `select-${uid}`
|
|
30
31
|
|
|
31
32
|
let animateLabel = $state(false)
|
|
32
|
-
let
|
|
33
|
-
let errorRaw = $derived(error)
|
|
33
|
+
let errorText = $derived(issues?.map((issue) => issue.message).join(', '))
|
|
34
34
|
</script>
|
|
35
35
|
|
|
36
36
|
<div
|
|
37
37
|
class={[
|
|
38
38
|
'np-select-container',
|
|
39
39
|
variant,
|
|
40
|
-
errorRaw && 'error',
|
|
41
40
|
disabled && 'disabled',
|
|
42
41
|
required && !noAsterisk && 'asterisk',
|
|
43
42
|
(value === null || value === undefined || value === '') && 'is-empty',
|
|
44
43
|
animateLabel && 'animate-label',
|
|
45
44
|
attributes.class,
|
|
46
45
|
]}
|
|
47
|
-
aria-disabled={disabled}
|
|
48
46
|
>
|
|
49
47
|
{#if variant === 'outlined'}
|
|
50
48
|
<div class="np-select-outline">
|
|
@@ -77,34 +75,22 @@
|
|
|
77
75
|
) {
|
|
78
76
|
animateLabel = true
|
|
79
77
|
}
|
|
80
|
-
|
|
81
|
-
}}
|
|
82
|
-
oninvalid={(event) => {
|
|
83
|
-
event.preventDefault()
|
|
84
|
-
const { currentTarget } = event
|
|
85
|
-
errorRaw = true
|
|
86
|
-
if (errorText === undefined) {
|
|
87
|
-
errorTextRaw = currentTarget.validationMessage
|
|
88
|
-
}
|
|
89
|
-
oninvalid?.(event)
|
|
78
|
+
oninput?.(event)
|
|
90
79
|
}}
|
|
91
80
|
{disabled}
|
|
92
81
|
{required}
|
|
93
82
|
id={selectId}
|
|
94
|
-
aria-
|
|
95
|
-
aria-
|
|
96
|
-
aria-describedby={supportingText && (!errorTextRaw || !errorRaw)
|
|
97
|
-
? `supporting-text-${uid}`
|
|
98
|
-
: undefined}
|
|
83
|
+
aria-errormessage={errorText ? `supporting-text-${uid}` : undefined}
|
|
84
|
+
aria-describedby={supportingText && !errorText ? `supporting-text-${uid}` : undefined}
|
|
99
85
|
bind:value
|
|
100
86
|
{...attributes}
|
|
101
87
|
class="np-select"
|
|
102
88
|
>
|
|
103
89
|
{@render children?.()}
|
|
104
90
|
</select>
|
|
105
|
-
{#if supportingText ||
|
|
106
|
-
<div id="supporting-text-{uid}" class="supporting-text" role={
|
|
107
|
-
{
|
|
91
|
+
{#if supportingText || errorText}
|
|
92
|
+
<div id="supporting-text-{uid}" class="supporting-text" role={errorText ? 'alert' : undefined}>
|
|
93
|
+
{errorText ?? supportingText}
|
|
108
94
|
</div>
|
|
109
95
|
{/if}
|
|
110
96
|
</div>
|
|
@@ -392,27 +378,31 @@
|
|
|
392
378
|
padding: 0.25rem 1rem 0;
|
|
393
379
|
}
|
|
394
380
|
|
|
395
|
-
.
|
|
396
|
-
.
|
|
397
|
-
.
|
|
398
|
-
.
|
|
381
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])) .supporting-text,
|
|
382
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])) label,
|
|
383
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])) .arrow,
|
|
384
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])):focus-within label {
|
|
399
385
|
color: var(--np-color-error);
|
|
400
386
|
}
|
|
401
387
|
|
|
402
|
-
.
|
|
403
|
-
.
|
|
388
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])):hover label,
|
|
389
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])):hover .arrow {
|
|
404
390
|
color: var(--np-color-on-error-container);
|
|
405
391
|
}
|
|
406
392
|
|
|
407
|
-
.
|
|
408
|
-
.
|
|
409
|
-
|
|
410
|
-
.
|
|
393
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])) .np-select-outline,
|
|
394
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])):focus-within
|
|
395
|
+
.np-select-outline,
|
|
396
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])) .np-select-filled,
|
|
397
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])):focus-within
|
|
398
|
+
.np-select-filled {
|
|
411
399
|
border-color: var(--np-color-error);
|
|
412
400
|
}
|
|
413
401
|
|
|
414
|
-
.
|
|
415
|
-
|
|
402
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])):hover
|
|
403
|
+
.np-select-outline,
|
|
404
|
+
.np-select-container:has(.np-select:is(:user-invalid, [aria-invalid='true'])):hover
|
|
405
|
+
.np-select-filled {
|
|
416
406
|
border-color: var(--np-color-on-error-container);
|
|
417
407
|
}
|
|
418
408
|
|
|
@@ -3,8 +3,9 @@ interface SelectProps extends HTMLSelectAttributes {
|
|
|
3
3
|
label?: string;
|
|
4
4
|
noAsterisk?: boolean;
|
|
5
5
|
supportingText?: string;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
issues?: {
|
|
7
|
+
message: string;
|
|
8
|
+
}[] | undefined;
|
|
8
9
|
variant?: 'outlined' | 'filled';
|
|
9
10
|
element?: HTMLSpanElement;
|
|
10
11
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Menu from '../menu/Menu.svelte'
|
|
3
|
-
import { isFirstInvalidControlInForm } from '../text-field/report-validity.js'
|
|
4
3
|
import type { SelectOption, SelectProps } from './types.ts'
|
|
5
4
|
import Item from '../list/Item.svelte'
|
|
6
5
|
import { tick } from 'svelte'
|
|
@@ -10,8 +9,7 @@
|
|
|
10
9
|
let {
|
|
11
10
|
options = [],
|
|
12
11
|
value = $bindable(),
|
|
13
|
-
|
|
14
|
-
errorText = '',
|
|
12
|
+
issues,
|
|
15
13
|
supportingText = '',
|
|
16
14
|
tabindex = 0,
|
|
17
15
|
start,
|
|
@@ -29,8 +27,6 @@
|
|
|
29
27
|
autofocus,
|
|
30
28
|
onchange,
|
|
31
29
|
oninput,
|
|
32
|
-
reportValidity = $bindable(),
|
|
33
|
-
checkValidity = $bindable(),
|
|
34
30
|
multiple,
|
|
35
31
|
virtualThreshold = 300,
|
|
36
32
|
clampMenuWidth = false,
|
|
@@ -38,7 +34,6 @@
|
|
|
38
34
|
}: SelectProps = $props()
|
|
39
35
|
|
|
40
36
|
const uid = $props.id()
|
|
41
|
-
let doValidity = $state(false)
|
|
42
37
|
if (value === undefined) {
|
|
43
38
|
if (multiple) {
|
|
44
39
|
value = options.filter((option) => option.selected).map((option) => option.value)
|
|
@@ -59,8 +54,7 @@
|
|
|
59
54
|
|
|
60
55
|
let widthProp = $derived(clampMenuWidth || useVirtualList ? 'width' : 'min-width')
|
|
61
56
|
|
|
62
|
-
let
|
|
63
|
-
let errorRaw = $state(error)
|
|
57
|
+
let errorText = $derived(issues?.map((i) => i.message).join(', '))
|
|
64
58
|
let selectElement = $state<HTMLSelectElement>()
|
|
65
59
|
let menuElement = $state<HTMLDivElement>()
|
|
66
60
|
let anchorElement = $state<HTMLDivElement>()
|
|
@@ -94,44 +88,6 @@
|
|
|
94
88
|
return options.find((option) => option.value === value)?.label || ''
|
|
95
89
|
})
|
|
96
90
|
|
|
97
|
-
reportValidity = () => {
|
|
98
|
-
if (selectElement) {
|
|
99
|
-
const valid = selectElement.reportValidity()
|
|
100
|
-
if (valid) {
|
|
101
|
-
errorRaw = error
|
|
102
|
-
errorTextRaw = errorText
|
|
103
|
-
}
|
|
104
|
-
return valid
|
|
105
|
-
}
|
|
106
|
-
return false
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
checkValidity = () => {
|
|
110
|
-
if (selectElement) {
|
|
111
|
-
return selectElement.checkValidity()
|
|
112
|
-
}
|
|
113
|
-
return false
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
$effect(() => {
|
|
117
|
-
errorRaw = error
|
|
118
|
-
errorTextRaw = errorText
|
|
119
|
-
selectElement?.setCustomValidity(error ? errorText : '')
|
|
120
|
-
})
|
|
121
|
-
const onReset = () => {
|
|
122
|
-
errorRaw = error
|
|
123
|
-
}
|
|
124
|
-
$effect(() => {
|
|
125
|
-
if (selectElement) {
|
|
126
|
-
selectElement.form?.addEventListener('reset', onReset)
|
|
127
|
-
}
|
|
128
|
-
return () => {
|
|
129
|
-
if (selectElement) {
|
|
130
|
-
selectElement.form?.removeEventListener('reset', onReset)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
91
|
let cachedRowHeight = 0
|
|
136
92
|
const ensureRowHeight = () => {
|
|
137
93
|
if (!cachedRowHeight && menuElement) {
|
|
@@ -158,14 +114,6 @@
|
|
|
158
114
|
else if (bottom > scrollTop + clientHeight) viewport.scrollTop = bottom - clientHeight
|
|
159
115
|
}
|
|
160
116
|
|
|
161
|
-
const finalizeSelection = async () => {
|
|
162
|
-
await tick()
|
|
163
|
-
if (doValidity && checkValidity()) {
|
|
164
|
-
errorRaw = error
|
|
165
|
-
errorTextRaw = errorText
|
|
166
|
-
}
|
|
167
|
-
selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
168
|
-
}
|
|
169
117
|
const toggleValue = (option: SelectOption) => {
|
|
170
118
|
if (multiple) {
|
|
171
119
|
let arr = Array.isArray(value) ? [...value] : []
|
|
@@ -185,7 +133,8 @@
|
|
|
185
133
|
toggleValue(option)
|
|
186
134
|
if (!multiple) menuElement?.hidePopover()
|
|
187
135
|
event.preventDefault()
|
|
188
|
-
await
|
|
136
|
+
await tick()
|
|
137
|
+
selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
189
138
|
}
|
|
190
139
|
|
|
191
140
|
const openMenuAndFocus = async (index: number) => {
|
|
@@ -262,23 +211,6 @@
|
|
|
262
211
|
performTypeahead('')
|
|
263
212
|
}
|
|
264
213
|
}
|
|
265
|
-
|
|
266
|
-
const handleInvalid = (
|
|
267
|
-
event: Event & {
|
|
268
|
-
currentTarget: EventTarget & HTMLSelectElement
|
|
269
|
-
},
|
|
270
|
-
) => {
|
|
271
|
-
event.preventDefault()
|
|
272
|
-
const { currentTarget } = event
|
|
273
|
-
errorRaw = true
|
|
274
|
-
doValidity = true
|
|
275
|
-
if (errorText === '') {
|
|
276
|
-
errorTextRaw = currentTarget.validationMessage
|
|
277
|
-
}
|
|
278
|
-
if (isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
|
|
279
|
-
field?.focus()
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
214
|
</script>
|
|
283
215
|
|
|
284
216
|
{#snippet arrows()}
|
|
@@ -301,7 +233,6 @@
|
|
|
301
233
|
<div
|
|
302
234
|
{id}
|
|
303
235
|
class="field"
|
|
304
|
-
class:error={errorRaw}
|
|
305
236
|
class:no-label={!label?.length}
|
|
306
237
|
class:with-start={start}
|
|
307
238
|
class:menu-open={menuOpen}
|
|
@@ -416,7 +347,7 @@
|
|
|
416
347
|
{#if multiple}
|
|
417
348
|
<select
|
|
418
349
|
tabindex="-1"
|
|
419
|
-
aria-invalid={
|
|
350
|
+
aria-invalid={attributes['aria-invalid']}
|
|
420
351
|
{disabled}
|
|
421
352
|
{required}
|
|
422
353
|
{name}
|
|
@@ -424,7 +355,6 @@
|
|
|
424
355
|
multiple
|
|
425
356
|
{onchange}
|
|
426
357
|
{oninput}
|
|
427
|
-
oninvalid={handleInvalid}
|
|
428
358
|
bind:value
|
|
429
359
|
bind:this={selectElement}
|
|
430
360
|
>
|
|
@@ -437,13 +367,13 @@
|
|
|
437
367
|
{:else}
|
|
438
368
|
<select
|
|
439
369
|
tabindex="-1"
|
|
370
|
+
aria-invalid={attributes['aria-invalid']}
|
|
440
371
|
{disabled}
|
|
441
372
|
{required}
|
|
442
373
|
{name}
|
|
443
374
|
{form}
|
|
444
375
|
{onchange}
|
|
445
376
|
{oninput}
|
|
446
|
-
oninvalid={handleInvalid}
|
|
447
377
|
bind:value
|
|
448
378
|
bind:this={selectElement}
|
|
449
379
|
>
|
|
@@ -470,10 +400,10 @@
|
|
|
470
400
|
</div>
|
|
471
401
|
</div>
|
|
472
402
|
</div>
|
|
473
|
-
{#if supportingText ||
|
|
474
|
-
<div class="supporting-text" role={
|
|
403
|
+
{#if supportingText || errorText}
|
|
404
|
+
<div class="supporting-text" role={errorText ? 'alert' : undefined}>
|
|
475
405
|
<span>
|
|
476
|
-
{
|
|
406
|
+
{errorText ?? supportingText}
|
|
477
407
|
</span>
|
|
478
408
|
</div>
|
|
479
409
|
{/if}
|
|
@@ -651,10 +581,10 @@
|
|
|
651
581
|
border-bottom-color: var(--np-color-primary);
|
|
652
582
|
border-bottom-width: 3px;
|
|
653
583
|
}
|
|
654
|
-
.
|
|
584
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])) .active-indicator::before {
|
|
655
585
|
border-bottom-color: var(--np-color-error);
|
|
656
586
|
}
|
|
657
|
-
.
|
|
587
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])) .active-indicator::after {
|
|
658
588
|
border-bottom-color: var(--np-color-error);
|
|
659
589
|
}
|
|
660
590
|
.disabled .active-indicator::before {
|
|
@@ -752,7 +682,7 @@
|
|
|
752
682
|
justify-content: space-between;
|
|
753
683
|
padding: 0.25rem 1rem 0;
|
|
754
684
|
}
|
|
755
|
-
.
|
|
685
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])) .supporting-text {
|
|
756
686
|
color: var(--np-color-error);
|
|
757
687
|
}
|
|
758
688
|
.disabled .supporting-text {
|
|
@@ -840,8 +770,8 @@
|
|
|
840
770
|
opacity: 1;
|
|
841
771
|
}
|
|
842
772
|
|
|
843
|
-
.field:not(
|
|
844
|
-
.field:not(
|
|
773
|
+
.field:not(:has(select:is(:user-invalid, [aria-invalid='true']))).menu-open .down,
|
|
774
|
+
.field:not(:has(select:is(:user-invalid, [aria-invalid='true']))):focus .down {
|
|
845
775
|
color: var(--np-color-primary);
|
|
846
776
|
}
|
|
847
777
|
.icon .down {
|
|
@@ -886,8 +816,8 @@
|
|
|
886
816
|
margin-inline-start: 1rem;
|
|
887
817
|
margin-inline-end: 0.75rem;
|
|
888
818
|
}
|
|
889
|
-
.
|
|
890
|
-
.
|
|
819
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])) .start,
|
|
820
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])) .end {
|
|
891
821
|
color: var(--np-color-error);
|
|
892
822
|
}
|
|
893
823
|
.disabled .start,
|
|
@@ -1006,9 +936,9 @@
|
|
|
1006
936
|
.field:focus .label {
|
|
1007
937
|
color: var(--np-color-primary);
|
|
1008
938
|
}
|
|
1009
|
-
.
|
|
1010
|
-
.
|
|
1011
|
-
.
|
|
939
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])) .label,
|
|
940
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])).menu-open .label,
|
|
941
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])):focus .label {
|
|
1012
942
|
color: var(--np-color-error);
|
|
1013
943
|
}
|
|
1014
944
|
.disabled .label {
|
|
@@ -1162,9 +1092,9 @@
|
|
|
1162
1092
|
border-color: var(--np-color-primary);
|
|
1163
1093
|
color: var(--np-color-primary);
|
|
1164
1094
|
}
|
|
1165
|
-
.
|
|
1166
|
-
.
|
|
1167
|
-
.
|
|
1095
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])) .np-outline,
|
|
1096
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])).menu-open .np-outline,
|
|
1097
|
+
.field:has(select:is(:user-invalid, [aria-invalid='true'])):focus .np-outline {
|
|
1168
1098
|
border-color: var(--np-color-error);
|
|
1169
1099
|
}
|
|
1170
1100
|
.disabled .np-outline {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { SelectProps } from './types.ts';
|
|
2
|
-
declare const Select: import("svelte").Component<SelectProps, {}, "element" | "value"
|
|
2
|
+
declare const Select: import("svelte").Component<SelectProps, {}, "element" | "value">;
|
|
3
3
|
type Select = ReturnType<typeof Select>;
|
|
4
4
|
export default Select;
|
package/dist/select/types.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ export interface SelectOption {
|
|
|
9
9
|
export interface SelectProps extends Omit<HTMLSelectAttributes, 'size' | 'autocomplete'> {
|
|
10
10
|
label?: string;
|
|
11
11
|
supportingText?: string;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
issues?: {
|
|
13
|
+
message: string;
|
|
14
|
+
}[];
|
|
14
15
|
variant?: 'outlined' | 'filled';
|
|
15
16
|
start?: Snippet;
|
|
16
17
|
end?: Snippet;
|
|
@@ -18,7 +19,5 @@ export interface SelectProps extends Omit<HTMLSelectAttributes, 'size' | 'autoco
|
|
|
18
19
|
element?: HTMLSpanElement;
|
|
19
20
|
options: SelectOption[];
|
|
20
21
|
clampMenuWidth?: boolean;
|
|
21
|
-
reportValidity?: () => boolean;
|
|
22
|
-
checkValidity?: () => boolean;
|
|
23
22
|
virtualThreshold?: number;
|
|
24
23
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { isFirstInvalidControlInForm } from './report-validity.js'
|
|
3
|
-
import type { FocusEventHandler, EventHandler } from 'svelte/elements'
|
|
4
2
|
import type { TextFieldProps } from './types.ts'
|
|
5
3
|
|
|
6
4
|
let {
|
|
7
5
|
value = $bindable(),
|
|
8
|
-
|
|
9
|
-
errorText = '',
|
|
6
|
+
issues,
|
|
10
7
|
prefixText = '',
|
|
11
8
|
suffixText = '',
|
|
12
9
|
supportingText = '',
|
|
@@ -19,13 +16,8 @@
|
|
|
19
16
|
element = $bindable(),
|
|
20
17
|
populated = false,
|
|
21
18
|
inputElement = $bindable(),
|
|
22
|
-
|
|
23
|
-
checkValidity = $bindable(),
|
|
19
|
+
placeholder = ' ',
|
|
24
20
|
children,
|
|
25
|
-
oninput,
|
|
26
|
-
oninvalid,
|
|
27
|
-
onfocus,
|
|
28
|
-
onblur,
|
|
29
21
|
focused = $bindable(false),
|
|
30
22
|
clientWidth = $bindable(),
|
|
31
23
|
clientHeight = $bindable(),
|
|
@@ -33,106 +25,7 @@
|
|
|
33
25
|
}: TextFieldProps = $props()
|
|
34
26
|
|
|
35
27
|
const uid = $props.id()
|
|
36
|
-
|
|
37
|
-
let errorRaw: boolean = $state(error)
|
|
38
|
-
let errorTextRaw: string = $state(errorText)
|
|
39
|
-
let focusOnInvalid = $state(true)
|
|
40
|
-
let doValidity = $state(false)
|
|
41
|
-
|
|
42
|
-
reportValidity = () => {
|
|
43
|
-
if (inputElement) {
|
|
44
|
-
const valid = inputElement.reportValidity()
|
|
45
|
-
if (valid) {
|
|
46
|
-
errorRaw = error
|
|
47
|
-
errorTextRaw = errorText
|
|
48
|
-
}
|
|
49
|
-
return valid
|
|
50
|
-
}
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
checkValidity = () => {
|
|
55
|
-
if (inputElement) {
|
|
56
|
-
return inputElement.checkValidity()
|
|
57
|
-
}
|
|
58
|
-
return false
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
$effect(() => {
|
|
62
|
-
errorRaw = error
|
|
63
|
-
errorTextRaw = errorText
|
|
64
|
-
inputElement?.setCustomValidity(error ? errorText : '')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const onReset = () => {
|
|
68
|
-
errorRaw = error
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const onInputEvent = (
|
|
72
|
-
event: Event & {
|
|
73
|
-
currentTarget: (EventTarget & HTMLInputElement) | HTMLTextAreaElement
|
|
74
|
-
},
|
|
75
|
-
) => {
|
|
76
|
-
doValidity = true
|
|
77
|
-
;(oninput as EventHandler)?.(event)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const onInvalidEvent = (
|
|
81
|
-
event: Event & {
|
|
82
|
-
currentTarget: HTMLInputElement | HTMLTextAreaElement
|
|
83
|
-
},
|
|
84
|
-
) => {
|
|
85
|
-
event.preventDefault()
|
|
86
|
-
const { currentTarget } = event
|
|
87
|
-
errorRaw = true
|
|
88
|
-
if (errorText === '') {
|
|
89
|
-
errorTextRaw = currentTarget.validationMessage
|
|
90
|
-
}
|
|
91
|
-
if (focusOnInvalid && isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
|
|
92
|
-
currentTarget.focus()
|
|
93
|
-
}
|
|
94
|
-
;(oninvalid as EventHandler)?.(event)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const onFocusEvent = (
|
|
98
|
-
event: FocusEvent & {
|
|
99
|
-
currentTarget: EventTarget & (HTMLInputElement | HTMLTextAreaElement)
|
|
100
|
-
},
|
|
101
|
-
) => {
|
|
102
|
-
focused = true
|
|
103
|
-
;(onfocus as FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>)?.(event)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const onBlurEvent = (
|
|
107
|
-
event: FocusEvent & {
|
|
108
|
-
currentTarget: EventTarget & (HTMLInputElement | HTMLTextAreaElement)
|
|
109
|
-
},
|
|
110
|
-
) => {
|
|
111
|
-
focused = false
|
|
112
|
-
if (doValidity) {
|
|
113
|
-
focusOnInvalid = false
|
|
114
|
-
if (checkValidity()) {
|
|
115
|
-
errorRaw = error
|
|
116
|
-
errorTextRaw = errorText
|
|
117
|
-
} else {
|
|
118
|
-
focusOnInvalid = true
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
focusOnInvalid = true
|
|
122
|
-
}
|
|
123
|
-
;(onblur as FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>)?.(event)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
$effect(() => {
|
|
127
|
-
if (inputElement) {
|
|
128
|
-
inputElement.form?.addEventListener('reset', onReset)
|
|
129
|
-
}
|
|
130
|
-
return () => {
|
|
131
|
-
if (inputElement) {
|
|
132
|
-
inputElement.form?.removeEventListener('reset', onReset)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
})
|
|
28
|
+
let errorText = $derived(issues?.map((issue) => issue.message).join(', '))
|
|
136
29
|
</script>
|
|
137
30
|
|
|
138
31
|
<label
|
|
@@ -149,14 +42,12 @@
|
|
|
149
42
|
>
|
|
150
43
|
<div
|
|
151
44
|
class="field"
|
|
152
|
-
class:error={errorRaw}
|
|
153
45
|
class:resizable={attributes.type === 'textarea'}
|
|
154
46
|
class:no-label={!label?.length}
|
|
155
47
|
class:with-start={start}
|
|
156
48
|
class:with-end={end}
|
|
157
49
|
class:disabled={attributes.disabled}
|
|
158
|
-
class:populated
|
|
159
|
-
class:focused
|
|
50
|
+
class:populated
|
|
160
51
|
class:outlined={variant === 'outlined'}
|
|
161
52
|
>
|
|
162
53
|
<div class="container-overflow">
|
|
@@ -206,16 +97,13 @@
|
|
|
206
97
|
<div class="content">
|
|
207
98
|
{#if attributes.type === 'textarea'}
|
|
208
99
|
<textarea
|
|
209
|
-
aria-errormessage={
|
|
210
|
-
aria-describedby={supportingText &&
|
|
100
|
+
aria-errormessage={errorText ? `supporting-text-${uid}` : undefined}
|
|
101
|
+
aria-describedby={supportingText && !errorText
|
|
211
102
|
? `supporting-text-${uid}`
|
|
212
103
|
: undefined}
|
|
213
104
|
{...attributes}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
oninvalid={onInvalidEvent}
|
|
217
|
-
onfocus={onFocusEvent}
|
|
218
|
-
onblur={onBlurEvent}
|
|
105
|
+
{placeholder}
|
|
106
|
+
bind:focused
|
|
219
107
|
bind:value
|
|
220
108
|
bind:this={inputElement}
|
|
221
109
|
class="input"
|
|
@@ -229,20 +117,14 @@
|
|
|
229
117
|
</span>
|
|
230
118
|
{/if}
|
|
231
119
|
<input
|
|
232
|
-
aria-errormessage={
|
|
233
|
-
|
|
234
|
-
: undefined}
|
|
235
|
-
aria-describedby={supportingText && (!errorTextRaw || !errorRaw)
|
|
120
|
+
aria-errormessage={errorText ? `supporting-text-${uid}` : undefined}
|
|
121
|
+
aria-describedby={supportingText && !errorText
|
|
236
122
|
? `supporting-text-${uid}`
|
|
237
123
|
: undefined}
|
|
238
124
|
{...attributes}
|
|
125
|
+
{placeholder}
|
|
239
126
|
bind:value
|
|
240
127
|
bind:this={inputElement}
|
|
241
|
-
aria-invalid={errorRaw}
|
|
242
|
-
oninput={onInputEvent}
|
|
243
|
-
oninvalid={onInvalidEvent}
|
|
244
|
-
onfocus={onFocusEvent}
|
|
245
|
-
onblur={onBlurEvent}
|
|
246
128
|
class="input"
|
|
247
129
|
/>
|
|
248
130
|
{@render children?.()}
|
|
@@ -262,10 +144,10 @@
|
|
|
262
144
|
{/if}
|
|
263
145
|
</div>
|
|
264
146
|
</div>
|
|
265
|
-
{#if supportingText ||
|
|
266
|
-
<div class="supporting-text" role={
|
|
147
|
+
{#if supportingText || errorText || attributes.maxlength}
|
|
148
|
+
<div class="supporting-text" role={errorText ? 'alert' : undefined}>
|
|
267
149
|
<span id="supporting-text-{uid}">
|
|
268
|
-
{
|
|
150
|
+
{errorText ?? supportingText}
|
|
269
151
|
</span>
|
|
270
152
|
{#if attributes.maxlength}
|
|
271
153
|
<span>{value?.length || 0}/{attributes.maxlength}</span>
|
|
@@ -306,13 +188,16 @@
|
|
|
306
188
|
);
|
|
307
189
|
border-bottom-width: 3px;
|
|
308
190
|
}
|
|
309
|
-
.
|
|
191
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])) .active-indicator::before,
|
|
192
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])) .active-indicator::before {
|
|
310
193
|
border-bottom-color: var(--np-color-error);
|
|
311
194
|
}
|
|
312
|
-
.
|
|
195
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])) .active-indicator::after,
|
|
196
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])) .active-indicator::after {
|
|
313
197
|
border-bottom-color: var(--np-color-error);
|
|
314
198
|
}
|
|
315
|
-
.
|
|
199
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])):hover .active-indicator::after,
|
|
200
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])):hover .active-indicator::after {
|
|
316
201
|
border-bottom-color: var(--np-color-on-error-container);
|
|
317
202
|
}
|
|
318
203
|
.disabled .active-indicator::before {
|
|
@@ -412,7 +297,8 @@
|
|
|
412
297
|
justify-content: space-between;
|
|
413
298
|
padding: 0.25rem 1rem 0;
|
|
414
299
|
}
|
|
415
|
-
.
|
|
300
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])) .supporting-text,
|
|
301
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])) .supporting-text {
|
|
416
302
|
color: var(--np-color-error);
|
|
417
303
|
}
|
|
418
304
|
.disabled .supporting-text {
|
|
@@ -494,7 +380,10 @@
|
|
|
494
380
|
.no-label .content,
|
|
495
381
|
.field:has(input:focus-visible) .content,
|
|
496
382
|
.field:has(textarea:focus-visible) .content,
|
|
497
|
-
.field
|
|
383
|
+
.field:has(input:-webkit-autofill) .content,
|
|
384
|
+
.field.populated .content,
|
|
385
|
+
.field:has(input:not(:placeholder-shown)) .content,
|
|
386
|
+
.field:has(textarea:not(:placeholder-shown)) .content {
|
|
498
387
|
opacity: 1;
|
|
499
388
|
}
|
|
500
389
|
|
|
@@ -564,11 +453,13 @@
|
|
|
564
453
|
margin-inline-start: 1rem;
|
|
565
454
|
margin-inline-end: 0.75rem;
|
|
566
455
|
}
|
|
567
|
-
.
|
|
456
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])) .end,
|
|
457
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])) .end {
|
|
568
458
|
color: var(--np-color-error);
|
|
569
459
|
}
|
|
570
460
|
|
|
571
|
-
.
|
|
461
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])):hover .end,
|
|
462
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])):hover .end {
|
|
572
463
|
color: var(--np-color-on-error-container);
|
|
573
464
|
}
|
|
574
465
|
.disabled .start,
|
|
@@ -620,11 +511,21 @@
|
|
|
620
511
|
.with-end .np-outline .label-wrapper {
|
|
621
512
|
margin-inline-end: 3.25rem;
|
|
622
513
|
}
|
|
514
|
+
.with-start:has(input:-webkit-autofill) .with-start:has(input:focus-visible) .label-wrapper,
|
|
515
|
+
.with-start:has(input:not(:placeholder-shown))
|
|
516
|
+
.with-start:has(input:focus-visible)
|
|
517
|
+
.label-wrapper,
|
|
518
|
+
.with-start:has(textarea:not(:placeholder-shown))
|
|
519
|
+
.with-start:has(input:focus-visible)
|
|
520
|
+
.label-wrapper,
|
|
623
521
|
.with-start.populated .with-start:has(input:focus-visible) .label-wrapper,
|
|
624
522
|
.with-start:has(textarea:focus-visible) .label-wrapper {
|
|
625
523
|
inset-inline-end: -2.25rem;
|
|
626
524
|
}
|
|
627
525
|
|
|
526
|
+
.with-end:has(input:-webkit-autofill) .with-end:has(input:focus-visible) .label-wrapper,
|
|
527
|
+
.with-end:has(input:not(:placeholder-shown)) .with-end:has(input:focus-visible) .label-wrapper,
|
|
528
|
+
.with-end:has(textarea:not(:placeholder-shown)) .with-end:has(input:focus-visible) .label-wrapper,
|
|
628
529
|
.with-end.populated .with-end:has(input:focus-visible) .label-wrapper,
|
|
629
530
|
.with-end:has(textarea:focus-visible) .label-wrapper {
|
|
630
531
|
margin-inline-end: 1rem;
|
|
@@ -650,7 +551,10 @@
|
|
|
650
551
|
top: 1rem;
|
|
651
552
|
inset-inline-start: 0rem;
|
|
652
553
|
}
|
|
554
|
+
.field:has(input:-webkit-autofill) .label,
|
|
653
555
|
.field.populated .label,
|
|
556
|
+
.field:has(input:not(:placeholder-shown)) .label,
|
|
557
|
+
.field:has(textarea:not(:placeholder-shown)) .label,
|
|
654
558
|
.field:has(input:focus-visible) .label,
|
|
655
559
|
.field:has(textarea:focus-visible) .label {
|
|
656
560
|
font-size: 0.75rem;
|
|
@@ -659,7 +563,10 @@
|
|
|
659
563
|
position: absolute;
|
|
660
564
|
top: var(--floating-label-top, 0.5rem);
|
|
661
565
|
}
|
|
566
|
+
.with-start:has(input:-webkit-autofill) .label,
|
|
662
567
|
.with-start.populated .label,
|
|
568
|
+
.with-start:has(input:not(:placeholder-shown)) .label,
|
|
569
|
+
.with-start:has(textarea:not(:placeholder-shown)) .label,
|
|
663
570
|
.with-start:has(input:focus-visible) .label,
|
|
664
571
|
.with-start:has(textarea:focus-visible) .label {
|
|
665
572
|
inset-inline-start: var(--floating-label-inline-start, 0);
|
|
@@ -688,14 +595,16 @@
|
|
|
688
595
|
.field:has(textarea:focus-visible) .label {
|
|
689
596
|
color: var(--_label-text-color, var(--np-color-primary));
|
|
690
597
|
}
|
|
691
|
-
.
|
|
692
|
-
.
|
|
693
|
-
.
|
|
598
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])) .label,
|
|
599
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])) .label,
|
|
600
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true']):focus-visible) .label,
|
|
601
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true']):focus-visible) .label {
|
|
694
602
|
color: var(--np-color-error);
|
|
695
603
|
}
|
|
696
|
-
.
|
|
697
|
-
.
|
|
698
|
-
.
|
|
604
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])):hover .label,
|
|
605
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])):hover .label,
|
|
606
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true']):focus-visible):hover .label,
|
|
607
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true']):focus-visible):hover .label {
|
|
699
608
|
color: var(--np-color-on-error-container);
|
|
700
609
|
}
|
|
701
610
|
|
|
@@ -710,6 +619,9 @@
|
|
|
710
619
|
overflow: hidden;
|
|
711
620
|
}
|
|
712
621
|
.disabled.no-label .content,
|
|
622
|
+
.disabled:has(input:-webkit-autofill) .content,
|
|
623
|
+
.disabled:has(input:not(:placeholder-shown)) .content,
|
|
624
|
+
.disabled:has(textarea:not(:placeholder-shown)) .content,
|
|
713
625
|
.disabled.populated .content {
|
|
714
626
|
opacity: 0.38;
|
|
715
627
|
}
|
|
@@ -786,6 +698,9 @@
|
|
|
786
698
|
}
|
|
787
699
|
.field:has(input:focus-visible) .outline-notch::before,
|
|
788
700
|
.field:has(textarea:focus-visible) .outline-notch::before,
|
|
701
|
+
.field:has(input:-webkit-autofill) .outline-notch::before,
|
|
702
|
+
.field:has(input:not(:placeholder-shown)) .outline-notch::before,
|
|
703
|
+
.field:has(textarea:not(:placeholder-shown)) .outline-notch::before,
|
|
789
704
|
.field.populated .outline-notch::before {
|
|
790
705
|
border-top-style: none;
|
|
791
706
|
}
|
|
@@ -854,9 +769,10 @@
|
|
|
854
769
|
color: var(--np-color-on-surface);
|
|
855
770
|
}
|
|
856
771
|
|
|
857
|
-
.
|
|
858
|
-
.
|
|
859
|
-
.
|
|
772
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true'])) .np-outline,
|
|
773
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true'])) .np-outline,
|
|
774
|
+
.field:has(input:is(:user-invalid, [aria-invalid='true']):focus-visible) .np-outline,
|
|
775
|
+
.field:has(textarea:is(:user-invalid, [aria-invalid='true']):focus-visible) .np-outline {
|
|
860
776
|
border-color: var(--np-color-error);
|
|
861
777
|
}
|
|
862
778
|
|
|
@@ -875,9 +791,4 @@
|
|
|
875
791
|
.disabled .outline-notch {
|
|
876
792
|
opacity: 0.12;
|
|
877
793
|
}
|
|
878
|
-
|
|
879
|
-
input:-webkit-autofill {
|
|
880
|
-
-webkit-background-clip: text;
|
|
881
|
-
-webkit-text-fill-color: var(--np-color-on-surface);
|
|
882
|
-
}
|
|
883
794
|
</style>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { TextFieldProps } from './types.ts';
|
|
2
|
-
declare const TextField: import("svelte").Component<TextFieldProps, {}, "element" | "value" | "
|
|
2
|
+
declare const TextField: import("svelte").Component<TextFieldProps, {}, "element" | "value" | "inputElement" | "clientWidth" | "clientHeight" | "focused">;
|
|
3
3
|
type TextField = ReturnType<typeof TextField>;
|
|
4
4
|
export default TextField;
|
|
@@ -3,8 +3,9 @@ import type { HTMLInputAttributes, HTMLTextareaAttributes } from 'svelte/element
|
|
|
3
3
|
interface FieldProps {
|
|
4
4
|
label?: string;
|
|
5
5
|
supportingText?: string;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
issues?: {
|
|
7
|
+
message: string;
|
|
8
|
+
}[];
|
|
8
9
|
prefixText?: string;
|
|
9
10
|
suffixText?: string;
|
|
10
11
|
variant?: 'outlined' | 'filled';
|
|
@@ -14,8 +15,6 @@ interface FieldProps {
|
|
|
14
15
|
element?: HTMLSpanElement;
|
|
15
16
|
inputElement?: HTMLInputElement | HTMLTextAreaElement;
|
|
16
17
|
populated?: boolean;
|
|
17
|
-
reportValidity?: () => boolean;
|
|
18
|
-
checkValidity?: () => boolean;
|
|
19
18
|
clientWidth?: number;
|
|
20
19
|
clientHeight?: number;
|
|
21
20
|
focused?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "noph-ui",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://noph.dev",
|
|
6
6
|
"repository": {
|
|
@@ -54,26 +54,26 @@
|
|
|
54
54
|
"svelte": "^5.32.1"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@eslint/js": "^9.
|
|
57
|
+
"@eslint/js": "^9.37.0",
|
|
58
58
|
"@material/material-color-utilities": "^0.3.0",
|
|
59
59
|
"@playwright/test": "^1.55.1",
|
|
60
|
-
"@sveltejs/adapter-auto": "^6.1.
|
|
61
|
-
"@sveltejs/kit": "^2.
|
|
60
|
+
"@sveltejs/adapter-auto": "^6.1.1",
|
|
61
|
+
"@sveltejs/kit": "^2.44.0",
|
|
62
62
|
"@sveltejs/package": "^2.5.4",
|
|
63
63
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
64
64
|
"@types/eslint": "^9.6.1",
|
|
65
|
-
"eslint": "^9.
|
|
65
|
+
"eslint": "^9.37.0",
|
|
66
66
|
"eslint-config-prettier": "^10.1.8",
|
|
67
67
|
"eslint-plugin-svelte": "^3.12.4",
|
|
68
68
|
"globals": "^16.4.0",
|
|
69
69
|
"prettier": "^3.6.2",
|
|
70
70
|
"prettier-plugin-svelte": "^3.4.0",
|
|
71
71
|
"publint": "^0.3.13",
|
|
72
|
-
"svelte": "^5.39.
|
|
72
|
+
"svelte": "^5.39.8",
|
|
73
73
|
"svelte-check": "^4.3.2",
|
|
74
|
-
"typescript": "^5.9.
|
|
74
|
+
"typescript": "^5.9.3",
|
|
75
75
|
"typescript-eslint": "^8.45.0",
|
|
76
|
-
"vite": "^7.1.
|
|
76
|
+
"vite": "^7.1.9",
|
|
77
77
|
"vitest": "^3.2.4"
|
|
78
78
|
},
|
|
79
79
|
"svelte": "./dist/index.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const isFirstInvalidControlInForm: (form: HTMLFormElement | null, control: HTMLElement) => boolean;
|