@communitiesuk/svelte-component-library 0.1.19-beta.21 → 0.1.19-beta.24
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/data-vis/Histogram.svelte +18 -4
- package/dist/components/ui/BasicMultiSelect.svelte +650 -91
- package/dist/components/ui/BasicMultiSelect.svelte.d.ts +19 -5
- package/dist/components/ui/CheckBox.svelte +1 -0
- package/dist/components/ui/Masthead.svelte +8 -0
- package/dist/components/ui/Masthead.svelte.d.ts +2 -0
- package/dist/components/ui/Tabs.svelte +2 -0
- package/package.json +1 -1
|
@@ -141,10 +141,24 @@
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
let colorScale = $derived(
|
|
145
|
-
customColorScale
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
let colorScale = $derived(() => {
|
|
145
|
+
if (customColorScale) return customColorScale;
|
|
146
|
+
|
|
147
|
+
if (!startColor || !endColor || !nBins) return [];
|
|
148
|
+
|
|
149
|
+
if (skew) {
|
|
150
|
+
if (
|
|
151
|
+
!midColor ||
|
|
152
|
+
averageValue == null ||
|
|
153
|
+
xTickFirst == null ||
|
|
154
|
+
xTickLast == null
|
|
155
|
+
) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return interpolateColors(startColor, endColor, nBins, midColor, skew);
|
|
161
|
+
});
|
|
148
162
|
|
|
149
163
|
$inspect({ colorScale });
|
|
150
164
|
|
|
@@ -1,72 +1,174 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
import { browser } from "$app/environment";
|
|
4
|
-
|
|
4
|
+
import IconSearch from "../../icons/IconSearch.svelte";
|
|
5
|
+
import crossIconUrl from "./../../assets/govuk_publishing_components/images/cross-icon.svg";
|
|
5
6
|
type Option = { id: string | number; label: string };
|
|
6
7
|
type SelectedWithColor = Option & { color: string };
|
|
8
|
+
type SelectedItem = Option | SelectedWithColor;
|
|
7
9
|
|
|
8
|
-
// ---------- Props ----------
|
|
9
10
|
let {
|
|
10
11
|
options = [],
|
|
11
|
-
|
|
12
|
+
selected = $bindable<SelectedItem[]>([]),
|
|
12
13
|
colorArray = ["#808080"],
|
|
13
|
-
placeholder = "
|
|
14
|
+
placeholder = "Search and select…",
|
|
15
|
+
label = "Choose options",
|
|
16
|
+
hint,
|
|
17
|
+
enableColors = true,
|
|
18
|
+
startsWithSearch = true,
|
|
19
|
+
resetButton = false,
|
|
20
|
+
}: {
|
|
21
|
+
options?: Option[];
|
|
22
|
+
selected?: SelectedItem[];
|
|
23
|
+
colorArray?: string[];
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
hint?: string;
|
|
27
|
+
enableColors?: boolean;
|
|
28
|
+
startsWithSearch?: boolean;
|
|
29
|
+
resetButton?: boolean;
|
|
14
30
|
} = $props();
|
|
15
31
|
|
|
16
32
|
let showDropdown = $state(false);
|
|
17
33
|
let search = $state("");
|
|
18
34
|
|
|
19
|
-
|
|
35
|
+
let initialSelected = [];
|
|
36
|
+
|
|
37
|
+
const isAtDefault = $derived(() => {
|
|
38
|
+
if (!hasInitialSelection) return true;
|
|
39
|
+
|
|
40
|
+
const current = selected
|
|
41
|
+
.map((s) => s.id)
|
|
42
|
+
.sort()
|
|
43
|
+
.join(",");
|
|
44
|
+
const initial = initialSelected
|
|
45
|
+
.map((s) => s.id)
|
|
46
|
+
.sort()
|
|
47
|
+
.join(",");
|
|
48
|
+
|
|
49
|
+
return current === initial;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const hasInitialSelection = $derived(() => initialSelected.length > 0);
|
|
53
|
+
|
|
54
|
+
function resetToInitial() {
|
|
55
|
+
selected = initialSelected.map((item) => ({ ...item }));
|
|
56
|
+
search = "";
|
|
57
|
+
queueMicrotask(() => inputEl?.focus());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Cursor used ONLY when palette is exhausted (Option 2)
|
|
61
|
+
let colorCursor = $state(0);
|
|
62
|
+
|
|
63
|
+
function nextColorPreferUnused(used?: Set<string>) {
|
|
64
|
+
if (!Array.isArray(colorArray) || colorArray.length === 0) return "#808080";
|
|
65
|
+
|
|
66
|
+
const usedColors =
|
|
67
|
+
used ??
|
|
68
|
+
new Set(
|
|
69
|
+
selected
|
|
70
|
+
.filter(
|
|
71
|
+
(s): s is SelectedWithColor => "color" in s && !!(s as any).color,
|
|
72
|
+
)
|
|
73
|
+
.map((s) => (s as SelectedWithColor).color),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// 1) Prefer unused
|
|
77
|
+
const unused = colorArray.find((c) => !usedColors.has(c));
|
|
78
|
+
if (unused) return unused;
|
|
79
|
+
|
|
80
|
+
// 2) Otherwise cycle
|
|
81
|
+
const color = colorArray[colorCursor % colorArray.length];
|
|
82
|
+
colorCursor = (colorCursor + 1) % colorArray.length;
|
|
83
|
+
return color;
|
|
84
|
+
}
|
|
85
|
+
|
|
20
86
|
$effect(() => {
|
|
21
|
-
if (!
|
|
22
|
-
if (!
|
|
87
|
+
if (!enableColors) return;
|
|
88
|
+
if (!Array.isArray(selected)) return;
|
|
89
|
+
if (!Array.isArray(colorArray) || colorArray.length === 0) return;
|
|
90
|
+
|
|
91
|
+
if (colorCursor >= colorArray.length) colorCursor = 0;
|
|
23
92
|
|
|
24
93
|
const used = new Set(
|
|
25
|
-
|
|
94
|
+
selected
|
|
95
|
+
.filter(
|
|
96
|
+
(s): s is SelectedWithColor => "color" in s && !!(s as any).color,
|
|
97
|
+
)
|
|
98
|
+
.map((s) => (s as SelectedWithColor).color),
|
|
26
99
|
);
|
|
100
|
+
|
|
27
101
|
let changed = false;
|
|
28
102
|
|
|
29
|
-
const updated =
|
|
30
|
-
if (item.color) return item;
|
|
31
|
-
|
|
103
|
+
const updated = selected.map((item) => {
|
|
104
|
+
if ("color" in item && (item as any).color) return item;
|
|
105
|
+
|
|
106
|
+
const color = nextColorPreferUnused(used);
|
|
32
107
|
used.add(color);
|
|
33
108
|
changed = true;
|
|
34
|
-
|
|
109
|
+
|
|
110
|
+
return { ...(item as Option), color };
|
|
35
111
|
});
|
|
36
112
|
|
|
37
|
-
if (changed)
|
|
113
|
+
if (changed) selected = updated;
|
|
38
114
|
});
|
|
39
115
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
(o) =>
|
|
44
|
-
!selectedWithColors.some((s) => s.id === o.id) &&
|
|
45
|
-
o.label.toLowerCase().includes(search.toLowerCase()),
|
|
46
|
-
),
|
|
47
|
-
);
|
|
116
|
+
const filteredOptions = $derived.by(() => {
|
|
117
|
+
const q = search.trim().toLowerCase();
|
|
118
|
+
const selectedIds = new Set(selected.map((s) => s.id));
|
|
48
119
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
120
|
+
// remove already-selected
|
|
121
|
+
const available = options
|
|
122
|
+
.filter((o) => !selectedIds.has(o.id))
|
|
123
|
+
// ✅ alphabetical sort
|
|
124
|
+
.slice() // avoid mutating original
|
|
125
|
+
.sort((a, b) =>
|
|
126
|
+
a.label.localeCompare(b.label, undefined, {
|
|
127
|
+
sensitivity: "base", // case-insensitive
|
|
128
|
+
numeric: true, // "Item 2" < "Item 10"
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// apply search match after sort (or before—either is fine)
|
|
133
|
+
const matched = q
|
|
134
|
+
? available.filter((o) => {
|
|
135
|
+
const label = o.label.toLowerCase();
|
|
136
|
+
return startsWithSearch ? label.startsWith(q) : label.includes(q);
|
|
137
|
+
})
|
|
138
|
+
: available;
|
|
139
|
+
|
|
140
|
+
return matched;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
function selectOption(option: Option) {
|
|
144
|
+
if (enableColors) {
|
|
145
|
+
const color = nextColorPreferUnused();
|
|
146
|
+
selected = [...selected, { ...option, color }];
|
|
147
|
+
} else {
|
|
148
|
+
selected = [...selected, option];
|
|
149
|
+
}
|
|
54
150
|
|
|
55
|
-
function select(option: Option) {
|
|
56
|
-
const color = getFirstAvailableColor();
|
|
57
|
-
selectedWithColors = [...selectedWithColors, { ...option, color }];
|
|
58
151
|
search = "";
|
|
59
152
|
}
|
|
60
153
|
|
|
61
|
-
function remove(
|
|
62
|
-
|
|
154
|
+
function remove(id: Option["id"]) {
|
|
155
|
+
selected = selected.filter((s) => s.id !== id);
|
|
63
156
|
}
|
|
64
157
|
|
|
65
158
|
// Close dropdown on outside click — browser only
|
|
66
159
|
let container: HTMLDivElement | null = null;
|
|
160
|
+
let inputEl: HTMLInputElement | null = null;
|
|
161
|
+
|
|
162
|
+
function open() {
|
|
163
|
+
showDropdown = true;
|
|
164
|
+
queueMicrotask(() => inputEl?.focus());
|
|
165
|
+
}
|
|
67
166
|
|
|
68
167
|
onMount(() => {
|
|
69
168
|
if (!browser) return;
|
|
169
|
+
if (resetButton === true) {
|
|
170
|
+
initialSelected = selected.map((item) => ({ ...item }));
|
|
171
|
+
}
|
|
70
172
|
|
|
71
173
|
function handleOutside(e: MouseEvent) {
|
|
72
174
|
if (container && !container.contains(e.target as Node)) {
|
|
@@ -77,109 +179,566 @@
|
|
|
77
179
|
document.addEventListener("mousedown", handleOutside);
|
|
78
180
|
return () => document.removeEventListener("mousedown", handleOutside);
|
|
79
181
|
});
|
|
182
|
+
|
|
183
|
+
function clearAll() {
|
|
184
|
+
selected = [];
|
|
185
|
+
search = "";
|
|
186
|
+
queueMicrotask(() => inputEl?.focus());
|
|
187
|
+
}
|
|
80
188
|
</script>
|
|
81
189
|
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
190
|
+
<div class="gem-c-select-with-search" bind:this={container}>
|
|
191
|
+
<label class="govuk-label" for="ms-input">{label}</label>
|
|
192
|
+
{#if hint}
|
|
193
|
+
<div class="govuk-hint">{hint}</div>
|
|
194
|
+
{/if}
|
|
195
|
+
|
|
196
|
+
<div
|
|
197
|
+
class:choices={true}
|
|
198
|
+
class:is-open={showDropdown}
|
|
199
|
+
class:is-focused={showDropdown}
|
|
200
|
+
data-type="select-multiple"
|
|
201
|
+
on:click={open}
|
|
202
|
+
>
|
|
203
|
+
<div class="choices__search-row">
|
|
204
|
+
<div class="choices__box">
|
|
205
|
+
<div class="choices__inner">
|
|
206
|
+
<input
|
|
207
|
+
id="ms-input"
|
|
208
|
+
class="choices__input"
|
|
209
|
+
bind:this={inputEl}
|
|
210
|
+
bind:value={search}
|
|
211
|
+
{placeholder}
|
|
212
|
+
autocomplete="off"
|
|
213
|
+
on:focus={() => (showDropdown = true)}
|
|
214
|
+
on:input={() => (showDropdown = true)}
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{#if selected.length}
|
|
219
|
+
<div
|
|
220
|
+
class="choices__list choices__list--multiple"
|
|
221
|
+
aria-label="Selected items"
|
|
222
|
+
>
|
|
223
|
+
{#each selected as item (item.id)}
|
|
224
|
+
<span class="choices__item">
|
|
225
|
+
{#if enableColors && "color" in item}
|
|
226
|
+
<span
|
|
227
|
+
class="choices__item-circle"
|
|
228
|
+
style={`background:${(item as any).color}`}
|
|
229
|
+
></span>
|
|
230
|
+
{/if}
|
|
231
|
+
|
|
232
|
+
<span class="choices__item-label">{item.label}</span>
|
|
233
|
+
|
|
234
|
+
<button
|
|
235
|
+
type="button"
|
|
236
|
+
class="choices__button"
|
|
237
|
+
on:click|stopPropagation={() => remove(item.id)}
|
|
238
|
+
aria-label={`Remove ${item.label}`}
|
|
239
|
+
title={`Remove ${item.label}`}
|
|
240
|
+
>
|
|
241
|
+
<img
|
|
242
|
+
src={crossIconUrl}
|
|
243
|
+
alt=""
|
|
244
|
+
aria-hidden="true"
|
|
245
|
+
width="18"
|
|
246
|
+
height="18"
|
|
247
|
+
/>
|
|
248
|
+
</button>
|
|
249
|
+
</span>
|
|
250
|
+
{/each}
|
|
251
|
+
</div>
|
|
252
|
+
{/if}
|
|
253
|
+
{#if selected.length || (resetButton && hasInitialSelection && !isAtDefault)}
|
|
254
|
+
<div class="choices__actions">
|
|
255
|
+
{#if selected.length}
|
|
256
|
+
<button
|
|
257
|
+
type="button"
|
|
258
|
+
class="choices__clear-all"
|
|
259
|
+
on:click|stopPropagation={clearAll}
|
|
260
|
+
>
|
|
261
|
+
Remove all selected
|
|
262
|
+
</button>
|
|
263
|
+
{/if}
|
|
264
|
+
|
|
265
|
+
{#if resetButton && hasInitialSelection && !isAtDefault}
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
class="choices__reset"
|
|
269
|
+
on:click|stopPropagation={resetToInitial}
|
|
270
|
+
>
|
|
271
|
+
Reset to default
|
|
272
|
+
</button>
|
|
273
|
+
{/if}
|
|
274
|
+
</div>
|
|
275
|
+
{/if}
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<button
|
|
279
|
+
type="button"
|
|
280
|
+
class="search-addon-btn"
|
|
281
|
+
aria-label="Search"
|
|
282
|
+
on:click|stopPropagation={() => open()}
|
|
283
|
+
>
|
|
284
|
+
<span class="search-addon-icon"><IconSearch /></span>
|
|
285
|
+
</button>
|
|
99
286
|
</div>
|
|
100
287
|
</div>
|
|
101
|
-
|
|
102
288
|
{#if showDropdown}
|
|
103
|
-
<
|
|
289
|
+
<div
|
|
290
|
+
class="choices__list choices__list--dropdown"
|
|
291
|
+
class:is-open={showDropdown}
|
|
292
|
+
role="listbox"
|
|
293
|
+
aria-label="Options"
|
|
294
|
+
>
|
|
104
295
|
{#each filteredOptions as o (o.id)}
|
|
105
|
-
<
|
|
296
|
+
<div
|
|
297
|
+
class="choices__item choices__item--selectable"
|
|
298
|
+
role="option"
|
|
299
|
+
on:click={() => selectOption(o)}
|
|
300
|
+
>
|
|
301
|
+
{o.label}
|
|
302
|
+
</div>
|
|
106
303
|
{/each}
|
|
107
304
|
|
|
108
305
|
{#if filteredOptions.length === 0}
|
|
109
|
-
<
|
|
306
|
+
<div class="choices__item" aria-disabled="true" style="opacity:0.75">
|
|
307
|
+
No results found
|
|
308
|
+
</div>
|
|
110
309
|
{/if}
|
|
111
|
-
</
|
|
310
|
+
</div>
|
|
112
311
|
{/if}
|
|
113
312
|
</div>
|
|
114
313
|
|
|
115
314
|
<style>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
315
|
+
:root {
|
|
316
|
+
--govuk-black: #0b0c0c;
|
|
317
|
+
--govuk-blue: #1d70b8;
|
|
318
|
+
--govuk-grey: #b1b4b6;
|
|
319
|
+
--govuk-light-grey: #f3f2f1;
|
|
320
|
+
--govuk-focus: #fd0;
|
|
321
|
+
|
|
322
|
+
--select-height: 46px;
|
|
323
|
+
--item-height: 44px;
|
|
324
|
+
--addon-width: 46px; /* ✅ used for dropdown width alignment */
|
|
119
325
|
}
|
|
120
326
|
|
|
121
|
-
.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
327
|
+
:global(.gem-c-select-with-search) {
|
|
328
|
+
display: block;
|
|
329
|
+
width: 100%; /* ✅ inherit parent width */
|
|
330
|
+
max-width: 100%; /* ✅ never exceed parent */
|
|
331
|
+
box-sizing: border-box;
|
|
126
332
|
}
|
|
127
333
|
|
|
128
|
-
|
|
334
|
+
:global(.govuk-label) {
|
|
335
|
+
font-family: "GDS Transport", arial, sans-serif;
|
|
336
|
+
font-size: 1.1875rem;
|
|
337
|
+
line-height: 1.31;
|
|
338
|
+
color: var(--govuk-black);
|
|
339
|
+
display: block;
|
|
340
|
+
margin-bottom: 5px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* ========================================
|
|
344
|
+
CHOICES BASE
|
|
345
|
+
======================================== */
|
|
346
|
+
|
|
347
|
+
:global(.gem-c-select-with-search) .choices {
|
|
129
348
|
width: 100%;
|
|
130
|
-
|
|
349
|
+
max-width: 100%;
|
|
350
|
+
box-sizing: border-box;
|
|
351
|
+
|
|
352
|
+
display: flex;
|
|
353
|
+
flex-direction: column;
|
|
354
|
+
align-items: stretch;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.choices__search-row {
|
|
358
|
+
display: flex;
|
|
359
|
+
flex-direction: row; /* side by side */
|
|
360
|
+
align-items: flex-start; /* ✅ align items to bottom of row */
|
|
361
|
+
gap: 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* ✅ ONE box around input + divider + chips */
|
|
365
|
+
:global(.gem-c-select-with-search) .choices__box {
|
|
366
|
+
border: 2px solid var(--govuk-black);
|
|
367
|
+
border-radius: 0;
|
|
368
|
+
background: white;
|
|
369
|
+
|
|
370
|
+
flex: 1 1 auto;
|
|
371
|
+
min-width: 0;
|
|
372
|
+
|
|
373
|
+
/* ✅ stacks input, divider, chips in one box */
|
|
374
|
+
display: flex;
|
|
375
|
+
flex-direction: column;
|
|
376
|
+
|
|
377
|
+
padding-right: 0; /* reserve space for button */
|
|
378
|
+
box-sizing: border-box;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* Yellow focus ring around WHOLE box */
|
|
382
|
+
:global(.gem-c-select-with-search) .choices.is-focused .choices__box,
|
|
383
|
+
:global(.gem-c-select-with-search) .choices.is-open .choices__box {
|
|
384
|
+
outline: 3px solid var(--govuk-focus);
|
|
385
|
+
outline-offset: 0;
|
|
386
|
+
box-shadow: inset 0 0 0 2px var(--govuk-black);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
:global(.gem-c-select-with-search) .choices__inner {
|
|
390
|
+
border: 0;
|
|
391
|
+
background: white;
|
|
392
|
+
min-height: var(--select-height);
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
padding: 5px;
|
|
396
|
+
gap: 8px;
|
|
397
|
+
cursor: text;
|
|
398
|
+
flex: 0 0 var(--select-height);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
:global(.gem-c-select-with-search) .choices__input {
|
|
131
402
|
border: none;
|
|
403
|
+
font-size: 19px;
|
|
404
|
+
margin: 0;
|
|
405
|
+
width: 100%;
|
|
406
|
+
min-width: 0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
:global(.gem-c-select-with-search) .choices__input:focus {
|
|
132
410
|
outline: none;
|
|
133
411
|
}
|
|
134
412
|
|
|
135
|
-
|
|
136
|
-
|
|
413
|
+
/* ✅ Divider is now an internal border line */
|
|
414
|
+
.ms-divider {
|
|
415
|
+
border-top: 1px solid var(--govuk-grey);
|
|
416
|
+
margin: 0 5px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* ========================================
|
|
420
|
+
MULTI SELECT CHIPS (now INSIDE the same box)
|
|
421
|
+
======================================== */
|
|
422
|
+
|
|
423
|
+
:global(.gem-c-select-with-search) .choices__list--multiple {
|
|
137
424
|
display: flex;
|
|
138
425
|
flex-wrap: wrap;
|
|
139
|
-
|
|
426
|
+
align-items: flex-start;
|
|
427
|
+
gap: 4px 10px;
|
|
428
|
+
width: 100%;
|
|
429
|
+
|
|
430
|
+
border-top: 1px solid var(--govuk-grey); /* ✅ the ONE divider */
|
|
431
|
+
margin: 0 5px; /* aligns with input padding */
|
|
432
|
+
padding: 8px 8px 10px; /* slightly nicer spacing */
|
|
433
|
+
width: calc(100% - 10px);
|
|
434
|
+
|
|
435
|
+
box-sizing: border-box;
|
|
140
436
|
}
|
|
141
437
|
|
|
142
|
-
.
|
|
143
|
-
background: #f3f2f1;
|
|
144
|
-
padding: 2px 6px;
|
|
145
|
-
border-radius: 4px;
|
|
438
|
+
:global(.gem-c-select-with-search) .choices__list--multiple .choices__item {
|
|
146
439
|
display: inline-flex;
|
|
147
440
|
align-items: center;
|
|
148
|
-
|
|
441
|
+
background: var(--govuk-light-grey);
|
|
442
|
+
color: var(--govuk-black);
|
|
443
|
+
box-shadow: 0 2px 0 var(--govuk-grey);
|
|
444
|
+
border-radius: 0;
|
|
445
|
+
|
|
446
|
+
min-height: 32px;
|
|
447
|
+
padding: 2px 0 2px 10px;
|
|
448
|
+
margin: 4px 10px 0 0;
|
|
449
|
+
line-height: 1.2;
|
|
450
|
+
|
|
451
|
+
max-width: 100%;
|
|
452
|
+
box-sizing: border-box;
|
|
149
453
|
}
|
|
150
454
|
|
|
151
|
-
.
|
|
152
|
-
width:
|
|
153
|
-
height:
|
|
455
|
+
:global(.gem-c-select-with-search) .choices__item-circle {
|
|
456
|
+
width: 16px;
|
|
457
|
+
height: 16px;
|
|
154
458
|
border-radius: 50%;
|
|
155
|
-
|
|
459
|
+
margin-right: 8px;
|
|
460
|
+
border: 1px solid var(--govuk-black);
|
|
156
461
|
}
|
|
157
462
|
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
463
|
+
:global(.gem-c-select-with-search) .choices__item-label {
|
|
464
|
+
flex: 1 1 auto;
|
|
465
|
+
min-width: 0;
|
|
466
|
+
overflow: visible;
|
|
467
|
+
text-overflow: unset;
|
|
468
|
+
white-space: normal;
|
|
469
|
+
word-break: break-word;
|
|
470
|
+
max-width: 26ch;
|
|
471
|
+
line-height: 1.2;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
:global(.gem-c-select-with-search)
|
|
475
|
+
.choices[data-type*="select-multiple"]
|
|
476
|
+
.choices__button {
|
|
477
|
+
width: 32px;
|
|
478
|
+
padding: 0;
|
|
479
|
+
|
|
480
|
+
border: 0;
|
|
481
|
+
margin-left: 8px;
|
|
482
|
+
|
|
483
|
+
background: transparent;
|
|
484
|
+
color: var(--govuk-black);
|
|
485
|
+
font-size: 20px;
|
|
486
|
+
line-height: 1;
|
|
487
|
+
|
|
488
|
+
opacity: 0.85;
|
|
489
|
+
cursor: pointer;
|
|
490
|
+
transition:
|
|
491
|
+
background-color 120ms ease,
|
|
492
|
+
opacity 120ms ease;
|
|
493
|
+
|
|
494
|
+
align-self: stretch;
|
|
495
|
+
height: auto;
|
|
496
|
+
min-height: 100%;
|
|
497
|
+
padding: 0 10px;
|
|
498
|
+
border-left: 1px solid var(--govuk-grey);
|
|
499
|
+
display: inline-flex;
|
|
500
|
+
align-items: center;
|
|
501
|
+
justify-content: center;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
:global(.gem-c-select-with-search)
|
|
505
|
+
.choices[data-type*="select-multiple"]
|
|
506
|
+
.choices__button:hover {
|
|
507
|
+
background-color: #e0e0e0;
|
|
508
|
+
opacity: 1;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
:global(.gem-c-select-with-search)
|
|
512
|
+
.choices[data-type*="select-multiple"]
|
|
513
|
+
.choices__button:focus {
|
|
514
|
+
outline: 3px solid var(--govuk-focus);
|
|
515
|
+
outline-offset: 0;
|
|
516
|
+
opacity: 1;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* ========================================
|
|
520
|
+
SEARCH BUTTON
|
|
521
|
+
======================================== */
|
|
522
|
+
|
|
523
|
+
.search-addon-btn {
|
|
524
|
+
width: var(--addon-width); /* keeps the width fixed */
|
|
525
|
+
height: var(--select-height); /* match the input box height */
|
|
526
|
+
display: inline-flex;
|
|
527
|
+
align-items: center; /* center the icon vertically */
|
|
528
|
+
justify-content: center; /* center the icon horizontally */
|
|
529
|
+
background-color: var(--govuk-blue); /* keep the blue */
|
|
530
|
+
color: #fff;
|
|
531
|
+
border: 0;
|
|
532
|
+
padding: 0;
|
|
533
|
+
font-size: 19px;
|
|
534
|
+
font-family: "GDS Transport", arial, sans-serif;
|
|
535
|
+
cursor: pointer;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.search-addon-btn:focus-visible {
|
|
539
|
+
outline: 3px solid var(--govuk-focus);
|
|
540
|
+
box-shadow: inset 0 0 0 4px var(--govuk-black);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.search-addon-icon {
|
|
544
|
+
position: relative;
|
|
545
|
+
width: var(--addon-width);
|
|
546
|
+
height: var(--addon-width);
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: center;
|
|
549
|
+
justify-content: center;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
:global(.gem-c-select-with-search) .choices__list--dropdown {
|
|
553
|
+
/* IMPORTANT: ensure it participates in normal layout */
|
|
554
|
+
position: static !important;
|
|
555
|
+
inset: auto !important; /* cancels top/left/right/bottom if set somewhere */
|
|
556
|
+
transform: none !important;
|
|
557
|
+
|
|
558
|
+
/* layout */
|
|
559
|
+
display: block !important;
|
|
161
560
|
width: 100%;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
margin-top:
|
|
165
|
-
|
|
166
|
-
|
|
561
|
+
box-sizing: border-box;
|
|
562
|
+
|
|
563
|
+
margin-top: 0;
|
|
564
|
+
border: 2px solid var(--govuk-black);
|
|
565
|
+
border-top: none;
|
|
566
|
+
background: white;
|
|
567
|
+
|
|
568
|
+
max-height: 300px;
|
|
167
569
|
overflow-y: auto;
|
|
570
|
+
z-index: auto; /* z-index not needed when in normal flow */
|
|
571
|
+
width: calc(100% - var(--addon-width));
|
|
572
|
+
margin-left: 0;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
:global(.gem-c-select-with-search) .choices__list--dropdown .choices__item {
|
|
576
|
+
display: flex;
|
|
577
|
+
align-items: center;
|
|
578
|
+
min-height: var(--item-height);
|
|
579
|
+
padding: 12px 10px;
|
|
580
|
+
position: relative;
|
|
581
|
+
width: 100%;
|
|
582
|
+
|
|
583
|
+
box-sizing: border-box;
|
|
584
|
+
flex: 0 0 auto; /* don't shrink into columns */
|
|
585
|
+
white-space: normal; /* allow wrappintext */
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
:global(.gem-c-select-with-search)
|
|
589
|
+
.choices__list--dropdown
|
|
590
|
+
.choices__item::after {
|
|
591
|
+
content: "";
|
|
592
|
+
position: absolute;
|
|
593
|
+
left: 15px;
|
|
594
|
+
right: 15px;
|
|
595
|
+
bottom: 0;
|
|
596
|
+
height: 1px;
|
|
597
|
+
background: var(--govuk-grey);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
:global(.gem-c-select-with-search) .choices__item--selectable:hover {
|
|
601
|
+
background: var(--govuk-blue);
|
|
602
|
+
color: white;
|
|
603
|
+
cursor: pointer;
|
|
604
|
+
}
|
|
605
|
+
:global(.gem-c-select-with-search) .choices__list--dropdown {
|
|
606
|
+
display: block !important;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
:global(.gem-c-select-with-search)
|
|
610
|
+
.choices.is-open
|
|
611
|
+
.choices__box
|
|
612
|
+
.choices__inner,
|
|
613
|
+
:global(.gem-c-select-with-search)
|
|
614
|
+
.choices.is-focused
|
|
615
|
+
.choices__box
|
|
616
|
+
.choices__inner {
|
|
617
|
+
border-bottom: 0;
|
|
618
|
+
box-shadow: none;
|
|
619
|
+
margin-bottom: 0 !important;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/* Only when chips are present: make the inner section visually merge into chips */
|
|
623
|
+
:global(.gem-c-select-with-search)
|
|
624
|
+
.choices.is-open
|
|
625
|
+
.choices__box:has(.choices__list--multiple)
|
|
626
|
+
.choices__inner,
|
|
627
|
+
:global(.gem-c-select-with-search)
|
|
628
|
+
.choices.is-focused
|
|
629
|
+
.choices__box:has(.choices__list--multiple)
|
|
630
|
+
.choices__inner {
|
|
631
|
+
border-bottom: 0;
|
|
632
|
+
padding-bottom: 0; /* optional: removes gap above the divider */
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
:global(.gem-c-select-with-search) .choices.is-focused .choices__box,
|
|
636
|
+
:global(.gem-c-select-with-search) .choices.is-open .choices__box {
|
|
637
|
+
outline: none;
|
|
638
|
+
/* keep a subtle black inset so focus isn't invisible */
|
|
639
|
+
box-shadow: inset 0 0 0 2px var(--govuk-black);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/* Remove yellow outline on the blue search button */
|
|
643
|
+
.search-addon-btn:focus-visible {
|
|
644
|
+
outline: none;
|
|
645
|
+
box-shadow: inset 0 0 0 3px var(--govuk-black);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/* Remove yellow outline on the chip remove buttons */
|
|
649
|
+
:global(.gem-c-select-with-search)
|
|
650
|
+
.choices[data-type*="select-multiple"]
|
|
651
|
+
.choices__button:focus {
|
|
652
|
+
outline: none;
|
|
653
|
+
box-shadow: inset 0 0 0 3px var(--govuk-black);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.choices__actions {
|
|
657
|
+
/* stack inside the box under chips */
|
|
658
|
+
display: block;
|
|
659
|
+
width: 100%;
|
|
660
|
+
box-sizing: border-box;
|
|
661
|
+
|
|
662
|
+
/* spacing aligned with your chips area */
|
|
663
|
+
margin: 0 5px;
|
|
664
|
+
width: calc(100% - 10px);
|
|
665
|
+
padding: 8px 8px 10px;
|
|
666
|
+
|
|
667
|
+
/* optional divider line (remove if you don't want another line) */
|
|
668
|
+
.choices__actions {
|
|
669
|
+
border-top: 0;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
text-align: left; /* keeps it looking like it belongs "under" */
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.choices__clear-all {
|
|
676
|
+
background: transparent;
|
|
677
|
+
border: 0;
|
|
168
678
|
padding: 0;
|
|
169
|
-
|
|
679
|
+
color: var(--govuk-blue);
|
|
680
|
+
font-family: "GDS Transport", arial, sans-serif;
|
|
681
|
+
font-size: 19px;
|
|
682
|
+
cursor: pointer;
|
|
683
|
+
text-decoration: underline;
|
|
170
684
|
}
|
|
171
685
|
|
|
172
|
-
.
|
|
173
|
-
|
|
686
|
+
.choices__clear-all:hover {
|
|
687
|
+
color: #003078; /* slightly darker GOV.UK blue */
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.choices__clear-all:focus-visible {
|
|
691
|
+
outline: 3px solid var(--govuk-focus);
|
|
692
|
+
outline-offset: 0;
|
|
693
|
+
box-shadow: inset 0 0 0 3px var(--govuk-black);
|
|
694
|
+
text-decoration: none;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
:global(.gem-c-select-with-search) .choices__list--dropdown {
|
|
698
|
+
margin: 0 !important; /* ✅ kills the 16px */
|
|
699
|
+
padding: 0;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.choices__reset {
|
|
703
|
+
background: transparent;
|
|
704
|
+
border: 0;
|
|
705
|
+
padding: 0;
|
|
706
|
+
margin-left: 16px;
|
|
707
|
+
|
|
708
|
+
color: var(--govuk-blue);
|
|
709
|
+
font-family: "GDS Transport", arial, sans-serif;
|
|
710
|
+
font-size: 19px;
|
|
174
711
|
cursor: pointer;
|
|
712
|
+
text-decoration: underline;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.choices__reset:hover {
|
|
716
|
+
color: #003078;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.choices__reset:focus-visible {
|
|
720
|
+
outline: 3px solid var(--govuk-focus);
|
|
721
|
+
outline-offset: 0;
|
|
722
|
+
box-shadow: inset 0 0 0 3px var(--govuk-black);
|
|
723
|
+
text-decoration: none;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
:global(.gem-c-select-with-search) .choices__list--dropdown {
|
|
727
|
+
scrollbar-width: thin;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
:global(.gem-c-select-with-search)
|
|
731
|
+
.choices__list--dropdown::-webkit-scrollbar {
|
|
732
|
+
width: 10px;
|
|
175
733
|
}
|
|
176
734
|
|
|
177
|
-
.
|
|
178
|
-
|
|
735
|
+
:global(.gem-c-select-with-search)
|
|
736
|
+
.choices__list--dropdown::-webkit-scrollbar-thumb {
|
|
737
|
+
background-color: var(--govuk-grey);
|
|
738
|
+
border-radius: 0;
|
|
179
739
|
}
|
|
180
740
|
|
|
181
|
-
.
|
|
182
|
-
|
|
183
|
-
padding: 8px;
|
|
741
|
+
.choices__actions:empty {
|
|
742
|
+
display: none;
|
|
184
743
|
}
|
|
185
744
|
</style>
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
type Option = {
|
|
2
|
+
id: string | number;
|
|
3
|
+
label: string;
|
|
4
|
+
};
|
|
5
|
+
type SelectedWithColor = Option & {
|
|
6
|
+
color: string;
|
|
7
|
+
};
|
|
8
|
+
type SelectedItem = Option | SelectedWithColor;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
options?: Option[];
|
|
11
|
+
selected?: SelectedItem[];
|
|
12
|
+
colorArray?: string[];
|
|
5
13
|
placeholder?: string;
|
|
6
|
-
|
|
14
|
+
label?: string;
|
|
15
|
+
hint?: string;
|
|
16
|
+
enableColors?: boolean;
|
|
17
|
+
startsWithSearch?: boolean;
|
|
18
|
+
resetButton?: boolean;
|
|
19
|
+
};
|
|
20
|
+
declare const BasicMultiSelect: import("svelte").Component<$$ComponentProps, {}, "selected">;
|
|
7
21
|
type BasicMultiSelect = ReturnType<typeof BasicMultiSelect>;
|
|
8
22
|
export default BasicMultiSelect;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import homepageIllustration from "./../../assets/images/homepage-illustration.svg";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
3
4
|
|
|
4
5
|
// Define component props with types and default values
|
|
5
6
|
let {
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
paddingTop = "30px",
|
|
17
18
|
titlePaddingTop = false,
|
|
18
19
|
paddingBottom = "30px",
|
|
20
|
+
contentSnippet = undefined,
|
|
19
21
|
} = $props<{
|
|
20
22
|
title?: string;
|
|
21
23
|
description?: string;
|
|
@@ -30,7 +32,10 @@
|
|
|
30
32
|
paddingTop?: string;
|
|
31
33
|
titlePaddingTop?: boolean;
|
|
32
34
|
paddingBottom?: string;
|
|
35
|
+
contentSnippet?: Snippet;
|
|
33
36
|
}>();
|
|
37
|
+
|
|
38
|
+
let arg = "hello";
|
|
34
39
|
</script>
|
|
35
40
|
|
|
36
41
|
<div
|
|
@@ -46,6 +51,9 @@
|
|
|
46
51
|
>
|
|
47
52
|
<h1 class="govuk-heading-xl app-masthead__title">{@html title}</h1>
|
|
48
53
|
<p class="app-masthead__description">{description}</p>
|
|
54
|
+
{#if contentSnippet}
|
|
55
|
+
{@render contentSnippet()}
|
|
56
|
+
{/if}
|
|
49
57
|
{#if includeButton === true}
|
|
50
58
|
<a
|
|
51
59
|
href={buttonHref}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
1
2
|
type $$ComponentProps = {
|
|
2
3
|
title?: string;
|
|
3
4
|
description?: string;
|
|
@@ -12,6 +13,7 @@ type $$ComponentProps = {
|
|
|
12
13
|
paddingTop?: string;
|
|
13
14
|
titlePaddingTop?: boolean;
|
|
14
15
|
paddingBottom?: string;
|
|
16
|
+
contentSnippet?: Snippet;
|
|
15
17
|
};
|
|
16
18
|
declare const Masthead: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
19
|
type Masthead = ReturnType<typeof Masthead>;
|