@communitiesuk/svelte-component-library 0.1.19-beta.2 → 0.1.19-beta.21
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/README.md +7 -0
- package/dist/components/data-vis/Histogram.svelte +282 -0
- package/dist/components/data-vis/Histogram.svelte.d.ts +75 -0
- package/dist/components/data-vis/axis/Axis.svelte +145 -34
- package/dist/components/data-vis/axis/Axis.svelte.d.ts +34 -30
- package/dist/components/data-vis/axis/Ticks.svelte +163 -60
- package/dist/components/data-vis/axis/Ticks.svelte.d.ts +26 -30
- package/dist/components/data-vis/line-chart/LineChart.svelte +51 -21
- package/dist/components/data-vis/line-chart/LineChart.svelte.d.ts +14 -6
- package/dist/components/data-vis/position-chart/PositionChart.svelte +255 -117
- package/dist/components/data-vis/position-chart/PositionChart.svelte.d.ts +28 -4
- package/dist/components/data-vis/position-chart/PositionChartAxis.svelte +39 -34
- package/dist/components/data-vis/position-chart/PositionChartAxis.svelte.d.ts +6 -2
- package/dist/components/layout/Footer.svelte +9 -0
- package/dist/components/layout/Footer.svelte.d.ts +1 -0
- package/dist/components/layout/PhaseBanner.svelte +10 -1
- package/dist/components/layout/PhaseBanner.svelte.d.ts +1 -0
- package/dist/components/layout/ServiceNavigation.svelte +19 -1
- package/dist/components/layout/ServiceNavigation.svelte.d.ts +2 -0
- package/dist/components/ui/BasicMultiSelect.svelte +185 -0
- package/dist/components/ui/BasicMultiSelect.svelte.d.ts +8 -0
- package/dist/components/ui/Button.svelte +1 -0
- package/dist/components/ui/Card.svelte +48 -60
- package/dist/components/ui/Card.svelte.d.ts +26 -12
- package/dist/components/ui/CardHeader.svelte +46 -0
- package/dist/components/ui/CardHeader.svelte.d.ts +21 -0
- package/dist/components/ui/ChartExporter.svelte +142 -0
- package/dist/components/ui/ChartExporter.svelte.d.ts +16 -0
- package/dist/components/ui/Details.svelte +10 -2
- package/dist/components/ui/Details.svelte.d.ts +2 -0
- package/dist/components/ui/Masthead.svelte +36 -6
- package/dist/components/ui/Masthead.svelte.d.ts +4 -0
- package/dist/components/ui/PostcodeOrAreaSearch.svelte +12 -0
- package/dist/components/ui/PostcodeOrAreaSearch.svelte.d.ts +4 -0
- package/dist/components/ui/RelatedContent.svelte +4 -1
- package/dist/components/ui/RelatedContent.svelte.d.ts +1 -0
- package/dist/components/ui/SearchAutocomplete.svelte +185 -34
- package/dist/components/ui/SearchAutocomplete.svelte.d.ts +5 -0
- package/dist/components/ui/Tabs.svelte +190 -18
- package/dist/components/ui/Tabs.svelte.d.ts +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/package.json +4 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
|
+
import { on } from "svelte/events";
|
|
4
5
|
import Search from "./Search.svelte"; // Base component
|
|
5
6
|
import "accessible-autocomplete/dist/accessible-autocomplete.min.css";
|
|
6
7
|
import { browser } from "$app/environment";
|
|
@@ -51,6 +52,11 @@
|
|
|
51
52
|
hint?: string; // Add hint prop
|
|
52
53
|
selectedValue?: any; // Bindable selected value, updated on selection
|
|
53
54
|
maxSuggestions?: number; // Maximum number of suggestions to display
|
|
55
|
+
autoselect?: boolean; // Auto-highlight first suggestion
|
|
56
|
+
hideHint?: boolean; // Hide the hint input element when autoselect is true
|
|
57
|
+
prefixMatchOnly?: boolean; // Only show suggestions that start with the query (better for hint behavior)
|
|
58
|
+
autoFocusSubmitOnSelection?: boolean; // Auto-focus submit button when selection is confirmed
|
|
59
|
+
allowFreeTextSubmission?: boolean; // Treat free-typed input as confirmed when submitted (works with or without form)
|
|
54
60
|
};
|
|
55
61
|
|
|
56
62
|
let {
|
|
@@ -85,6 +91,11 @@
|
|
|
85
91
|
hint = undefined, // Add hint destructuring
|
|
86
92
|
selectedValue = $bindable(), // Bindable prop for selected value
|
|
87
93
|
maxSuggestions = undefined, // Maximum number of suggestions to display
|
|
94
|
+
autoselect = true, // Default to false as per library default
|
|
95
|
+
hideHint = false, // Default to false - show hint by default
|
|
96
|
+
prefixMatchOnly = false, // Default to false - show all matches
|
|
97
|
+
autoFocusSubmitOnSelection = false, // Default to false - don't auto-focus by default
|
|
98
|
+
allowFreeTextSubmission = false, // Default to false - only confirmed suggestions submit
|
|
88
99
|
...restSearchProps // Other props for the base Search component
|
|
89
100
|
}: Props = $props();
|
|
90
101
|
|
|
@@ -254,19 +265,50 @@
|
|
|
254
265
|
populateResults([]);
|
|
255
266
|
return;
|
|
256
267
|
}
|
|
268
|
+
|
|
257
269
|
const lowerQuery = query.toLowerCase();
|
|
258
|
-
const filtered = options.filter((option) => {
|
|
259
|
-
const label = typeof option === "string" ? option : option.label;
|
|
260
|
-
return label.toLowerCase().includes(lowerQuery);
|
|
261
|
-
});
|
|
262
270
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
?
|
|
267
|
-
|
|
271
|
+
if (prefixMatchOnly) {
|
|
272
|
+
// Only show suggestions that start with the query
|
|
273
|
+
const filtered = options.filter((option) => {
|
|
274
|
+
const label = typeof option === "string" ? option : option.label;
|
|
275
|
+
return label.toLowerCase().startsWith(lowerQuery);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Apply maxSuggestions limit if specified
|
|
279
|
+
const limitedResults =
|
|
280
|
+
maxSuggestions && maxSuggestions > 0
|
|
281
|
+
? filtered.slice(0, maxSuggestions)
|
|
282
|
+
: filtered;
|
|
283
|
+
|
|
284
|
+
populateResults(limitedResults);
|
|
285
|
+
} else {
|
|
286
|
+
// Split results into two groups: starts-with and contains (existing behavior)
|
|
287
|
+
const startsWithResults: Suggestion[] = [];
|
|
288
|
+
const containsResults: Suggestion[] = [];
|
|
289
|
+
|
|
290
|
+
options.forEach((option) => {
|
|
291
|
+
const label = typeof option === "string" ? option : option.label;
|
|
292
|
+
const lowerLabel = label.toLowerCase();
|
|
293
|
+
|
|
294
|
+
if (lowerLabel.startsWith(lowerQuery)) {
|
|
295
|
+
startsWithResults.push(option);
|
|
296
|
+
} else if (lowerLabel.includes(lowerQuery)) {
|
|
297
|
+
containsResults.push(option);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
268
300
|
|
|
269
|
-
|
|
301
|
+
// Combine results: starts-with first (for better hint behavior), then contains
|
|
302
|
+
const filtered = [...startsWithResults, ...containsResults];
|
|
303
|
+
|
|
304
|
+
// Apply maxSuggestions limit if specified
|
|
305
|
+
const limitedResults =
|
|
306
|
+
maxSuggestions && maxSuggestions > 0
|
|
307
|
+
? filtered.slice(0, maxSuggestions)
|
|
308
|
+
: filtered;
|
|
309
|
+
|
|
310
|
+
populateResults(limitedResults);
|
|
311
|
+
}
|
|
270
312
|
};
|
|
271
313
|
|
|
272
314
|
// Determine which source to use
|
|
@@ -297,6 +339,7 @@
|
|
|
297
339
|
const dynamicSourceFunction = sourceSelector
|
|
298
340
|
? (query: string, populateResults: (results: Suggestion[]) => void) => {
|
|
299
341
|
const selectedSource = sourceSelector(query, options || []);
|
|
342
|
+
|
|
300
343
|
// Handle invalid returns by falling back to default logic
|
|
301
344
|
if (selectedSource === "api") {
|
|
302
345
|
getResultsFromApi(query, populateResults);
|
|
@@ -368,34 +411,39 @@
|
|
|
368
411
|
const handleConfirm = (confirmedValue: Suggestion | undefined) => {
|
|
369
412
|
if (confirmedValue === undefined || isSubmitting) return;
|
|
370
413
|
|
|
371
|
-
|
|
372
|
-
|
|
414
|
+
isSubmitting = true;
|
|
415
|
+
|
|
416
|
+
// Update selectedValue
|
|
373
417
|
selectedValue =
|
|
374
418
|
typeof confirmedValue === "string"
|
|
375
419
|
? confirmedValue
|
|
376
420
|
: confirmedValue.value;
|
|
377
421
|
|
|
378
|
-
//
|
|
422
|
+
// Mark as accepted to prevent form submit handler from processing again
|
|
379
423
|
const inputElement =
|
|
380
424
|
autocompleteInstance?.inputElement as HTMLInputElement;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
425
|
+
if (inputElement) {
|
|
426
|
+
inputElement.value = inputValueTemplate(confirmedValue);
|
|
427
|
+
inputElement.dataset.autocompleteAccepted = "true";
|
|
428
|
+
}
|
|
384
429
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
430
|
+
// Auto-focus submit button if enabled
|
|
431
|
+
if (autoFocusSubmitOnSelection) {
|
|
432
|
+
const submitButton = containerElement?.querySelector(
|
|
433
|
+
'button[type="submit"]',
|
|
434
|
+
) as HTMLButtonElement | null;
|
|
435
|
+
if (submitButton) {
|
|
436
|
+
requestAnimationFrame(() => submitButton.focus());
|
|
437
|
+
}
|
|
438
|
+
}
|
|
388
439
|
|
|
389
|
-
// Submit form
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
form.submit(); // Fallback for older browsers
|
|
440
|
+
// Submit form if present
|
|
441
|
+
const form = containerElement?.closest("form");
|
|
442
|
+
if (form) {
|
|
443
|
+
form.requestSubmit?.() ?? form.submit();
|
|
394
444
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
isSubmitting = false;
|
|
398
|
-
}, 500);
|
|
445
|
+
|
|
446
|
+
isSubmitting = false;
|
|
399
447
|
};
|
|
400
448
|
|
|
401
449
|
// Initialise accessible-autocomplete
|
|
@@ -406,6 +454,8 @@
|
|
|
406
454
|
inputClasses: searchInput.classList, // Pass original classes directly
|
|
407
455
|
source: finalSourceFunction,
|
|
408
456
|
minLength: minLength,
|
|
457
|
+
autoselect: autoselect,
|
|
458
|
+
hintClasses: hideHint ? "hidden-hint" : "",
|
|
409
459
|
confirmOnBlur: confirmOnBlur,
|
|
410
460
|
showNoOptionsFound: showNoOptionsFound,
|
|
411
461
|
defaultValue: defaultValue,
|
|
@@ -430,10 +480,8 @@
|
|
|
430
480
|
const autocompleteInputElement = containerElement?.querySelector(
|
|
431
481
|
".gem-c-search-with-autocomplete__input",
|
|
432
482
|
) as HTMLInputElement | null;
|
|
433
|
-
|
|
434
|
-
//
|
|
435
|
-
// autocompleteInputElement,
|
|
436
|
-
// ); // Updated log
|
|
483
|
+
|
|
484
|
+
// Apply hint visibility classes via hintClasses API
|
|
437
485
|
|
|
438
486
|
// Post-initialisation tweaks
|
|
439
487
|
if (autocompleteInputElement) {
|
|
@@ -444,8 +492,15 @@
|
|
|
444
492
|
".gem-c-search-with-autocomplete__menu",
|
|
445
493
|
);
|
|
446
494
|
// Listen for input changes on the autocomplete field
|
|
447
|
-
autocompleteInputElement
|
|
495
|
+
on(autocompleteInputElement, "input", () => {
|
|
448
496
|
const val = autocompleteInputElement.value;
|
|
497
|
+
|
|
498
|
+
// Reset isSubmitting flag when user starts typing again
|
|
499
|
+
if (isSubmitting) {
|
|
500
|
+
console.log("User typing, resetting isSubmitting flag");
|
|
501
|
+
isSubmitting = false;
|
|
502
|
+
}
|
|
503
|
+
|
|
449
504
|
// Remove any existing 'too-short' warning before adding a new one to ensure we don't accumulate multiple warning items.
|
|
450
505
|
suggestionsMenu
|
|
451
506
|
?.querySelector(
|
|
@@ -476,7 +531,7 @@
|
|
|
476
531
|
// autocompleteInputElement.classList.add("autocomplete__input"); // Add specific class if needed
|
|
477
532
|
|
|
478
533
|
// Add Enter key workaround from original JS
|
|
479
|
-
autocompleteInputElement
|
|
534
|
+
on(autocompleteInputElement, "keydown", (e) => {
|
|
480
535
|
if (isSubmitting) return; // Don't interfere if already submitting
|
|
481
536
|
const dropdownVisible =
|
|
482
537
|
autocompleteInputElement.getAttribute("aria-expanded") === "true";
|
|
@@ -499,6 +554,44 @@
|
|
|
499
554
|
// );
|
|
500
555
|
}
|
|
501
556
|
|
|
557
|
+
// Handle free-text submission when allowFreeTextSubmission is enabled
|
|
558
|
+
// handleConfirm is only called by the library when selecting from dropdown
|
|
559
|
+
// We need to catch: (1) form submit events, (2) button clicks outside forms
|
|
560
|
+
if (allowFreeTextSubmission) {
|
|
561
|
+
const updateFreeText = () => {
|
|
562
|
+
if (!autocompleteInputElement) return;
|
|
563
|
+
|
|
564
|
+
const wasAccepted =
|
|
565
|
+
autocompleteInputElement.dataset.autocompleteAccepted === "true";
|
|
566
|
+
const value = autocompleteInputElement.value?.trim();
|
|
567
|
+
|
|
568
|
+
if (value && !wasAccepted) {
|
|
569
|
+
selectedValue = value;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Reset flag after submission
|
|
573
|
+
setTimeout(() => {
|
|
574
|
+
if (autocompleteInputElement) {
|
|
575
|
+
autocompleteInputElement.dataset.autocompleteAccepted = "false";
|
|
576
|
+
}
|
|
577
|
+
}, 100);
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// Handle form submission
|
|
581
|
+
const form = containerElement?.closest("form");
|
|
582
|
+
if (form) {
|
|
583
|
+
on(form, "submit", updateFreeText);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Handle button clicks (for non-form usage)
|
|
587
|
+
const submitButton = containerElement?.querySelector(
|
|
588
|
+
'button[type="submit"]',
|
|
589
|
+
) as HTMLButtonElement | null;
|
|
590
|
+
if (submitButton && !form) {
|
|
591
|
+
on(submitButton, "click", updateFreeText);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
502
595
|
// IMPORTANT: Remove the original Search.svelte input, as accessible-autocomplete replaces it.
|
|
503
596
|
// We render it initially so accessible-autocomplete can grab its id, name, value.
|
|
504
597
|
if (searchInput) {
|
|
@@ -539,6 +632,64 @@
|
|
|
539
632
|
position: relative;
|
|
540
633
|
}
|
|
541
634
|
|
|
635
|
+
/* Hide hint when requested via hintClasses - must come AFTER the default styling for proper cascade */
|
|
636
|
+
.gem-c-search-with-autocomplete
|
|
637
|
+
.gem-c-search-with-autocomplete__hint.hidden-hint {
|
|
638
|
+
display: none !important;
|
|
639
|
+
visibility: hidden !important;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/* Default hint styling when visible - ensure smooth overlapping with main input */
|
|
643
|
+
.gem-c-search-with-autocomplete .gem-c-search-with-autocomplete__hint {
|
|
644
|
+
/* Use identical positioning and sizing as main input */
|
|
645
|
+
position: absolute !important;
|
|
646
|
+
top: 0 !important;
|
|
647
|
+
left: 0 !important;
|
|
648
|
+
z-index: 99 !important;
|
|
649
|
+
|
|
650
|
+
/* Visual styling for hint */
|
|
651
|
+
color: rgba(0, 0, 0, 0.4);
|
|
652
|
+
background: transparent;
|
|
653
|
+
pointer-events: none;
|
|
654
|
+
|
|
655
|
+
/* Copy EXACT styling from main input */
|
|
656
|
+
margin: 0;
|
|
657
|
+
width: 100%;
|
|
658
|
+
height: 2.1052631579em;
|
|
659
|
+
padding: 0.3157894737em;
|
|
660
|
+
border: 2px solid transparent; /* Transparent instead of visible */
|
|
661
|
+
border-radius: 0;
|
|
662
|
+
box-sizing: border-box;
|
|
663
|
+
-webkit-appearance: none;
|
|
664
|
+
-moz-appearance: none;
|
|
665
|
+
appearance: none;
|
|
666
|
+
font-family: "GDS Transport", arial, sans-serif;
|
|
667
|
+
-webkit-font-smoothing: antialiased;
|
|
668
|
+
-moz-osx-font-smoothing: grayscale;
|
|
669
|
+
font-weight: 400;
|
|
670
|
+
font-size: 1.1875rem;
|
|
671
|
+
line-height: 1.4736842105;
|
|
672
|
+
|
|
673
|
+
/* Text alignment */
|
|
674
|
+
text-align: left;
|
|
675
|
+
white-space: nowrap;
|
|
676
|
+
overflow: hidden;
|
|
677
|
+
|
|
678
|
+
/* Ensure visibility - but allow hidden-hint to override */
|
|
679
|
+
display: block;
|
|
680
|
+
visibility: visible;
|
|
681
|
+
opacity: 1;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/* Custom focus styles for submit button (when focused after autocomplete selection) */
|
|
685
|
+
.gem-c-search-with-autocomplete .gem-c-search__submit:focus {
|
|
686
|
+
background-color: #ffdd00; /* GDS focus yellow */
|
|
687
|
+
border-color: #0b0c0c; /* GDS text color for contrast */
|
|
688
|
+
color: #0b0c0c; /* Dark text on yellow background */
|
|
689
|
+
outline: 3px solid #ffdd00;
|
|
690
|
+
outline-offset: 0;
|
|
691
|
+
}
|
|
692
|
+
|
|
542
693
|
.gem-c-search-with-autocomplete__menu {
|
|
543
694
|
margin: 0;
|
|
544
695
|
padding: 0;
|
|
@@ -36,6 +36,11 @@ type Props = {
|
|
|
36
36
|
hint?: string;
|
|
37
37
|
selectedValue?: any;
|
|
38
38
|
maxSuggestions?: number;
|
|
39
|
+
autoselect?: boolean;
|
|
40
|
+
hideHint?: boolean;
|
|
41
|
+
prefixMatchOnly?: boolean;
|
|
42
|
+
autoFocusSubmitOnSelection?: boolean;
|
|
43
|
+
allowFreeTextSubmission?: boolean;
|
|
39
44
|
};
|
|
40
45
|
declare const SearchAutocomplete: import("svelte").Component<Props, {}, "selectedValue">;
|
|
41
46
|
type SearchAutocomplete = ReturnType<typeof SearchAutocomplete>;
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
idPrefix = "tab",
|
|
20
20
|
selectedTabId = $bindable(),
|
|
21
21
|
autoAddHeadings = true,
|
|
22
|
+
forceTabBehavior = false,
|
|
22
23
|
} = $props<{
|
|
23
24
|
title?: string;
|
|
24
25
|
tabs: TabItem[];
|
|
25
26
|
idPrefix?: string;
|
|
26
27
|
selectedTabId?: string | null;
|
|
27
28
|
autoAddHeadings?: boolean;
|
|
29
|
+
forceTabBehavior?: boolean;
|
|
28
30
|
}>();
|
|
29
31
|
|
|
30
32
|
// Component state variables
|
|
@@ -32,6 +34,9 @@
|
|
|
32
34
|
let isSupported = $state(false);
|
|
33
35
|
let isMobile = $state(false);
|
|
34
36
|
|
|
37
|
+
// Derived value: use mobile behavior only if isMobile is true AND forceTabBehavior is false
|
|
38
|
+
let useMobileBehavior = $derived(isMobile && !forceTabBehavior);
|
|
39
|
+
|
|
35
40
|
// DOM element references for programmatic focus
|
|
36
41
|
let tabElements: { [key: string]: HTMLAnchorElement } = {};
|
|
37
42
|
|
|
@@ -56,7 +61,7 @@
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
// Update URL hash on non-mobile views
|
|
59
|
-
if (!
|
|
64
|
+
if (!useMobileBehavior) {
|
|
60
65
|
// Use history.replaceState to update the displayed URL hash without causing scroll/navigation.
|
|
61
66
|
const currentUrl = window.location.href;
|
|
62
67
|
const hashIndex = currentUrl.indexOf("#");
|
|
@@ -70,7 +75,7 @@
|
|
|
70
75
|
// Handle keyboard navigation
|
|
71
76
|
function handleKeydown(event: KeyboardEvent, currentIndex: number): void {
|
|
72
77
|
// Skip navigation on mobile or if component isn't ready
|
|
73
|
-
if (
|
|
78
|
+
if (useMobileBehavior || !isSupported || !isInitialized) return;
|
|
74
79
|
|
|
75
80
|
// Initialize to null, indicating no valid navigation key pressed yet.
|
|
76
81
|
// Will be updated to a valid index (0+) if ArrowLeft/Right is pressed.
|
|
@@ -107,7 +112,7 @@
|
|
|
107
112
|
// Handle tab click
|
|
108
113
|
function handleTabClick(event: MouseEvent, tabId: string): void {
|
|
109
114
|
// On mobile or without JS support, let default browser behavior happen
|
|
110
|
-
if (
|
|
115
|
+
if (useMobileBehavior || !isSupported) return;
|
|
111
116
|
event.preventDefault();
|
|
112
117
|
selectTab(tabId);
|
|
113
118
|
}
|
|
@@ -115,7 +120,7 @@
|
|
|
115
120
|
// Handle hash change
|
|
116
121
|
function handleHashChange(): void {
|
|
117
122
|
// Skip on mobile or when not properly initialized
|
|
118
|
-
if (
|
|
123
|
+
if (useMobileBehavior || !isSupported || !isInitialized) return;
|
|
119
124
|
|
|
120
125
|
const hash = window.location.hash.substring(1);
|
|
121
126
|
if (hash && tabs.some((tab) => tab.id === hash)) {
|
|
@@ -225,32 +230,45 @@
|
|
|
225
230
|
});
|
|
226
231
|
</script>
|
|
227
232
|
|
|
228
|
-
<div
|
|
233
|
+
<div
|
|
234
|
+
class="govuk-tabs"
|
|
235
|
+
data-module="govuk-tabs"
|
|
236
|
+
data-force-desktop={forceTabBehavior || null}
|
|
237
|
+
>
|
|
229
238
|
<h2 class="govuk-tabs__title">
|
|
230
239
|
{title}
|
|
231
240
|
</h2>
|
|
232
241
|
|
|
233
242
|
<ul
|
|
234
243
|
class="govuk-tabs__list"
|
|
235
|
-
role={isSupported && !
|
|
244
|
+
role={isSupported && !useMobileBehavior ? "tablist" : null}
|
|
236
245
|
>
|
|
237
246
|
{#each tabs as tab, index}
|
|
238
247
|
{@const isSelected = selectedTabId === tab.id}
|
|
239
248
|
{#key tab.id}
|
|
240
249
|
<li
|
|
241
250
|
class="govuk-tabs__list-item"
|
|
242
|
-
class:govuk-tabs__list-item--selected={isSelected &&
|
|
243
|
-
|
|
251
|
+
class:govuk-tabs__list-item--selected={isSelected &&
|
|
252
|
+
!useMobileBehavior}
|
|
253
|
+
role={isSupported && !useMobileBehavior ? "presentation" : null}
|
|
244
254
|
>
|
|
245
255
|
<!-- svelte-ignore binding_property_non_reactive -->
|
|
246
256
|
<a
|
|
247
257
|
class="govuk-tabs__tab"
|
|
248
258
|
href={"#" + tab.id}
|
|
249
|
-
id={isSupported && !
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
259
|
+
id={isSupported && !useMobileBehavior
|
|
260
|
+
? `${idPrefix}_${tab.id}`
|
|
261
|
+
: null}
|
|
262
|
+
role={isSupported && !useMobileBehavior ? "tab" : null}
|
|
263
|
+
aria-controls={isSupported && !useMobileBehavior ? tab.id : null}
|
|
264
|
+
aria-selected={isSupported && !useMobileBehavior
|
|
265
|
+
? isSelected
|
|
266
|
+
: null}
|
|
267
|
+
tabindex={isSupported && !useMobileBehavior
|
|
268
|
+
? isSelected
|
|
269
|
+
? 0
|
|
270
|
+
: -1
|
|
271
|
+
: null}
|
|
254
272
|
onclick={(e) => handleTabClick(e, tab.id)}
|
|
255
273
|
onkeydown={(e) => handleKeydown(e, index)}
|
|
256
274
|
bind:this={tabElements[tab.id]}
|
|
@@ -266,13 +284,15 @@
|
|
|
266
284
|
{@const isSelected = selectedTabId === tab.id}
|
|
267
285
|
<div
|
|
268
286
|
class="govuk-tabs__panel"
|
|
269
|
-
class:govuk-tabs__panel--hidden={!isSelected &&
|
|
287
|
+
class:govuk-tabs__panel--hidden={!isSelected &&
|
|
288
|
+
isSupported &&
|
|
289
|
+
!useMobileBehavior}
|
|
270
290
|
id={tab.id}
|
|
271
|
-
role={isSupported && !
|
|
272
|
-
aria-labelledby={isSupported && !
|
|
291
|
+
role={isSupported && !useMobileBehavior ? "tabpanel" : null}
|
|
292
|
+
aria-labelledby={isSupported && !useMobileBehavior
|
|
273
293
|
? `${idPrefix}_${tab.id}`
|
|
274
294
|
: null}
|
|
275
|
-
hidden={!isSelected && isSupported && !
|
|
295
|
+
hidden={!isSelected && isSupported && !useMobileBehavior}
|
|
276
296
|
>
|
|
277
297
|
{#if autoAddHeadings}
|
|
278
298
|
<h2 class="govuk-heading-l">{tab.label}</h2>
|
|
@@ -287,7 +307,8 @@
|
|
|
287
307
|
{:else if tab.content satisfies Snippet}
|
|
288
308
|
{@render tab.content()}
|
|
289
309
|
{:else if tab.content}
|
|
290
|
-
|
|
310
|
+
{@const Component = tab.content}
|
|
311
|
+
<Component />
|
|
291
312
|
{/if}
|
|
292
313
|
</div>
|
|
293
314
|
{/each}
|
|
@@ -303,4 +324,155 @@
|
|
|
303
324
|
.govuk-tabs__panel--hidden {
|
|
304
325
|
display: none;
|
|
305
326
|
}
|
|
327
|
+
|
|
328
|
+
/*
|
|
329
|
+
* Force desktop tab appearance when forceTabBehavior is enabled
|
|
330
|
+
* These styles are copied verbatim from GOV.UK Frontend's desktop (40.0625em+) styles
|
|
331
|
+
* but applied at mobile breakpoints when data-force-desktop attribute is present
|
|
332
|
+
*/
|
|
333
|
+
@media (max-width: 40.0624em) {
|
|
334
|
+
:global(.govuk-frontend-supported)
|
|
335
|
+
.govuk-tabs[data-force-desktop]
|
|
336
|
+
.govuk-tabs__list {
|
|
337
|
+
margin-bottom: 0;
|
|
338
|
+
border-bottom: 1px solid #b1b4b6;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
:global(.govuk-frontend-supported)
|
|
342
|
+
.govuk-tabs[data-force-desktop]
|
|
343
|
+
.govuk-tabs__list:after {
|
|
344
|
+
content: "";
|
|
345
|
+
display: block;
|
|
346
|
+
clear: both;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
:global(.govuk-frontend-supported)
|
|
350
|
+
.govuk-tabs[data-force-desktop]
|
|
351
|
+
.govuk-tabs__title {
|
|
352
|
+
display: none;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
:global(.govuk-frontend-supported)
|
|
356
|
+
.govuk-tabs[data-force-desktop]
|
|
357
|
+
.govuk-tabs__list-item {
|
|
358
|
+
position: relative;
|
|
359
|
+
margin-right: 5px;
|
|
360
|
+
margin-bottom: 0;
|
|
361
|
+
margin-left: 0;
|
|
362
|
+
padding: 10px 20px;
|
|
363
|
+
float: left;
|
|
364
|
+
background-color: #f3f2f1;
|
|
365
|
+
text-align: center;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
:global(.govuk-frontend-supported)
|
|
369
|
+
.govuk-tabs[data-force-desktop]
|
|
370
|
+
.govuk-tabs__list-item:before {
|
|
371
|
+
content: none;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
:global(.govuk-frontend-supported)
|
|
375
|
+
.govuk-tabs[data-force-desktop]
|
|
376
|
+
.govuk-tabs__list-item--selected {
|
|
377
|
+
position: relative;
|
|
378
|
+
margin-top: -5px;
|
|
379
|
+
margin-bottom: -1px;
|
|
380
|
+
padding: 14px 19px 16px;
|
|
381
|
+
border: 1px solid #b1b4b6;
|
|
382
|
+
border-bottom: 0;
|
|
383
|
+
background-color: #fff;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
:global(.govuk-frontend-supported)
|
|
387
|
+
.govuk-tabs[data-force-desktop]
|
|
388
|
+
.govuk-tabs__list-item--selected
|
|
389
|
+
.govuk-tabs__tab {
|
|
390
|
+
text-decoration: none;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
:global(.govuk-frontend-supported)
|
|
394
|
+
.govuk-tabs[data-force-desktop]
|
|
395
|
+
.govuk-tabs__tab {
|
|
396
|
+
margin-bottom: 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
:global(.govuk-frontend-supported)
|
|
400
|
+
.govuk-tabs[data-force-desktop]
|
|
401
|
+
.govuk-tabs__tab:link,
|
|
402
|
+
:global(.govuk-frontend-supported)
|
|
403
|
+
.govuk-tabs[data-force-desktop]
|
|
404
|
+
.govuk-tabs__tab:visited {
|
|
405
|
+
color: #0b0c0c;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@media print {
|
|
409
|
+
:global(.govuk-frontend-supported)
|
|
410
|
+
.govuk-tabs[data-force-desktop]
|
|
411
|
+
.govuk-tabs__tab:link,
|
|
412
|
+
:global(.govuk-frontend-supported)
|
|
413
|
+
.govuk-tabs[data-force-desktop]
|
|
414
|
+
.govuk-tabs__tab:visited {
|
|
415
|
+
color: #000;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
:global(.govuk-frontend-supported)
|
|
420
|
+
.govuk-tabs[data-force-desktop]
|
|
421
|
+
.govuk-tabs__tab:hover {
|
|
422
|
+
color: rgba(11, 12, 12, 0.99);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
:global(.govuk-frontend-supported)
|
|
426
|
+
.govuk-tabs[data-force-desktop]
|
|
427
|
+
.govuk-tabs__tab:active,
|
|
428
|
+
:global(.govuk-frontend-supported)
|
|
429
|
+
.govuk-tabs[data-force-desktop]
|
|
430
|
+
.govuk-tabs__tab:focus {
|
|
431
|
+
color: #0b0c0c;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
@media print {
|
|
435
|
+
:global(.govuk-frontend-supported)
|
|
436
|
+
.govuk-tabs[data-force-desktop]
|
|
437
|
+
.govuk-tabs__tab:active,
|
|
438
|
+
:global(.govuk-frontend-supported)
|
|
439
|
+
.govuk-tabs[data-force-desktop]
|
|
440
|
+
.govuk-tabs__tab:focus {
|
|
441
|
+
color: #000;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
:global(.govuk-frontend-supported)
|
|
446
|
+
.govuk-tabs[data-force-desktop]
|
|
447
|
+
.govuk-tabs__tab:after {
|
|
448
|
+
content: "";
|
|
449
|
+
position: absolute;
|
|
450
|
+
top: 0;
|
|
451
|
+
right: 0;
|
|
452
|
+
bottom: 0;
|
|
453
|
+
left: 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
:global(.govuk-frontend-supported)
|
|
457
|
+
.govuk-tabs[data-force-desktop]
|
|
458
|
+
.govuk-tabs__panel {
|
|
459
|
+
margin-bottom: 0;
|
|
460
|
+
padding: 30px 20px;
|
|
461
|
+
border: 1px solid #b1b4b6;
|
|
462
|
+
border-top: 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
:global(.govuk-frontend-supported)
|
|
466
|
+
.govuk-tabs[data-force-desktop]
|
|
467
|
+
.govuk-tabs__panel
|
|
468
|
+
> :last-child {
|
|
469
|
+
margin-bottom: 0;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
:global(.govuk-frontend-supported)
|
|
473
|
+
.govuk-tabs[data-force-desktop]
|
|
474
|
+
.govuk-tabs__panel--hidden {
|
|
475
|
+
display: none;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
306
478
|
</style>
|
|
@@ -12,6 +12,7 @@ type $$ComponentProps = {
|
|
|
12
12
|
idPrefix?: string;
|
|
13
13
|
selectedTabId?: string | null;
|
|
14
14
|
autoAddHeadings?: boolean;
|
|
15
|
+
forceTabBehavior?: boolean;
|
|
15
16
|
};
|
|
16
17
|
declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "selectedTabId">;
|
|
17
18
|
type Tabs = ReturnType<typeof Tabs>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "./main.css";
|
|
2
2
|
export { default as InsetText } from './components/content/InsetText.svelte';
|
|
3
3
|
export { default as WarningText } from './components/content/WarningText.svelte';
|
|
4
|
+
export { default as Histogram } from './components/data-vis/Histogram.svelte';
|
|
4
5
|
export { default as Axis } from './components/data-vis/axis/Axis.svelte';
|
|
5
6
|
export { default as Ticks } from './components/data-vis/axis/Ticks.svelte';
|
|
6
7
|
export { default as Line } from './components/data-vis/line-chart/Line.svelte';
|
|
@@ -28,8 +29,11 @@ export { default as MobileNav } from './components/layout/service-navigation-nes
|
|
|
28
29
|
export { default as ServiceNavigationNestedMobile } from './components/layout/service-navigation-nested-mobile/ServiceNavigationNestedMobile.svelte';
|
|
29
30
|
export { default as SideNav } from './components/layout/service-navigation-nested-mobile/SideNav.svelte';
|
|
30
31
|
export { default as Accordion } from './components/ui/Accordion.svelte';
|
|
32
|
+
export { default as BasicMultiSelect } from './components/ui/BasicMultiSelect.svelte';
|
|
31
33
|
export { default as Button } from './components/ui/Button.svelte';
|
|
32
34
|
export { default as Card } from './components/ui/Card.svelte';
|
|
35
|
+
export { default as CardHeader } from './components/ui/CardHeader.svelte';
|
|
36
|
+
export { default as ChartExporter } from './components/ui/ChartExporter.svelte';
|
|
33
37
|
export { default as CheckBox } from './components/ui/CheckBox.svelte';
|
|
34
38
|
export { default as ContentsList } from './components/ui/ContentsList.svelte';
|
|
35
39
|
export { default as CookieBanner } from './components/ui/CookieBanner.svelte';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import "./main.css";
|
|
3
3
|
export { default as InsetText } from './components/content/InsetText.svelte';
|
|
4
4
|
export { default as WarningText } from './components/content/WarningText.svelte';
|
|
5
|
+
export { default as Histogram } from './components/data-vis/Histogram.svelte';
|
|
5
6
|
export { default as Axis } from './components/data-vis/axis/Axis.svelte';
|
|
6
7
|
export { default as Ticks } from './components/data-vis/axis/Ticks.svelte';
|
|
7
8
|
export { default as Line } from './components/data-vis/line-chart/Line.svelte';
|
|
@@ -29,8 +30,11 @@ export { default as MobileNav } from './components/layout/service-navigation-nes
|
|
|
29
30
|
export { default as ServiceNavigationNestedMobile } from './components/layout/service-navigation-nested-mobile/ServiceNavigationNestedMobile.svelte';
|
|
30
31
|
export { default as SideNav } from './components/layout/service-navigation-nested-mobile/SideNav.svelte';
|
|
31
32
|
export { default as Accordion } from './components/ui/Accordion.svelte';
|
|
33
|
+
export { default as BasicMultiSelect } from './components/ui/BasicMultiSelect.svelte';
|
|
32
34
|
export { default as Button } from './components/ui/Button.svelte';
|
|
33
35
|
export { default as Card } from './components/ui/Card.svelte';
|
|
36
|
+
export { default as CardHeader } from './components/ui/CardHeader.svelte';
|
|
37
|
+
export { default as ChartExporter } from './components/ui/ChartExporter.svelte';
|
|
34
38
|
export { default as CheckBox } from './components/ui/CheckBox.svelte';
|
|
35
39
|
export { default as ContentsList } from './components/ui/ContentsList.svelte';
|
|
36
40
|
export { default as CookieBanner } from './components/ui/CookieBanner.svelte';
|