@communitiesuk/svelte-component-library 0.1.17

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 (217) hide show
  1. package/README.md +188 -0
  2. package/dist/assets/css/govuk-frontend.min.css +2 -0
  3. package/dist/assets/css/govuk-frontend.min.css.map +1 -0
  4. package/dist/assets/fonts/bold-affa96571d-v2.woff +0 -0
  5. package/dist/assets/fonts/bold-b542beb274-v2.woff2 +0 -0
  6. package/dist/assets/fonts/light-94a07e06a1-v2.woff2 +0 -0
  7. package/dist/assets/fonts/light-f591b13f7d-v2.woff +0 -0
  8. package/dist/assets/govuk_publishing_components/images/icon-autocomplete-search-suggestion.svg +4 -0
  9. package/dist/assets/govuk_publishing_components/images/icon-close.svg +1 -0
  10. package/dist/assets/images/favicon.ico +0 -0
  11. package/dist/assets/images/favicon.svg +1 -0
  12. package/dist/assets/images/govuk-crest-2x.png +0 -0
  13. package/dist/assets/images/govuk-crest.png +0 -0
  14. package/dist/assets/images/govuk-crest.svg +1 -0
  15. package/dist/assets/images/govuk-icon-180.png +0 -0
  16. package/dist/assets/images/govuk-icon-192.png +0 -0
  17. package/dist/assets/images/govuk-icon-512.png +0 -0
  18. package/dist/assets/images/govuk-icon-mask.svg +1 -0
  19. package/dist/assets/images/govuk-opengraph-image.png +0 -0
  20. package/dist/assets/images/homepage-illustration.svg +1 -0
  21. package/dist/assets/images/homepage.svg +44 -0
  22. package/dist/assets/images/masthead-illustration.svg +123 -0
  23. package/dist/assets/images/oflog_crest_black.png +0 -0
  24. package/dist/assets/images/oflog_crest_white.png +0 -0
  25. package/dist/assets/images/undraw_approved-wireframe_odf4.svg +1 -0
  26. package/dist/assets/images/undraw_collaborators_rgw4.svg +1 -0
  27. package/dist/assets/images/undraw_content-creator_vuqg.svg +1 -0
  28. package/dist/assets/images/undraw_online-media_opxh.svg +1 -0
  29. package/dist/assets/images/undraw_pull-request_zlsu.svg +1 -0
  30. package/dist/assets/images/undraw_reviewed-docs_g0cg.svg +1 -0
  31. package/dist/components/FilterPanel/codeBlocks.d.ts +3 -0
  32. package/dist/components/FilterPanel/codeBlocks.js +418 -0
  33. package/dist/components/content/InsetText.svelte +21 -0
  34. package/dist/components/content/InsetText.svelte.d.ts +7 -0
  35. package/dist/components/content/WarningText.svelte +27 -0
  36. package/dist/components/content/WarningText.svelte.d.ts +8 -0
  37. package/dist/components/data-vis/axis/Axis.svelte +51 -0
  38. package/dist/components/data-vis/axis/Axis.svelte.d.ts +33 -0
  39. package/dist/components/data-vis/axis/Ticks.svelte +113 -0
  40. package/dist/components/data-vis/axis/Ticks.svelte.d.ts +33 -0
  41. package/dist/components/data-vis/line-chart/Line.svelte +150 -0
  42. package/dist/components/data-vis/line-chart/Line.svelte.d.ts +85 -0
  43. package/dist/components/data-vis/line-chart/LineChart.svelte +249 -0
  44. package/dist/components/data-vis/line-chart/LineChart.svelte.d.ts +73 -0
  45. package/dist/components/data-vis/line-chart/Lines.svelte +138 -0
  46. package/dist/components/data-vis/line-chart/Lines.svelte.d.ts +57 -0
  47. package/dist/components/data-vis/line-chart/Marker.svelte +61 -0
  48. package/dist/components/data-vis/line-chart/Marker.svelte.d.ts +37 -0
  49. package/dist/components/data-vis/line-chart/SeriesLabel.svelte +67 -0
  50. package/dist/components/data-vis/line-chart/SeriesLabel.svelte.d.ts +43 -0
  51. package/dist/components/data-vis/line-chart/ValueLabel.svelte +50 -0
  52. package/dist/components/data-vis/line-chart/ValueLabel.svelte.d.ts +25 -0
  53. package/dist/components/data-vis/map/Map.svelte +392 -0
  54. package/dist/components/data-vis/map/Map.svelte.d.ts +47 -0
  55. package/dist/components/data-vis/map/MapLegend.svelte +41 -0
  56. package/dist/components/data-vis/map/MapLegend.svelte.d.ts +15 -0
  57. package/dist/components/data-vis/map/NonStandardControls.svelte +42 -0
  58. package/dist/components/data-vis/map/NonStandardControls.svelte.d.ts +13 -0
  59. package/dist/components/data-vis/map/Tooltip.svelte +41 -0
  60. package/dist/components/data-vis/map/Tooltip.svelte.d.ts +19 -0
  61. package/dist/components/data-vis/map/colorbrewer.d.ts +337 -0
  62. package/dist/components/data-vis/map/colorbrewer.js +1523 -0
  63. package/dist/components/data-vis/map/colors.d.ts +13 -0
  64. package/dist/components/data-vis/map/colors.js +65 -0
  65. package/dist/components/data-vis/map/dataJoin.d.ts +2 -0
  66. package/dist/components/data-vis/map/dataJoin.js +27 -0
  67. package/dist/components/data-vis/map/fullTopo.json +1 -0
  68. package/dist/components/data-vis/map/jenks.d.ts +1 -0
  69. package/dist/components/data-vis/map/jenks.js +51 -0
  70. package/dist/components/data-vis/map/lad2023.json +1 -0
  71. package/dist/components/data-vis/map/mapUtils.d.ts +5 -0
  72. package/dist/components/data-vis/map/mapUtils.js +86 -0
  73. package/dist/components/data-vis/map/topo.json +1 -0
  74. package/dist/components/data-vis/table/Table.svelte +247 -0
  75. package/dist/components/data-vis/table/Table.svelte.d.ts +19 -0
  76. package/dist/components/layout/Breadcrumbs.svelte +191 -0
  77. package/dist/components/layout/Breadcrumbs.svelte.d.ts +24 -0
  78. package/dist/components/layout/Footer.svelte +171 -0
  79. package/dist/components/layout/Footer.svelte.d.ts +30 -0
  80. package/dist/components/layout/Header.svelte +43 -0
  81. package/dist/components/layout/Header.svelte.d.ts +7 -0
  82. package/dist/components/layout/InternalHeader.svelte +628 -0
  83. package/dist/components/layout/InternalHeader.svelte.d.ts +15 -0
  84. package/dist/components/layout/PhaseBanner.svelte +28 -0
  85. package/dist/components/layout/PhaseBanner.svelte.d.ts +9 -0
  86. package/dist/components/layout/ServiceNavigation.svelte +143 -0
  87. package/dist/components/layout/ServiceNavigation.svelte.d.ts +13 -0
  88. package/dist/components/layout/SideNavigation.svelte +345 -0
  89. package/dist/components/layout/SideNavigation.svelte.d.ts +25 -0
  90. package/dist/components/layout/service-navigation-nested-mobile/HeaderNav.svelte +91 -0
  91. package/dist/components/layout/service-navigation-nested-mobile/HeaderNav.svelte.d.ts +15 -0
  92. package/dist/components/layout/service-navigation-nested-mobile/MobileNav.svelte +233 -0
  93. package/dist/components/layout/service-navigation-nested-mobile/MobileNav.svelte.d.ts +27 -0
  94. package/dist/components/layout/service-navigation-nested-mobile/ServiceNavigationNestedMobile.svelte +70 -0
  95. package/dist/components/layout/service-navigation-nested-mobile/ServiceNavigationNestedMobile.svelte.d.ts +11 -0
  96. package/dist/components/layout/service-navigation-nested-mobile/SideNav.svelte +276 -0
  97. package/dist/components/layout/service-navigation-nested-mobile/SideNav.svelte.d.ts +22 -0
  98. package/dist/components/ui/Accordion.svelte +244 -0
  99. package/dist/components/ui/Accordion.svelte.d.ts +23 -0
  100. package/dist/components/ui/Breadcrumbs.svelte +198 -0
  101. package/dist/components/ui/Breadcrumbs.svelte.d.ts +24 -0
  102. package/dist/components/ui/Button.svelte +96 -0
  103. package/dist/components/ui/Button.svelte.d.ts +17 -0
  104. package/dist/components/ui/CheckBox.svelte +198 -0
  105. package/dist/components/ui/CheckBox.svelte.d.ts +27 -0
  106. package/dist/components/ui/ContentsList.svelte +1117 -0
  107. package/dist/components/ui/ContentsList.svelte.d.ts +25 -0
  108. package/dist/components/ui/DateInput.svelte +255 -0
  109. package/dist/components/ui/DateInput.svelte.d.ts +59 -0
  110. package/dist/components/ui/Details.svelte +12 -0
  111. package/dist/components/ui/Details.svelte.d.ts +13 -0
  112. package/dist/components/ui/FilterPanel.svelte +588 -0
  113. package/dist/components/ui/FilterPanel.svelte.d.ts +74 -0
  114. package/dist/components/ui/Footer.svelte +171 -0
  115. package/dist/components/ui/Footer.svelte.d.ts +30 -0
  116. package/dist/components/ui/Header.svelte +43 -0
  117. package/dist/components/ui/Header.svelte.d.ts +7 -0
  118. package/dist/components/ui/Masthead.svelte +267 -0
  119. package/dist/components/ui/Masthead.svelte.d.ts +12 -0
  120. package/dist/components/ui/NavigationExample.svelte +117 -0
  121. package/dist/components/ui/NavigationExample.svelte.d.ts +3 -0
  122. package/dist/components/ui/NotificationBanner.svelte +93 -0
  123. package/dist/components/ui/NotificationBanner.svelte.d.ts +15 -0
  124. package/dist/components/ui/Radios.svelte +176 -0
  125. package/dist/components/ui/Radios.svelte.d.ts +28 -0
  126. package/dist/components/ui/RelatedContent.svelte +596 -0
  127. package/dist/components/ui/RelatedContent.svelte.d.ts +29 -0
  128. package/dist/components/ui/Search.svelte +499 -0
  129. package/dist/components/ui/Search.svelte.d.ts +32 -0
  130. package/dist/components/ui/SearchAutocomplete.svelte +655 -0
  131. package/dist/components/ui/SearchAutocomplete.svelte.d.ts +37 -0
  132. package/dist/components/ui/Select.svelte +116 -0
  133. package/dist/components/ui/Select.svelte.d.ts +22 -0
  134. package/dist/components/ui/ServiceNavigation.svelte +143 -0
  135. package/dist/components/ui/ServiceNavigation.svelte.d.ts +13 -0
  136. package/dist/components/ui/SideNavigation.svelte +346 -0
  137. package/dist/components/ui/SideNavigation.svelte.d.ts +25 -0
  138. package/dist/components/ui/Tabs.svelte +306 -0
  139. package/dist/components/ui/Tabs.svelte.d.ts +18 -0
  140. package/dist/components/ui/WhatsNew.svelte +155 -0
  141. package/dist/components/ui/WhatsNew.svelte.d.ts +29 -0
  142. package/dist/config.d.ts +51 -0
  143. package/dist/config.js +44 -0
  144. package/dist/icons/DoubleChevronButton.svelte +62 -0
  145. package/dist/icons/DoubleChevronButton.svelte.d.ts +13 -0
  146. package/dist/icons/IconSearch.svelte +42 -0
  147. package/dist/icons/IconSearch.svelte.d.ts +6 -0
  148. package/dist/icons/SingleChevronButtonWithLabel.svelte +132 -0
  149. package/dist/icons/SingleChevronButtonWithLabel.svelte.d.ts +19 -0
  150. package/dist/index.d.ts +44 -0
  151. package/dist/index.js +45 -0
  152. package/dist/main.css +1 -0
  153. package/dist/package-wrapping/BaseInformation.svelte +82 -0
  154. package/dist/package-wrapping/BaseInformation.svelte.d.ts +15 -0
  155. package/dist/package-wrapping/BaseNameAndStatus.svelte +108 -0
  156. package/dist/package-wrapping/BaseNameAndStatus.svelte.d.ts +10 -0
  157. package/dist/package-wrapping/CodeBlock.svelte +62 -0
  158. package/dist/package-wrapping/CodeBlock.svelte.d.ts +12 -0
  159. package/dist/package-wrapping/ComponentDemo.svelte +114 -0
  160. package/dist/package-wrapping/ComponentDemo.svelte.d.ts +25 -0
  161. package/dist/package-wrapping/ComponentDemoTEMP.svelte +305 -0
  162. package/dist/package-wrapping/ComponentDemoTEMP.svelte.d.ts +21 -0
  163. package/dist/package-wrapping/ComponentDetails.svelte +123 -0
  164. package/dist/package-wrapping/ComponentDetails.svelte.d.ts +13 -0
  165. package/dist/package-wrapping/DividerLine.svelte +21 -0
  166. package/dist/package-wrapping/DividerLine.svelte.d.ts +17 -0
  167. package/dist/package-wrapping/InputForParameter.svelte +205 -0
  168. package/dist/package-wrapping/InputForParameter.svelte.d.ts +13 -0
  169. package/dist/package-wrapping/InputForParameterUpdated.svelte +222 -0
  170. package/dist/package-wrapping/InputForParameterUpdated.svelte.d.ts +17 -0
  171. package/dist/package-wrapping/InputForParameterUpdatedTEMP.svelte +203 -0
  172. package/dist/package-wrapping/InputForParameterUpdatedTEMP.svelte.d.ts +17 -0
  173. package/dist/package-wrapping/ListOfComponentStatuses.svelte +19 -0
  174. package/dist/package-wrapping/ListOfComponentStatuses.svelte.d.ts +11 -0
  175. package/dist/package-wrapping/OverlayAndComponentContainer.svelte +426 -0
  176. package/dist/package-wrapping/OverlayAndComponentContainer.svelte.d.ts +33 -0
  177. package/dist/package-wrapping/ParametersSection.svelte +235 -0
  178. package/dist/package-wrapping/ParametersSection.svelte.d.ts +19 -0
  179. package/dist/package-wrapping/ParsingErrorToastsContainer.svelte +50 -0
  180. package/dist/package-wrapping/ParsingErrorToastsContainer.svelte.d.ts +15 -0
  181. package/dist/package-wrapping/Pill.svelte +54 -0
  182. package/dist/package-wrapping/Pill.svelte.d.ts +25 -0
  183. package/dist/package-wrapping/PlaygroundDetails.svelte +106 -0
  184. package/dist/package-wrapping/PlaygroundDetails.svelte.d.ts +13 -0
  185. package/dist/package-wrapping/ScreenSizeRadio.svelte +24 -0
  186. package/dist/package-wrapping/ScreenSizeRadio.svelte.d.ts +11 -0
  187. package/dist/package-wrapping/ScreenSizeRadioUpdated.svelte +23 -0
  188. package/dist/package-wrapping/ScreenSizeRadioUpdated.svelte.d.ts +11 -0
  189. package/dist/package-wrapping/SidebarContainer.svelte +103 -0
  190. package/dist/package-wrapping/SidebarContainer.svelte.d.ts +23 -0
  191. package/dist/package-wrapping/WrapperDetailsUpdate.svelte +40 -0
  192. package/dist/package-wrapping/WrapperDetailsUpdate.svelte.d.ts +15 -0
  193. package/dist/package-wrapping/templates/Template.svelte +100 -0
  194. package/dist/package-wrapping/templates/Template.svelte.d.ts +25 -0
  195. package/dist/templates/ComponentPageTemplate.svelte +1 -0
  196. package/dist/templates/ComponentPageTemplate.svelte.d.ts +26 -0
  197. package/dist/utils/data-transformations/convertCSV.d.ts +2 -0
  198. package/dist/utils/data-transformations/convertCSV.js +22 -0
  199. package/dist/utils/data-transformations/getValueFromParametersArray.d.ts +1 -0
  200. package/dist/utils/data-transformations/getValueFromParametersArray.js +9 -0
  201. package/dist/utils/layoutNavHelpers.d.ts +70 -0
  202. package/dist/utils/layoutNavHelpers.js +129 -0
  203. package/dist/utils/package-wrapping-specific/addIndexAndInitialValue.d.ts +1 -0
  204. package/dist/utils/package-wrapping-specific/addIndexAndInitialValue.js +21 -0
  205. package/dist/utils/package-wrapping-specific/createBindableParametersValuesArray.d.ts +1 -0
  206. package/dist/utils/package-wrapping-specific/createBindableParametersValuesArray.js +12 -0
  207. package/dist/utils/package-wrapping-specific/createParametersObject.d.ts +1 -0
  208. package/dist/utils/package-wrapping-specific/createParametersObject.js +29 -0
  209. package/dist/utils/package-wrapping-specific/defineDefaultEventHandler.d.ts +1 -0
  210. package/dist/utils/package-wrapping-specific/defineDefaultEventHandler.js +14 -0
  211. package/dist/utils/package-wrapping-specific/trackVisibleParameters.d.ts +1 -0
  212. package/dist/utils/package-wrapping-specific/trackVisibleParameters.js +29 -0
  213. package/dist/utils/syntax-highlighting/shikiHighlight.d.ts +7 -0
  214. package/dist/utils/syntax-highlighting/shikiHighlight.js +76 -0
  215. package/dist/utils/text-string-conversion/textStringConversion.d.ts +9 -0
  216. package/dist/utils/text-string-conversion/textStringConversion.js +86 -0
  217. package/package.json +113 -0
