govuk_publishing_components 44.1.0 → 44.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/govuk_publishing_components/components/_share-links.scss +32 -0
  3. data/app/views/govuk_publishing_components/components/_share_links.html.erb +4 -1
  4. data/app/views/govuk_publishing_components/components/docs/share_links.yml +36 -0
  5. data/lib/govuk_publishing_components/version.rb +1 -1
  6. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +336 -225
  7. data/node_modules/govuk-frontend/dist/govuk/all.bundle.js.map +1 -1
  8. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +334 -226
  9. data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs.map +1 -1
  10. data/node_modules/govuk-frontend/dist/govuk/all.mjs +3 -0
  11. data/node_modules/govuk-frontend/dist/govuk/all.mjs.map +1 -1
  12. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.svg +1 -0
  13. data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
  14. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +21 -1
  15. data/node_modules/govuk-frontend/dist/govuk/common/index.mjs.map +1 -1
  16. data/node_modules/govuk-frontend/dist/govuk/components/_index.scss +1 -0
  17. data/node_modules/govuk-frontend/dist/govuk/components/_index.scss.map +1 -1
  18. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +92 -26
  19. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js.map +1 -1
  20. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +92 -26
  21. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs.map +1 -1
  22. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +12 -21
  23. data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs.map +1 -1
  24. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +86 -20
  25. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js.map +1 -1
  26. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +86 -20
  27. data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs.map +1 -1
  28. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +6 -16
  29. data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs.map +1 -1
  30. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +89 -23
  31. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js.map +1 -1
  32. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +89 -23
  33. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs.map +1 -1
  34. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +10 -19
  35. data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs.map +1 -1
  36. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +113 -47
  37. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js.map +1 -1
  38. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +113 -47
  39. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs.map +1 -1
  40. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +7 -16
  41. data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs.map +1 -1
  42. data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss +7 -2
  43. data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss.map +1 -1
  44. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +86 -20
  45. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js.map +1 -1
  46. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +86 -20
  47. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs.map +1 -1
  48. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +6 -16
  49. data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs.map +1 -1
  50. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +87 -21
  51. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js.map +1 -1
  52. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +87 -21
  53. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs.map +1 -1
  54. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +7 -16
  55. data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs.map +1 -1
  56. data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss +8 -10
  57. data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss.map +1 -1
  58. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss +8 -0
  59. data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss.map +1 -1
  60. data/node_modules/govuk-frontend/dist/govuk/components/header/fixtures.json +12 -0
  61. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +87 -21
  62. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js.map +1 -1
  63. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +87 -21
  64. data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs.map +1 -1
  65. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +7 -16
  66. data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs.map +1 -1
  67. data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-full-width-border.html +24 -0
  68. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +86 -20
  69. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js.map +1 -1
  70. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +86 -20
  71. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs.map +1 -1
  72. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +6 -16
  73. data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs.map +1 -1
  74. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +89 -23
  75. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js.map +1 -1
  76. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +89 -23
  77. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs.map +1 -1
  78. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +9 -18
  79. data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs.map +1 -1
  80. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +113 -47
  81. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js.map +1 -1
  82. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +113 -47
  83. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs.map +1 -1
  84. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +7 -16
  85. data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs.map +1 -1
  86. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/README.md +15 -0
  87. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss +168 -0
  88. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss.map +1 -0
  89. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss +4 -0
  90. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss.map +1 -0
  91. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/fixtures.json +464 -0
  92. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro-options.json +138 -0
  93. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro.njk +3 -0
  94. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +249 -0
  95. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js.map +1 -0
  96. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +241 -0
  97. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs.map +1 -0
  98. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +85 -0
  99. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs.map +1 -0
  100. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-default.html +57 -0
  101. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-html-navigation-items.html +49 -0
  102. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-large-navigation.html +153 -0
  103. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-long-service-name.html +20 -0
  104. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-a-current-item.html +58 -0
  105. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-an-active-item.html +58 -0
  106. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-non-link-navigation-items.html +49 -0
  107. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-link.html +20 -0
  108. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name-and-navigation.html +63 -0
  109. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name.html +18 -0
  110. data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template.njk +102 -0
  111. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +93 -26
  112. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js.map +1 -1
  113. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +93 -26
  114. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs.map +1 -1
  115. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +13 -21
  116. data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs.map +1 -1
  117. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +93 -27
  118. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js.map +1 -1
  119. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +93 -27
  120. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs.map +1 -1
  121. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +13 -22
  122. data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs.map +1 -1
  123. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss +4 -3
  124. data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss.map +1 -1
  125. data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
  126. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +16 -3
  127. data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs.map +1 -1
  128. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs +49 -5
  129. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs.map +1 -1
  130. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css +2 -2
  131. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css.map +1 -1
  132. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
  133. data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js.map +1 -1
  134. data/node_modules/govuk-frontend/dist/govuk/init.mjs +72 -10
  135. data/node_modules/govuk-frontend/dist/govuk/init.mjs.map +1 -1
  136. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +3 -0
  137. data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss.map +1 -1
  138. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +5 -1
  139. data/node_modules/govuk-frontend/package.json +8 -8
  140. metadata +29 -4
  141. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest-2x.png +0 -0
  142. data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.png +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"character-count.mjs","sources":["../../../../src/govuk/components/character-count/character-count.mjs"],"sourcesContent":["import { closestAttributeValue } from '../../common/closest-attribute-value.mjs'\nimport { mergeConfigs, validateConfig } from '../../common/index.mjs'\nimport { normaliseDataset } from '../../common/normalise-dataset.mjs'\nimport { ConfigError, ElementError } from '../../errors/index.mjs'\nimport { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs'\nimport { I18n } from '../../i18n.mjs'\n\n/**\n * Character count component\n *\n * Tracks the number of characters or words in the `.govuk-js-character-count`\n * `<textarea>` inside the element. Displays a message with the remaining number\n * of characters/words available, or the number of characters/words in excess.\n *\n * You can configure the message to only appear after a certain percentage\n * of the available characters/words has been entered.\n *\n * @preserve\n */\nexport class CharacterCount extends GOVUKFrontendComponent {\n /** @private */\n $module\n\n /** @private */\n $textarea\n\n /** @private */\n $visibleCountMessage\n\n /** @private */\n $screenReaderCountMessage\n\n /**\n * @private\n * @type {number | null}\n */\n lastInputTimestamp = null\n\n /** @private */\n lastInputValue = ''\n\n /**\n * @private\n * @type {number | null}\n */\n valueChecker = null\n\n /**\n * @private\n * @type {CharacterCountConfig}\n */\n config\n\n /** @private */\n i18n\n\n /** @private */\n maxLength\n\n /**\n * @param {Element | null} $module - HTML element to use for character count\n * @param {CharacterCountConfig} [config] - Character count config\n */\n constructor($module, config = {}) {\n super()\n\n if (!($module instanceof HTMLElement)) {\n throw new ElementError({\n componentName: 'Character count',\n element: $module,\n identifier: 'Root element (`$module`)'\n })\n }\n\n const $textarea = $module.querySelector('.govuk-js-character-count')\n if (\n !(\n $textarea instanceof HTMLTextAreaElement ||\n $textarea instanceof HTMLInputElement\n )\n ) {\n throw new ElementError({\n componentName: 'Character count',\n element: $textarea,\n expectedType: 'HTMLTextareaElement or HTMLInputElement',\n identifier: 'Form field (`.govuk-js-character-count`)'\n })\n }\n\n // Read config set using dataset ('data-' values)\n const datasetConfig = normaliseDataset(CharacterCount, $module.dataset)\n\n // To ensure data-attributes take complete precedence, even if they change\n // the type of count, we need to reset the `maxlength` and `maxwords` from\n // the JavaScript config.\n //\n // We can't mutate `config`, though, as it may be shared across multiple\n // components inside `initAll`.\n /** @type {CharacterCountConfig} */\n let configOverrides = {}\n if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {\n configOverrides = {\n maxlength: undefined,\n maxwords: undefined\n }\n }\n\n this.config = mergeConfigs(\n CharacterCount.defaults,\n config,\n configOverrides,\n datasetConfig\n )\n\n // Check for valid config\n const errors = validateConfig(CharacterCount.schema, this.config)\n if (errors[0]) {\n throw new ConfigError(`Character count: ${errors[0]}`)\n }\n\n this.i18n = new I18n(this.config.i18n, {\n // Read the fallback if necessary rather than have it set in the defaults\n locale: closestAttributeValue($module, 'lang')\n })\n\n // Determine the limit attribute (characters or words)\n this.maxLength = this.config.maxwords ?? this.config.maxlength ?? Infinity\n\n this.$module = $module\n this.$textarea = $textarea\n\n const textareaDescriptionId = `${this.$textarea.id}-info`\n const $textareaDescription = document.getElementById(textareaDescriptionId)\n if (!$textareaDescription) {\n throw new ElementError({\n componentName: 'Character count',\n element: $textareaDescription,\n identifier: `Count message (\\`id=\"${textareaDescriptionId}\"\\`)`\n })\n }\n\n // Inject a description for the textarea if none is present already\n // for when the component was rendered with no maxlength, maxwords\n // nor custom textareaDescriptionText\n if (`${$textareaDescription.textContent}`.match(/^\\s*$/)) {\n $textareaDescription.textContent = this.i18n.t('textareaDescription', {\n count: this.maxLength\n })\n }\n\n // Move the textarea description to be immediately after the textarea\n // Kept for backwards compatibility\n this.$textarea.insertAdjacentElement('afterend', $textareaDescription)\n\n // Create the *screen reader* specific live-updating counter\n // This doesn't need any styling classes, as it is never visible\n const $screenReaderCountMessage = document.createElement('div')\n $screenReaderCountMessage.className =\n 'govuk-character-count__sr-status govuk-visually-hidden'\n $screenReaderCountMessage.setAttribute('aria-live', 'polite')\n this.$screenReaderCountMessage = $screenReaderCountMessage\n $textareaDescription.insertAdjacentElement(\n 'afterend',\n $screenReaderCountMessage\n )\n\n // Create our live-updating counter element, copying the classes from the\n // textarea description for backwards compatibility as these may have been\n // configured\n const $visibleCountMessage = document.createElement('div')\n $visibleCountMessage.className = $textareaDescription.className\n $visibleCountMessage.classList.add('govuk-character-count__status')\n $visibleCountMessage.setAttribute('aria-hidden', 'true')\n this.$visibleCountMessage = $visibleCountMessage\n $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage)\n\n // Hide the textarea description\n $textareaDescription.classList.add('govuk-visually-hidden')\n\n // Remove hard limit if set\n this.$textarea.removeAttribute('maxlength')\n\n this.bindChangeEvents()\n\n // When the page is restored after navigating 'back' in some browsers the\n // state of form controls is not restored until *after* the DOMContentLoaded\n // event is fired, so we need to sync after the pageshow event.\n window.addEventListener('pageshow', () => this.updateCountMessage())\n\n // Although we've set up handlers to sync state on the pageshow event, init\n // could be called after those events have fired, for example if they are\n // added to the page dynamically, so update now too.\n this.updateCountMessage()\n }\n\n /**\n * Bind change events\n *\n * Set up event listeners on the $textarea so that the count messages update\n * when the user types.\n *\n * @private\n */\n bindChangeEvents() {\n this.$textarea.addEventListener('keyup', () => this.handleKeyUp())\n\n // Bind focus/blur events to start/stop polling\n this.$textarea.addEventListener('focus', () => this.handleFocus())\n this.$textarea.addEventListener('blur', () => this.handleBlur())\n }\n\n /**\n * Handle key up event\n *\n * Update the visible character counter and keep track of when the last update\n * happened for each keypress\n *\n * @private\n */\n handleKeyUp() {\n this.updateVisibleCountMessage()\n this.lastInputTimestamp = Date.now()\n }\n\n /**\n * Handle focus event\n *\n * Speech recognition software such as Dragon NaturallySpeaking will modify\n * the fields by directly changing its `value`. These changes don't trigger\n * events in JavaScript, so we need to poll to handle when and if they occur.\n *\n * Once the keyup event hasn't been detected for at least 1000 ms (1s), check\n * if the textarea value has changed and update the count message if it has.\n *\n * This is so that the update triggered by the manual comparison doesn't\n * conflict with debounced KeyboardEvent updates.\n *\n * @private\n */\n handleFocus() {\n this.valueChecker = window.setInterval(() => {\n if (\n !this.lastInputTimestamp ||\n Date.now() - 500 >= this.lastInputTimestamp\n ) {\n this.updateIfValueChanged()\n }\n }, 1000)\n }\n\n /**\n * Handle blur event\n *\n * Stop checking the textarea value once the textarea no longer has focus\n *\n * @private\n */\n handleBlur() {\n // Cancel value checking on blur\n if (this.valueChecker) {\n window.clearInterval(this.valueChecker)\n }\n }\n\n /**\n * Update count message if textarea value has changed\n *\n * @private\n */\n updateIfValueChanged() {\n if (this.$textarea.value !== this.lastInputValue) {\n this.lastInputValue = this.$textarea.value\n this.updateCountMessage()\n }\n }\n\n /**\n * Update count message\n *\n * Helper function to update both the visible and screen reader-specific\n * counters simultaneously (e.g. on init)\n *\n * @private\n */\n updateCountMessage() {\n this.updateVisibleCountMessage()\n this.updateScreenReaderCountMessage()\n }\n\n /**\n * Update visible count message\n *\n * @private\n */\n updateVisibleCountMessage() {\n const remainingNumber = this.maxLength - this.count(this.$textarea.value)\n const isError = remainingNumber < 0\n\n // If input is over the threshold, remove the disabled class which renders\n // the counter invisible.\n this.$visibleCountMessage.classList.toggle(\n 'govuk-character-count__message--disabled',\n !this.isOverThreshold()\n )\n\n // Update styles\n this.$textarea.classList.toggle('govuk-textarea--error', isError)\n this.$visibleCountMessage.classList.toggle('govuk-error-message', isError)\n this.$visibleCountMessage.classList.toggle('govuk-hint', !isError)\n\n // Update message\n this.$visibleCountMessage.textContent = this.getCountMessage()\n }\n\n /**\n * Update screen reader count message\n *\n * @private\n */\n updateScreenReaderCountMessage() {\n // If over the threshold, remove the aria-hidden attribute, allowing screen\n // readers to announce the content of the element.\n if (this.isOverThreshold()) {\n this.$screenReaderCountMessage.removeAttribute('aria-hidden')\n } else {\n this.$screenReaderCountMessage.setAttribute('aria-hidden', 'true')\n }\n\n // Update message\n this.$screenReaderCountMessage.textContent = this.getCountMessage()\n }\n\n /**\n * Count the number of characters (or words, if `config.maxwords` is set)\n * in the given text\n *\n * @private\n * @param {string} text - The text to count the characters of\n * @returns {number} the number of characters (or words) in the text\n */\n count(text) {\n if (this.config.maxwords) {\n const tokens = text.match(/\\S+/g) ?? [] // Matches consecutive non-whitespace chars\n return tokens.length\n }\n\n return text.length\n }\n\n /**\n * Get count message\n *\n * @private\n * @returns {string} Status message\n */\n getCountMessage() {\n const remainingNumber = this.maxLength - this.count(this.$textarea.value)\n const countType = this.config.maxwords ? 'words' : 'characters'\n return this.formatCountMessage(remainingNumber, countType)\n }\n\n /**\n * Formats the message shown to users according to what's counted\n * and how many remain\n *\n * @private\n * @param {number} remainingNumber - The number of words/characaters remaining\n * @param {string} countType - \"words\" or \"characters\"\n * @returns {string} Status message\n */\n formatCountMessage(remainingNumber, countType) {\n if (remainingNumber === 0) {\n return this.i18n.t(`${countType}AtLimit`)\n }\n\n const translationKeySuffix =\n remainingNumber < 0 ? 'OverLimit' : 'UnderLimit'\n\n return this.i18n.t(`${countType}${translationKeySuffix}`, {\n count: Math.abs(remainingNumber)\n })\n }\n\n /**\n * Check if count is over threshold\n *\n * Checks whether the value is over the configured threshold for the input.\n * If there is no configured threshold, it is set to 0 and this function will\n * always return true.\n *\n * @private\n * @returns {boolean} true if the current count is over the config.threshold\n * (or no threshold is set)\n */\n isOverThreshold() {\n // No threshold means we're always above threshold so save some computation\n if (!this.config.threshold) {\n return true\n }\n\n // Determine the remaining number of characters/words\n const currentLength = this.count(this.$textarea.value)\n const maxLength = this.maxLength\n\n const thresholdValue = (maxLength * this.config.threshold) / 100\n\n return thresholdValue <= currentLength\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-character-count'\n\n /**\n * Character count default config\n *\n * @see {@link CharacterCountConfig}\n * @constant\n * @type {CharacterCountConfig}\n */\n static defaults = Object.freeze({\n threshold: 0,\n i18n: {\n // Characters\n charactersUnderLimit: {\n one: 'You have %{count} character remaining',\n other: 'You have %{count} characters remaining'\n },\n charactersAtLimit: 'You have 0 characters remaining',\n charactersOverLimit: {\n one: 'You have %{count} character too many',\n other: 'You have %{count} characters too many'\n },\n // Words\n wordsUnderLimit: {\n one: 'You have %{count} word remaining',\n other: 'You have %{count} words remaining'\n },\n wordsAtLimit: 'You have 0 words remaining',\n wordsOverLimit: {\n one: 'You have %{count} word too many',\n other: 'You have %{count} words too many'\n },\n textareaDescription: {\n other: ''\n }\n }\n })\n\n /**\n * Character count config schema\n *\n * @constant\n * @satisfies {Schema}\n */\n static schema = Object.freeze({\n properties: {\n i18n: { type: 'object' },\n maxwords: { type: 'number' },\n maxlength: { type: 'number' },\n threshold: { type: 'number' }\n },\n anyOf: [\n {\n required: ['maxwords'],\n errorMessage: 'Either \"maxlength\" or \"maxwords\" must be provided'\n },\n {\n required: ['maxlength'],\n errorMessage: 'Either \"maxlength\" or \"maxwords\" must be provided'\n }\n ]\n })\n}\n\n/**\n * Character count config\n *\n * @see {@link CharacterCount.defaults}\n * @typedef {object} CharacterCountConfig\n * @property {number} [maxlength] - The maximum number of characters.\n * If maxwords is provided, the maxlength option will be ignored.\n * @property {number} [maxwords] - The maximum number of words. If maxwords is\n * provided, the maxlength option will be ignored.\n * @property {number} [threshold=0] - The percentage value of the limit at\n * which point the count message is displayed. If this attribute is set, the\n * count message will be hidden by default.\n * @property {CharacterCountTranslations} [i18n=CharacterCount.defaults.i18n] - Character count translations\n */\n\n/**\n * Character count translations\n *\n * @see {@link CharacterCount.defaults.i18n}\n * @typedef {object} CharacterCountTranslations\n *\n * Messages shown to users as they type. It provides feedback on how many words\n * or characters they have remaining or if they are over the limit. This also\n * includes a message used as an accessible description for the textarea.\n * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed\n * when the number of characters is under the configured maximum, `maxlength`.\n * This message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining characters. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {string} [charactersAtLimit] - Message displayed when the number of\n * characters reaches the configured maximum, `maxlength`. This message is\n * displayed visually and through assistive technologies.\n * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed\n * when the number of characters is over the configured maximum, `maxlength`.\n * This message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining characters. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when\n * the number of words is under the configured maximum, `maxlength`. This\n * message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining words. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {string} [wordsAtLimit] - Message displayed when the number of\n * words reaches the configured maximum, `maxlength`. This message is\n * displayed visually and through assistive technologies.\n * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when\n * the number of words is over the configured maximum, `maxlength`. This\n * message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining words. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {TranslationPluralForms} [textareaDescription] - Message made\n * available to assistive technologies, if none is already present in the\n * HTML, to describe that the component accepts only a limited amount of\n * content. It is visible on the page when JavaScript is unavailable. The\n * component will replace the `%{count}` placeholder with the value of the\n * `maxlength` or `maxwords` parameter.\n */\n\n/**\n * @typedef {import('../../common/index.mjs').Schema} Schema\n * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms\n */\n"],"names":["CharacterCount","GOVUKFrontendComponent","constructor","$module","config","_ref","_this$config$maxwords","$textarea","$visibleCountMessage","$screenReaderCountMessage","lastInputTimestamp","lastInputValue","valueChecker","i18n","maxLength","HTMLElement","ElementError","componentName","element","identifier","querySelector","HTMLTextAreaElement","HTMLInputElement","expectedType","datasetConfig","normaliseDataset","dataset","configOverrides","maxlength","undefined","maxwords","mergeConfigs","defaults","errors","validateConfig","schema","ConfigError","I18n","locale","closestAttributeValue","Infinity","textareaDescriptionId","id","$textareaDescription","document","getElementById","textContent","match","t","count","insertAdjacentElement","createElement","className","setAttribute","classList","add","removeAttribute","bindChangeEvents","window","addEventListener","updateCountMessage","handleKeyUp","handleFocus","handleBlur","updateVisibleCountMessage","Date","now","setInterval","updateIfValueChanged","clearInterval","value","updateScreenReaderCountMessage","remainingNumber","isError","toggle","isOverThreshold","getCountMessage","text","_text$match","tokens","length","countType","formatCountMessage","translationKeySuffix","Math","abs","threshold","currentLength","thresholdValue","moduleName","Object","freeze","charactersUnderLimit","one","other","charactersAtLimit","charactersOverLimit","wordsUnderLimit","wordsAtLimit","wordsOverLimit","textareaDescription","properties","type","anyOf","required","errorMessage"],"mappings":"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"}
