@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.
Files changed (43) hide show
  1. package/README.md +7 -0
  2. package/dist/components/data-vis/Histogram.svelte +282 -0
  3. package/dist/components/data-vis/Histogram.svelte.d.ts +75 -0
  4. package/dist/components/data-vis/axis/Axis.svelte +145 -34
  5. package/dist/components/data-vis/axis/Axis.svelte.d.ts +34 -30
  6. package/dist/components/data-vis/axis/Ticks.svelte +163 -60
  7. package/dist/components/data-vis/axis/Ticks.svelte.d.ts +26 -30
  8. package/dist/components/data-vis/line-chart/LineChart.svelte +51 -21
  9. package/dist/components/data-vis/line-chart/LineChart.svelte.d.ts +14 -6
  10. package/dist/components/data-vis/position-chart/PositionChart.svelte +255 -117
  11. package/dist/components/data-vis/position-chart/PositionChart.svelte.d.ts +28 -4
  12. package/dist/components/data-vis/position-chart/PositionChartAxis.svelte +39 -34
  13. package/dist/components/data-vis/position-chart/PositionChartAxis.svelte.d.ts +6 -2
  14. package/dist/components/layout/Footer.svelte +9 -0
  15. package/dist/components/layout/Footer.svelte.d.ts +1 -0
  16. package/dist/components/layout/PhaseBanner.svelte +10 -1
  17. package/dist/components/layout/PhaseBanner.svelte.d.ts +1 -0
  18. package/dist/components/layout/ServiceNavigation.svelte +19 -1
  19. package/dist/components/layout/ServiceNavigation.svelte.d.ts +2 -0
  20. package/dist/components/ui/BasicMultiSelect.svelte +185 -0
  21. package/dist/components/ui/BasicMultiSelect.svelte.d.ts +8 -0
  22. package/dist/components/ui/Button.svelte +1 -0
  23. package/dist/components/ui/Card.svelte +48 -60
  24. package/dist/components/ui/Card.svelte.d.ts +26 -12
  25. package/dist/components/ui/CardHeader.svelte +46 -0
  26. package/dist/components/ui/CardHeader.svelte.d.ts +21 -0
  27. package/dist/components/ui/ChartExporter.svelte +142 -0
  28. package/dist/components/ui/ChartExporter.svelte.d.ts +16 -0
  29. package/dist/components/ui/Details.svelte +10 -2
  30. package/dist/components/ui/Details.svelte.d.ts +2 -0
  31. package/dist/components/ui/Masthead.svelte +36 -6
  32. package/dist/components/ui/Masthead.svelte.d.ts +4 -0
  33. package/dist/components/ui/PostcodeOrAreaSearch.svelte +12 -0
  34. package/dist/components/ui/PostcodeOrAreaSearch.svelte.d.ts +4 -0
  35. package/dist/components/ui/RelatedContent.svelte +4 -1
  36. package/dist/components/ui/RelatedContent.svelte.d.ts +1 -0
  37. package/dist/components/ui/SearchAutocomplete.svelte +185 -34
  38. package/dist/components/ui/SearchAutocomplete.svelte.d.ts +5 -0
  39. package/dist/components/ui/Tabs.svelte +190 -18
  40. package/dist/components/ui/Tabs.svelte.d.ts +1 -0
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +4 -0
  43. 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
- // Apply maxSuggestions limit if specified
264
- const limitedResults =
265
- maxSuggestions && maxSuggestions > 0
266
- ? filtered.slice(0, maxSuggestions)
267
- : filtered;
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
- populateResults(limitedResults);
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
- // Re-assign selectedValue before any form-based guard checks (!form) so bindings still update
372
- // (e.g. when no <form> exists around the component usage) and search component value is being used clienside without a page reload
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
- // Type assertion needed here
422
+ // Mark as accepted to prevent form submit handler from processing again
379
423
  const inputElement =
380
424
  autocompleteInstance?.inputElement as HTMLInputElement;
381
- const form = containerElement?.closest("form");
382
-
383
- if (!inputElement || !form) return;
425
+ if (inputElement) {
426
+ inputElement.value = inputValueTemplate(confirmedValue);
427
+ inputElement.dataset.autocompleteAccepted = "true";
428
+ }
384
429
 
385
- isSubmitting = true;
386
- inputElement.value = inputValueTemplate(confirmedValue);
387
- inputElement.dataset.autocompleteAccepted = "true"; // Set tracking attribute
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
- if (form.requestSubmit) {
391
- form.requestSubmit();
392
- } else {
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
- // Reset flag after a short delay in case submission fails/is prevented
396
- setTimeout(() => {
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
- // console.log(
434
- // "SearchAutocomplete: Input element queried from DOM:",
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.addEventListener("input", () => {
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.addEventListener("keydown", (e) => {
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 (!isMobile) {
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 (isMobile || !isSupported || !isInitialized) return;
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 (isMobile || !isSupported) return;
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 (isMobile || !isSupported || !isInitialized) return;
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 class="govuk-tabs" data-module="govuk-tabs">
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 && !isMobile ? "tablist" : null}
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 && !isMobile}
243
- role={isSupported && !isMobile ? "presentation" : null}
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 && !isMobile ? `${idPrefix}_${tab.id}` : null}
250
- role={isSupported && !isMobile ? "tab" : null}
251
- aria-controls={isSupported && !isMobile ? tab.id : null}
252
- aria-selected={isSupported && !isMobile ? isSelected : null}
253
- tabindex={isSupported && !isMobile ? (isSelected ? 0 : -1) : null}
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 && isSupported && !isMobile}
287
+ class:govuk-tabs__panel--hidden={!isSelected &&
288
+ isSupported &&
289
+ !useMobileBehavior}
270
290
  id={tab.id}
271
- role={isSupported && !isMobile ? "tabpanel" : null}
272
- aria-labelledby={isSupported && !isMobile
291
+ role={isSupported && !useMobileBehavior ? "tabpanel" : null}
292
+ aria-labelledby={isSupported && !useMobileBehavior
273
293
  ? `${idPrefix}_${tab.id}`
274
294
  : null}
275
- hidden={!isSelected && isSupported && !isMobile}
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
- <svelte:component this={tab.content} />
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';