govuk_tech_docs 6.5.2.1 → 6.6.0.beta

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/publish-gem.yaml +1 -1
  3. data/lib/govuk_tech_docs/version.rb +1 -1
  4. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +202 -137
  5. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +202 -137
  6. data/node_modules/govuk-frontend/dist/govuk/common/configuration.mjs +5 -1
  7. data/node_modules/govuk-frontend/dist/govuk/common/create-element.mjs +13 -0
  8. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
  9. data/node_modules/govuk-frontend/dist/govuk/component.mjs +2 -1
  10. data/node_modules/govuk-frontend/dist/govuk/components/_index.import.scss +2 -0
  11. data/node_modules/govuk-frontend/dist/govuk/components/_index.scss +2 -0
  12. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +87 -62
  13. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +87 -62
  14. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +60 -56
  15. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +17 -6
  16. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +17 -6
  17. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +90 -32
  18. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +90 -32
  19. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +64 -27
  20. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +12 -5
  21. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +12 -5
  22. data/node_modules/govuk-frontend/dist/govuk/components/cookie-banner/_mixin.scss +6 -0
  23. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +17 -6
  24. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +17 -6
  25. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +43 -18
  26. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +43 -18
  27. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +16 -12
  28. data/node_modules/govuk-frontend/dist/govuk/components/feedback/_feedback.import.scss +6 -0
  29. data/node_modules/govuk-frontend/dist/govuk/components/feedback/_index.import.scss +8 -0
  30. data/node_modules/govuk-frontend/dist/govuk/components/feedback/_index.scss +5 -0
  31. data/node_modules/govuk-frontend/dist/govuk/components/feedback/_mixin.scss +48 -0
  32. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js +54 -30
  33. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs +54 -30
  34. data/node_modules/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs +27 -24
  35. data/node_modules/govuk-frontend/dist/govuk/components/footer/_mixin.scss +6 -0
  36. data/node_modules/govuk-frontend/dist/govuk/components/language-navigation/_index.import.scss +8 -0
  37. data/node_modules/govuk-frontend/dist/govuk/components/language-navigation/_index.scss +6 -0
  38. data/node_modules/govuk-frontend/dist/govuk/components/language-navigation/_language-navigation.import.scss +6 -0
  39. data/node_modules/govuk-frontend/dist/govuk/components/language-navigation/_mixin.scss +68 -0
  40. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +17 -6
  41. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +17 -6
  42. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +32 -9
  43. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +32 -9
  44. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +5 -3
  45. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +12 -5
  46. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +12 -5
  47. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_mixin.scss +48 -3
  48. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +12 -5
  49. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +12 -5
  50. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +14 -6
  51. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +14 -6
  52. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +2 -1
  53. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +12 -5
  54. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +12 -5
  55. data/node_modules/govuk-frontend/dist/govuk/custom-properties/_frontend-version.mixin.scss +1 -1
  56. data/node_modules/govuk-frontend/dist/govuk/custom-properties/_functional-colours.mixin.scss +1 -0
  57. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +8 -5
  58. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
  59. data/node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss +1 -1
  60. data/node_modules/govuk-frontend/dist/govuk/init.mjs +8 -8
  61. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +16 -2
  62. data/package-lock.json +4 -4
  63. data/package.json +1 -1
  64. metadata +10 -1
@@ -1,4 +1,5 @@
1
1
  import { ConfigurableComponent } from '../../common/configuration.mjs';
2
+ import { createElement } from '../../common/create-element.mjs';
2
3
  import { ElementError } from '../../errors/index.mjs';
3
4
  import { I18n } from '../../i18n.mjs';
4
5
 