1
+ {"version":3,"file":"character-count.mjs","sources":["../../../../src/govuk/components/character-count/character-count.mjs"],"sourcesContent":["import { closestAttributeValue } from '../../common/closest-attribute-value.mjs'\nimport {\n formatErrorMessage,\n mergeConfigs,\n validateConfig\n} from '../../common/index.mjs'\nimport { normaliseDataset } from '../../common/normalise-dataset.mjs'\nimport { ConfigError, ElementError } from '../../errors/index.mjs'\nimport { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs'\nimport { I18n } from '../../i18n.mjs'\n\n/**\n * Character count component\n *\n * Tracks the number of characters or words in the `.govuk-js-character-count`\n * `<textarea>` inside the element. Displays a message with the remaining number\n * of characters/words available, or the number of characters/words in excess.\n *\n * You can configure the message to only appear after a certain percentage\n * of the available characters/words has been entered.\n *\n * @preserve\n */\nexport class CharacterCount extends GOVUKFrontendComponent {\n /** @private */\n $textarea\n\n /** @private */\n $visibleCountMessage\n\n /** @private */\n $screenReaderCountMessage\n\n /**\n * @private\n * @type {number | null}\n */\n lastInputTimestamp = null\n\n /** @private */\n lastInputValue = ''\n\n /**\n * @private\n * @type {number | null}\n */\n valueChecker = null\n\n /**\n * @private\n * @type {CharacterCountConfig}\n */\n config\n\n /** @private */\n i18n\n\n /** @private */\n maxLength\n\n /**\n * @param {Element | null} $root - HTML element to use for character count\n * @param {CharacterCountConfig} [config] - Character count config\n */\n constructor($root, config = {}) {\n super($root)\n\n const $textarea = this.$root.querySelector('.govuk-js-character-count')\n if (\n !(\n $textarea instanceof HTMLTextAreaElement ||\n $textarea instanceof HTMLInputElement\n )\n ) {\n throw new ElementError({\n component: CharacterCount,\n element: $textarea,\n expectedType: 'HTMLTextareaElement or HTMLInputElement',\n identifier: 'Form field (`.govuk-js-character-count`)'\n })\n }\n\n // Read config set using dataset ('data-' values)\n const datasetConfig = normaliseDataset(CharacterCount, this.$root.dataset)\n\n // To ensure data-attributes take complete precedence, even if they change\n // the type of count, we need to reset the `maxlength` and `maxwords` from\n // the JavaScript config.\n //\n // We can't mutate `config`, though, as it may be shared across multiple\n // components inside `initAll`.\n /** @type {CharacterCountConfig} */\n let configOverrides = {}\n if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {\n configOverrides = {\n maxlength: undefined,\n maxwords: undefined\n }\n }\n\n this.config = mergeConfigs(\n CharacterCount.defaults,\n config,\n configOverrides,\n datasetConfig\n )\n\n // Check for valid config\n const errors = validateConfig(CharacterCount.schema, this.config)\n if (errors[0]) {\n throw new ConfigError(formatErrorMessage(CharacterCount, errors[0]))\n }\n\n this.i18n = new I18n(this.config.i18n, {\n // Read the fallback if necessary rather than have it set in the defaults\n locale: closestAttributeValue(this.$root, 'lang')\n })\n\n // Determine the limit attribute (characters or words)\n this.maxLength = this.config.maxwords ?? this.config.maxlength ?? Infinity\n\n this.$textarea = $textarea\n\n const textareaDescriptionId = `${this.$textarea.id}-info`\n const $textareaDescription = document.getElementById(textareaDescriptionId)\n if (!$textareaDescription) {\n throw new ElementError({\n component: CharacterCount,\n element: $textareaDescription,\n identifier: `Count message (\\`id=\"${textareaDescriptionId}\"\\`)`\n })\n }\n\n // Inject a description for the textarea if none is present already\n // for when the component was rendered with no maxlength, maxwords\n // nor custom textareaDescriptionText\n if (`${$textareaDescription.textContent}`.match(/^\\s*$/)) {\n $textareaDescription.textContent = this.i18n.t('textareaDescription', {\n count: this.maxLength\n })\n }\n\n // Move the textarea description to be immediately after the textarea\n // Kept for backwards compatibility\n this.$textarea.insertAdjacentElement('afterend', $textareaDescription)\n\n // Create the *screen reader* specific live-updating counter\n // This doesn't need any styling classes, as it is never visible\n const $screenReaderCountMessage = document.createElement('div')\n $screenReaderCountMessage.className =\n 'govuk-character-count__sr-status govuk-visually-hidden'\n $screenReaderCountMessage.setAttribute('aria-live', 'polite')\n this.$screenReaderCountMessage = $screenReaderCountMessage\n $textareaDescription.insertAdjacentElement(\n 'afterend',\n $screenReaderCountMessage\n )\n\n // Create our live-updating counter element, copying the classes from the\n // textarea description for backwards compatibility as these may have been\n // configured\n const $visibleCountMessage = document.createElement('div')\n $visibleCountMessage.className = $textareaDescription.className\n $visibleCountMessage.classList.add('govuk-character-count__status')\n $visibleCountMessage.setAttribute('aria-hidden', 'true')\n this.$visibleCountMessage = $visibleCountMessage\n $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage)\n\n // Hide the textarea description\n $textareaDescription.classList.add('govuk-visually-hidden')\n\n // Remove hard limit if set\n this.$textarea.removeAttribute('maxlength')\n\n this.bindChangeEvents()\n\n // When the page is restored after navigating 'back' in some browsers the\n // state of form controls is not restored until *after* the DOMContentLoaded\n // event is fired, so we need to sync after the pageshow event.\n window.addEventListener('pageshow', () => this.updateCountMessage())\n\n // Although we've set up handlers to sync state on the pageshow event, init\n // could be called after those events have fired, for example if they are\n // added to the page dynamically, so update now too.\n this.updateCountMessage()\n }\n\n /**\n * Bind change events\n *\n * Set up event listeners on the $textarea so that the count messages update\n * when the user types.\n *\n * @private\n */\n bindChangeEvents() {\n this.$textarea.addEventListener('keyup', () => this.handleKeyUp())\n\n // Bind focus/blur events to start/stop polling\n this.$textarea.addEventListener('focus', () => this.handleFocus())\n this.$textarea.addEventListener('blur', () => this.handleBlur())\n }\n\n /**\n * Handle key up event\n *\n * Update the visible character counter and keep track of when the last update\n * happened for each keypress\n *\n * @private\n */\n handleKeyUp() {\n this.updateVisibleCountMessage()\n this.lastInputTimestamp = Date.now()\n }\n\n /**\n * Handle focus event\n *\n * Speech recognition software such as Dragon NaturallySpeaking will modify\n * the fields by directly changing its `value`. These changes don't trigger\n * events in JavaScript, so we need to poll to handle when and if they occur.\n *\n * Once the keyup event hasn't been detected for at least 1000 ms (1s), check\n * if the textarea value has changed and update the count message if it has.\n *\n * This is so that the update triggered by the manual comparison doesn't\n * conflict with debounced KeyboardEvent updates.\n *\n * @private\n */\n handleFocus() {\n this.valueChecker = window.setInterval(() => {\n if (\n !this.lastInputTimestamp ||\n Date.now() - 500 >= this.lastInputTimestamp\n ) {\n this.updateIfValueChanged()\n }\n }, 1000)\n }\n\n /**\n * Handle blur event\n *\n * Stop checking the textarea value once the textarea no longer has focus\n *\n * @private\n */\n handleBlur() {\n // Cancel value checking on blur\n if (this.valueChecker) {\n window.clearInterval(this.valueChecker)\n }\n }\n\n /**\n * Update count message if textarea value has changed\n *\n * @private\n */\n updateIfValueChanged() {\n if (this.$textarea.value !== this.lastInputValue) {\n this.lastInputValue = this.$textarea.value\n this.updateCountMessage()\n }\n }\n\n /**\n * Update count message\n *\n * Helper function to update both the visible and screen reader-specific\n * counters simultaneously (e.g. on init)\n *\n * @private\n */\n updateCountMessage() {\n this.updateVisibleCountMessage()\n this.updateScreenReaderCountMessage()\n }\n\n /**\n * Update visible count message\n *\n * @private\n */\n updateVisibleCountMessage() {\n const remainingNumber = this.maxLength - this.count(this.$textarea.value)\n const isError = remainingNumber < 0\n\n // If input is over the threshold, remove the disabled class which renders\n // the counter invisible.\n this.$visibleCountMessage.classList.toggle(\n 'govuk-character-count__message--disabled',\n !this.isOverThreshold()\n )\n\n // Update styles\n this.$textarea.classList.toggle('govuk-textarea--error', isError)\n this.$visibleCountMessage.classList.toggle('govuk-error-message', isError)\n this.$visibleCountMessage.classList.toggle('govuk-hint', !isError)\n\n // Update message\n this.$visibleCountMessage.textContent = this.getCountMessage()\n }\n\n /**\n * Update screen reader count message\n *\n * @private\n */\n updateScreenReaderCountMessage() {\n // If over the threshold, remove the aria-hidden attribute, allowing screen\n // readers to announce the content of the element.\n if (this.isOverThreshold()) {\n this.$screenReaderCountMessage.removeAttribute('aria-hidden')\n } else {\n this.$screenReaderCountMessage.setAttribute('aria-hidden', 'true')\n }\n\n // Update message\n this.$screenReaderCountMessage.textContent = this.getCountMessage()\n }\n\n /**\n * Count the number of characters (or words, if `config.maxwords` is set)\n * in the given text\n *\n * @private\n * @param {string} text - The text to count the characters of\n * @returns {number} the number of characters (or words) in the text\n */\n count(text) {\n if (this.config.maxwords) {\n const tokens = text.match(/\\S+/g) ?? [] // Matches consecutive non-whitespace chars\n return tokens.length\n }\n\n return text.length\n }\n\n /**\n * Get count message\n *\n * @private\n * @returns {string} Status message\n */\n getCountMessage() {\n const remainingNumber = this.maxLength - this.count(this.$textarea.value)\n const countType = this.config.maxwords ? 'words' : 'characters'\n return this.formatCountMessage(remainingNumber, countType)\n }\n\n /**\n * Formats the message shown to users according to what's counted\n * and how many remain\n *\n * @private\n * @param {number} remainingNumber - The number of words/characaters remaining\n * @param {string} countType - \"words\" or \"characters\"\n * @returns {string} Status message\n */\n formatCountMessage(remainingNumber, countType) {\n if (remainingNumber === 0) {\n return this.i18n.t(`${countType}AtLimit`)\n }\n\n const translationKeySuffix =\n remainingNumber < 0 ? 'OverLimit' : 'UnderLimit'\n\n return this.i18n.t(`${countType}${translationKeySuffix}`, {\n count: Math.abs(remainingNumber)\n })\n }\n\n /**\n * Check if count is over threshold\n *\n * Checks whether the value is over the configured threshold for the input.\n * If there is no configured threshold, it is set to 0 and this function will\n * always return true.\n *\n * @private\n * @returns {boolean} true if the current count is over the config.threshold\n * (or no threshold is set)\n */\n isOverThreshold() {\n // No threshold means we're always above threshold so save some computation\n if (!this.config.threshold) {\n return true\n }\n\n // Determine the remaining number of characters/words\n const currentLength = this.count(this.$textarea.value)\n const maxLength = this.maxLength\n\n const thresholdValue = (maxLength * this.config.threshold) / 100\n\n return thresholdValue <= currentLength\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-character-count'\n\n /**\n * Character count default config\n *\n * @see {@link CharacterCountConfig}\n * @constant\n * @type {CharacterCountConfig}\n */\n static defaults = Object.freeze({\n threshold: 0,\n i18n: {\n // Characters\n charactersUnderLimit: {\n one: 'You have %{count} character remaining',\n other: 'You have %{count} characters remaining'\n },\n charactersAtLimit: 'You have 0 characters remaining',\n charactersOverLimit: {\n one: 'You have %{count} character too many',\n other: 'You have %{count} characters too many'\n },\n // Words\n wordsUnderLimit: {\n one: 'You have %{count} word remaining',\n other: 'You have %{count} words remaining'\n },\n wordsAtLimit: 'You have 0 words remaining',\n wordsOverLimit: {\n one: 'You have %{count} word too many',\n other: 'You have %{count} words too many'\n },\n textareaDescription: {\n other: ''\n }\n }\n })\n\n /**\n * Character count config schema\n *\n * @constant\n * @satisfies {Schema}\n */\n static schema = Object.freeze({\n properties: {\n i18n: { type: 'object' },\n maxwords: { type: 'number' },\n maxlength: { type: 'number' },\n threshold: { type: 'number' }\n },\n anyOf: [\n {\n required: ['maxwords'],\n errorMessage: 'Either \"maxlength\" or \"maxwords\" must be provided'\n },\n {\n required: ['maxlength'],\n errorMessage: 'Either \"maxlength\" or \"maxwords\" must be provided'\n }\n ]\n })\n}\n\n/**\n * Character count config\n *\n * @see {@link CharacterCount.defaults}\n * @typedef {object} CharacterCountConfig\n * @property {number} [maxlength] - The maximum number of characters.\n * If maxwords is provided, the maxlength option will be ignored.\n * @property {number} [maxwords] - The maximum number of words. If maxwords is\n * provided, the maxlength option will be ignored.\n * @property {number} [threshold=0] - The percentage value of the limit at\n * which point the count message is displayed. If this attribute is set, the\n * count message will be hidden by default.\n * @property {CharacterCountTranslations} [i18n=CharacterCount.defaults.i18n] - Character count translations\n */\n\n/**\n * Character count translations\n *\n * @see {@link CharacterCount.defaults.i18n}\n * @typedef {object} CharacterCountTranslations\n *\n * Messages shown to users as they type. It provides feedback on how many words\n * or characters they have remaining or if they are over the limit. This also\n * includes a message used as an accessible description for the textarea.\n * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed\n * when the number of characters is under the configured maximum, `maxlength`.\n * This message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining characters. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {string} [charactersAtLimit] - Message displayed when the number of\n * characters reaches the configured maximum, `maxlength`. This message is\n * displayed visually and through assistive technologies.\n * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed\n * when the number of characters is over the configured maximum, `maxlength`.\n * This message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining characters. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when\n * the number of words is under the configured maximum, `maxlength`. This\n * message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining words. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {string} [wordsAtLimit] - Message displayed when the number of\n * words reaches the configured maximum, `maxlength`. This message is\n * displayed visually and through assistive technologies.\n * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when\n * the number of words is over the configured maximum, `maxlength`. This\n * message is displayed visually and through assistive technologies. The\n * component will replace the `%{count}` placeholder with the number of\n * remaining words. This is a [pluralised list of\n * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).\n * @property {TranslationPluralForms} [textareaDescription] - Message made\n * available to assistive technologies, if none is already present in the\n * HTML, to describe that the component accepts only a limited amount of\n * content. It is visible on the page when JavaScript is unavailable. The\n * component will replace the `%{count}` placeholder with the value of the\n * `maxlength` or `maxwords` parameter.\n */\n\n/**\n * @typedef {import('../../common/index.mjs').Schema} Schema\n * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms\n */\n"],"names":["CharacterCount","GOVUKFrontendComponent","constructor","$root","config","_ref","_this$config$maxwords","$textarea","$visibleCountMessage","$screenReaderCountMessage","lastInputTimestamp","lastInputValue","valueChecker","i18n","maxLength","querySelector","HTMLTextAreaElement","HTMLInputElement","ElementError","component","element","expectedType","identifier","datasetConfig","normaliseDataset","dataset","configOverrides","maxlength","undefined","maxwords","mergeConfigs","defaults","errors","validateConfig","schema","ConfigError","formatErrorMessage","I18n","locale","closestAttributeValue","Infinity","textareaDescriptionId","id","$textareaDescription","document","getElementById","textContent","match","t","count","insertAdjacentElement","createElement","className","setAttribute","classList","add","removeAttribute","bindChangeEvents","window","addEventListener","updateCountMessage","handleKeyUp","handleFocus","handleBlur","updateVisibleCountMessage","Date","now","setInterval","updateIfValueChanged","clearInterval","value","updateScreenReaderCountMessage","remainingNumber","isError","toggle","isOverThreshold","getCountMessage","text","_text$match","tokens","length","countType","formatCountMessage","translationKeySuffix","Math","abs","threshold","currentLength","thresholdValue","moduleName","Object","freeze","charactersUnderLimit","one","other","charactersAtLimit","charactersOverLimit","wordsUnderLimit","wordsAtLimit","wordsOverLimit","textareaDescription","properties","type","anyOf","required","errorMessage"],"mappings":"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"}
@@ -4,6 +4,56 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
+ function isInitialised($root, moduleName) {
8
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
9
+ }
10
+
11
+ /**
12
+ * Checks if GOV.UK Frontend is supported on this page
13
+ *
14
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
15
+ * won't be supported.
16
+ *
17
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
18
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
19
+ */
20
+ function isSupported($scope = document.body) {
21
+ if (!$scope) {
22
+ return false;
23
+ }
24
+ return $scope.classList.contains('govuk-frontend-supported');
25
+ }
26
+ function formatErrorMessage(Component, message) {
27
+ return `${Component.moduleName}: ${message}`;
28
+ }
29
+
30
+ /**
31
+ * Schema for component config
32
+ *
33
+ * @typedef {object} Schema
34
+ * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
35
+ * @property {SchemaCondition[]} [anyOf] - List of schema conditions
36
+ */
37
+
38
+ /**
39
+ * Schema property for component config
40
+ *
41
+ * @typedef {object} SchemaProperty
42
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
43
+ */
44
+
45
+ /**
46
+ * Schema condition for component config
47
+ *
48
+ * @typedef {object} SchemaCondition
49
+ * @property {string[]} required - List of required config fields
50
+ * @property {string} errorMessage - Error message when required config fields not provided
51
+ */
52
+ /**
53
+ * @typedef ComponentWithModuleName
54
+ * @property {string} moduleName - Name of the component
55
+ */
56
+
7
57
  class GOVUKFrontendError extends Error {
8
58
  constructor(...args) {
9
59
  super(...args);
@@ -27,60 +77,85 @@
27
77
  let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
28
78
  if (typeof messageOrOptions === 'object') {
29
79
  const {
30
- componentName,
80
+ component,
31
81
  identifier,
32
82
  element,
33
83
  expectedType
34
84
  } = messageOrOptions;
35
- message = `${componentName}: ${identifier}`;
85
+ message = identifier;
36
86
  message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
87
+ message = formatErrorMessage(component, message);
37
88
  }
38
89
  super(message);
39
90
  this.name = 'ElementError';
40
91
  }
41
92
  }