@@ -0,0 +1,306 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import { replaceState } from "$app/navigation";
4
+ import type { SvelteComponent, Snippet } from "svelte";
5
+ import DOMPurify from "dompurify";
6
+
7
+ // Define Tab type
8
+ export type TabItem = {
9
+ id: string;
10
+ label: string;
11
+ content: string | typeof SvelteComponent | Snippet;
12
+ contentIsHtml?: boolean;
13
+ };
14
+
15
+ // Component props
16
+ let {
17
+ title = "Contents",
18
+ tabs = [],
19
+ idPrefix = "tab",
20
+ selectedTabId = $bindable(),
21
+ autoAddHeadings = true,
22
+ } = $props<{
23
+ title?: string;
24
+ tabs: TabItem[];
25
+ idPrefix?: string;
26
+ selectedTabId?: string | null;
27
+ autoAddHeadings?: boolean;
28
+ }>();
29
+
30
+ // Component state variables
31
+ let isInitialized = $state(false);
32
+ let isSupported = $state(false);
33
+ let isMobile = $state(false);
34
+
35
+ // DOM element references for programmatic focus
36
+ let tabElements: { [key: string]: HTMLAnchorElement } = {};
37
+
38
+ // Track media query for responsive behavior
39
+ let tabletMql: MediaQueryList | null = null;
40
+
41
+ // Handle tab selection - integrate focus and hash logic directly
42
+ function selectTab(tabId: string, shouldFocus = false): void {
43
+ // Skip if component isn't ready, or tab is already selected
44
+ if (!isSupported || !isInitialized || selectedTabId === tabId) return;
45
+
46
+ // Update the core bindable state
47
+ selectedTabId = tabId;
48
+
49
+ // Handle optional focus
50
+ if (shouldFocus && tabElements[tabId]) {
51
+ // Use setTimeout to defer focus until after Svelte updates the DOM (e.g., tabindex)
52
+ setTimeout(() => {
53
+ const tabElement = tabElements[tabId];
54
+ tabElement?.focus(); // Optional chaining for safety
55
+ }, 0);
56
+ }
57
+
58
+ // Update URL hash on non-mobile views
59
+ if (!isMobile) {
60
+ // Use history.replaceState to update the displayed URL hash without causing scroll/navigation.
61
+ const currentUrl = window.location.href;
62
+ const hashIndex = currentUrl.indexOf("#");
63
+ const baseUrl =
64
+ hashIndex !== -1 ? currentUrl.slice(0, hashIndex) : currentUrl;
65
+ const newUrl = `${baseUrl}#${tabId}`;
66
+ replaceState(newUrl, {}); // Use SvelteKit's function
67
+ }
68
+ }
69
+
70
+ // Handle keyboard navigation
71
+ function handleKeydown(event: KeyboardEvent, currentIndex: number): void {
72
+ // Skip navigation on mobile or if component isn't ready
73
+ if (isMobile || !isSupported || !isInitialized) return;
74
+
75
+ // Initialize to null, indicating no valid navigation key pressed yet.
76
+ // Will be updated to a valid index (0+) if ArrowLeft/Right is pressed.
77
+ let newIndex: number | null = null;
78
+ const numTabs = tabs.length;
79
+ if (numTabs === 0) return; // Cannot navigate an empty list
80
+
81
+ // Define indices and conditions for readability
82
+ const isFirstTab = currentIndex === 0;
83
+ const isLastTab = currentIndex === numTabs - 1;
84
+ const previousIndex = currentIndex - 1;
85
+ const nextIndex = currentIndex + 1;
86
+ const firstIndex = 0;
87
+ const lastIndex = numTabs - 1;
88
+
89
+ if (event.key === "ArrowLeft" || event.key === "Left") {
90
+ event.preventDefault();
91
+ // If it's the first tab, wrap to last, otherwise go to previous.
92
+ newIndex = isFirstTab ? lastIndex : previousIndex;
93
+ } else if (event.key === "ArrowRight" || event.key === "Right") {
94
+ event.preventDefault();
95
+ // If it's the last tab, wrap to first, otherwise go to next.
96
+ newIndex = isLastTab ? firstIndex : nextIndex;
97
+ }
98
+
99
+ // If a navigation key was pressed (newIndex is not null) and the index is valid
100
+ if (newIndex !== null && tabs[newIndex]) {
101
+ const targetTabId = tabs[newIndex].id;
102
+ // Pass true to ensure focus shifts to the new tab
103
+ selectTab(targetTabId, true);
104
+ }
105
+ }
106
+
107
+ // Handle tab click
108
+ function handleTabClick(event: MouseEvent, tabId: string): void {
109
+ // On mobile or without JS support, let default browser behavior happen
110
+ if (isMobile || !isSupported) return;
111
+ event.preventDefault();
112
+ selectTab(tabId);
113
+ }
114
+
115
+ // Handle hash change
116
+ function handleHashChange(): void {
117
+ // Skip on mobile or when not properly initialized
118
+ if (isMobile || !isSupported || !isInitialized) return;
119
+
120
+ const hash = window.location.hash.substring(1);
121
+ if (hash && tabs.some((tab) => tab.id === hash)) {
122
+ // Only select if the hash corresponds to a *different* tab
123
+ if (selectedTabId !== hash) {
124
+ // Focus the tab when navigating via hash change
125
+ selectTab(hash, true);
126
+ }
127
+ }
128
+ }
129
+
130
+ // Initialize tabs on mount
131
+ onMount(() => {
132
+ isSupported =
133
+ document.body?.classList.contains("govuk-frontend-supported") ?? false;
134
+
135
+ // Check URL hash for deep linking AFTER initial prop value is set
136
+ const hash = window.location.hash.substring(1);
137
+ if (hash) {
138
+ const tabFromHash = tabs.find((tab) => tab.id === hash);
139
+ if (tabFromHash && tabFromHash.id !== selectedTabId) {
140
+ // Update state if hash points to a valid, different tab
141
+ // Use selectTab to handle focus and potential URL update if needed
142
+ selectTab(tabFromHash.id, true);
143
+ }
144
+ }
145
+
146
+ // If after hash check, no tab is selected AND we have tabs, select the first one.
147
+ // This handles the case where the initial prop value was null/invalid and no valid hash was present.
148
+ if (!selectedTabId && tabs.length > 0) {
149
+ selectedTabId = tabs[0].id;
150
+ }
151
+
152
+ // Setup responsive behavior
153
+ if (window) {
154
+ const breakpoint = getBreakpoint();
155
+ if (breakpoint) {
156
+ tabletMql = window.matchMedia(`(min-width: ${breakpoint})`);
157
+ isMobile = !tabletMql.matches;
158
+
159
+ if ("addEventListener" in tabletMql) {
160
+ tabletMql.addEventListener("change", handleMediaChange);
161
+ } else {
162
+ // @ts-ignore - Deprecated fallback
163
+ tabletMql.addListener(handleMediaChange);
164
+ }
165
+ }
166
+ }
167
+
168
+ // Listen for hash changes
169
+ window.addEventListener("hashchange", handleHashChange);
170
+
171
+ isInitialized = true;
172
+
173
+ // Cleanup
174
+ return () => {
175
+ if (tabletMql) {
176
+ if ("removeEventListener" in tabletMql) {
177
+ tabletMql.removeEventListener("change", handleMediaChange);
178
+ } else {
179
+ // @ts-ignore - Deprecated fallback
180
+ tabletMql.removeListener(handleMediaChange);
181
+ }
182
+ }
183
+ window.removeEventListener("hashchange", handleHashChange);
184
+
185
+ // Clear tab element references to prevent memory leaks
186
+ Object.keys(tabElements).forEach((key) => {
187
+ delete tabElements[key];
188
+ });
189
+ };
190
+ });
191
+
192
+ // Handle media query changes
193
+ function handleMediaChange(
194
+ event: MediaQueryListEvent | MediaQueryList,
195
+ ): void {
196
+ // Handle both modern and deprecated event/object types
197
+ isMobile = !event.matches;
198
+ }
199
+
200
+ // Get breakpoint from CSS custom property
201
+ function getBreakpoint(): string | null {
202
+ if (typeof window === "undefined") return null; // Guard for SSR
203
+ const property = `--govuk-frontend-breakpoint-tablet`;
204
+ const value = window
205
+ .getComputedStyle(document.documentElement)
206
+ .getPropertyValue(property);
207
+ return value ? value.trim() : null;
208
+ }
209
+
210
+ // Effect to ensure valid tab selection if tabs array changes externally
211
+ $effect(() => {
212
+ // Run this effect whenever isInitialized or tabs changes
213
+ // Check if the currently selected tab ID still exists in the updated tabs array
214
+ if (
215
+ isInitialized &&
216
+ selectedTabId && // Only run if a tab is actually selected
217
+ !tabs.some((tab) => tab.id === selectedTabId)
218
+ ) {
219
+ // If selected tab ID is no longer valid, default to the first available tab
220
+ console.log(
221
+ `Effect: selectedTabId '${selectedTabId}' no longer valid. Resetting.`,
222
+ ); // Optional Debug
223
+ selectedTabId = tabs[0]?.id ?? null; // Use optional chaining and nullish coalescing
224
+ }
225
+ });
226
+ </script>
227
+
228
+ <div class="govuk-tabs" data-module="govuk-tabs">
229
+ <h2 class="govuk-tabs__title">
230
+ {title}
231
+ </h2>
232
+
233
+ <ul
234
+ class="govuk-tabs__list"
235
+ role={isSupported && !isMobile ? "tablist" : null}
236
+ >
237
+ {#each tabs as tab, index}
238
+ {@const isSelected = selectedTabId === tab.id}
239
+ {#key tab.id}
240
+ <li
241
+ class="govuk-tabs__list-item"
242
+ class:govuk-tabs__list-item--selected={isSelected && !isMobile}
243
+ role={isSupported && !isMobile ? "presentation" : null}
244
+ >
245
+ <!-- svelte-ignore binding_property_non_reactive -->
246
+ <a
247
+ class="govuk-tabs__tab"
248
+ 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}
254
+ onclick={(e) => handleTabClick(e, tab.id)}
255
+ onkeydown={(e) => handleKeydown(e, index)}
256
+ bind:this={tabElements[tab.id]}
257
+ >
258
+ {tab.label}
259
+ </a>
260
+ </li>
261
+ {/key}
262
+ {/each}
263
+ </ul>
264
+
265
+ {#each tabs as tab}
266
+ {@const isSelected = selectedTabId === tab.id}
267
+ <div
268
+ class="govuk-tabs__panel"
269
+ class:govuk-tabs__panel--hidden={!isSelected && isSupported && !isMobile}
270
+ id={tab.id}
271
+ role={isSupported && !isMobile ? "tabpanel" : null}
272
+ aria-labelledby={isSupported && !isMobile
273
+ ? `${idPrefix}_${tab.id}`
274
+ : null}
275
+ hidden={!isSelected && isSupported && !isMobile}
276
+ >
277
+ {#if autoAddHeadings}
278
+ <h2 class="govuk-heading-l">{tab.label}</h2>
279
+ {/if}
280
+
281
+ {#if typeof tab.content === "string"}
282
+ {#if tab.contentIsHtml}
283
+ {@html DOMPurify.sanitize(tab.content)}
284
+ {:else}
285
+ <p class="govuk-body">{tab.content}</p>
286
+ {/if}
287
+ {:else if tab.content satisfies Snippet}
288
+ {@render tab.content()}
289
+ {:else if tab.content}
290
+ <svelte:component this={tab.content} />
291
+ {/if}
292
+ </div>
293
+ {/each}
294
+ </div>
295
+
296
+ <style>
297
+ /* Override components.css tab panel style adding top margin to tab panels */
298
+ .govuk-tabs__panel[role="tabpanel"] {
299
+ margin-top: 0;
300
+ }
301
+
302
+ /* Ensure hidden panels are truly hidden */
303
+ .govuk-tabs__panel--hidden {
304
+ display: none;
305
+ }
306
+ </style>
@@ -0,0 +1,18 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { Snippet } from "svelte";
3
+ export type TabItem = {
4
+ id: string;
5
+ label: string;
6
+ content: string | typeof SvelteComponent | Snippet;
7
+ contentIsHtml?: boolean;
8
+ };
9
+ type $$ComponentProps = {
10
+ title?: string;
11
+ tabs: TabItem[];
12
+ idPrefix?: string;
13
+ selectedTabId?: string | null;
14
+ autoAddHeadings?: boolean;
15
+ };
16
+ declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "selectedTabId">;
17
+ type Tabs = ReturnType<typeof Tabs>;
18
+ export default Tabs;
@@ -0,0 +1,155 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Represents a single news item in the What's New section
4
+ */
5
+ interface NewsItem {
6
+ /** The date of the news item (e.g., "June 2025") */
7
+ date: string;
8
+ /** The main content/description of the news item */
9
+ content: string;
10
+ /** Optional URL to release notes or more information */
11
+ releaseNotesUrl?: string;
12
+ /** Optional version number for releases (e.g., "v0.1.16") */
13
+ releaseVersion?: string;
14
+ /** Optional array of component links to display as a bulleted list */
15
+ componentLinks?: Array<{
16
+ /** Display text for the link */
17
+ text: string;
18
+ /** URL/href for the link */
19
+ href: string;
20
+ }>;
21
+ }
22
+
23
+ /**
24
+ * WhatsNew Component
25
+ *
26
+ * A flexible component for displaying news, updates, and release information.
27
+ * Commonly used on homepages or documentation sites to communicate recent changes,
28
+ * new features, or important announcements to users.
29
+ *
30
+ * @example
31
+ * ```svelte
32
+ * <!-- Basic usage with defaults -->
33
+ * <WhatsNew />
34
+ *
35
+ * <!-- Custom usage -->
36
+ * <WhatsNew
37
+ * title="Latest Updates"
38
+ * titleId="updates"
39
+ * componentLinksIntroText="New components available:"
40
+ * newsItems={[
41
+ * {
42
+ * date: "June 2025",
43
+ * content: "Released new component library",
44
+ * releaseNotesUrl: "https://github.com/example/releases/tag/v1.0.0",
45
+ * releaseVersion: "v1.0.0",
46
+ * componentLinks: [
47
+ * { text: "Button component", href: "/components/button" }
48
+ * ]
49
+ * }
50
+ * ]}
51
+ * />
52
+ * ```
53
+ */
54
+
55
+ // Define component props with types and default values
56
+ let {
57
+ /** The main heading text for the news section */
58
+ title = "What's new",
59
+ /** The HTML id attribute for the heading element (useful for anchor links) */
60
+ titleId = "whats-new",
61
+ /**
62
+ * Introductory text that appears before component links lists.
63
+ * Set to empty string to hide this text entirely.
64
+ */
65
+ componentLinksIntroText = "This the first step to refresh the GOV.UK brand. It includes updates to the:",
66
+ /**
67
+ * Array of news items to display. Each item can include:
68
+ * - date: When the news occurred
69
+ * - content: Main description
70
+ * - releaseNotesUrl & releaseVersion: For linking to release notes
71
+ * - componentLinks: For displaying related component links
72
+ */
73
+ newsItems = [
74
+ {
75
+ date: "15 May 2025",
76
+ content:
77
+ "We released GOV.UK Frontend v5.10.1 with the correct colour for the dot in the refreshed GOV.UK logo, as well as small fixes to the implementation of the brand refresh.",
78
+ releaseNotesUrl:
79
+ "https://github.com/alphagov/govuk-frontend/releases/tag/v5.10.1",
80
+ releaseVersion: "v5.10.1",
81
+ },
82
+ {
83
+ date: "1 May 2025",
84
+ content: "We released GOV.UK Frontend v5.10.0.",
85
+ releaseNotesUrl:
86
+ "https://github.com/alphagov/govuk-frontend/releases/tag/v5.10.0",
87
+ releaseVersion: "v5.10.0",
88
+ componentLinks: [
89
+ { text: "GOV.UK header component", href: "/components/header/" },
90
+ { text: "GOV.UK footer component", href: "/components/footer/" },
91
+ {
92
+ text: "Service navigation component",
93
+ href: "/components/service-navigation/",
94
+ },
95
+ {
96
+ text: "Cookie banner component",
97
+ href: "/components/cookie-banner/",
98
+ },
99
+ ],
100
+ },
101
+ ] as NewsItem[],
102
+ } = $props<{
103
+ title?: string;
104
+ titleId?: string;
105
+ componentLinksIntroText?: string;
106
+ newsItems?: NewsItem[];
107
+ }>();
108
+ </script>
109
+
110
+ <div class="app-whats-new">
111
+ <div class="govuk-width-container">
112
+ <div class="govuk-main-wrapper govuk-main-wrapper--l">
113
+ <div class="govuk-grid-row">
114
+ <div class="govuk-grid-column-two-thirds-from-desktop">
115
+ <h2 id={titleId} class="govuk-heading-l">{title}</h2>
116
+
117
+ {#each newsItems as item, index}
118
+ <p class="govuk-body">
119
+ <strong>{item.date}:</strong>
120
+ {item.content}
121
+ </p>
122
+
123
+ {#if item.releaseNotesUrl && item.releaseVersion}
124
+ <p class="govuk-body">
125
+ Read the <a href={item.releaseNotesUrl} class="govuk-link"
126
+ >release notes for {item.releaseVersion}</a
127
+ > to see what's changed.
128
+ </p>
129
+ {/if}
130
+
131
+ {#if item.componentLinks && item.componentLinks.length > 0}
132
+ <p class="govuk-body">
133
+ {componentLinksIntroText}
134
+ </p>
135
+ <ul class="govuk-list govuk-list--bullet">
136
+ {#each item.componentLinks as link}
137
+ <li>
138
+ <a href={link.href} class="govuk-link">{link.text}</a>
139
+ </li>
140
+ {/each}
141
+ </ul>
142
+ {/if}
143
+ {/each}
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <style>
151
+ .app-whats-new {
152
+ border-bottom: 1px solid #b1b4b6;
153
+ background-color: #f8f8f8;
154
+ }
155
+ </style>
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Represents a single news item in the What's New section
3
+ */
4
+ interface NewsItem {
5
+ /** The date of the news item (e.g., "June 2025") */
6
+ date: string;
7
+ /** The main content/description of the news item */
8
+ content: string;
9
+ /** Optional URL to release notes or more information */
10
+ releaseNotesUrl?: string;
11
+ /** Optional version number for releases (e.g., "v0.1.16") */
12
+ releaseVersion?: string;
13
+ /** Optional array of component links to display as a bulleted list */
14
+ componentLinks?: Array<{
15
+ /** Display text for the link */
16
+ text: string;
17
+ /** URL/href for the link */
18
+ href: string;
19
+ }>;
20
+ }
21
+ type $$ComponentProps = {
22
+ title?: string;
23
+ titleId?: string;
24
+ componentLinksIntroText?: string;
25
+ newsItems?: NewsItem[];
26
+ };
27
+ declare const WhatsNew: import("svelte").Component<$$ComponentProps, {}, "">;
28
+ type WhatsNew = ReturnType<typeof WhatsNew>;
29
+ export default WhatsNew;
@@ -0,0 +1,51 @@
1
+ export const foldersLookup: {
2
+ ui: string;
3
+ "data-vis": string;
4
+ };
5
+ export const componentStatusProgressBackgroundColorLookup: {
6
+ "To be developed": string;
7
+ "In progress": string;
8
+ "Baseline completed": string;
9
+ "In use": string;
10
+ };
11
+ export const componentStausLookup: {};
12
+ export namespace propPillLookup {
13
+ export namespace _true {
14
+ let size: string;
15
+ let text: string;
16
+ let textColor: string;
17
+ let bgColor: string;
18
+ let padding: string;
19
+ }
20
+ export { _true as true };
21
+ export namespace bindable {
22
+ let size_1: string;
23
+ export { size_1 as size };
24
+ let text_1: string;
25
+ export { text_1 as text };
26
+ let textColor_1: string;
27
+ export { textColor_1 as textColor };
28
+ let bgColor_1: string;
29
+ export { bgColor_1 as bgColor };
30
+ let padding_1: string;
31
+ export { padding_1 as padding };
32
+ }
33
+ export namespace _false {
34
+ let text_2: string;
35
+ export { text_2 as text };
36
+ let textColor_2: string;
37
+ export { textColor_2 as textColor };
38
+ let bgColor_2: string;
39
+ export { bgColor_2 as bgColor };
40
+ let padding_2: string;
41
+ export { padding_2 as padding };
42
+ }
43
+ export { _false as false };
44
+ }
45
+ export namespace defaultScreenWidthBreakpoints {
46
+ let xs: number;
47
+ let sm: number;
48
+ let md: number;
49
+ let lg: number;
50
+ let xl: number;
51
+ }
package/dist/config.js ADDED
@@ -0,0 +1,44 @@
1
+ export const foldersLookup = {
2
+ ui: "user interaction",
3
+ "data-vis": "data visualisation",
4
+ };
5
+
6
+ export const componentStatusProgressBackgroundColorLookup = {
7
+ "To be developed": "#b53c17",
8
+ "In progress": "#542059",
9
+ "Baseline completed": "#5C53A2",
10
+ "In use": "#136F63",
11
+ };
12
+
13
+ export const componentStausLookup = {};
14
+
15
+ export const propPillLookup = {
16
+ true: {
17
+ size: "xs",
18
+ text: "{p}",
19
+ textColor: "white",
20
+ bgColor: "#ba029b",
21
+ padding: "0.4rem 0.32rem 0.5rem 0.3rem",
22
+ },
23
+ bindable: {
24
+ size: "xs",
25
+ text: "{b}",
26
+ textColor: "white",
27
+ bgColor: "#1B4079",
28
+ padding: "0.4rem 0.32rem 0.5rem 0.3rem",
29
+ },
30
+ false: {
31
+ text: "=>",
32
+ textColor: "white",
33
+ bgColor: "#00695c",
34
+ padding: "0.1rem 0.15rem",
35
+ },
36
+ };
37
+
38
+ export const defaultScreenWidthBreakpoints = {
39
+ xs: 420,
40
+ sm: 640,
41
+ md: 768,
42
+ lg: 1024,
43
+ xl: 1280,
44
+ };
@@ -0,0 +1,62 @@
1
+ <script>
2
+ let { open = $bindable(), onClickFunction } = $props();
3
+ </script>
4
+
5
+ <button onclick={onClickFunction}>
6
+ <svg width="30" height="30">
7
+ {#each [8, 16] as xPos}
8
+ <g
9
+ transform="translate({xPos + (open ? 6 : 0)},15)rotate({open
10
+ ? 45
11
+ : -135})"
12
+ >
13
+ {#each [-5, 5] as cxcy}
14
+ <circle cx={cxcy} cy={cxcy}></circle>
15
+ {/each}
16
+ <path d="M -5 -5 l0 9.75 m0.25 0.25 l9.75 0"></path>
17
+ <rect transform="translate(-6, 4)" width="2" height="2" rx="4px"></rect>
18
+ </g>
19
+ {/each}
20
+ </svg>
21
+ </button>
22
+
23
+ <style>
24
+ svg {
25
+ cursor: pointer;
26
+ transition: background-color 0.3s ease;
27
+ }
28
+
29
+ svg:hover {
30
+ border-radius: 3px;
31
+ background-color: #f3f3f3;
32
+ }
33
+
34
+ path {
35
+ stroke: #6b7280;
36
+ fill: none;
37
+ stroke-width: 2px;
38
+ }
39
+
40
+ svg:hover path {
41
+ stroke: #4b5563;
42
+ }
43
+
44
+ circle {
45
+ fill: #6b7280;
46
+ stroke: none;
47
+ r: 1;
48
+ }
49
+
50
+ svg:hover circle {
51
+ fill: #4b5563;
52
+ }
53
+
54
+ rect {
55
+ stroke: none;
56
+ fill: #6b7280;
57
+ }
58
+
59
+ svg:hover rect {
60
+ fill: #4b5563;
61
+ }
62
+ </style>
@@ -0,0 +1,13 @@
1
+ export default DoubleChevronButton;
2
+ type DoubleChevronButton = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const DoubleChevronButton: import("svelte").Component<{
7
+ open?: any;
8
+ onClickFunction: any;
9
+ }, {}, "open">;
10
+ type $$ComponentProps = {
11
+ open?: any;
12
+ onClickFunction: any;
13
+ };