@@ -25,24 +26,16 @@ class Accordion extends ConfigurableComponent {
25
26
  constructor($root, config = {}) {
26
27
  super($root, config);
27
28
  this.i18n = void 0;
28
- this.controlsClass = 'govuk-accordion__controls';
29
- this.showAllClass = 'govuk-accordion__show-all';
30
- this.showAllTextClass = 'govuk-accordion__show-all-text';
31
29
  this.sectionClass = 'govuk-accordion__section';
32
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
30
+ this.sectionExpandedModifier = 'govuk-accordion__section--expanded';
33
31
  this.sectionButtonClass = 'govuk-accordion__section-button';
34
32
  this.sectionHeaderClass = 'govuk-accordion__section-header';
35
33
  this.sectionHeadingClass = 'govuk-accordion__section-heading';
36
- this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
37
34
  this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
38
- this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
39
- this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
40
- this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
41
- this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
42
- this.upChevronIconClass = 'govuk-accordion-nav__chevron';
43
- this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
35
+ this.sectionToggleTextClass = 'govuk-accordion__section-toggle-text';
36
+ this.iconClass = 'govuk-accordion-nav__chevron';
37
+ this.iconOpenModifier = 'govuk-accordion-nav__chevron--down';
44
38
  this.sectionSummaryClass = 'govuk-accordion__section-summary';
45
- this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
46
39
  this.sectionContentClass = 'govuk-accordion__section-content';
47
40
  this.$sections = void 0;
48
41
  this.$showAllButton = null;
@@ -62,19 +55,23 @@ class Accordion extends ConfigurableComponent {
62
55
  this.updateShowAllButton(this.areAllSectionsOpen());
63
56
  }
64
57
  initControls() {
65
- this.$showAllButton = document.createElement('button');
66
- this.$showAllButton.setAttribute('type', 'button');
67
- this.$showAllButton.setAttribute('class', this.showAllClass);
68
- this.$showAllButton.setAttribute('aria-expanded', 'false');
69
- this.$showAllIcon = document.createElement('span');
70
- this.$showAllIcon.classList.add(this.upChevronIconClass);
58
+ this.$showAllButton = createElement('button', {
59
+ type: 'button',
60
+ class: 'govuk-accordion__show-all',
61
+ 'aria-expanded': 'false'
62
+ });
63
+ this.$showAllIcon = createElement('span', {
64
+ class: this.iconClass
65
+ });
71
66
  this.$showAllButton.appendChild(this.$showAllIcon);
72
- const $accordionControls = document.createElement('div');
73
- $accordionControls.setAttribute('class', this.controlsClass);
67
+ const $accordionControls = createElement('div', {
68
+ class: 'govuk-accordion__controls'
69
+ });
74
70
  $accordionControls.appendChild(this.$showAllButton);
75
71
  this.$root.insertBefore($accordionControls, this.$root.firstChild);
76
- this.$showAllText = document.createElement('span');
77
- this.$showAllText.classList.add(this.showAllTextClass);
72
+ this.$showAllText = createElement('span', {
73
+ class: 'govuk-accordion__show-all-text'
74
+ });
78
75
  this.$showAllButton.appendChild(this.$showAllText);
79
76
  this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
80
77
  if ('onbeforematch' in document) {
@@ -112,39 +109,45 @@ class Accordion extends ConfigurableComponent {
112
109
  identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
113
110
  });
114
111
  }
115
- const $button = document.createElement('button');
116
- $button.setAttribute('type', 'button');
117
- $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
112
+ const $button = createElement('button', {
113
+ type: 'button',
114
+ 'aria-controls': `${this.$root.id}-content-${index + 1}`
115
+ });
118
116
  for (const attr of Array.from($span.attributes)) {
119
117
  if (attr.name !== 'id') {
120
118
  $button.setAttribute(attr.name, attr.value);
121
119
  }
122
120
  }
123
- const $headingText = document.createElement('span');
124
- $headingText.classList.add(this.sectionHeadingTextClass);
125
- $headingText.id = $span.id;
126
- const $headingTextFocus = document.createElement('span');
127
- $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
121
+ const $headingText = createElement('span', {
122
+ class: this.sectionHeadingTextClass,
123
+ id: $span.id
124
+ });
125
+ const $headingTextFocus = createElement('span', {
126
+ class: 'govuk-accordion__section-heading-text-focus'
127
+ });
128
128
  $headingText.appendChild($headingTextFocus);
129
129
  Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
130
- const $showHideToggle = document.createElement('span');
131
- $showHideToggle.classList.add(this.sectionShowHideToggleClass);
132
- $showHideToggle.setAttribute('data-nosnippet', '');
133
- const $showHideToggleFocus = document.createElement('span');
134
- $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
130
+ const $showHideToggle = createElement('span', {
131
+ class: 'govuk-accordion__section-toggle',
132
+ 'data-nosnippet': ''
133
+ });
134
+ const $showHideToggleFocus = createElement('span', {
135
+ class: 'govuk-accordion__section-toggle-focus'
136
+ });
135
137
  $showHideToggle.appendChild($showHideToggleFocus);
136
- const $showHideText = document.createElement('span');
137
- const $showHideIcon = document.createElement('span');
138
- $showHideIcon.classList.add(this.upChevronIconClass);
139
- $showHideToggleFocus.appendChild($showHideIcon);
140
- $showHideText.classList.add(this.sectionShowHideTextClass);
141
- $showHideToggleFocus.appendChild($showHideText);
138
+ $showHideToggleFocus.appendChild(createElement('span', {
139
+ class: this.iconClass
140
+ }));
141
+ $showHideToggleFocus.appendChild(createElement('span', {
142
+ class: this.sectionToggleTextClass
143
+ }));
142
144
  $button.appendChild($headingText);
143
145
  $button.appendChild(this.getButtonPunctuationEl());
144
146
  if ($summary) {
145
- const $summarySpan = document.createElement('span');
146
- const $summarySpanFocus = document.createElement('span');
147
- $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
147
+ const $summarySpan = createElement('span');
148
+ const $summarySpanFocus = createElement('span', {
149
+ class: 'govuk-accordion__section-summary-focus'
150
+ });
148
151
  $summarySpan.appendChild($summarySpanFocus);
149
152
  for (const attr of Array.from($summary.attributes)) {
150
153
  $summarySpan.setAttribute(attr.name, attr.value);
@@ -182,8 +185,8 @@ class Accordion extends ConfigurableComponent {
182
185
  this.updateShowAllButton(nowExpanded);
183
186
  }
184
187
  setExpanded(expanded, $section) {
185
- const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
186
- const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
188
+ const $showHideIcon = $section.querySelector(`.${this.iconClass}`);
189
+ const $showHideText = $section.querySelector(`.${this.sectionToggleTextClass}`);
187
190
  const $button = $section.querySelector(`.${this.sectionButtonClass}`);
188
191
  const $content = $section.querySelector(`.${this.sectionContentClass}`);
189
192
  if (!$content) {
@@ -212,17 +215,17 @@ class Accordion extends ConfigurableComponent {
212
215
  $button.setAttribute('aria-label', ariaLabelParts.join(' , '));
213
216
  if (expanded) {
214
217
  $content.removeAttribute('hidden');
215
- $section.classList.add(this.sectionExpandedClass);
216
- $showHideIcon.classList.remove(this.downChevronIconClass);
218
+ $section.classList.add(this.sectionExpandedModifier);
219
+ $showHideIcon.classList.remove(this.iconOpenModifier);
217
220
  } else {
218
221
  $content.setAttribute('hidden', 'until-found');
219
- $section.classList.remove(this.sectionExpandedClass);
220
- $showHideIcon.classList.add(this.downChevronIconClass);
222
+ $section.classList.remove(this.sectionExpandedModifier);
223
+ $showHideIcon.classList.add(this.iconOpenModifier);
221
224
  }
222
225
  this.updateShowAllButton(this.areAllSectionsOpen());
223
226
  }
224
227
  isExpanded($section) {
225
- return $section.classList.contains(this.sectionExpandedClass);
228
+ return $section.classList.contains(this.sectionExpandedModifier);
226
229
  }
227
230
  areAllSectionsOpen() {
228
231
  return Array.from(this.$sections).every($section => this.isExpanded($section));
@@ -233,7 +236,7 @@ class Accordion extends ConfigurableComponent {
233
236
  }
234
237
  this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
235
238
  this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
236
- this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
239
+ this.$showAllIcon.classList.toggle(this.iconOpenModifier, !expanded);
237
240
  }
238
241
 
239
242
  /**
@@ -276,10 +279,11 @@ class Accordion extends ConfigurableComponent {
276
279
  }
277
280
  }
278
281
  getButtonPunctuationEl() {
279
- const $punctuationEl = document.createElement('span');
280
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
281
- $punctuationEl.textContent = ', ';
282
- return $punctuationEl;
282
+ const $element = createElement('span', {
283
+ class: 'govuk-visually-hidden govuk-accordion__section-heading-divider'
284
+ });
285
+ $element.textContent = ', ';
286
+ return $element;
283
287
  }
284
288
  }
285
289
 
@@ -29,6 +29,9 @@
29
29
  function isObject(option) {
30
30
  return !!option && typeof option === 'object' && !isArray(option);
31
31
  }
32
+ function isScope($scope) {
33
+ return !!$scope && ($scope instanceof Element || $scope instanceof Document);
34
+ }
32
35
  function formatErrorMessage(Component, message) {
33
36
  return `${Component.moduleName}: ${message}`;
34
37
  }
@@ -47,11 +50,14 @@
47
50
  /**
48
51
  * Checks if GOV.UK Frontend is supported on this page
49
52
  *
50
- * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
53
+ * @param {Element | Document | null | string} [scopeOrMessage] - HTML element `<body>` checked for browser support or support error message
51
54
  */
52
- constructor($scope = document.body) {
53
- const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
54
- super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
55
+ constructor(scopeOrMessage = document.body) {
56
+ let supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
57
+ if (!isScope(scopeOrMessage)) {
58
+ supportMessage = 'GOV.UK Frontend initialised without `<script type="module">`';
59
+ }
60
+ super(typeof scopeOrMessage === 'string' ? scopeOrMessage : supportMessage);
55
61
  this.name = 'SupportError';
56
62
  }
57
63
  }
@@ -92,6 +98,7 @@
92
98
  * @import { ComponentWithModuleName } from '../common/index.mjs'
93
99
  */
94
100
 
101
+ const _self = typeof globalThis !== 'undefined' ? globalThis : self;
95
102
  class Component {
96
103
  /**
97
104
  * Returns the root element of the component
@@ -145,7 +152,7 @@
145
152
  /**
146
153
  * @typedef {typeof Component & ChildClass} ChildClassConstructor
147
154
  */
148
- Component.elementType = HTMLElement;
155
+ Component.elementType = _self.HTMLElement;
149
156
 
150
157
  const configOverride = Symbol.for('configOverride');
151
158
  class ConfigurableComponent extends Component {
@@ -170,10 +177,14 @@
170
177
  throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
171
178
  }
172
179
  const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
173
- this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
180
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {});
181
+ this._config = mergeConfigs(this._config, this[configOverride](datasetConfig), datasetConfig);
174
182
  }
175
183
  }
176
184
  function normaliseString(value, property) {
185
+ if (property != null && property.type && !['string', 'number', 'boolean'].includes(property.type)) {
186
+ return;
187
+ }
177
188
  const trimmedValue = value ? value.trim() : '';
178
189
  let output;
179
190
  let outputType = property == null ? void 0 : property.type;
@@ -23,6 +23,9 @@ function isArray(option) {
23
23
  function isObject(option) {
24
24
  return !!option && typeof option === 'object' && !isArray(option);
25
25
  }
26
+ function isScope($scope) {
27
+ return !!$scope && ($scope instanceof Element || $scope instanceof Document);
28
+ }
26
29
  function formatErrorMessage(Component, message) {
27
30
  return `${Component.moduleName}: ${message}`;
28
31
  }
@@ -41,11 +44,14 @@ class SupportError extends GOVUKFrontendError {
41
44
  /**
42
45
  * Checks if GOV.UK Frontend is supported on this page
43
46
  *
44
- * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
47
+ * @param {Element | Document | null | string} [scopeOrMessage] - HTML element `<body>` checked for browser support or support error message
45
48
  */
46
- constructor($scope = document.body) {
47
- const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
48
- super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
49
+ constructor(scopeOrMessage = document.body) {
50
+ let supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
51
+ if (!isScope(scopeOrMessage)) {
52
+ supportMessage = 'GOV.UK Frontend initialised without `<script type="module">`';
53
+ }
54
+ super(typeof scopeOrMessage === 'string' ? scopeOrMessage : supportMessage);
49
55
  this.name = 'SupportError';
50
56
  }
51
57
  }
@@ -86,6 +92,7 @@ class InitError extends GOVUKFrontendError {
86
92
  * @import { ComponentWithModuleName } from '../common/index.mjs'
87
93
  */
88
94
 
95
+ const _self = typeof globalThis !== 'undefined' ? globalThis : self;
89
96
  class Component {
90
97
  /**
91
98
  * Returns the root element of the component
@@ -139,7 +146,7 @@ class Component {
139
146
  /**
140
147
  * @typedef {typeof Component & ChildClass} ChildClassConstructor
141
148
  */
142
- Component.elementType = HTMLElement;
149
+ Component.elementType = _self.HTMLElement;
143
150
 
144
151
  const configOverride = Symbol.for('configOverride');
145
152
  class ConfigurableComponent extends Component {
@@ -164,10 +171,14 @@ class ConfigurableComponent extends Component {
164
171
  throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
165
172
  }
166
173
  const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
167
- this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
174
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {});
175
+ this._config = mergeConfigs(this._config, this[configOverride](datasetConfig), datasetConfig);
168
176
  }
169
177
  }
170
178
  function normaliseString(value, property) {
179
+ if (property != null && property.type && !['string', 'number', 'boolean'].includes(property.type)) {
180
+ return;
181
+ }
171
182
  const trimmedValue = value ? value.trim() : '';
172
183
  let output;
173
184
  let outputType = property == null ? void 0 : property.type;
@@ -34,6 +34,9 @@
34
34
  function isObject(option) {
35
35
  return !!option && typeof option === 'object' && !isArray(option);
36
36
  }
37
+ function isScope($scope) {
38
+ return !!$scope && ($scope instanceof Element || $scope instanceof Document);
39
+ }
37
40
  function formatErrorMessage(Component, message) {
38
41
  return `${Component.moduleName}: ${message}`;
39
42
  }
@@ -52,11 +55,14 @@
52
55
  /**
53
56
  * Checks if GOV.UK Frontend is supported on this page
54
57
  *
55
- * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
58
+ * @param {Element | Document | null | string} [scopeOrMessage] - HTML element `<body>` checked for browser support or support error message
56
59
  */
57
- constructor($scope = document.body) {
58
- const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
59
- super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
60
+ constructor(scopeOrMessage = document.body) {
61
+ let supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
62
+ if (!isScope(scopeOrMessage)) {
63
+ supportMessage = 'GOV.UK Frontend initialised without `<script type="module">`';
64
+ }
65
+ super(typeof scopeOrMessage === 'string' ? scopeOrMessage : supportMessage);
60
66
  this.name = 'SupportError';
61
67
  }
62
68
  }
@@ -97,6 +103,7 @@
97
103
  * @import { ComponentWithModuleName } from '../common/index.mjs'
98
104
  */
99
105
 
106
+ const _self = typeof globalThis !== 'undefined' ? globalThis : self;
100
107
  class Component {
101
108
  /**
102
109
  * Returns the root element of the component
@@ -150,7 +157,7 @@
150
157
  /**
151
158
  * @typedef {typeof Component & ChildClass} ChildClassConstructor
152
159
  */
153
- Component.elementType = HTMLElement;
160
+ Component.elementType = _self.HTMLElement;
154
161
 
155
162
  const configOverride = Symbol.for('configOverride');
156
163
  class ConfigurableComponent extends Component {
@@ -175,10 +182,14 @@
175
182
  throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
176
183
  }
177
184
  const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
178
- this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
185
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {});
186
+ this._config = mergeConfigs(this._config, this[configOverride](datasetConfig), datasetConfig);
179
187
  }
180
188
  }
181
189
  function normaliseString(value, property) {
190
+ if (property != null && property.type && !['string', 'number', 'boolean'].includes(property.type)) {
191
+ return;
192
+ }
182
193
  const trimmedValue = value ? value.trim() : '';
183
194
  let output;
184
195
  let outputType = property == null ? void 0 : property.type;
@@ -318,6 +329,17 @@
318
329
  * @import { CompatibleClass, Config, CreateAllOptions, OnErrorCallback } from '../init.mjs'
319
330
  */
320
331
 
332
+ function createElement(tagName, attributes = {}, children = []) {
333
+ const $element = document.createElement(tagName);
334
+ Object.entries(attributes).forEach(([name, value]) => {
335
+ $element.setAttribute(name, value);
336
+ });
337
+ for (const child of children) {
338
+ $element.append(child);
339
+ }
340
+ return $element;
341
+ }
342
+
321
343
  class I18n {
322
344
  constructor(translations = {}, config = {}) {
323
345
  var _config$locale;
@@ -408,6 +430,14 @@
408
430
  maxwords: undefined
409
431
  };
410
432
  }
433
+ if ('maxwords' in datasetConfig || this.config.maxwords !== undefined) {
434
+ console.warn(formatErrorMessage(CharacterCount, 'Option `maxwords` is deprecated. Use `maxlength` with `countType: "words"` instead.'));
435
+ if (!('maxlength' in datasetConfig)) {
436
+ var _datasetConfig$maxwor;
437
+ configOverrides.maxlength = (_datasetConfig$maxwor = datasetConfig.maxwords) != null ? _datasetConfig$maxwor : this.config.maxwords;
438
+ configOverrides.countType = 'words';
439
+ }
440
+ }
411
441
  return configOverrides;
412
442
  }
413
443
 
@@ -416,10 +446,11 @@
416
446
  * @param {CharacterCountConfig} [config] - Character count config
417
447
  */
418
448
  constructor($root, config = {}) {
419
- var _ref, _this$config$maxwords;
449
+ var _this$config$maxlengt;
420
450
  super($root, config);
421
451
  this.$textarea = void 0;
422
452
  this.count = 0;
453
+ this.segmenter = null;
423
454
  this.$visibleCountMessage = void 0;
424
455
  this.$screenReaderCountMessage = void 0;
425
456
  this.lastInputTimestamp = null;
@@ -443,7 +474,15 @@
443
474
  this.i18n = new I18n(this.config.i18n, {
444
475
  locale: closestAttributeValue(this.$root, 'lang')
445
476
  });
446
- this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
477
+ if (this.config.countType === 'characters' || this.config.countType === 'words' && this.config.maxwords === undefined) {
478
+ if (!('Segmenter' in Intl)) {
479
+ throw new SupportError(formatErrorMessage(CharacterCount, 'Support for "Intl.Segmenter" required'));
480
+ }
481
+ this.segmenter = new Intl.Segmenter(this.i18n.locale, {
482
+ granularity: this.config.countType === 'words' ? 'word' : 'grapheme'
483
+ });
484
+ }
485
+ this.maxLength = (_this$config$maxlengt = this.config.maxlength) != null ? _this$config$maxlengt : Infinity;
447
486
  this.$textarea = $textarea;
448
487
  const textareaDescriptionId = `${this.$textarea.id}-info`;
449
488
  const $textareaDescription = document.getElementById(textareaDescriptionId);
@@ -461,15 +500,16 @@
461
500
  });
462
501
  }
463
502
  this.$textarea.insertAdjacentElement('afterend', $textareaDescription);
464
- const $screenReaderCountMessage = document.createElement('div');
465
- $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
466
- $screenReaderCountMessage.setAttribute('aria-live', 'polite');
503
+ const $screenReaderCountMessage = createElement('div', {
504
+ class: 'govuk-character-count__sr-status govuk-visually-hidden',
505
+ 'aria-live': 'polite'
506
+ });
467
507
  this.$screenReaderCountMessage = $screenReaderCountMessage;
468
508
  $textareaDescription.insertAdjacentElement('afterend', $screenReaderCountMessage);
469
- const $visibleCountMessage = document.createElement('div');
470
- $visibleCountMessage.className = $textareaDescription.className;
471
- $visibleCountMessage.classList.add('govuk-character-count__status');
472
- $visibleCountMessage.setAttribute('aria-hidden', 'true');
509
+ const $visibleCountMessage = createElement('div', {
510
+ class: `${$textareaDescription.className} govuk-character-count__status`,
511
+ 'aria-hidden': 'true'
512
+ });
473
513
  this.$visibleCountMessage = $visibleCountMessage;
474
514
  $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage);
475
515
  $textareaDescription.classList.add('govuk-visually-hidden');
@@ -537,26 +577,38 @@
537
577
  }
538
578
  updateCount() {
539
579
  const text = this.$textarea.value;
540
- if (this.config.maxwords) {
541
- var _text$match;
542
- const tokens = (_text$match = text.match(/\S+/g)) != null ? _text$match : [];
543
- this.count = tokens.length;
580
+ if (this.config.countType === 'characters') {
581
+ this.count = this.segmenter ? Array.from(this.segmenter.segment(text)).length : 0;
582
+ return;
583
+ }
584
+ if (this.config.countType === 'words') {
585
+ if (this.config.maxwords !== undefined) {
586
+ var _text$match$length, _text$match;
587
+ this.count = (_text$match$length = (_text$match = text.match(/\S+/g)) == null ? void 0 : _text$match.length) != null ? _text$match$length : 0;
588
+ return;
589
+ }
590
+ const segments = this.segmenter ? Array.from(this.segmenter.segment(text)) : [];
591
+ this.count = segments.filter(segment => segment.isWordLike).length;
544
592
  return;
545
593
  }
546
594
  this.count = text.length;
547
595
  }
548
596
  getCountMessage() {
549
597
  const remainingNumber = this.maxLength - this.count;
550
- const countType = this.config.maxwords ? 'words' : 'characters';
551
- return this.formatCountMessage(remainingNumber, countType);
598
+ return this.formatCountMessage(remainingNumber);
552
599
  }
553
600
  formatCountMessage(remainingNumber, countType) {
601
+ countType = countType != null ? countType : this.config.countType;
602
+ let translationKeyPrefix = 'characters';
603
+ let translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
604
+ if (countType === 'words') {
605
+ translationKeyPrefix = 'words';
606
+ }
554
607
  if (remainingNumber === 0) {
555
- return this.i18n.t(`${countType}AtLimit`);
608
+ translationKeySuffix = 'AtLimit';
556
609
  }
557
- const translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
558
- return this.i18n.t(`${countType}${translationKeySuffix}`, {
559
- count: Math.abs(remainingNumber)
610
+ return this.i18n.t(`${translationKeyPrefix}${translationKeySuffix}`, {
611
+ count: remainingNumber === 0 ? undefined : Math.abs(remainingNumber)
560
612
  });
561
613
  }
562
614
  isOverThreshold() {
@@ -575,13 +627,15 @@
575
627
  *
576
628
  * @see {@link CharacterCount.defaults}
577
629
  * @typedef {object} CharacterCountConfig
578
- * @property {number} [maxlength] - The maximum number of characters.
579
- * If maxwords is provided, the maxlength option will be ignored.
580
- * @property {number} [maxwords] - The maximum number of words. If maxwords is
581
- * provided, the maxlength option will be ignored.
630
+ * @property {number} [maxlength] - The maximum number of characters (or words
631
+ * if `countType` is set to `"words"`).
632
+ * @property {number} [maxwords] - Deprecated. Use `maxlength` and
633
+ * `countType: "words"` instead.
582
634
  * @property {number} [threshold=0] - The percentage value of the limit at
583
635
  * which point the count message is displayed. If this attribute is set, the
584
636
  * count message will be hidden by default.
637
+ * @property {'characters' | 'length' | 'words'} [countType] - The count type
638
+ * (`"characters"`, `"length"` or `"words"`) used to count the text.
585
639
  * @property {CharacterCountTranslations} [i18n=CharacterCount.defaults.i18n] - Character count translations
586
640
  */
587
641
 
@@ -627,9 +681,9 @@
627
681
  * @property {TranslationPluralForms} [textareaDescription] - Message made
628
682
  * available to assistive technologies, if none is already present in the
629
683
  * HTML, to describe that the component accepts only a limited amount of
630
- * content. It is visible on the page when JavaScript is unavailable. The
631
- * component will replace the `%{count}` placeholder with the value of the
632
- * `maxlength` or `maxwords` parameter.
684
+ * content. It is visible on the page if `countType` is not supported or
685
+ * JavaScript is unavailable. The component will replace the `%{count}`
686
+ * placeholder with the value of the `maxlength` parameter.
633
687
  */
634
688
 
635
689
  /**
@@ -639,6 +693,7 @@
639
693
  CharacterCount.moduleName = 'govuk-character-count';
640
694
  CharacterCount.defaults = Object.freeze({
641
695
  threshold: 0,
696
+ countType: 'length',
642
697
  i18n: {
643
698
  charactersUnderLimit: {
644
699
  one: 'You have %{count} character remaining',
@@ -676,6 +731,9 @@
676
731
  },
677
732
  threshold: {
678
733
  type: 'number'
734
+ },
735
+ countType: {
736
+ type: 'string'
679
737
  }
680
738
  },
681
739
  anyOf: [{