42
-
43
- function isSupported($scope = document.body) {
44
- if (!$scope) {
45
- return false;
93
+ class InitError extends GOVUKFrontendError {
94
+ constructor(componentOrMessage) {
95
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
96
+ super(message);
97
+ this.name = 'InitError';
46
98
  }
47
- return $scope.classList.contains('govuk-frontend-supported');
48
99
  }
49
-
50
- /**
51
- * Schema for component config
52
- *
53
- * @typedef {object} Schema
54
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
55
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
56
- */
57
-
58
100
  /**
59
- * Schema property for component config
60
- *
61
- * @typedef {object} SchemaProperty
62
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
63
- */
64
-
65
- /**
66
- * Schema condition for component config
67
- *
68
- * @typedef {object} SchemaCondition
69
- * @property {string[]} required - List of required config fields
70
- * @property {string} errorMessage - Error message when required config fields not provided
101
+ * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
71
102
  */
72
103
 
73
104
  class GOVUKFrontendComponent {
74
- constructor() {
75
- this.checkSupport();
105
+ /**
106
+ * Returns the root element of the component
107
+ *
108
+ * @protected
109
+ * @returns {RootElementType} - the root element of component
110
+ */
111
+ get $root() {
112
+ return this._$root;
76
113
  }
77
- checkSupport() {
114
+ constructor($root) {
115
+ this._$root = void 0;
116
+ const childConstructor = this.constructor;
117
+ if (typeof childConstructor.moduleName !== 'string') {
118
+ throw new InitError(`\`moduleName\` not defined in component`);
119
+ }
120
+ if (!($root instanceof childConstructor.elementType)) {
121
+ throw new ElementError({
122
+ element: $root,
123
+ component: childConstructor,
124
+ identifier: 'Root element (`$root`)',
125
+ expectedType: childConstructor.elementType.name
126
+ });
127
+ } else {
128
+ this._$root = $root;
129
+ }
130
+ childConstructor.checkSupport();
131
+ this.checkInitialised();
132
+ const moduleName = childConstructor.moduleName;
133
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
134
+ }
135
+ checkInitialised() {
136
+ const constructor = this.constructor;
137
+ const moduleName = constructor.moduleName;
138
+ if (moduleName && isInitialised(this.$root, moduleName)) {
139
+ throw new InitError(constructor);
140
+ }
141
+ }
142
+ static checkSupport() {
78
143
  if (!isSupported()) {
79
144
  throw new SupportError();
80
145
  }
81
146
  }
82
147
  }
83
148
 
149
+ /**
150
+ * @typedef ChildClass
151
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
152
+ */
153
+
154
+ /**
155
+ * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
156
+ */
157
+ GOVUKFrontendComponent.elementType = HTMLElement;
158
+
84
159
  /**
85
160
  * Checkboxes component
86
161
  *
@@ -99,27 +174,18 @@
99
174
  * (for example if the user has navigated back), and set up event handlers to
100
175
  * keep the reveal in sync with the checkbox state.
101
176
  *
102
- * @param {Element | null} $module - HTML element to use for checkboxes
177
+ * @param {Element | null} $root - HTML element to use for checkboxes
103
178
  */
104
- constructor($module) {
105
- super();
106
- this.$module = void 0;
179
+ constructor($root) {
180
+ super($root);
107
181
  this.$inputs = void 0;
108
- if (!($module instanceof HTMLElement)) {
109
- throw new ElementError({
110
- componentName: 'Checkboxes',
111
- element: $module,
112
- identifier: 'Root element (`$module`)'
113
- });
114
- }
115
- const $inputs = $module.querySelectorAll('input[type="checkbox"]');
182
+ const $inputs = this.$root.querySelectorAll('input[type="checkbox"]');
116
183
  if (!$inputs.length) {
117
184
  throw new ElementError({
118
- componentName: 'Checkboxes',
185
+ component: Checkboxes,
119
186
  identifier: 'Form inputs (`<input type="checkbox">`)'
120
187
  });
121
188
  }
122
- this.$module = $module;
123
189
  this.$inputs = $inputs;
124
190
  this.$inputs.forEach($input => {
125
191
  const targetId = $input.getAttribute('data-aria-controls');
@@ -128,7 +194,7 @@
128
194
  }
129
195
  if (!document.getElementById(targetId)) {
130
196
  throw new ElementError({
131
- componentName: 'Checkboxes',
197
+ component: Checkboxes,
132
198
  identifier: `Conditional reveal (\`id="${targetId}"\`)`
133
199
  });
134
200
  }
@@ -137,7 +203,7 @@
137
203
  });
138
204
  window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
139
205
  this.syncAllConditionalReveals();
140
- this.$module.addEventListener('click', event => this.handleClick(event));
206
+ this.$root.addEventListener('click', event => this.handleClick(event));
141
207
  }
142
208
  syncAllConditionalReveals() {
143
209
  this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
@@ -1 +1 @@
1
- {"version":3,"file":"checkboxes.bundle.js","sources":["../../../../src/govuk/errors/index.mjs","../../../../src/govuk/common/index.mjs","../../../../src/govuk/govuk-frontend-component.mjs","../../../../src/govuk/components/checkboxes/checkboxes.mjs"],"sourcesContent":["/**\n * GOV.UK Frontend error\n *\n * A base class for `Error`s thrown by GOV.UK Frontend.\n *\n * It is meant to be extended into specific types of errors\n * to be thrown by our code.\n *\n * @example\n * ```js\n * class MissingRootError extends GOVUKFrontendError {\n * // Setting an explicit name is important as extending the class will not\n * // set a new `name` on the subclass. The `name` property is important\n * // to ensure intelligible error names even if the class name gets\n * // mangled by a minifier\n * name = \"MissingRootError\"\n * }\n * ```\n * @abstract\n */\nexport class GOVUKFrontendError extends Error {\n name = 'GOVUKFrontendError'\n}\n\n/**\n * Indicates that GOV.UK Frontend is not supported\n */\nexport class SupportError extends GOVUKFrontendError {\n name = 'SupportError'\n\n /**\n * Checks if GOV.UK Frontend is supported on this page\n *\n * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support\n */\n constructor($scope = document.body) {\n const supportMessage =\n 'noModule' in HTMLScriptElement.prototype\n ? 'GOV.UK Frontend initialised without `<body class=\"govuk-frontend-supported\">` from template `<script>` snippet'\n : 'GOV.UK Frontend is not supported in this browser'\n\n super(\n $scope\n ? supportMessage\n : 'GOV.UK Frontend initialised without `<script type=\"module\">`'\n )\n }\n}\n\n/**\n * Indicates that a component has received an illegal configuration\n */\nexport class ConfigError extends GOVUKFrontendError {\n name = 'ConfigError'\n}\n\n/**\n * Indicates an issue with an element (possibly `null` or `undefined`)\n */\nexport class ElementError extends GOVUKFrontendError {\n name = 'ElementError'\n\n /**\n * @internal\n * @overload\n * @param {string} message - Element error message\n */\n\n /**\n * @internal\n * @overload\n * @param {ElementErrorOptions} options - Element error options\n */\n\n /**\n * @internal\n * @param {string | ElementErrorOptions} messageOrOptions - Element error message or options\n */\n constructor(messageOrOptions) {\n let message = typeof messageOrOptions === 'string' ? messageOrOptions : ''\n\n // Build message from options\n if (typeof messageOrOptions === 'object') {\n const { componentName, identifier, element, expectedType } =\n messageOrOptions\n\n // Add prefix and identifier\n message = `${componentName}: ${identifier}`\n\n // Append reason\n message += element\n ? ` is not of type ${expectedType ?? 'HTMLElement'}`\n : ' not found'\n }\n\n super(message)\n }\n}\n\n/**\n * Element error options\n *\n * @internal\n * @typedef {object} ElementErrorOptions\n * @property {string} componentName - The name of the component throwing the error\n * @property {string} identifier - An identifier that'll let the user understand which element has an error. This is whatever makes the most sense\n * @property {Element | null} [element] - The element in error\n * @property {string} [expectedType] - The type that was expected for the identifier\n */\n","import { normaliseString } from './normalise-string.mjs'\n\n/**\n * Common helpers which do not require polyfill.\n *\n * IMPORTANT: If a helper require a polyfill, please isolate it in its own module\n * so that the polyfill can be properly tree-shaken and does not burden\n * the components that do not need that helper\n */\n\n/**\n * Config merging function\n *\n * Takes any number of objects and combines them together, with\n * greatest priority on the LAST item passed in.\n *\n * @internal\n * @param {...{ [key: string]: unknown }} configObjects - Config objects to merge\n * @returns {{ [key: string]: unknown }} A merged config object\n */\nexport function mergeConfigs(...configObjects) {\n // Start with an empty object as our base\n /** @type {{ [key: string]: unknown }} */\n const formattedConfigObject = {}\n\n // Loop through each of the passed objects\n for (const configObject of configObjects) {\n for (const key of Object.keys(configObject)) {\n const option = formattedConfigObject[key]\n const override = configObject[key]\n\n // Push their keys one-by-one into formattedConfigObject. Any duplicate\n // keys with object values will be merged, otherwise the new value will\n // override the existing value.\n if (isObject(option) && isObject(override)) {\n // @ts-expect-error Index signature for type 'string' is missing\n formattedConfigObject[key] = mergeConfigs(option, override)\n } else {\n // Apply override\n formattedConfigObject[key] = override\n }\n }\n }\n\n return formattedConfigObject\n}\n\n/**\n * Extracts keys starting with a particular namespace from dataset ('data-*')\n * object, removing the namespace in the process, normalising all values\n *\n * @internal\n * @param {{ schema: Schema }} Component - Component class\n * @param {DOMStringMap} dataset - The object to extract key-value pairs from\n * @param {string} namespace - The namespace to filter keys with\n * @returns {ObjectNested | undefined} Nested object with dot-separated key namespace removed\n */\nexport function extractConfigByNamespace(Component, dataset, namespace) {\n const property = Component.schema.properties[namespace]\n\n // Only extract configs for object schema properties\n if (property?.type !== 'object') {\n return\n }\n\n // Add default empty config\n const newObject = {\n [namespace]: /** @type {ObjectNested} */ ({})\n }\n\n for (const [key, value] of Object.entries(dataset)) {\n /** @type {ObjectNested | ObjectNested[NestedKey]} */\n let current = newObject\n\n // Split the key into parts, using . as our namespace separator\n const keyParts = key.split('.')\n\n /**\n * Create new level per part\n *\n * e.g. 'i18n.textareaDescription.other' becomes\n * `{ i18n: { textareaDescription: { other } } }`\n */\n for (const [index, name] of keyParts.entries()) {\n if (typeof current === 'object') {\n // Drop down to nested object until the last part\n if (index < keyParts.length - 1) {\n // New nested object (optionally) replaces existing value\n if (!isObject(current[name])) {\n current[name] = {}\n }\n\n // Drop down into new or existing nested object\n current = current[name]\n } else if (key !== namespace) {\n // Normalised value (optionally) replaces existing value\n current[name] = normaliseString(value)\n }\n }\n }\n }\n\n return newObject[namespace]\n}\n\n/**\n * Get hash fragment from URL\n *\n * Extract the hash fragment (everything after the hash) from a URL,\n * but not including the hash symbol\n *\n * @private\n * @param {string} url - URL\n * @returns {string | undefined} Fragment from URL, without the hash\n */\nexport function getFragmentFromUrl(url) {\n if (!url.includes('#')) {\n return undefined\n }\n\n return url.split('#').pop()\n}\n\n/**\n * Get GOV.UK Frontend breakpoint value from CSS custom property\n *\n * @private\n * @param {string} name - Breakpoint name\n * @returns {{ property: string, value?: string }} Breakpoint object\n */\nexport function getBreakpoint(name) {\n const property = `--govuk-frontend-breakpoint-${name}`\n\n // Get value from `<html>` with breakpoints on CSS :root\n const value = window\n .getComputedStyle(document.documentElement)\n .getPropertyValue(property)\n\n return {\n property,\n value: value || undefined\n }\n}\n\n/**\n * Move focus to element\n *\n * Sets tabindex to -1 to make the element programmatically focusable,\n * but removes it on blur as the element doesn't need to be focused again.\n *\n * @private\n * @template {HTMLElement} FocusElement\n * @param {FocusElement} $element - HTML element\n * @param {object} [options] - Handler options\n * @param {function(this: FocusElement): void} [options.onBeforeFocus] - Callback before focus\n * @param {function(this: FocusElement): void} [options.onBlur] - Callback on blur\n */\nexport function setFocus($element, options = {}) {\n const isFocusable = $element.getAttribute('tabindex')\n\n if (!isFocusable) {\n $element.setAttribute('tabindex', '-1')\n }\n\n /**\n * Handle element focus\n */\n function onFocus() {\n $element.addEventListener('blur', onBlur, { once: true })\n }\n\n /**\n * Handle element blur\n */\n function onBlur() {\n options.onBlur?.call($element)\n\n if (!isFocusable) {\n $element.removeAttribute('tabindex')\n }\n }\n\n // Add listener to reset element on blur, after focus\n $element.addEventListener('focus', onFocus, { once: true })\n\n // Focus element\n options.onBeforeFocus?.call($element)\n $element.focus()\n}\n\n/**\n * Checks if GOV.UK Frontend is supported on this page\n *\n * Some browsers will load and run our JavaScript but GOV.UK Frontend\n * won't be supported.\n *\n * @internal\n * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support\n * @returns {boolean} Whether GOV.UK Frontend is supported on this page\n */\nexport function isSupported($scope = document.body) {\n if (!$scope) {\n return false\n }\n\n return $scope.classList.contains('govuk-frontend-supported')\n}\n\n/**\n * Validate component config by schema\n *\n * Follows limited examples in JSON schema for wider support in future\n *\n * {@link https://ajv.js.org/json-schema.html#compound-keywords}\n * {@link https://ajv.js.org/packages/ajv-errors.html#single-message}\n *\n * @internal\n * @param {Schema} schema - Config schema\n * @param {{ [key: string]: unknown }} config - Component config\n * @returns {string[]} List of validation errors\n */\nexport function validateConfig(schema, config) {\n const validationErrors = []\n\n // Check errors for each schema\n for (const [name, conditions] of Object.entries(schema)) {\n const errors = []\n\n // Check errors for each schema condition\n if (Array.isArray(conditions)) {\n for (const { required, errorMessage } of conditions) {\n if (!required.every((key) => !!config[key])) {\n errors.push(errorMessage) // Missing config key value\n }\n }\n\n // Check one condition passes or add errors\n if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {\n validationErrors.push(...errors)\n }\n }\n }\n\n return validationErrors\n}\n\n/**\n * Check for an array\n *\n * @internal\n * @param {unknown} option - Option to check\n * @returns {boolean} Whether the option is an array\n */\nfunction isArray(option) {\n return Array.isArray(option)\n}\n\n/**\n * Check for an object\n *\n * @internal\n * @param {unknown} option - Option to check\n * @returns {boolean} Whether the option is an object\n */\nfunction isObject(option) {\n return !!option && typeof option === 'object' && !isArray(option)\n}\n\n/**\n * Schema for component config\n *\n * @typedef {object} Schema\n * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties\n * @property {SchemaCondition[]} [anyOf] - List of schema conditions\n */\n\n/**\n * Schema property for component config\n *\n * @typedef {object} SchemaProperty\n * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type\n */\n\n/**\n * Schema condition for component config\n *\n * @typedef {object} SchemaCondition\n * @property {string[]} required - List of required config fields\n * @property {string} errorMessage - Error message when required config fields not provided\n */\n\n/**\n * @internal\n * @typedef {keyof ObjectNested} NestedKey\n * @typedef {{ [key: string]: string | boolean | number | ObjectNested | undefined }} ObjectNested\n */\n","import { isSupported } from './common/index.mjs'\nimport { SupportError } from './errors/index.mjs'\n\n/**\n * Base Component class\n *\n * Centralises the behaviours shared by our components\n *\n * @internal\n * @abstract\n */\nexport class GOVUKFrontendComponent {\n /**\n * Constructs a new component, validating that GOV.UK Frontend is supported\n *\n * @internal\n */\n constructor() {\n this.checkSupport()\n }\n\n /**\n * Validates whether GOV.UK Frontend is supported\n *\n * @private\n * @throws {SupportError} when GOV.UK Frontend is not supported\n */\n checkSupport() {\n if (!isSupported()) {\n throw new SupportError()\n }\n }\n}\n","import { ElementError } from '../../errors/index.mjs'\nimport { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs'\n\n/**\n * Checkboxes component\n *\n * @preserve\n */\nexport class Checkboxes extends GOVUKFrontendComponent {\n /** @private */\n $module\n\n /** @private */\n $inputs\n\n /**\n * Checkboxes can be associated with a 'conditionally revealed' content block\n * – for example, a checkbox for 'Phone' could reveal an additional form field\n * for the user to enter their phone number.\n *\n * These associations are made using a `data-aria-controls` attribute, which\n * is promoted to an aria-controls attribute during initialisation.\n *\n * We also need to restore the state of any conditional reveals on the page\n * (for example if the user has navigated back), and set up event handlers to\n * keep the reveal in sync with the checkbox state.\n *\n * @param {Element | null} $module - HTML element to use for checkboxes\n */\n constructor($module) {\n super()\n\n if (!($module instanceof HTMLElement)) {\n throw new ElementError({\n componentName: 'Checkboxes',\n element: $module,\n identifier: 'Root element (`$module`)'\n })\n }\n\n const $inputs = $module.querySelectorAll('input[type=\"checkbox\"]')\n if (!$inputs.length) {\n throw new ElementError({\n componentName: 'Checkboxes',\n identifier: 'Form inputs (`<input type=\"checkbox\">`)'\n })\n }\n\n this.$module = $module\n this.$inputs = $inputs\n\n this.$inputs.forEach(($input) => {\n const targetId = $input.getAttribute('data-aria-controls')\n\n // Skip radios without data-aria-controls attributes\n if (!targetId) {\n return\n }\n\n // Throw if target conditional element does not exist.\n if (!document.getElementById(targetId)) {\n throw new ElementError({\n componentName: 'Checkboxes',\n identifier: `Conditional reveal (\\`id=\"${targetId}\"\\`)`\n })\n }\n\n // Promote the data-aria-controls attribute to a aria-controls attribute\n // so that the relationship is exposed in the AOM\n $input.setAttribute('aria-controls', targetId)\n $input.removeAttribute('data-aria-controls')\n })\n\n // When the page is restored after navigating 'back' in some browsers the\n // state of form controls is not restored until *after* the DOMContentLoaded\n // event is fired, so we need to sync after the pageshow event.\n window.addEventListener('pageshow', () => this.syncAllConditionalReveals())\n\n // Although we've set up handlers to sync state on the pageshow event, init\n // could be called after those events have fired, for example if they are\n // added to the page dynamically, so sync now too.\n this.syncAllConditionalReveals()\n\n // Handle events\n this.$module.addEventListener('click', (event) => this.handleClick(event))\n }\n\n /**\n * Sync the conditional reveal states for all checkboxes in this $module.\n *\n * @private\n */\n syncAllConditionalReveals() {\n this.$inputs.forEach(($input) =>\n this.syncConditionalRevealWithInputState($input)\n )\n }\n\n /**\n * Sync conditional reveal with the input state\n *\n * Synchronise the visibility of the conditional reveal, and its accessible\n * state, with the input's checked state.\n *\n * @private\n * @param {HTMLInputElement} $input - Checkbox input\n */\n syncConditionalRevealWithInputState($input) {\n const targetId = $input.getAttribute('aria-controls')\n if (!targetId) {\n return\n }\n\n const $target = document.getElementById(targetId)\n if ($target?.classList.contains('govuk-checkboxes__conditional')) {\n const inputIsChecked = $input.checked\n\n $input.setAttribute('aria-expanded', inputIsChecked.toString())\n $target.classList.toggle(\n 'govuk-checkboxes__conditional--hidden',\n !inputIsChecked\n )\n }\n }\n\n /**\n * Uncheck other checkboxes\n *\n * Find any other checkbox inputs with the same name value, and uncheck them.\n * This is useful for when a “None of these\" checkbox is checked.\n *\n * @private\n * @param {HTMLInputElement} $input - Checkbox input\n */\n unCheckAllInputsExcept($input) {\n const allInputsWithSameName = document.querySelectorAll(\n `input[type=\"checkbox\"][name=\"${$input.name}\"]`\n )\n\n allInputsWithSameName.forEach(($inputWithSameName) => {\n const hasSameFormOwner = $input.form === $inputWithSameName.form\n if (hasSameFormOwner && $inputWithSameName !== $input) {\n $inputWithSameName.checked = false\n this.syncConditionalRevealWithInputState($inputWithSameName)\n }\n })\n }\n\n /**\n * Uncheck exclusive checkboxes\n *\n * Find any checkbox inputs with the same name value and the 'exclusive'\n * behaviour, and uncheck them. This helps prevent someone checking both a\n * regular checkbox and a \"None of these\" checkbox in the same fieldset.\n *\n * @private\n * @param {HTMLInputElement} $input - Checkbox input\n */\n unCheckExclusiveInputs($input) {\n const allInputsWithSameNameAndExclusiveBehaviour =\n document.querySelectorAll(\n `input[data-behaviour=\"exclusive\"][type=\"checkbox\"][name=\"${$input.name}\"]`\n )\n\n allInputsWithSameNameAndExclusiveBehaviour.forEach(($exclusiveInput) => {\n const hasSameFormOwner = $input.form === $exclusiveInput.form\n if (hasSameFormOwner) {\n $exclusiveInput.checked = false\n this.syncConditionalRevealWithInputState($exclusiveInput)\n }\n })\n }\n\n /**\n * Click event handler\n *\n * Handle a click within the $module – if the click occurred on a checkbox,\n * sync the state of any associated conditional reveal with the checkbox\n * state.\n *\n * @private\n * @param {MouseEvent} event - Click event\n */\n handleClick(event) {\n const $clickedInput = event.target\n\n // Ignore clicks on things that aren't checkbox inputs\n if (\n !($clickedInput instanceof HTMLInputElement) ||\n $clickedInput.type !== 'checkbox'\n ) {\n return\n }\n\n // If the checkbox conditionally-reveals some content, sync the state\n const hasAriaControls = $clickedInput.getAttribute('aria-controls')\n if (hasAriaControls) {\n this.syncConditionalRevealWithInputState($clickedInput)\n }\n\n // No further behaviour needed for unchecking\n if (!$clickedInput.checked) {\n return\n }\n\n // Handle 'exclusive' checkbox behaviour (ie \"None of these\")\n const hasBehaviourExclusive =\n $clickedInput.getAttribute('data-behaviour') === 'exclusive'\n if (hasBehaviourExclusive) {\n this.unCheckAllInputsExcept($clickedInput)\n } else {\n this.unCheckExclusiveInputs($clickedInput)\n }\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-checkboxes'\n}\n"],"names":["GOVUKFrontendError","Error","constructor","args","name","SupportError","$scope","document","body","supportMessage","HTMLScriptElement","prototype","ElementError","messageOrOptions","message","componentName","identifier","element","expectedType","isSupported","classList","contains","GOVUKFrontendComponent","checkSupport","Checkboxes","$module","$inputs","HTMLElement","querySelectorAll","length","forEach","$input","targetId","getAttribute","getElementById","setAttribute","removeAttribute","window","addEventListener","syncAllConditionalReveals","event","handleClick","syncConditionalRevealWithInputState","$target","inputIsChecked","checked","toString","toggle","unCheckAllInputsExcept","allInputsWithSameName","$inputWithSameName","hasSameFormOwner","form","unCheckExclusiveInputs","allInputsWithSameNameAndExclusiveBehaviour","$exclusiveInput","$clickedInput","target","HTMLInputElement","type","hasAriaControls","hasBehaviourExclusive","moduleName"],"mappings":"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"}
1
+ {"version":3,"file":"checkboxes.bundle.js","sources":["../../../../src/govuk/common/index.mjs","../../../../src/govuk/errors/index.mjs","../../../../src/govuk/govuk-frontend-component.mjs","../../../../src/govuk/components/checkboxes/checkboxes.mjs"],"sourcesContent":["import { normaliseString } from './normalise-string.mjs'\n\n/**\n * Common helpers which do not require polyfill.\n *\n * IMPORTANT: If a helper require a polyfill, please isolate it in its own module\n * so that the polyfill can be properly tree-shaken and does not burden\n * the components that do not need that helper\n */\n\n/**\n * Config merging function\n *\n * Takes any number of objects and combines them together, with\n * greatest priority on the LAST item passed in.\n *\n * @internal\n * @param {...{ [key: string]: unknown }} configObjects - Config objects to merge\n * @returns {{ [key: string]: unknown }} A merged config object\n */\nexport function mergeConfigs(...configObjects) {\n // Start with an empty object as our base\n /** @type {{ [key: string]: unknown }} */\n const formattedConfigObject = {}\n\n // Loop through each of the passed objects\n for (const configObject of configObjects) {\n for (const key of Object.keys(configObject)) {\n const option = formattedConfigObject[key]\n const override = configObject[key]\n\n // Push their keys one-by-one into formattedConfigObject. Any duplicate\n // keys with object values will be merged, otherwise the new value will\n // override the existing value.\n if (isObject(option) && isObject(override)) {\n // @ts-expect-error Index signature for type 'string' is missing\n formattedConfigObject[key] = mergeConfigs(option, override)\n } else {\n // Apply override\n formattedConfigObject[key] = override\n }\n }\n }\n\n return formattedConfigObject\n}\n\n/**\n * Extracts keys starting with a particular namespace from dataset ('data-*')\n * object, removing the namespace in the process, normalising all values\n *\n * @internal\n * @param {{ schema: Schema }} Component - Component class\n * @param {DOMStringMap} dataset - The object to extract key-value pairs from\n * @param {string} namespace - The namespace to filter keys with\n * @returns {ObjectNested | undefined} Nested object with dot-separated key namespace removed\n */\nexport function extractConfigByNamespace(Component, dataset, namespace) {\n const property = Component.schema.properties[namespace]\n\n // Only extract configs for object schema properties\n if (property?.type !== 'object') {\n return\n }\n\n // Add default empty config\n const newObject = {\n [namespace]: /** @type {ObjectNested} */ ({})\n }\n\n for (const [key, value] of Object.entries(dataset)) {\n /** @type {ObjectNested | ObjectNested[NestedKey]} */\n let current = newObject\n\n // Split the key into parts, using . as our namespace separator\n const keyParts = key.split('.')\n\n /**\n * Create new level per part\n *\n * e.g. 'i18n.textareaDescription.other' becomes\n * `{ i18n: { textareaDescription: { other } } }`\n */\n for (const [index, name] of keyParts.entries()) {\n if (typeof current === 'object') {\n // Drop down to nested object until the last part\n if (index < keyParts.length - 1) {\n // New nested object (optionally) replaces existing value\n if (!isObject(current[name])) {\n current[name] = {}\n }\n\n // Drop down into new or existing nested object\n current = current[name]\n } else if (key !== namespace) {\n // Normalised value (optionally) replaces existing value\n current[name] = normaliseString(value)\n }\n }\n }\n }\n\n return newObject[namespace]\n}\n\n/**\n * Get hash fragment from URL\n *\n * Extract the hash fragment (everything after the hash) from a URL,\n * but not including the hash symbol\n *\n * @private\n * @param {string} url - URL\n * @returns {string | undefined} Fragment from URL, without the hash\n */\nexport function getFragmentFromUrl(url) {\n if (!url.includes('#')) {\n return undefined\n }\n\n return url.split('#').pop()\n}\n\n/**\n * Get GOV.UK Frontend breakpoint value from CSS custom property\n *\n * @private\n * @param {string} name - Breakpoint name\n * @returns {{ property: string, value?: string }} Breakpoint object\n */\nexport function getBreakpoint(name) {\n const property = `--govuk-frontend-breakpoint-${name}`\n\n // Get value from `<html>` with breakpoints on CSS :root\n const value = window\n .getComputedStyle(document.documentElement)\n .getPropertyValue(property)\n\n return {\n property,\n value: value || undefined\n }\n}\n\n/**\n * Move focus to element\n *\n * Sets tabindex to -1 to make the element programmatically focusable,\n * but removes it on blur as the element doesn't need to be focused again.\n *\n * @private\n * @template {HTMLElement} FocusElement\n * @param {FocusElement} $element - HTML element\n * @param {object} [options] - Handler options\n * @param {function(this: FocusElement): void} [options.onBeforeFocus] - Callback before focus\n * @param {function(this: FocusElement): void} [options.onBlur] - Callback on blur\n */\nexport function setFocus($element, options = {}) {\n const isFocusable = $element.getAttribute('tabindex')\n\n if (!isFocusable) {\n $element.setAttribute('tabindex', '-1')\n }\n\n /**\n * Handle element focus\n */\n function onFocus() {\n $element.addEventListener('blur', onBlur, { once: true })\n }\n\n /**\n * Handle element blur\n */\n function onBlur() {\n options.onBlur?.call($element)\n\n if (!isFocusable) {\n $element.removeAttribute('tabindex')\n }\n }\n\n // Add listener to reset element on blur, after focus\n $element.addEventListener('focus', onFocus, { once: true })\n\n // Focus element\n options.onBeforeFocus?.call($element)\n $element.focus()\n}\n\n/**\n * Checks if component is already initialised\n *\n * @internal\n * @param {Element} $root - HTML element to be checked\n * @param {string} moduleName - name of component module\n * @returns {boolean} Whether component is already initialised\n */\nexport function isInitialised($root, moduleName) {\n return (\n $root instanceof HTMLElement &&\n $root.hasAttribute(`data-${moduleName}-init`)\n )\n}\n\n/**\n * Checks if GOV.UK Frontend is supported on this page\n *\n * Some browsers will load and run our JavaScript but GOV.UK Frontend\n * won't be supported.\n *\n * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support\n * @returns {boolean} Whether GOV.UK Frontend is supported on this page\n */\nexport function isSupported($scope = document.body) {\n if (!$scope) {\n return false\n }\n\n return $scope.classList.contains('govuk-frontend-supported')\n}\n\n/**\n * Validate component config by schema\n *\n * Follows limited examples in JSON schema for wider support in future\n *\n * {@link https://ajv.js.org/json-schema.html#compound-keywords}\n * {@link https://ajv.js.org/packages/ajv-errors.html#single-message}\n *\n * @internal\n * @param {Schema} schema - Config schema\n * @param {{ [key: string]: unknown }} config - Component config\n * @returns {string[]} List of validation errors\n */\nexport function validateConfig(schema, config) {\n const validationErrors = []\n\n // Check errors for each schema\n for (const [name, conditions] of Object.entries(schema)) {\n const errors = []\n\n // Check errors for each schema condition\n if (Array.isArray(conditions)) {\n for (const { required, errorMessage } of conditions) {\n if (!required.every((key) => !!config[key])) {\n errors.push(errorMessage) // Missing config key value\n }\n }\n\n // Check one condition passes or add errors\n if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {\n validationErrors.push(...errors)\n }\n }\n }\n\n return validationErrors\n}\n\n/**\n * Check for an array\n *\n * @internal\n * @param {unknown} option - Option to check\n * @returns {boolean} Whether the option is an array\n */\nfunction isArray(option) {\n return Array.isArray(option)\n}\n\n/**\n * Check for an object\n *\n * @internal\n * @param {unknown} option - Option to check\n * @returns {boolean} Whether the option is an object\n */\nfunction isObject(option) {\n return !!option && typeof option === 'object' && !isArray(option)\n}\n\n/**\n * Format error message\n *\n * @internal\n * @param {ComponentWithModuleName} Component - Component that threw the error\n * @param {string} message - Error message\n * @returns {string} - Formatted error message\n */\nexport function formatErrorMessage(Component, message) {\n return `${Component.moduleName}: ${message}`\n}\n\n/**\n * Schema for component config\n *\n * @typedef {object} Schema\n * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties\n * @property {SchemaCondition[]} [anyOf] - List of schema conditions\n */\n\n/**\n * Schema property for component config\n *\n * @typedef {object} SchemaProperty\n * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type\n */\n\n/**\n * Schema condition for component config\n *\n * @typedef {object} SchemaCondition\n * @property {string[]} required - List of required config fields\n * @property {string} errorMessage - Error message when required config fields not provided\n */\n\n/**\n * @internal\n * @typedef {keyof ObjectNested} NestedKey\n * @typedef {{ [key: string]: string | boolean | number | ObjectNested | undefined }} ObjectNested\n */\n\n/* eslint-disable jsdoc/valid-types --\n * `{new(...args: any[] ): object}` is not recognised as valid\n * https://github.com/gajus/eslint-plugin-jsdoc/issues/145#issuecomment-1308722878\n * https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/131\n **/\n\n/**\n * @typedef ComponentWithModuleName\n * @property {string} moduleName - Name of the component\n */\n\n/* eslint-enable jsdoc/valid-types */\n","import { formatErrorMessage } from '../common/index.mjs'\n\n/**\n * GOV.UK Frontend error\n *\n * A base class for `Error`s thrown by GOV.UK Frontend.\n *\n * It is meant to be extended into specific types of errors\n * to be thrown by our code.\n *\n * @example\n * ```js\n * class MissingRootError extends GOVUKFrontendError {\n * // Setting an explicit name is important as extending the class will not\n * // set a new `name` on the subclass. The `name` property is important\n * // to ensure intelligible error names even if the class name gets\n * // mangled by a minifier\n * name = \"MissingRootError\"\n * }\n * ```\n * @virtual\n */\nexport class GOVUKFrontendError extends Error {\n name = 'GOVUKFrontendError'\n}\n\n/**\n * Indicates that GOV.UK Frontend is not supported\n */\nexport class SupportError extends GOVUKFrontendError {\n name = 'SupportError'\n\n /**\n * Checks if GOV.UK Frontend is supported on this page\n *\n * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support\n */\n constructor($scope = document.body) {\n const supportMessage =\n 'noModule' in HTMLScriptElement.prototype\n ? 'GOV.UK Frontend initialised without `<body class=\"govuk-frontend-supported\">` from template `<script>` snippet'\n : 'GOV.UK Frontend is not supported in this browser'\n\n super(\n $scope\n ? supportMessage\n : 'GOV.UK Frontend initialised without `<script type=\"module\">`'\n )\n }\n}\n\n/**\n * Indicates that a component has received an illegal configuration\n */\nexport class ConfigError extends GOVUKFrontendError {\n name = 'ConfigError'\n}\n\n/**\n * Indicates an issue with an element (possibly `null` or `undefined`)\n */\nexport class ElementError extends GOVUKFrontendError {\n name = 'ElementError'\n\n /**\n * @internal\n * @overload\n * @param {string} message - Element error message\n */\n\n /**\n * @internal\n * @overload\n * @param {ElementErrorOptions} options - Element error options\n */\n\n /**\n * @internal\n * @param {string | ElementErrorOptions} messageOrOptions - Element error message or options\n */\n constructor(messageOrOptions) {\n let message = typeof messageOrOptions === 'string' ? messageOrOptions : ''\n\n // Build message from options\n if (typeof messageOrOptions === 'object') {\n const { component, identifier, element, expectedType } = messageOrOptions\n\n message = identifier\n\n // Append reason\n message += element\n ? ` is not of type ${expectedType ?? 'HTMLElement'}`\n : ' not found'\n\n message = formatErrorMessage(component, message)\n }\n\n super(message)\n }\n}\n\n/**\n * Indicates that a component is already initialised\n */\nexport class InitError extends GOVUKFrontendError {\n name = 'InitError'\n\n /**\n * @internal\n * @param {ComponentWithModuleName | string} componentOrMessage - name of the component module\n */\n constructor(componentOrMessage) {\n const message =\n typeof componentOrMessage === 'string'\n ? componentOrMessage\n : formatErrorMessage(\n componentOrMessage,\n `Root element (\\`$root\\`) already initialised`\n )\n\n super(message)\n }\n}\n\n/**\n * Element error options\n *\n * @internal\n * @typedef {object} ElementErrorOptions\n * @property {string} identifier - An identifier that'll let the user understand which element has an error. This is whatever makes the most sense\n * @property {Element | null} [element] - The element in error\n * @property {string} [expectedType] - The type that was expected for the identifier\n * @property {ComponentWithModuleName} component - Component throwing the error\n */\n\n/**\n * @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName\n */\n","import { isInitialised, isSupported } from './common/index.mjs'\nimport { ElementError, InitError, SupportError } from './errors/index.mjs'\n\n/**\n * Base Component class\n *\n * Centralises the behaviours shared by our components\n *\n * @virtual\n * @template {Element} [RootElementType=HTMLElement]\n */\nexport class GOVUKFrontendComponent {\n /**\n * @type {typeof Element}\n */\n static elementType = HTMLElement\n\n // allows Typescript user to work around the lack of types\n // in GOVUKFrontend package, Typescript is not aware of $root\n // in components that extend GOVUKFrontendComponent\n /**\n * Returns the root element of the component\n *\n * @protected\n * @returns {RootElementType} - the root element of component\n */\n get $root() {\n return this._$root\n }\n\n /**\n * @protected\n * @type {RootElementType}\n */\n _$root\n\n /**\n * Constructs a new component, validating that GOV.UK Frontend is supported\n *\n * @internal\n * @param {Element | null} [$root] - HTML element to use for component\n */\n constructor($root) {\n const childConstructor = /** @type {ChildClassConstructor} */ (\n this.constructor\n )\n\n // TypeScript does not enforce that inheriting classes will define a `moduleName`\n // (even if we add a `@virtual` `static moduleName` property to this class).\n // While we trust users to do this correctly, we do a little check to provide them\n // a helpful error message.\n //\n // After this, we'll be sure that `childConstructor` has a `moduleName`\n // as expected of the `ChildClassConstructor` we've cast `this.constructor` to.\n if (typeof childConstructor.moduleName !== 'string') {\n throw new InitError(`\\`moduleName\\` not defined in component`)\n }\n\n if (!($root instanceof childConstructor.elementType)) {\n throw new ElementError({\n element: $root,\n component: childConstructor,\n identifier: 'Root element (`$root`)',\n expectedType: childConstructor.elementType.name\n })\n } else {\n this._$root = /** @type {RootElementType} */ ($root)\n }\n\n childConstructor.checkSupport()\n\n this.checkInitialised()\n\n const moduleName = childConstructor.moduleName\n\n this.$root.setAttribute(`data-${moduleName}-init`, '')\n }\n\n /**\n * Validates whether component is already initialised\n *\n * @private\n * @throws {InitError} when component is already initialised\n */\n checkInitialised() {\n const constructor = /** @type {ChildClassConstructor} */ (this.constructor)\n const moduleName = constructor.moduleName\n\n if (moduleName && isInitialised(this.$root, moduleName)) {\n throw new InitError(constructor)\n }\n }\n\n /**\n * Validates whether components are supported\n *\n * @throws {SupportError} when the components are not supported\n */\n static checkSupport() {\n if (!isSupported()) {\n throw new SupportError()\n }\n }\n}\n\n/**\n * @typedef ChildClass\n * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component\n */\n\n/**\n * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor\n */\n","import { ElementError } from '../../errors/index.mjs'\nimport { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs'\n\n/**\n * Checkboxes component\n *\n * @preserve\n */\nexport class Checkboxes extends GOVUKFrontendComponent {\n /** @private */\n $inputs\n\n /**\n * Checkboxes can be associated with a 'conditionally revealed' content block\n * – for example, a checkbox for 'Phone' could reveal an additional form field\n * for the user to enter their phone number.\n *\n * These associations are made using a `data-aria-controls` attribute, which\n * is promoted to an aria-controls attribute during initialisation.\n *\n * We also need to restore the state of any conditional reveals on the page\n * (for example if the user has navigated back), and set up event handlers to\n * keep the reveal in sync with the checkbox state.\n *\n * @param {Element | null} $root - HTML element to use for checkboxes\n */\n constructor($root) {\n super($root)\n\n const $inputs = this.$root.querySelectorAll('input[type=\"checkbox\"]')\n if (!$inputs.length) {\n throw new ElementError({\n component: Checkboxes,\n identifier: 'Form inputs (`<input type=\"checkbox\">`)'\n })\n }\n\n this.$inputs = $inputs\n\n this.$inputs.forEach(($input) => {\n const targetId = $input.getAttribute('data-aria-controls')\n\n // Skip radios without data-aria-controls attributes\n if (!targetId) {\n return\n }\n\n // Throw if target conditional element does not exist.\n if (!document.getElementById(targetId)) {\n throw new ElementError({\n component: Checkboxes,\n identifier: `Conditional reveal (\\`id=\"${targetId}\"\\`)`\n })\n }\n\n // Promote the data-aria-controls attribute to a aria-controls attribute\n // so that the relationship is exposed in the AOM\n $input.setAttribute('aria-controls', targetId)\n $input.removeAttribute('data-aria-controls')\n })\n\n // When the page is restored after navigating 'back' in some browsers the\n // state of form controls is not restored until *after* the DOMContentLoaded\n // event is fired, so we need to sync after the pageshow event.\n window.addEventListener('pageshow', () => this.syncAllConditionalReveals())\n\n // Although we've set up handlers to sync state on the pageshow event, init\n // could be called after those events have fired, for example if they are\n // added to the page dynamically, so sync now too.\n this.syncAllConditionalReveals()\n\n // Handle events\n this.$root.addEventListener('click', (event) => this.handleClick(event))\n }\n\n /**\n * Sync the conditional reveal states for all checkboxes in this component.\n *\n * @private\n */\n syncAllConditionalReveals() {\n this.$inputs.forEach(($input) =>\n this.syncConditionalRevealWithInputState($input)\n )\n }\n\n /**\n * Sync conditional reveal with the input state\n *\n * Synchronise the visibility of the conditional reveal, and its accessible\n * state, with the input's checked state.\n *\n * @private\n * @param {HTMLInputElement} $input - Checkbox input\n */\n syncConditionalRevealWithInputState($input) {\n const targetId = $input.getAttribute('aria-controls')\n if (!targetId) {\n return\n }\n\n const $target = document.getElementById(targetId)\n if ($target?.classList.contains('govuk-checkboxes__conditional')) {\n const inputIsChecked = $input.checked\n\n $input.setAttribute('aria-expanded', inputIsChecked.toString())\n $target.classList.toggle(\n 'govuk-checkboxes__conditional--hidden',\n !inputIsChecked\n )\n }\n }\n\n /**\n * Uncheck other checkboxes\n *\n * Find any other checkbox inputs with the same name value, and uncheck them.\n * This is useful for when a “None of these\" checkbox is checked.\n *\n * @private\n * @param {HTMLInputElement} $input - Checkbox input\n */\n unCheckAllInputsExcept($input) {\n const allInputsWithSameName = document.querySelectorAll(\n `input[type=\"checkbox\"][name=\"${$input.name}\"]`\n )\n\n allInputsWithSameName.forEach(($inputWithSameName) => {\n const hasSameFormOwner = $input.form === $inputWithSameName.form\n if (hasSameFormOwner && $inputWithSameName !== $input) {\n $inputWithSameName.checked = false\n this.syncConditionalRevealWithInputState($inputWithSameName)\n }\n })\n }\n\n /**\n * Uncheck exclusive checkboxes\n *\n * Find any checkbox inputs with the same name value and the 'exclusive'\n * behaviour, and uncheck them. This helps prevent someone checking both a\n * regular checkbox and a \"None of these\" checkbox in the same fieldset.\n *\n * @private\n * @param {HTMLInputElement} $input - Checkbox input\n */\n unCheckExclusiveInputs($input) {\n const allInputsWithSameNameAndExclusiveBehaviour =\n document.querySelectorAll(\n `input[data-behaviour=\"exclusive\"][type=\"checkbox\"][name=\"${$input.name}\"]`\n )\n\n allInputsWithSameNameAndExclusiveBehaviour.forEach(($exclusiveInput) => {\n const hasSameFormOwner = $input.form === $exclusiveInput.form\n if (hasSameFormOwner) {\n $exclusiveInput.checked = false\n this.syncConditionalRevealWithInputState($exclusiveInput)\n }\n })\n }\n\n /**\n * Click event handler\n *\n * Handle a click within the component root – if the click occurred on a checkbox,\n * sync the state of any associated conditional reveal with the checkbox\n * state.\n *\n * @private\n * @param {MouseEvent} event - Click event\n */\n handleClick(event) {\n const $clickedInput = event.target\n\n // Ignore clicks on things that aren't checkbox inputs\n if (\n !($clickedInput instanceof HTMLInputElement) ||\n $clickedInput.type !== 'checkbox'\n ) {\n return\n }\n\n // If the checkbox conditionally-reveals some content, sync the state\n const hasAriaControls = $clickedInput.getAttribute('aria-controls')\n if (hasAriaControls) {\n this.syncConditionalRevealWithInputState($clickedInput)\n }\n\n // No further behaviour needed for unchecking\n if (!$clickedInput.checked) {\n return\n }\n\n // Handle 'exclusive' checkbox behaviour (ie \"None of these\")\n const hasBehaviourExclusive =\n $clickedInput.getAttribute('data-behaviour') === 'exclusive'\n if (hasBehaviourExclusive) {\n this.unCheckAllInputsExcept($clickedInput)\n } else {\n this.unCheckExclusiveInputs($clickedInput)\n }\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-checkboxes'\n}\n"],"names":["isInitialised","$root","moduleName","HTMLElement","hasAttribute","isSupported","$scope","document","body","classList","contains","formatErrorMessage","Component","message","GOVUKFrontendError","Error","constructor","args","name","SupportError","supportMessage","HTMLScriptElement","prototype","ElementError","messageOrOptions","component","identifier","element","expectedType","InitError","componentOrMessage","GOVUKFrontendComponent","_$root","childConstructor","elementType","checkSupport","checkInitialised","setAttribute","Checkboxes","$inputs","querySelectorAll","length","forEach","$input","targetId","getAttribute","getElementById","removeAttribute","window","addEventListener","syncAllConditionalReveals","event","handleClick","syncConditionalRevealWithInputState","$target","inputIsChecked","checked","toString","toggle","unCheckAllInputsExcept","allInputsWithSameName","$inputWithSameName","hasSameFormOwner","form","unCheckExclusiveInputs","allInputsWithSameNameAndExclusiveBehaviour","$exclusiveInput","$clickedInput","target","HTMLInputElement","type","hasAriaControls","hasBehaviourExclusive"],"mappings":"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"}