govuk_publishing_components 44.1.0 → 44.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/govuk-frontend/dist/govuk/all.bundle.js +336 -225
- data/node_modules/govuk-frontend/dist/govuk/all.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs +334 -226
- data/node_modules/govuk-frontend/dist/govuk/all.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/all.mjs +3 -0
- data/node_modules/govuk-frontend/dist/govuk/all.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.svg +1 -0
- data/node_modules/govuk-frontend/dist/govuk/common/govuk-frontend-version.mjs +1 -1
- data/node_modules/govuk-frontend/dist/govuk/common/index.mjs +21 -1
- data/node_modules/govuk-frontend/dist/govuk/common/index.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/_index.scss +1 -0
- data/node_modules/govuk-frontend/dist/govuk/components/_index.scss.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js +92 -26
- data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs +92 -26
- data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs +12 -21
- data/node_modules/govuk-frontend/dist/govuk/components/accordion/accordion.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js +86 -20
- data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs +86 -20
- data/node_modules/govuk-frontend/dist/govuk/components/button/button.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs +6 -16
- data/node_modules/govuk-frontend/dist/govuk/components/button/button.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js +89 -23
- data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs +89 -23
- data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs +10 -19
- data/node_modules/govuk-frontend/dist/govuk/components/character-count/character-count.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js +113 -47
- data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs +113 -47
- data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs +7 -16
- data/node_modules/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss +7 -2
- data/node_modules/govuk-frontend/dist/govuk/components/details/_index.scss.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js +86 -20
- data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs +86 -20
- data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs +6 -16
- data/node_modules/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js +87 -21
- data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs +87 -21
- data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs +7 -16
- data/node_modules/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss +8 -10
- data/node_modules/govuk-frontend/dist/govuk/components/footer/_index.scss.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss +8 -0
- data/node_modules/govuk-frontend/dist/govuk/components/header/_index.scss.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/header/fixtures.json +12 -0
- data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js +87 -21
- data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs +87 -21
- data/node_modules/govuk-frontend/dist/govuk/components/header/header.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs +7 -16
- data/node_modules/govuk-frontend/dist/govuk/components/header/header.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/header/template-with-full-width-border.html +24 -0
- data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js +86 -20
- data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs +86 -20
- data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs +6 -16
- data/node_modules/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js +89 -23
- data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs +89 -23
- data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs +9 -18
- data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js +113 -47
- data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs +113 -47
- data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs +7 -16
- data/node_modules/govuk-frontend/dist/govuk/components/radios/radios.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/README.md +15 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss +168 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_index.scss.map +1 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss +4 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/_service-navigation.scss.map +1 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/fixtures.json +464 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro-options.json +138 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/macro.njk +3 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js +249 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js.map +1 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs +241 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs.map +1 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs +85 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.mjs.map +1 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-default.html +57 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-html-navigation-items.html +49 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-large-navigation.html +153 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-long-service-name.html +20 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-a-current-item.html +58 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-navigation-with-an-active-item.html +58 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-non-link-navigation-items.html +49 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-link.html +20 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name-and-navigation.html +63 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template-with-service-name.html +18 -0
- data/node_modules/govuk-frontend/dist/govuk/components/service-navigation/template.njk +102 -0
- data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js +93 -26
- data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs +93 -26
- data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs +13 -21
- data/node_modules/govuk-frontend/dist/govuk/components/skip-link/skip-link.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js +93 -27
- data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs +93 -27
- data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs +13 -22
- data/node_modules/govuk-frontend/dist/govuk/components/tabs/tabs.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss +4 -3
- data/node_modules/govuk-frontend/dist/govuk/components/warning-text/_index.scss.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
- data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs +16 -3
- data/node_modules/govuk-frontend/dist/govuk/errors/index.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs +49 -5
- data/node_modules/govuk-frontend/dist/govuk/govuk-frontend-component.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css +2 -2
- data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js +1 -1
- data/node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.js.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/init.mjs +72 -10
- data/node_modules/govuk-frontend/dist/govuk/init.mjs.map +1 -1
- data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +3 -0
- data/node_modules/govuk-frontend/dist/govuk/settings/_colours-organisations.scss.map +1 -1
- data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +5 -1
- data/node_modules/govuk-frontend/package.json +8 -8
- metadata +29 -4
- data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest-2x.png +0 -0
- data/node_modules/govuk-frontend/dist/govuk/assets/images/govuk-crest.png +0 -0
data/node_modules/govuk-frontend/dist/govuk/components/password-input/password-input.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"password-input.mjs","sources":["../../../../src/govuk/components/password-input/password-input.mjs"],"sourcesContent":["import { closestAttributeValue } from '../../common/closest-attribute-value.mjs'\nimport { mergeConfigs } from '../../common/index.mjs'\nimport { normaliseDataset } from '../../common/normalise-dataset.mjs'\nimport { ElementError } from '../../errors/index.mjs'\nimport { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs'\nimport { I18n } from '../../i18n.mjs'\n\n/**\n * Password input component\n *\n * @preserve\n */\nexport class PasswordInput extends GOVUKFrontendComponent {\n /** @private */\n $module\n\n /**\n * @private\n * @type {PasswordInputConfig}\n */\n config\n\n /** @private */\n i18n\n\n /**\n * @private\n * @type {HTMLInputElement}\n */\n $input\n\n /**\n * @private\n * @type {HTMLButtonElement}\n */\n $showHideButton\n\n /** @private */\n $screenReaderStatusMessage\n\n /**\n * @param {Element | null} $module - HTML element to use for password input\n * @param {PasswordInputConfig} [config] - Password input config\n */\n constructor($module, config = {}) {\n super()\n\n if (!($module instanceof HTMLElement)) {\n throw new ElementError({\n componentName: 'Password input',\n element: $module,\n identifier: 'Root element (`$module`)'\n })\n }\n\n const $input = $module.querySelector('.govuk-js-password-input-input')\n if (!($input instanceof HTMLInputElement)) {\n throw new ElementError({\n componentName: 'Password input',\n element: $input,\n expectedType: 'HTMLInputElement',\n identifier: 'Form field (`.govuk-js-password-input-input`)'\n })\n }\n\n if ($input.type !== 'password') {\n throw new ElementError(\n 'Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.'\n )\n }\n\n const $showHideButton = $module.querySelector(\n '.govuk-js-password-input-toggle'\n )\n if (!($showHideButton instanceof HTMLButtonElement)) {\n throw new ElementError({\n componentName: 'Password input',\n element: $showHideButton,\n expectedType: 'HTMLButtonElement',\n identifier: 'Button (`.govuk-js-password-input-toggle`)'\n })\n }\n\n if ($showHideButton.type !== 'button') {\n throw new ElementError(\n 'Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.'\n )\n }\n\n this.$module = $module\n this.$input = $input\n this.$showHideButton = $showHideButton\n\n this.config = mergeConfigs(\n PasswordInput.defaults,\n config,\n normaliseDataset(PasswordInput, $module.dataset)\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 // Show the toggle button element\n this.$showHideButton.removeAttribute('hidden')\n\n // Create and append the status text for screen readers.\n // This is injected between the input and button so that users get a sensible reading order if\n // moving through the page content linearly:\n // [password input] -> [your password is visible/hidden] -> [show/hide password]\n const $screenReaderStatusMessage = document.createElement('div')\n $screenReaderStatusMessage.className =\n 'govuk-password-input__sr-status govuk-visually-hidden'\n $screenReaderStatusMessage.setAttribute('aria-live', 'polite')\n this.$screenReaderStatusMessage = $screenReaderStatusMessage\n this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage)\n\n // Bind toggle button\n this.$showHideButton.addEventListener('click', this.toggle.bind(this))\n\n // Bind event to revert the password visibility to hidden\n if (this.$input.form) {\n this.$input.form.addEventListener('submit', () => this.hide())\n }\n\n // If the page is restored from bfcache and the password is visible, hide it again\n window.addEventListener('pageshow', (event) => {\n if (event.persisted && this.$input.type !== 'password') {\n this.hide()\n }\n })\n\n // Default the component to having the password hidden.\n this.hide()\n }\n\n /**\n * Toggle the visibility of the password input\n *\n * @private\n * @param {MouseEvent} event - Click event\n */\n toggle(event) {\n event.preventDefault()\n\n // If on this click, the field is type=\"password\", show the value\n if (this.$input.type === 'password') {\n this.show()\n return\n }\n\n // Otherwise, hide it\n // Being defensive - hiding should always be the default\n this.hide()\n }\n\n /**\n * Show the password input value in plain text.\n *\n * @private\n */\n show() {\n this.setType('text')\n }\n\n /**\n * Hide the password input value.\n *\n * @private\n */\n hide() {\n this.setType('password')\n }\n\n /**\n * Set the password input type\n *\n * @param {'text' | 'password'} type - Input type\n * @private\n */\n setType(type) {\n if (type === this.$input.type) {\n return\n }\n\n // Update input type\n this.$input.setAttribute('type', type)\n\n const isHidden = type === 'password'\n const prefixButton = isHidden ? 'show' : 'hide'\n const prefixStatus = isHidden ? 'passwordHidden' : 'passwordShown'\n\n // Update button text\n this.$showHideButton.innerText = this.i18n.t(`${prefixButton}Password`)\n\n // Update button aria-label\n this.$showHideButton.setAttribute(\n 'aria-label',\n this.i18n.t(`${prefixButton}PasswordAriaLabel`)\n )\n\n // Update status change text\n this.$screenReaderStatusMessage.innerText = this.i18n.t(\n `${prefixStatus}Announcement`\n )\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-password-input'\n\n /**\n * Password input default config\n *\n * @see {@link PasswordInputConfig}\n * @constant\n * @default\n * @type {PasswordInputConfig}\n */\n static defaults = Object.freeze({\n i18n: {\n showPassword: 'Show',\n hidePassword: 'Hide',\n showPasswordAriaLabel: 'Show password',\n hidePasswordAriaLabel: 'Hide password',\n passwordShownAnnouncement: 'Your password is visible',\n passwordHiddenAnnouncement: 'Your password is hidden'\n }\n })\n\n /**\n * Password input config schema\n *\n * @constant\n * @satisfies {Schema}\n */\n static schema = Object.freeze({\n properties: {\n i18n: { type: 'object' }\n }\n })\n}\n\n/**\n * Password input config\n *\n * @typedef {object} PasswordInputConfig\n * @property {PasswordInputTranslations} [i18n=PasswordInput.defaults.i18n] - Password input translations\n */\n\n/**\n * Password input translations\n *\n * @see {@link PasswordInput.defaults.i18n}\n * @typedef {object} PasswordInputTranslations\n *\n * Messages displayed to the user indicating the state of the show/hide toggle.\n * @property {string} [showPassword] - Visible text of the button when the\n * password is currently hidden. Plain text only.\n * @property {string} [hidePassword] - Visible text of the button when the\n * password is currently visible. Plain text only.\n * @property {string} [showPasswordAriaLabel] - aria-label of the button when\n * the password is currently hidden. Plain text only.\n * @property {string} [hidePasswordAriaLabel] - aria-label of the button when\n * the password is currently visible. Plain text only.\n * @property {string} [passwordShownAnnouncement] - Screen reader\n * announcement to make when the password has just become visible.\n * Plain text only.\n * @property {string} [passwordHiddenAnnouncement] - Screen reader\n * announcement to make when the password has just been hidden.\n * Plain text only.\n */\n\n/**\n * @typedef {import('../../common/index.mjs').Schema} Schema\n * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms\n */\n"],"names":["PasswordInput","GOVUKFrontendComponent","constructor","$module","config","i18n","$input","$showHideButton","$screenReaderStatusMessage","HTMLElement","ElementError","componentName","element","identifier","querySelector","HTMLInputElement","expectedType","type","HTMLButtonElement","mergeConfigs","defaults","normaliseDataset","dataset","I18n","locale","closestAttributeValue","removeAttribute","document","createElement","className","setAttribute","insertAdjacentElement","addEventListener","toggle","bind","form","hide","window","event","persisted","preventDefault","show","setType","isHidden","prefixButton","prefixStatus","innerText","t","moduleName","Object","freeze","showPassword","hidePassword","showPasswordAriaLabel","hidePasswordAriaLabel","passwordShownAnnouncement","passwordHiddenAnnouncement","schema","properties"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"password-input.mjs","sources":["../../../../src/govuk/components/password-input/password-input.mjs"],"sourcesContent":["import { closestAttributeValue } from '../../common/closest-attribute-value.mjs'\nimport { mergeConfigs } from '../../common/index.mjs'\nimport { normaliseDataset } from '../../common/normalise-dataset.mjs'\nimport { ElementError } from '../../errors/index.mjs'\nimport { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs'\nimport { I18n } from '../../i18n.mjs'\n\n/**\n * Password input component\n *\n * @preserve\n */\nexport class PasswordInput extends GOVUKFrontendComponent {\n /**\n * @private\n * @type {PasswordInputConfig}\n */\n config\n\n /** @private */\n i18n\n\n /**\n * @private\n * @type {HTMLInputElement}\n */\n $input\n\n /**\n * @private\n * @type {HTMLButtonElement}\n */\n $showHideButton\n\n /** @private */\n $screenReaderStatusMessage\n\n /**\n * @param {Element | null} $root - HTML element to use for password input\n * @param {PasswordInputConfig} [config] - Password input config\n */\n constructor($root, config = {}) {\n super($root)\n\n const $input = this.$root.querySelector('.govuk-js-password-input-input')\n if (!($input instanceof HTMLInputElement)) {\n throw new ElementError({\n component: PasswordInput,\n element: $input,\n expectedType: 'HTMLInputElement',\n identifier: 'Form field (`.govuk-js-password-input-input`)'\n })\n }\n\n if ($input.type !== 'password') {\n throw new ElementError(\n 'Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.'\n )\n }\n\n const $showHideButton = this.$root.querySelector(\n '.govuk-js-password-input-toggle'\n )\n if (!($showHideButton instanceof HTMLButtonElement)) {\n throw new ElementError({\n component: PasswordInput,\n element: $showHideButton,\n expectedType: 'HTMLButtonElement',\n identifier: 'Button (`.govuk-js-password-input-toggle`)'\n })\n }\n\n if ($showHideButton.type !== 'button') {\n throw new ElementError(\n 'Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.'\n )\n }\n\n this.$input = $input\n this.$showHideButton = $showHideButton\n\n this.config = mergeConfigs(\n PasswordInput.defaults,\n config,\n normaliseDataset(PasswordInput, this.$root.dataset)\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 // Show the toggle button element\n this.$showHideButton.removeAttribute('hidden')\n\n // Create and append the status text for screen readers.\n // This is injected between the input and button so that users get a sensible reading order if\n // moving through the page content linearly:\n // [password input] -> [your password is visible/hidden] -> [show/hide password]\n const $screenReaderStatusMessage = document.createElement('div')\n $screenReaderStatusMessage.className =\n 'govuk-password-input__sr-status govuk-visually-hidden'\n $screenReaderStatusMessage.setAttribute('aria-live', 'polite')\n this.$screenReaderStatusMessage = $screenReaderStatusMessage\n this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage)\n\n // Bind toggle button\n this.$showHideButton.addEventListener('click', this.toggle.bind(this))\n\n // Bind event to revert the password visibility to hidden\n if (this.$input.form) {\n this.$input.form.addEventListener('submit', () => this.hide())\n }\n\n // If the page is restored from bfcache and the password is visible, hide it again\n window.addEventListener('pageshow', (event) => {\n if (event.persisted && this.$input.type !== 'password') {\n this.hide()\n }\n })\n\n // Default the component to having the password hidden.\n this.hide()\n }\n\n /**\n * Toggle the visibility of the password input\n *\n * @private\n * @param {MouseEvent} event - Click event\n */\n toggle(event) {\n event.preventDefault()\n\n // If on this click, the field is type=\"password\", show the value\n if (this.$input.type === 'password') {\n this.show()\n return\n }\n\n // Otherwise, hide it\n // Being defensive - hiding should always be the default\n this.hide()\n }\n\n /**\n * Show the password input value in plain text.\n *\n * @private\n */\n show() {\n this.setType('text')\n }\n\n /**\n * Hide the password input value.\n *\n * @private\n */\n hide() {\n this.setType('password')\n }\n\n /**\n * Set the password input type\n *\n * @param {'text' | 'password'} type - Input type\n * @private\n */\n setType(type) {\n if (type === this.$input.type) {\n return\n }\n\n // Update input type\n this.$input.setAttribute('type', type)\n\n const isHidden = type === 'password'\n const prefixButton = isHidden ? 'show' : 'hide'\n const prefixStatus = isHidden ? 'passwordHidden' : 'passwordShown'\n\n // Update button text\n this.$showHideButton.innerText = this.i18n.t(`${prefixButton}Password`)\n\n // Update button aria-label\n this.$showHideButton.setAttribute(\n 'aria-label',\n this.i18n.t(`${prefixButton}PasswordAriaLabel`)\n )\n\n // Update status change text\n this.$screenReaderStatusMessage.innerText = this.i18n.t(\n `${prefixStatus}Announcement`\n )\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-password-input'\n\n /**\n * Password input default config\n *\n * @see {@link PasswordInputConfig}\n * @constant\n * @default\n * @type {PasswordInputConfig}\n */\n static defaults = Object.freeze({\n i18n: {\n showPassword: 'Show',\n hidePassword: 'Hide',\n showPasswordAriaLabel: 'Show password',\n hidePasswordAriaLabel: 'Hide password',\n passwordShownAnnouncement: 'Your password is visible',\n passwordHiddenAnnouncement: 'Your password is hidden'\n }\n })\n\n /**\n * Password input config schema\n *\n * @constant\n * @satisfies {Schema}\n */\n static schema = Object.freeze({\n properties: {\n i18n: { type: 'object' }\n }\n })\n}\n\n/**\n * Password input config\n *\n * @typedef {object} PasswordInputConfig\n * @property {PasswordInputTranslations} [i18n=PasswordInput.defaults.i18n] - Password input translations\n */\n\n/**\n * Password input translations\n *\n * @see {@link PasswordInput.defaults.i18n}\n * @typedef {object} PasswordInputTranslations\n *\n * Messages displayed to the user indicating the state of the show/hide toggle.\n * @property {string} [showPassword] - Visible text of the button when the\n * password is currently hidden. Plain text only.\n * @property {string} [hidePassword] - Visible text of the button when the\n * password is currently visible. Plain text only.\n * @property {string} [showPasswordAriaLabel] - aria-label of the button when\n * the password is currently hidden. Plain text only.\n * @property {string} [hidePasswordAriaLabel] - aria-label of the button when\n * the password is currently visible. Plain text only.\n * @property {string} [passwordShownAnnouncement] - Screen reader\n * announcement to make when the password has just become visible.\n * Plain text only.\n * @property {string} [passwordHiddenAnnouncement] - Screen reader\n * announcement to make when the password has just been hidden.\n * Plain text only.\n */\n\n/**\n * @typedef {import('../../common/index.mjs').Schema} Schema\n * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms\n */\n"],"names":["PasswordInput","GOVUKFrontendComponent","constructor","$root","config","i18n","$input","$showHideButton","$screenReaderStatusMessage","querySelector","HTMLInputElement","ElementError","component","element","expectedType","identifier","type","HTMLButtonElement","mergeConfigs","defaults","normaliseDataset","dataset","I18n","locale","closestAttributeValue","removeAttribute","document","createElement","className","setAttribute","insertAdjacentElement","addEventListener","toggle","bind","form","hide","window","event","persisted","preventDefault","show","setType","isHidden","prefixButton","prefixStatus","innerText","t","moduleName","Object","freeze","showPassword","hidePassword","showPasswordAriaLabel","hidePasswordAriaLabel","passwordShownAnnouncement","passwordHiddenAnnouncement","schema","properties"],"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
|
-
|
80
|
+
component,
|
31
81
|
identifier,
|
32
82
|
element,
|
33
83
|
expectedType
|
34
84
|
} = messageOrOptions;
|
35
|
-
message =
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
*
|
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
|
-
|
75
|
-
|
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
|
-
|
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
|
* Radios 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 radio state.
|
101
176
|
*
|
102
|
-
* @param {Element | null} $
|
177
|
+
* @param {Element | null} $root - HTML element to use for radios
|
103
178
|
*/
|
104
|
-
constructor($
|
105
|
-
super();
|
106
|
-
this.$module = void 0;
|
179
|
+
constructor($root) {
|
180
|
+
super($root);
|
107
181
|
this.$inputs = void 0;
|
108
|
-
|
109
|
-
throw new ElementError({
|
110
|
-
componentName: 'Radios',
|
111
|
-
element: $module,
|
112
|
-
identifier: 'Root element (`$module`)'
|
113
|
-
});
|
114
|
-
}
|
115
|
-
const $inputs = $module.querySelectorAll('input[type="radio"]');
|
182
|
+
const $inputs = this.$root.querySelectorAll('input[type="radio"]');
|
116
183
|
if (!$inputs.length) {
|
117
184
|
throw new ElementError({
|
118
|
-
|
185
|
+
component: Radios,
|
119
186
|
identifier: 'Form inputs (`<input type="radio">`)'
|
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
|
-
|
197
|
+
component: Radios,
|
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.$
|
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":"radios.bundle.js","sources":["../../../../src/govuk/errors/index.mjs","../../../../src/govuk/common/index.mjs","../../../../src/govuk/govuk-frontend-component.mjs","../../../../src/govuk/components/radios/radios.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 * Radios component\n *\n * @preserve\n */\nexport class Radios extends GOVUKFrontendComponent {\n /** @private */\n $module\n\n /** @private */\n $inputs\n\n /**\n * Radios can be associated with a 'conditionally revealed' content block –\n * for example, a radio for 'Phone' could reveal an additional form field for\n * 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 radio state.\n *\n * @param {Element | null} $module - HTML element to use for radios\n */\n constructor($module) {\n super()\n\n if (!($module instanceof HTMLElement)) {\n throw new ElementError({\n componentName: 'Radios',\n element: $module,\n identifier: 'Root element (`$module`)'\n })\n }\n\n const $inputs = $module.querySelectorAll('input[type=\"radio\"]')\n if (!$inputs.length) {\n throw new ElementError({\n componentName: 'Radios',\n identifier: 'Form inputs (`<input type=\"radio\">`)'\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: 'Radios',\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 radio buttons 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 - Radio 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-radios__conditional')) {\n const inputIsChecked = $input.checked\n\n $input.setAttribute('aria-expanded', inputIsChecked.toString())\n $target.classList.toggle(\n 'govuk-radios__conditional--hidden',\n !inputIsChecked\n )\n }\n }\n\n /**\n * Click event handler\n *\n * Handle a click within the $module – if the click occurred on a radio, sync\n * the state of the conditional reveal for all radio buttons in the same form\n * with the same name (because checking one radio could have un-checked a\n * radio in another $module)\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 radio buttons\n if (\n !($clickedInput instanceof HTMLInputElement) ||\n $clickedInput.type !== 'radio'\n ) {\n return\n }\n\n // We only need to consider radios with conditional reveals, which will have\n // aria-controls attributes.\n const $allInputs = document.querySelectorAll(\n 'input[type=\"radio\"][aria-controls]'\n )\n\n const $clickedInputForm = $clickedInput.form\n const $clickedInputName = $clickedInput.name\n\n $allInputs.forEach(($input) => {\n const hasSameFormOwner = $input.form === $clickedInputForm\n const hasSameName = $input.name === $clickedInputName\n\n if (hasSameName && hasSameFormOwner) {\n this.syncConditionalRevealWithInputState($input)\n }\n })\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-radios'\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","Radios","$module","$inputs","HTMLElement","querySelectorAll","length","forEach","$input","targetId","getAttribute","getElementById","setAttribute","removeAttribute","window","addEventListener","syncAllConditionalReveals","event","handleClick","syncConditionalRevealWithInputState","$target","inputIsChecked","checked","toString","toggle","$clickedInput","target","HTMLInputElement","type","$allInputs","$clickedInputForm","form","$clickedInputName","hasSameFormOwner","hasSameName","moduleName"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"radios.bundle.js","sources":["../../../../src/govuk/common/index.mjs","../../../../src/govuk/errors/index.mjs","../../../../src/govuk/govuk-frontend-component.mjs","../../../../src/govuk/components/radios/radios.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 * Radios component\n *\n * @preserve\n */\nexport class Radios extends GOVUKFrontendComponent {\n /** @private */\n $inputs\n\n /**\n * Radios can be associated with a 'conditionally revealed' content block –\n * for example, a radio for 'Phone' could reveal an additional form field for\n * 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 radio state.\n *\n * @param {Element | null} $root - HTML element to use for radios\n */\n constructor($root) {\n super($root)\n\n const $inputs = this.$root.querySelectorAll('input[type=\"radio\"]')\n if (!$inputs.length) {\n throw new ElementError({\n component: Radios,\n identifier: 'Form inputs (`<input type=\"radio\">`)'\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: Radios,\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 radio buttons 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 - Radio 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-radios__conditional')) {\n const inputIsChecked = $input.checked\n\n $input.setAttribute('aria-expanded', inputIsChecked.toString())\n $target.classList.toggle(\n 'govuk-radios__conditional--hidden',\n !inputIsChecked\n )\n }\n }\n\n /**\n * Click event handler\n *\n * Handle a click within the component root – if the click occurred on a radio, sync\n * the state of the conditional reveal for all radio buttons in the same form\n * with the same name (because checking one radio could have un-checked a\n * radio under the root of another Radio component)\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 radio buttons\n if (\n !($clickedInput instanceof HTMLInputElement) ||\n $clickedInput.type !== 'radio'\n ) {\n return\n }\n\n // We only need to consider radios with conditional reveals, which will have\n // aria-controls attributes.\n const $allInputs = document.querySelectorAll(\n 'input[type=\"radio\"][aria-controls]'\n )\n\n const $clickedInputForm = $clickedInput.form\n const $clickedInputName = $clickedInput.name\n\n $allInputs.forEach(($input) => {\n const hasSameFormOwner = $input.form === $clickedInputForm\n const hasSameName = $input.name === $clickedInputName\n\n if (hasSameName && hasSameFormOwner) {\n this.syncConditionalRevealWithInputState($input)\n }\n })\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'govuk-radios'\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","Radios","$inputs","querySelectorAll","length","forEach","$input","targetId","getAttribute","getElementById","removeAttribute","window","addEventListener","syncAllConditionalReveals","event","handleClick","syncConditionalRevealWithInputState","$target","inputIsChecked","checked","toString","toggle","$clickedInput","target","HTMLInputElement","type","$allInputs","$clickedInputForm","form","$clickedInputName","hasSameFormOwner","hasSameName"],"mappings":"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"}
|
@@ -1,3 +1,53 @@
|
|
1
|
+
function isInitialised($root, moduleName) {
|
2
|
+
return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
|
3
|
+
}
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Checks if GOV.UK Frontend is supported on this page
|
7
|
+
*
|
8
|
+
* Some browsers will load and run our JavaScript but GOV.UK Frontend
|
9
|
+
* won't be supported.
|
10
|
+
*
|
11
|
+
* @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
|
12
|
+
* @returns {boolean} Whether GOV.UK Frontend is supported on this page
|
13
|
+
*/
|
14
|
+
function isSupported($scope = document.body) {
|
15
|
+
if (!$scope) {
|
16
|
+
return false;
|
17
|
+
}
|
18
|
+
return $scope.classList.contains('govuk-frontend-supported');
|
19
|
+
}
|
20
|
+
function formatErrorMessage(Component, message) {
|
21
|
+
return `${Component.moduleName}: ${message}`;
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Schema for component config
|
26
|
+
*
|
27
|
+
* @typedef {object} Schema
|
28
|
+
* @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
|
29
|
+
* @property {SchemaCondition[]} [anyOf] - List of schema conditions
|
30
|
+
*/
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Schema property for component config
|
34
|
+
*
|
35
|
+
* @typedef {object} SchemaProperty
|
36
|
+
* @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
|
37
|
+
*/
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Schema condition for component config
|
41
|
+
*
|
42
|
+
* @typedef {object} SchemaCondition
|
43
|
+
* @property {string[]} required - List of required config fields
|
44
|
+
* @property {string} errorMessage - Error message when required config fields not provided
|
45
|
+
*/
|
46
|
+
/**
|
47
|
+
* @typedef ComponentWithModuleName
|
48
|
+
* @property {string} moduleName - Name of the component
|
49
|
+
*/
|
50
|
+
|
1
51
|
class GOVUKFrontendError extends Error {
|
2
52
|
constructor(...args) {
|
3
53
|
super(...args);
|
@@ -21,60 +71,85 @@ class ElementError extends GOVUKFrontendError {
|
|
21
71
|
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
|
22
72
|
if (typeof messageOrOptions === 'object') {
|
23
73
|
const {
|
24
|
-
|
74
|
+
component,
|
25
75
|
identifier,
|
26
76
|
element,
|
27
77
|
expectedType
|
28
78
|
} = messageOrOptions;
|
29
|
-
message =
|
79
|
+
message = identifier;
|
30
80
|
message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
|
81
|
+
message = formatErrorMessage(component, message);
|
31
82
|
}
|
32
83
|
super(message);
|
33
84
|
this.name = 'ElementError';
|
34
85
|
}
|
35
86
|
}
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
87
|
+
class InitError extends GOVUKFrontendError {
|
88
|
+
constructor(componentOrMessage) {
|
89
|
+
const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
|
90
|
+
super(message);
|
91
|
+
this.name = 'InitError';
|
40
92
|
}
|
41
|
-
return $scope.classList.contains('govuk-frontend-supported');
|
42
93
|
}
|
43
|
-
|
44
|
-
/**
|
45
|
-
* Schema for component config
|
46
|
-
*
|
47
|
-
* @typedef {object} Schema
|
48
|
-
* @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
|
49
|
-
* @property {SchemaCondition[]} [anyOf] - List of schema conditions
|
50
|
-
*/
|
51
|
-
|
52
94
|
/**
|
53
|
-
*
|
54
|
-
*
|
55
|
-
* @typedef {object} SchemaProperty
|
56
|
-
* @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
|
57
|
-
*/
|
58
|
-
|
59
|
-
/**
|
60
|
-
* Schema condition for component config
|
61
|
-
*
|
62
|
-
* @typedef {object} SchemaCondition
|
63
|
-
* @property {string[]} required - List of required config fields
|
64
|
-
* @property {string} errorMessage - Error message when required config fields not provided
|
95
|
+
* @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
|
65
96
|
*/
|
66
97
|
|
67
98
|
class GOVUKFrontendComponent {
|
68
|
-
|
69
|
-
|
99
|
+
/**
|
100
|
+
* Returns the root element of the component
|
101
|
+
*
|
102
|
+
* @protected
|
103
|
+
* @returns {RootElementType} - the root element of component
|
104
|
+
*/
|
105
|
+
get $root() {
|
106
|
+
return this._$root;
|
70
107
|
}
|
71
|
-
|
108
|
+
constructor($root) {
|
109
|
+
this._$root = void 0;
|
110
|
+
const childConstructor = this.constructor;
|
111
|
+
if (typeof childConstructor.moduleName !== 'string') {
|
112
|
+
throw new InitError(`\`moduleName\` not defined in component`);
|
113
|
+
}
|
114
|
+
if (!($root instanceof childConstructor.elementType)) {
|
115
|
+
throw new ElementError({
|
116
|
+
element: $root,
|
117
|
+
component: childConstructor,
|
118
|
+
identifier: 'Root element (`$root`)',
|
119
|
+
expectedType: childConstructor.elementType.name
|
120
|
+
});
|
121
|
+
} else {
|
122
|
+
this._$root = $root;
|
123
|
+
}
|
124
|
+
childConstructor.checkSupport();
|
125
|
+
this.checkInitialised();
|
126
|
+
const moduleName = childConstructor.moduleName;
|
127
|
+
this.$root.setAttribute(`data-${moduleName}-init`, '');
|
128
|
+
}
|
129
|
+
checkInitialised() {
|
130
|
+
const constructor = this.constructor;
|
131
|
+
const moduleName = constructor.moduleName;
|
132
|
+
if (moduleName && isInitialised(this.$root, moduleName)) {
|
133
|
+
throw new InitError(constructor);
|
134
|
+
}
|
135
|
+
}
|
136
|
+
static checkSupport() {
|
72
137
|
if (!isSupported()) {
|
73
138
|
throw new SupportError();
|
74
139
|
}
|
75
140
|
}
|
76
141
|
}
|
77
142
|
|
143
|
+
/**
|
144
|
+
* @typedef ChildClass
|
145
|
+
* @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
|
146
|
+
*/
|
147
|
+
|
148
|
+
/**
|
149
|
+
* @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
|
150
|
+
*/
|
151
|
+
GOVUKFrontendComponent.elementType = HTMLElement;
|
152
|
+
|
78
153
|
/**
|
79
154
|
* Radios component
|
80
155
|
*
|
@@ -93,27 +168,18 @@ class Radios extends GOVUKFrontendComponent {
|
|
93
168
|
* (for example if the user has navigated back), and set up event handlers to
|
94
169
|
* keep the reveal in sync with the radio state.
|
95
170
|
*
|
96
|
-
* @param {Element | null} $
|
171
|
+
* @param {Element | null} $root - HTML element to use for radios
|
97
172
|
*/
|
98
|
-
constructor($
|
99
|
-
super();
|
100
|
-
this.$module = void 0;
|
173
|
+
constructor($root) {
|
174
|
+
super($root);
|
101
175
|
this.$inputs = void 0;
|
102
|
-
|
103
|
-
throw new ElementError({
|
104
|
-
componentName: 'Radios',
|
105
|
-
element: $module,
|
106
|
-
identifier: 'Root element (`$module`)'
|
107
|
-
});
|
108
|
-
}
|
109
|
-
const $inputs = $module.querySelectorAll('input[type="radio"]');
|
176
|
+
const $inputs = this.$root.querySelectorAll('input[type="radio"]');
|
110
177
|
if (!$inputs.length) {
|
111
178
|
throw new ElementError({
|
112
|
-
|
179
|
+
component: Radios,
|
113
180
|
identifier: 'Form inputs (`<input type="radio">`)'
|
114
181
|
});
|
115
182
|
}
|
116
|
-
this.$module = $module;
|
117
183
|
this.$inputs = $inputs;
|
118
184
|
this.$inputs.forEach($input => {
|
119
185
|
const targetId = $input.getAttribute('data-aria-controls');
|
@@ -122,7 +188,7 @@ class Radios extends GOVUKFrontendComponent {
|
|
122
188
|
}
|
123
189
|
if (!document.getElementById(targetId)) {
|
124
190
|
throw new ElementError({
|
125
|
-
|
191
|
+
component: Radios,
|
126
192
|
identifier: `Conditional reveal (\`id="${targetId}"\`)`
|
127
193
|
});
|
128
194
|
}
|
@@ -131,7 +197,7 @@ class Radios extends GOVUKFrontendComponent {
|
|
131
197
|
});
|
132
198
|
window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
|
133
199
|
this.syncAllConditionalReveals();
|
134
|
-
this.$
|
200
|
+
this.$root.addEventListener('click', event => this.handleClick(event));
|
135
201
|
}
|
136
202
|
syncAllConditionalReveals() {
|
137
203
|
this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
|