@aiaiai-pt/design-system 0.17.1 → 0.19.0
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/components/ActionFormRenderer.svelte +41 -3
- package/components/ContrastToggle.svelte +36 -0
- package/components/ContrastToggle.svelte.d.ts +30 -0
- package/components/FeedView.svelte +190 -0
- package/components/FeedView.svelte.d.ts +54 -0
- package/components/FileUpload.svelte +14 -10
- package/components/FilterBar.svelte +147 -58
- package/components/FilterBar.svelte.d.ts +56 -14
- package/components/LinkHighlightToggle.svelte +37 -0
- package/components/LinkHighlightToggle.svelte.d.ts +31 -0
- package/components/MapCluster.svelte +84 -7
- package/components/MapCluster.svelte.d.ts +2 -0
- package/components/TextSizeAdjuster.svelte +150 -0
- package/components/TextSizeAdjuster.svelte.d.ts +42 -0
- package/components/index.d.ts +4 -0
- package/components/index.js +4 -0
- package/components/map-utils.d.ts +107 -0
- package/components/text-size.d.ts +50 -0
- package/components/text-size.js +72 -0
- package/package.json +1 -1
- package/tokens/components.css +19 -0
- package/tokens/semantic.css +75 -0
- package/tokens/themes/valongo.css +28 -1
|
@@ -397,6 +397,11 @@
|
|
|
397
397
|
{@const parameter = rawParameter as Entity}
|
|
398
398
|
{@const key = parameterKey(parameter)}
|
|
399
399
|
{@const type = parameterType(parameter)}
|
|
400
|
+
<!-- A11y (#244 C7 / Selo item 4): required fields are marked
|
|
401
|
+
`aria-required` on the control itself, so a screen reader announces
|
|
402
|
+
"required" on the field — not just the disconnected visual hint below.
|
|
403
|
+
Passed through the DS field components' `{...rest}` onto the input. -->
|
|
404
|
+
{@const ariaRequired = parameter.required ? "true" : undefined}
|
|
400
405
|
{#if type === "enum" || type === "select" || enumOptions(parameter).length}
|
|
401
406
|
<Select
|
|
402
407
|
label={String(parameter.label ?? key)}
|
|
@@ -405,6 +410,7 @@
|
|
|
405
410
|
options={enumOptions(parameter)}
|
|
406
411
|
placeholder="Select value"
|
|
407
412
|
onchange={(value: string) => setValue(key, value)}
|
|
413
|
+
aria-required={ariaRequired}
|
|
408
414
|
/>
|
|
409
415
|
{:else if type === "number" || type === "integer"}
|
|
410
416
|
<Input
|
|
@@ -416,6 +422,7 @@
|
|
|
416
422
|
const value = (event.target as HTMLInputElement).value;
|
|
417
423
|
setValue(key, value === "" ? "" : Number(value));
|
|
418
424
|
}}
|
|
425
|
+
aria-required={ariaRequired}
|
|
419
426
|
/>
|
|
420
427
|
{:else if type === "bool" || type === "boolean"}
|
|
421
428
|
<Select
|
|
@@ -427,12 +434,20 @@
|
|
|
427
434
|
{ value: "false", label: "No" },
|
|
428
435
|
]}
|
|
429
436
|
onchange={(value: string) => setValue(key, value === "true")}
|
|
437
|
+
aria-required={ariaRequired}
|
|
430
438
|
/>
|
|
431
439
|
{:else if type === "file"}
|
|
432
440
|
<!-- `file` parameter (#75 M5 slice 4b): upload-as-you-attach. The keys
|
|
433
441
|
ride payload.attachment_keys; raw_values never sees this param. -->
|
|
434
|
-
|
|
435
|
-
|
|
442
|
+
<!-- A11y: the file param is a labelled group (the FileUpload's own input
|
|
443
|
+
can't take a `for`/label from here), so SR users get the field name +
|
|
444
|
+
required state when they enter it. -->
|
|
445
|
+
<div class="afr-file-param" role="group" aria-labelledby={`${key}-file-label`}>
|
|
446
|
+
<span id={`${key}-file-label`} class="afr-file-label"
|
|
447
|
+
>{String(parameter.label ?? key)}{parameter.required
|
|
448
|
+
? " (required)"
|
|
449
|
+
: ""}</span
|
|
450
|
+
>
|
|
436
451
|
{#each fileUploads[key] ?? [] as f (f.key)}
|
|
437
452
|
<FileUploadItem
|
|
438
453
|
name={f.name}
|
|
@@ -491,6 +506,7 @@
|
|
|
491
506
|
name={key}
|
|
492
507
|
value={String(values[key] ?? "")}
|
|
493
508
|
oninput={(event: Event) => setValue(key, (event.target as HTMLInputElement).value)}
|
|
509
|
+
aria-required={ariaRequired}
|
|
494
510
|
/>
|
|
495
511
|
{/if}
|
|
496
512
|
{#if mode === "public-submit"}
|
|
@@ -526,7 +542,14 @@
|
|
|
526
542
|
<p class="muted">This action has no fields yet.</p>
|
|
527
543
|
{:else}
|
|
528
544
|
{@const Layout = resolvedLayout.component}
|
|
529
|
-
|
|
545
|
+
<!-- A11y: name the form region so SR users land on a labelled form, not an
|
|
546
|
+
anonymous group of inputs (Selo item 4 / WCAG 1.3.1). -->
|
|
547
|
+
<form
|
|
548
|
+
class="rendered-form"
|
|
549
|
+
aria-label={String(
|
|
550
|
+
renderedAction?.label ?? renderedAction?.key ?? "Form",
|
|
551
|
+
)}
|
|
552
|
+
>
|
|
530
553
|
<Layout {sections} field={fieldRow} />
|
|
531
554
|
</form>
|
|
532
555
|
{/if}
|
|
@@ -544,6 +567,15 @@
|
|
|
544
567
|
{#if captcha}
|
|
545
568
|
<div class="submit-captcha">{@render captcha()}</div>
|
|
546
569
|
{/if}
|
|
570
|
+
<!-- A11y: a disabled submit gives no reason on its own. This polite
|
|
571
|
+
status spells out the gate (and disappears when satisfied), so SR
|
|
572
|
+
users know WHY they can't submit yet — paired with the per-field
|
|
573
|
+
`aria-required` that marks which fields are needed. -->
|
|
574
|
+
{#if !canSubmit && !submitting}
|
|
575
|
+
<p class="submit-hint" role="status">
|
|
576
|
+
Fill in all required fields to submit.
|
|
577
|
+
</p>
|
|
578
|
+
{/if}
|
|
547
579
|
<Button
|
|
548
580
|
variant="primary"
|
|
549
581
|
loading={submitting}
|
|
@@ -642,6 +674,12 @@
|
|
|
642
674
|
color: var(--color-text-muted);
|
|
643
675
|
}
|
|
644
676
|
|
|
677
|
+
.submit-hint {
|
|
678
|
+
color: var(--color-text-secondary);
|
|
679
|
+
font-size: var(--type-body-sm-size);
|
|
680
|
+
margin: 0;
|
|
681
|
+
}
|
|
682
|
+
|
|
645
683
|
.rendered-form,
|
|
646
684
|
.admin-preview,
|
|
647
685
|
.preview-block,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component ContrastToggle
|
|
3
|
+
|
|
4
|
+
Citizen high-contrast preference (#244 C7 accessibility pack). A single on/off
|
|
5
|
+
switch: "on" asks for a high-contrast rendering (max text/surface separation,
|
|
6
|
+
stronger borders, underlined links) on top of the active light/dark scheme.
|
|
7
|
+
|
|
8
|
+
Presentational, mirrors the scheme/motion controls: it reports the new value
|
|
9
|
+
via `onchange`; the consumer persists it (cookie) and applies the
|
|
10
|
+
`:root[data-contrast="high"]` layer (tokens/semantic.css) server-side, so SSR
|
|
11
|
+
paints it with no flash. The label is a prop for i18n (default English).
|
|
12
|
+
|
|
13
|
+
@example
|
|
14
|
+
<ContrastToggle high={prefs.contrast === "high"} onchange={(v) => setContrast(v)} />
|
|
15
|
+
-->
|
|
16
|
+
<script>
|
|
17
|
+
import Toggle from "./Toggle.svelte";
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
/** @type {boolean} Whether high-contrast is currently on. */
|
|
21
|
+
high = false,
|
|
22
|
+
/** @type {((high: boolean) => void) | undefined} */
|
|
23
|
+
onchange = undefined,
|
|
24
|
+
/** @type {string} Accessible label (i18n). */
|
|
25
|
+
label = "High contrast",
|
|
26
|
+
...rest
|
|
27
|
+
} = $props();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<Toggle
|
|
31
|
+
checked={high}
|
|
32
|
+
{label}
|
|
33
|
+
{onchange}
|
|
34
|
+
data-testid="contrast-toggle"
|
|
35
|
+
{...rest}
|
|
36
|
+
/>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export default ContrastToggle;
|
|
2
|
+
type ContrastToggle = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* ContrastToggle
|
|
8
|
+
*
|
|
9
|
+
* Citizen high-contrast preference (#244 C7 accessibility pack). A single on/off
|
|
10
|
+
* switch: "on" asks for a high-contrast rendering (max text/surface separation,
|
|
11
|
+
* stronger borders, underlined links) on top of the active light/dark scheme.
|
|
12
|
+
*
|
|
13
|
+
* Presentational, mirrors the scheme/motion controls: it reports the new value
|
|
14
|
+
* via `onchange`; the consumer persists it (cookie) and applies the
|
|
15
|
+
* `:root[data-contrast="high"]` layer (tokens/semantic.css) server-side, so SSR
|
|
16
|
+
* paints it with no flash. The label is a prop for i18n (default English).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <ContrastToggle high={prefs.contrast === "high"} onchange={(v) => setContrast(v)} />
|
|
20
|
+
*/
|
|
21
|
+
declare const ContrastToggle: import("svelte").Component<{
|
|
22
|
+
high?: boolean;
|
|
23
|
+
onchange?: any;
|
|
24
|
+
label?: string;
|
|
25
|
+
} & Record<string, any>, {}, "">;
|
|
26
|
+
type $$ComponentProps = {
|
|
27
|
+
high?: boolean;
|
|
28
|
+
onchange?: any;
|
|
29
|
+
label?: string;
|
|
30
|
+
} & Record<string, any>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component FeedView
|
|
3
|
+
|
|
4
|
+
A segmented view-switch for one feed (e.g. list ⇄ map). Presentational only:
|
|
5
|
+
it renders the available views as a WAI-ARIA tablist and emits the chosen
|
|
6
|
+
view via `onchange` — it does NOT render the views' content (the consumer
|
|
7
|
+
swaps that in response to the change, typically by re-resolving the feed).
|
|
8
|
+
|
|
9
|
+
Keyboard (automatic activation, per the tablist pattern for cheap-to-reveal
|
|
10
|
+
views): Arrow keys move focus AND select; Home/End jump to the ends; focus
|
|
11
|
+
roves (active tab is the only tab stop).
|
|
12
|
+
|
|
13
|
+
Consumes --feedview-* tokens from components.css.
|
|
14
|
+
|
|
15
|
+
@example
|
|
16
|
+
<FeedView
|
|
17
|
+
views={[
|
|
18
|
+
{ value: 'list', label: 'List' },
|
|
19
|
+
{ value: 'map', label: 'Map' },
|
|
20
|
+
]}
|
|
21
|
+
bind:value={activeView}
|
|
22
|
+
onchange={(v) => goto(`?view=${v}`)}
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
@example With icons (icon is a Svelte component, e.g. phosphor-svelte)
|
|
26
|
+
<FeedView views={[
|
|
27
|
+
{ value: 'list', label: 'List', icon: ListIcon },
|
|
28
|
+
{ value: 'map', label: 'Map', icon: MapIcon },
|
|
29
|
+
]} bind:value={activeView} onchange={onChange} />
|
|
30
|
+
-->
|
|
31
|
+
<script module>
|
|
32
|
+
let _feedviewUid = 0;
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {{ value: string, label: string, icon?: import('svelte').Component }} FeedViewOption
|
|
35
|
+
*/
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<script>
|
|
39
|
+
let {
|
|
40
|
+
/** @type {FeedViewOption[]} The available views (first = default). */
|
|
41
|
+
views = [],
|
|
42
|
+
/** @type {string} The active view's value (bindable). */
|
|
43
|
+
value = $bindable(''),
|
|
44
|
+
/** @type {((value: string) => void) | undefined} Fires when the view changes. */
|
|
45
|
+
onchange = undefined,
|
|
46
|
+
/** @type {string} Accessible name for the tablist. */
|
|
47
|
+
label = 'View',
|
|
48
|
+
/** @type {string} */
|
|
49
|
+
class: className = '',
|
|
50
|
+
...rest
|
|
51
|
+
} = $props();
|
|
52
|
+
|
|
53
|
+
const uid = `feedview-${_feedviewUid++}`;
|
|
54
|
+
/** @type {HTMLButtonElement[]} */
|
|
55
|
+
let tabs = $state([]);
|
|
56
|
+
|
|
57
|
+
const activeIndex = $derived(Math.max(0, views.findIndex((v) => v.value === value)));
|
|
58
|
+
|
|
59
|
+
/** @param {string} v */
|
|
60
|
+
function select(v) {
|
|
61
|
+
if (v === value) return;
|
|
62
|
+
value = v;
|
|
63
|
+
onchange?.(v);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {number} index
|
|
68
|
+
* @param {boolean} focus
|
|
69
|
+
*/
|
|
70
|
+
function activate(index, focus) {
|
|
71
|
+
const view = views[index];
|
|
72
|
+
if (!view) return;
|
|
73
|
+
if (focus) tabs[index]?.focus();
|
|
74
|
+
select(view.value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @param {KeyboardEvent} event */
|
|
78
|
+
function onKeydown(event) {
|
|
79
|
+
const last = views.length - 1;
|
|
80
|
+
let next = -1;
|
|
81
|
+
switch (event.key) {
|
|
82
|
+
case 'ArrowRight':
|
|
83
|
+
case 'ArrowDown':
|
|
84
|
+
next = activeIndex >= last ? 0 : activeIndex + 1;
|
|
85
|
+
break;
|
|
86
|
+
case 'ArrowLeft':
|
|
87
|
+
case 'ArrowUp':
|
|
88
|
+
next = activeIndex <= 0 ? last : activeIndex - 1;
|
|
89
|
+
break;
|
|
90
|
+
case 'Home':
|
|
91
|
+
next = 0;
|
|
92
|
+
break;
|
|
93
|
+
case 'End':
|
|
94
|
+
next = last;
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
activate(next, true);
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<div
|
|
105
|
+
class="feedview {className}"
|
|
106
|
+
role="tablist"
|
|
107
|
+
aria-label={label}
|
|
108
|
+
onkeydown={onKeydown}
|
|
109
|
+
{...rest}
|
|
110
|
+
>
|
|
111
|
+
{#each views as view, i (view.value)}
|
|
112
|
+
{@const Icon = view.icon}
|
|
113
|
+
{@const isActive = view.value === value}
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
role="tab"
|
|
117
|
+
id={`${uid}-${view.value}`}
|
|
118
|
+
aria-selected={isActive}
|
|
119
|
+
tabindex={isActive ? 0 : -1}
|
|
120
|
+
class="feedview-tab"
|
|
121
|
+
class:feedview-tab-active={isActive}
|
|
122
|
+
bind:this={tabs[i]}
|
|
123
|
+
onclick={() => activate(i, false)}
|
|
124
|
+
>
|
|
125
|
+
{#if Icon}<span class="feedview-icon" aria-hidden="true"><Icon /></span>{/if}
|
|
126
|
+
<span class="feedview-label">{view.label}</span>
|
|
127
|
+
</button>
|
|
128
|
+
{/each}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<style>
|
|
132
|
+
.feedview {
|
|
133
|
+
display: inline-flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
gap: var(--space-2xs);
|
|
136
|
+
background: var(--feedview-bg);
|
|
137
|
+
border-radius: var(--feedview-radius);
|
|
138
|
+
padding: var(--feedview-padding);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.feedview-tab {
|
|
142
|
+
all: unset;
|
|
143
|
+
box-sizing: border-box;
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
gap: var(--space-xs);
|
|
148
|
+
height: var(--feedview-tab-height);
|
|
149
|
+
padding: 0 var(--feedview-tab-padding-x);
|
|
150
|
+
border-radius: var(--feedview-tab-radius);
|
|
151
|
+
font-family: var(--feedview-tab-font);
|
|
152
|
+
font-size: var(--feedview-tab-size);
|
|
153
|
+
letter-spacing: var(--feedview-tab-tracking);
|
|
154
|
+
color: var(--feedview-tab-color);
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
white-space: nowrap;
|
|
157
|
+
transition: all var(--feedview-tab-transition);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.feedview-tab:hover {
|
|
161
|
+
color: var(--color-text);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.feedview-tab:focus-visible {
|
|
165
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
166
|
+
outline-offset: var(--focus-ring-offset);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.feedview-tab-active {
|
|
170
|
+
color: var(--feedview-tab-color-active);
|
|
171
|
+
background: var(--feedview-tab-bg-active);
|
|
172
|
+
box-shadow: var(--feedview-tab-shadow-active);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.feedview-icon {
|
|
176
|
+
display: inline-flex;
|
|
177
|
+
align-items: center;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.feedview-icon :global(svg) {
|
|
181
|
+
width: var(--icon-size-sm);
|
|
182
|
+
height: var(--icon-size-sm);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@media (prefers-reduced-motion: reduce) {
|
|
186
|
+
.feedview-tab {
|
|
187
|
+
transition: none;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export default FeedView;
|
|
2
|
+
export type FeedViewOption = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: import("svelte").Component;
|
|
6
|
+
};
|
|
7
|
+
type FeedView = {
|
|
8
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
9
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* FeedView
|
|
13
|
+
*
|
|
14
|
+
* A segmented view-switch for one feed (e.g. list ⇄ map). Presentational only:
|
|
15
|
+
* it renders the available views as a WAI-ARIA tablist and emits the chosen
|
|
16
|
+
* view via `onchange` — it does NOT render the views' content (the consumer
|
|
17
|
+
* swaps that in response to the change, typically by re-resolving the feed).
|
|
18
|
+
*
|
|
19
|
+
* Keyboard (automatic activation, per the tablist pattern for cheap-to-reveal
|
|
20
|
+
* views): Arrow keys move focus AND select; Home/End jump to the ends; focus
|
|
21
|
+
* roves (active tab is the only tab stop).
|
|
22
|
+
*
|
|
23
|
+
* Consumes --feedview-* tokens from components.css.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* <FeedView
|
|
27
|
+
* views={[
|
|
28
|
+
* { value: 'list', label: 'List' },
|
|
29
|
+
* { value: 'map', label: 'Map' },
|
|
30
|
+
* ]}
|
|
31
|
+
* bind:value={activeView}
|
|
32
|
+
* onchange={(v) => goto(`?view=${v}`)}
|
|
33
|
+
* />
|
|
34
|
+
*
|
|
35
|
+
* @example With icons (icon is a Svelte component, e.g. phosphor-svelte)
|
|
36
|
+
* <FeedView views={[
|
|
37
|
+
* { value: 'list', label: 'List', icon: ListIcon },
|
|
38
|
+
* { value: 'map', label: 'Map', icon: MapIcon },
|
|
39
|
+
* ]} bind:value={activeView} onchange={onChange} />
|
|
40
|
+
*/
|
|
41
|
+
declare const FeedView: import("svelte").Component<{
|
|
42
|
+
views?: any[];
|
|
43
|
+
value?: string;
|
|
44
|
+
onchange?: any;
|
|
45
|
+
label?: string;
|
|
46
|
+
class?: string;
|
|
47
|
+
} & Record<string, any>, {}, "value">;
|
|
48
|
+
type $$ComponentProps = {
|
|
49
|
+
views?: any[];
|
|
50
|
+
value?: string;
|
|
51
|
+
onchange?: any;
|
|
52
|
+
label?: string;
|
|
53
|
+
class?: string;
|
|
54
|
+
} & Record<string, any>;
|
|
@@ -158,6 +158,20 @@
|
|
|
158
158
|
}
|
|
159
159
|
</script>
|
|
160
160
|
|
|
161
|
+
<!-- The file input is a SIBLING of the trigger button, not a child: a focusable
|
|
162
|
+
<input> nested inside a <button> is a nested-interactive a11y violation
|
|
163
|
+
(axe, #244 S5). It is visually hidden + pointer-events:none and is opened
|
|
164
|
+
via the `inputEl` ref from the button's onclick, so behaviour is unchanged. -->
|
|
165
|
+
<input
|
|
166
|
+
bind:this={inputEl}
|
|
167
|
+
type="file"
|
|
168
|
+
{accept}
|
|
169
|
+
{multiple}
|
|
170
|
+
class="fileupload-input"
|
|
171
|
+
onchange={handleInputChange}
|
|
172
|
+
tabindex={-1}
|
|
173
|
+
aria-hidden="true"
|
|
174
|
+
/>
|
|
161
175
|
<button
|
|
162
176
|
type="button"
|
|
163
177
|
class="fileupload {className}"
|
|
@@ -171,16 +185,6 @@
|
|
|
171
185
|
{...rest}
|
|
172
186
|
onclick={handleClick}
|
|
173
187
|
>
|
|
174
|
-
<input
|
|
175
|
-
bind:this={inputEl}
|
|
176
|
-
type="file"
|
|
177
|
-
{accept}
|
|
178
|
-
{multiple}
|
|
179
|
-
class="fileupload-input"
|
|
180
|
-
onchange={handleInputChange}
|
|
181
|
-
tabindex={-1}
|
|
182
|
-
aria-hidden="true"
|
|
183
|
-
/>
|
|
184
188
|
{#if children}
|
|
185
189
|
{@render children()}
|
|
186
190
|
{:else}
|