@ministryofjustice/frontend 5.0.0 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/moj/all.bundle.js +1598 -1062
  2. package/moj/all.bundle.js.map +1 -1
  3. package/moj/all.bundle.mjs +1894 -1054
  4. package/moj/all.bundle.mjs.map +1 -1
  5. package/moj/all.mjs +7 -90
  6. package/moj/all.mjs.map +1 -1
  7. package/moj/all.scss +1 -0
  8. package/moj/all.scss.map +1 -1
  9. package/moj/common/index.mjs +57 -0
  10. package/moj/common/index.mjs.map +1 -0
  11. package/moj/common/moj-frontend-version.mjs +14 -0
  12. package/moj/common/moj-frontend-version.mjs.map +1 -0
  13. package/moj/components/add-another/add-another.bundle.js +105 -76
  14. package/moj/components/add-another/add-another.bundle.js.map +1 -1
  15. package/moj/components/add-another/add-another.bundle.mjs +222 -71
  16. package/moj/components/add-another/add-another.bundle.mjs.map +1 -1
  17. package/moj/components/add-another/add-another.mjs +103 -72
  18. package/moj/components/add-another/add-another.mjs.map +1 -1
  19. package/moj/components/alert/alert.bundle.js +115 -191
  20. package/moj/components/alert/alert.bundle.js.map +1 -1
  21. package/moj/components/alert/alert.bundle.mjs +354 -186
  22. package/moj/components/alert/alert.bundle.mjs.map +1 -1
  23. package/moj/components/alert/alert.mjs +55 -140
  24. package/moj/components/alert/alert.mjs.map +1 -1
  25. package/moj/components/button-menu/README.md +3 -1
  26. package/moj/components/button-menu/button-menu.bundle.js +91 -120
  27. package/moj/components/button-menu/button-menu.bundle.js.map +1 -1
  28. package/moj/components/button-menu/button-menu.bundle.mjs +329 -114
  29. package/moj/components/button-menu/button-menu.bundle.mjs.map +1 -1
  30. package/moj/components/button-menu/button-menu.mjs +89 -116
  31. package/moj/components/button-menu/button-menu.mjs.map +1 -1
  32. package/moj/components/date-picker/date-picker.bundle.js +174 -154
  33. package/moj/components/date-picker/date-picker.bundle.js.map +1 -1
  34. package/moj/components/date-picker/date-picker.bundle.mjs +411 -147
  35. package/moj/components/date-picker/date-picker.bundle.mjs.map +1 -1
  36. package/moj/components/date-picker/date-picker.mjs +172 -150
  37. package/moj/components/date-picker/date-picker.mjs.map +1 -1
  38. package/moj/components/filter/template.njk +1 -1
  39. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js +133 -44
  40. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js.map +1 -1
  41. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs +374 -41
  42. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs.map +1 -1
  43. package/moj/components/filter-toggle-button/filter-toggle-button.mjs +131 -40
  44. package/moj/components/filter-toggle-button/filter-toggle-button.mjs.map +1 -1
  45. package/moj/components/form-validator/form-validator.bundle.js +159 -69
  46. package/moj/components/form-validator/form-validator.bundle.js.map +1 -1
  47. package/moj/components/form-validator/form-validator.bundle.mjs +399 -65
  48. package/moj/components/form-validator/form-validator.bundle.mjs.map +1 -1
  49. package/moj/components/form-validator/form-validator.mjs +134 -54
  50. package/moj/components/form-validator/form-validator.mjs.map +1 -1
  51. package/moj/components/multi-file-upload/multi-file-upload.bundle.js +291 -117
  52. package/moj/components/multi-file-upload/multi-file-upload.bundle.js.map +1 -1
  53. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs +527 -109
  54. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs.map +1 -1
  55. package/moj/components/multi-file-upload/multi-file-upload.mjs +288 -101
  56. package/moj/components/multi-file-upload/multi-file-upload.mjs.map +1 -1
  57. package/moj/components/multi-file-upload/template.njk +1 -1
  58. package/moj/components/multi-select/multi-select.bundle.js +106 -41
  59. package/moj/components/multi-select/multi-select.bundle.js.map +1 -1
  60. package/moj/components/multi-select/multi-select.bundle.mjs +346 -37
  61. package/moj/components/multi-select/multi-select.bundle.mjs.map +1 -1
  62. package/moj/components/multi-select/multi-select.mjs +104 -37
  63. package/moj/components/multi-select/multi-select.mjs.map +1 -1
  64. package/moj/components/password-reveal/_password-reveal.scss +3 -1
  65. package/moj/components/password-reveal/_password-reveal.scss.map +1 -1
  66. package/moj/components/password-reveal/password-reveal.bundle.js +32 -29
  67. package/moj/components/password-reveal/password-reveal.bundle.js.map +1 -1
  68. package/moj/components/password-reveal/password-reveal.bundle.mjs +149 -24
  69. package/moj/components/password-reveal/password-reveal.bundle.mjs.map +1 -1
  70. package/moj/components/password-reveal/password-reveal.mjs +30 -25
  71. package/moj/components/password-reveal/password-reveal.mjs.map +1 -1
  72. package/moj/components/rich-text-editor/README.md +4 -3
  73. package/moj/components/rich-text-editor/rich-text-editor.bundle.js +127 -62
  74. package/moj/components/rich-text-editor/rich-text-editor.bundle.js.map +1 -1
  75. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs +367 -58
  76. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs.map +1 -1
  77. package/moj/components/rich-text-editor/rich-text-editor.mjs +125 -58
  78. package/moj/components/rich-text-editor/rich-text-editor.mjs.map +1 -1
  79. package/moj/components/search-toggle/search-toggle.bundle.js +94 -26
  80. package/moj/components/search-toggle/search-toggle.bundle.js.map +1 -1
  81. package/moj/components/search-toggle/search-toggle.bundle.mjs +334 -22
  82. package/moj/components/search-toggle/search-toggle.bundle.mjs.map +1 -1
  83. package/moj/components/search-toggle/search-toggle.mjs +92 -22
  84. package/moj/components/search-toggle/search-toggle.mjs.map +1 -1
  85. package/moj/components/sortable-table/_sortable-table.scss +3 -42
  86. package/moj/components/sortable-table/_sortable-table.scss.map +1 -1
  87. package/moj/components/sortable-table/sortable-table.bundle.js +200 -83
  88. package/moj/components/sortable-table/sortable-table.bundle.js.map +1 -1
  89. package/moj/components/sortable-table/sortable-table.bundle.mjs +439 -78
  90. package/moj/components/sortable-table/sortable-table.bundle.mjs.map +1 -1
  91. package/moj/components/sortable-table/sortable-table.mjs +198 -79
  92. package/moj/components/sortable-table/sortable-table.mjs.map +1 -1
  93. package/moj/core/_all.scss +3 -0
  94. package/moj/core/_all.scss.map +1 -0
  95. package/moj/core/_moj-frontend-properties.scss +7 -0
  96. package/moj/core/_moj-frontend-properties.scss.map +1 -0
  97. package/moj/filters/prototype-kit-13-filters.js +4 -3
  98. package/moj/helpers.bundle.js +22 -77
  99. package/moj/helpers.bundle.js.map +1 -1
  100. package/moj/helpers.bundle.mjs +23 -74
  101. package/moj/helpers.bundle.mjs.map +1 -1
  102. package/moj/helpers.mjs +23 -74
  103. package/moj/helpers.mjs.map +1 -1
  104. package/moj/moj-frontend.min.css +1 -1
  105. package/moj/moj-frontend.min.css.map +1 -1
  106. package/moj/moj-frontend.min.js +1 -1
  107. package/moj/moj-frontend.min.js.map +1 -1
  108. package/package.json +1 -1
  109. package/moj/version.bundle.js +0 -12
  110. package/moj/version.bundle.js.map +0 -1
  111. package/moj/version.bundle.mjs +0 -4
  112. package/moj/version.bundle.mjs.map +0 -1
  113. package/moj/version.mjs +0 -4
  114. package/moj/version.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"form-validator.bundle.js","sources":["../../../../src/moj/helpers.mjs","../../../../src/moj/components/form-validator/form-validator.mjs"],"sourcesContent":["export function removeAttributeValue(el, attr, value) {\n let re, m\n if (el.getAttribute(attr)) {\n if (el.getAttribute(attr) === value) {\n el.removeAttribute(attr)\n } else {\n re = new RegExp(`(^|\\\\s)${value}(\\\\s|$)`)\n m = el.getAttribute(attr).match(re)\n if (m && m.length === 3) {\n el.setAttribute(\n attr,\n el.getAttribute(attr).replace(re, m[1] && m[2] ? ' ' : '')\n )\n }\n }\n }\n}\n\nexport function addAttributeValue(el, attr, value) {\n let re\n if (!el.getAttribute(attr)) {\n el.setAttribute(attr, value)\n } else {\n re = new RegExp(`(^|\\\\s)${value}(\\\\s|$)`)\n if (!re.test(el.getAttribute(attr))) {\n el.setAttribute(attr, `${el.getAttribute(attr)} ${value}`)\n }\n }\n}\n\nexport function dragAndDropSupported() {\n const div = document.createElement('div')\n return typeof div.ondrop !== 'undefined'\n}\n\nexport function formDataSupported() {\n return typeof FormData === 'function'\n}\n\nexport function fileApiSupported() {\n const input = document.createElement('input')\n input.type = 'file'\n return typeof input.files !== 'undefined'\n}\n\n/**\n * Find an elements next sibling\n *\n * Utility function to find an elements next sibling matching the provided\n * selector.\n *\n * @param {Element | null} $element - Element to find siblings for\n * @param {string} [selector] - selector for required sibling\n */\nexport function getNextSibling($element, selector) {\n if (!$element || !($element instanceof HTMLElement)) {\n return\n }\n\n // Get the next sibling element\n let $sibling = $element.nextElementSibling\n\n // If there's no selector, return the first sibling\n if (!selector) return $sibling\n\n // If the sibling matches our selector, use it\n // If not, jump to the next sibling and continue the loop\n while ($sibling) {\n if ($sibling.matches(selector)) return $sibling\n $sibling = $sibling.nextElementSibling\n }\n}\n\n/**\n * Find an elements preceding sibling\n *\n * Utility function to find an elements previous sibling matching the provided\n * selector.\n *\n * @param {Element | null} $element - Element to find siblings for\n * @param {string} [selector] - selector for required sibling\n */\nexport function getPreviousSibling($element, selector) {\n if (!$element || !($element instanceof HTMLElement)) {\n return\n }\n\n // Get the previous sibling element\n let $sibling = $element.previousElementSibling\n\n // If there's no selector, return the first sibling\n if (!selector) return $sibling\n\n // If the sibling matches our selector, use it\n // If not, jump to the next sibling and continue the loop\n while ($sibling) {\n if ($sibling.matches(selector)) return $sibling\n $sibling = $sibling.previousElementSibling\n }\n}\n\n/**\n * @param {Element | null} $element\n * @param {string} [selector]\n */\nexport function findNearestMatchingElement($element, selector) {\n // If no element or selector is provided, return\n if (!$element || !($element instanceof HTMLElement) || !selector) {\n return\n }\n\n // Start with the current element\n let $currentElement = $element\n\n while ($currentElement) {\n // First check the current element\n if ($currentElement.matches(selector)) {\n return $currentElement\n }\n\n // Check all previous siblings\n let $sibling = $currentElement.previousElementSibling\n while ($sibling) {\n // Check if the sibling itself is a heading\n if ($sibling.matches(selector)) {\n return $sibling\n }\n $sibling = $sibling.previousElementSibling\n }\n\n // If no match found in siblings, move up to parent\n $currentElement = $currentElement.parentElement\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 * @param {HTMLElement} $element - HTML element\n * @param {object} [options] - Handler options\n * @param {function(this: HTMLElement): void} [options.onBeforeFocus] - Callback before focus\n * @param {function(this: HTMLElement): 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 if (options.onBlur) {\n options.onBlur.call($element)\n }\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 if (options.onBeforeFocus) {\n options.onBeforeFocus.call($element)\n }\n $element.focus()\n}\n","import { addAttributeValue, removeAttributeValue } from '../../helpers.mjs'\n\nexport class FormValidator {\n /**\n * @param {Element | null} form - HTML element to use for form validator\n * @param {FormValidatorConfig} [config] - Button menu config\n */\n constructor(form, config = {}) {\n if (!form || !(form instanceof HTMLFormElement)) {\n return this\n }\n\n this.form = form\n this.errors = []\n this.validators = []\n this.form.addEventListener('submit', this.onSubmit.bind(this))\n this.summary =\n config.summary || document.querySelector('.govuk-error-summary')\n this.originalTitle = document.title\n }\n\n escapeHtml(string) {\n return String(string).replace(/[&<>\"'`=/]/g, function fromEntityMap(s) {\n return FormValidator.entityMap[s]\n })\n }\n\n resetTitle() {\n document.title = this.originalTitle\n }\n\n updateTitle() {\n document.title = `${this.errors.length} errors - ${document.title}`\n }\n\n showSummary() {\n this.summary.innerHTML = this.getSummaryHtml()\n this.summary.classList.remove('moj-hidden')\n this.summary.setAttribute('aria-labelledby', 'errorSummary-heading')\n this.summary.focus()\n }\n\n getSummaryHtml() {\n let html =\n '<h2 id=\"error-summary-title\" class=\"govuk-error-summary__title\">There is a problem</h2>'\n html += '<div class=\"govuk-error-summary__body\">'\n html += '<ul class=\"govuk-list govuk-error-summary__list\">'\n for (const error of this.errors) {\n html += '<li>'\n html += `<a href=\"#${this.escapeHtml(error.fieldName)}\">`\n html += this.escapeHtml(error.message)\n html += '</a>'\n html += '</li>'\n }\n html += '</ul>'\n html += '</div>'\n return html\n }\n\n hideSummary() {\n this.summary.classList.add('moj-hidden')\n this.summary.removeAttribute('aria-labelledby')\n }\n\n onSubmit(event) {\n this.removeInlineErrors()\n this.hideSummary()\n this.resetTitle()\n if (!this.validate()) {\n event.preventDefault()\n this.updateTitle()\n this.showSummary()\n this.showInlineErrors()\n }\n }\n\n showInlineErrors() {\n for (const error of this.errors) {\n this.showInlineError(error)\n }\n }\n\n showInlineError(error) {\n const errorSpan = document.createElement('span')\n errorSpan.id = `${error.fieldName}-error`\n errorSpan.classList.add('govuk-error-message')\n errorSpan.innerHTML = this.escapeHtml(error.message)\n\n const control = document.querySelector(`#${error.fieldName}`)\n const fieldset = control.closest('.govuk-fieldset')\n const fieldContainer = (fieldset || control).closest('.govuk-form-group')\n\n const label = fieldContainer.querySelector('label')\n const legend = fieldContainer.querySelector('legend')\n\n fieldContainer.classList.add('govuk-form-group--error')\n\n if (fieldset && legend) {\n legend.after(errorSpan)\n fieldContainer.setAttribute('aria-invalid', 'true')\n addAttributeValue(fieldset, 'aria-describedby', errorSpan.id)\n } else if (label && control) {\n label.after(errorSpan)\n control.setAttribute('aria-invalid', 'true')\n addAttributeValue(control, 'aria-describedby', errorSpan.id)\n }\n }\n\n removeInlineErrors() {\n for (const error of this.errors) {\n this.removeInlineError(error)\n }\n }\n\n removeInlineError(error) {\n const errorSpan = document.querySelector(`#${error.fieldName}-error`)\n\n const control = document.querySelector(`#${error.fieldName}`)\n const fieldset = control.closest('.govuk-fieldset')\n const fieldContainer = (fieldset || control).closest('.govuk-form-group')\n\n const label = fieldContainer.querySelector('label')\n const legend = fieldContainer.querySelector('legend')\n\n errorSpan.remove()\n fieldContainer.classList.remove('govuk-form-group--error')\n\n if (fieldset && legend) {\n fieldContainer.removeAttribute('aria-invalid')\n removeAttributeValue(fieldset, 'aria-describedby', errorSpan.id)\n } else if (label && control) {\n control.removeAttribute('aria-invalid')\n removeAttributeValue(control, 'aria-describedby', errorSpan.id)\n }\n }\n\n addValidator(fieldName, rules) {\n this.validators.push({\n fieldName,\n rules,\n field: this.form.elements[fieldName]\n })\n }\n\n validate() {\n this.errors = []\n let validator = null\n let validatorReturnValue = true\n let i\n let j\n for (i = 0; i < this.validators.length; i++) {\n validator = this.validators[i]\n for (j = 0; j < validator.rules.length; j++) {\n validatorReturnValue = validator.rules[j].method(\n validator.field,\n validator.rules[j].params\n )\n\n if (\n typeof validatorReturnValue === 'boolean' &&\n !validatorReturnValue\n ) {\n this.errors.push({\n fieldName: validator.fieldName,\n message: validator.rules[j].message\n })\n break\n } else if (typeof validatorReturnValue === 'string') {\n this.errors.push({\n fieldName: validatorReturnValue,\n message: validator.rules[j].message\n })\n break\n }\n }\n }\n return this.errors.length === 0\n }\n\n static entityMap = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;'\n }\n}\n\n/**\n * @typedef {object} FormValidatorConfig\n * @property {HTMLElement} [summary] - HTML element to use for error summary\n */\n"],"names":["removeAttributeValue","el","attr","value","re","m","getAttribute","removeAttribute","RegExp","match","length","setAttribute","replace","addAttributeValue","test","FormValidator","constructor","form","config","HTMLFormElement","errors","validators","addEventListener","onSubmit","bind","summary","document","querySelector","originalTitle","title","escapeHtml","string","String","fromEntityMap","s","entityMap","resetTitle","updateTitle","showSummary","innerHTML","getSummaryHtml","classList","remove","focus","html","error","fieldName","message","hideSummary","add","event","removeInlineErrors","validate","preventDefault","showInlineErrors","showInlineError","errorSpan","createElement","id","control","fieldset","closest","fieldContainer","label","legend","after","removeInlineError","addValidator","rules","push","field","elements","validator","validatorReturnValue","i","j","method","params"],"mappings":"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"}
1
+ {"version":3,"file":"form-validator.bundle.js","sources":["../../../../src/moj/helpers.mjs","../../../../src/moj/components/form-validator/form-validator.mjs"],"sourcesContent":["/**\n * @param {Element} $element - Element to remove attribute value from\n * @param {string} attr - Attribute name\n * @param {string} value - Attribute value\n */\nexport function removeAttributeValue($element, attr, value) {\n let re, m\n if ($element.getAttribute(attr)) {\n if ($element.getAttribute(attr) === value) {\n $element.removeAttribute(attr)\n } else {\n re = new RegExp(`(^|\\\\s)${value}(\\\\s|$)`)\n m = $element.getAttribute(attr).match(re)\n if (m && m.length === 3) {\n $element.setAttribute(\n attr,\n $element.getAttribute(attr).replace(re, m[1] && m[2] ? ' ' : '')\n )\n }\n }\n }\n}\n\n/**\n * @param {Element} $element - Element to add attribute value to\n * @param {string} attr - Attribute name\n * @param {string} value - Attribute value\n */\nexport function addAttributeValue($element, attr, value) {\n let re\n if (!$element.getAttribute(attr)) {\n $element.setAttribute(attr, value)\n } else {\n re = new RegExp(`(^|\\\\s)${value}(\\\\s|$)`)\n if (!re.test($element.getAttribute(attr))) {\n $element.setAttribute(attr, `${$element.getAttribute(attr)} ${value}`)\n }\n }\n}\n\n/**\n * Find an elements next sibling\n *\n * Utility function to find an elements next sibling matching the provided\n * selector.\n *\n * @param {Element | null} $element - Element to find siblings for\n * @param {string} [selector] - selector for required sibling\n */\nexport function getNextSibling($element, selector) {\n if (!$element || !($element instanceof HTMLElement)) {\n return\n }\n\n // Get the next sibling element\n let $sibling = $element.nextElementSibling\n\n // If there's no selector, return the first sibling\n if (!selector) return $sibling\n\n // If the sibling matches our selector, use it\n // If not, jump to the next sibling and continue the loop\n while ($sibling) {\n if ($sibling.matches(selector)) return $sibling\n $sibling = $sibling.nextElementSibling\n }\n}\n\n/**\n * Find an elements preceding sibling\n *\n * Utility function to find an elements previous sibling matching the provided\n * selector.\n *\n * @param {Element | null} $element - Element to find siblings for\n * @param {string} [selector] - selector for required sibling\n */\nexport function getPreviousSibling($element, selector) {\n if (!$element || !($element instanceof HTMLElement)) {\n return\n }\n\n // Get the previous sibling element\n let $sibling = $element.previousElementSibling\n\n // If there's no selector, return the first sibling\n if (!selector) return $sibling\n\n // If the sibling matches our selector, use it\n // If not, jump to the next sibling and continue the loop\n while ($sibling) {\n if ($sibling.matches(selector)) return $sibling\n $sibling = $sibling.previousElementSibling\n }\n}\n\n/**\n * @param {Element | null} $element\n * @param {string} [selector]\n */\nexport function findNearestMatchingElement($element, selector) {\n // If no element or selector is provided, return\n if (!$element || !($element instanceof HTMLElement) || !selector) {\n return\n }\n\n // Start with the current element\n let $currentElement = $element\n\n while ($currentElement) {\n // First check the current element\n if ($currentElement.matches(selector)) {\n return $currentElement\n }\n\n // Check all previous siblings\n let $sibling = $currentElement.previousElementSibling\n while ($sibling) {\n // Check if the sibling itself is a heading\n if ($sibling.matches(selector)) {\n return $sibling\n }\n $sibling = $sibling.previousElementSibling\n }\n\n // If no match found in siblings, move up to parent\n $currentElement = $currentElement.parentElement\n }\n}\n","import { ConfigurableComponent } from 'govuk-frontend'\n\nimport { addAttributeValue, removeAttributeValue } from '../../helpers.mjs'\n\n/**\n * @augments {ConfigurableComponent<FormValidatorConfig, HTMLFormElement>}\n */\nexport class FormValidator extends ConfigurableComponent {\n /**\n * @param {Element | null} $root - HTML element to use for form validator\n * @param {FormValidatorConfig} [config] - Form validator config\n */\n constructor($root, config = {}) {\n super($root, config)\n\n const $summary =\n this.config.summary.element ||\n document.querySelector(this.config.summary.selector)\n\n if (!$summary || !($summary instanceof HTMLElement)) {\n return this\n }\n\n this.$summary = $summary\n\n this.errors = /** @type {ValidationError[]} */ ([])\n this.validators = /** @type {Validator[]} */ ([])\n this.originalTitle = document.title\n\n this.$root.addEventListener('submit', this.onSubmit.bind(this))\n }\n\n escapeHtml(string = '') {\n return String(string).replace(\n /[&<>\"'`=/]/g,\n (name) => FormValidator.entityMap[name]\n )\n }\n\n resetTitle() {\n document.title = this.originalTitle\n }\n\n updateTitle() {\n document.title = `${this.errors.length} errors - ${document.title}`\n }\n\n showSummary() {\n this.$summary.innerHTML = this.getSummaryHtml()\n this.$summary.classList.remove('moj-hidden')\n this.$summary.setAttribute('aria-labelledby', 'errorSummary-heading')\n this.$summary.focus()\n }\n\n getSummaryHtml() {\n let html =\n '<h2 id=\"error-summary-title\" class=\"govuk-error-summary__title\">There is a problem</h2>'\n html += '<div class=\"govuk-error-summary__body\">'\n html += '<ul class=\"govuk-list govuk-error-summary__list\">'\n for (const error of this.errors) {\n html += '<li>'\n html += `<a href=\"#${this.escapeHtml(error.fieldName)}\">`\n html += this.escapeHtml(error.message)\n html += '</a>'\n html += '</li>'\n }\n html += '</ul>'\n html += '</div>'\n return html\n }\n\n hideSummary() {\n this.$summary.classList.add('moj-hidden')\n this.$summary.removeAttribute('aria-labelledby')\n }\n\n /**\n * @param {SubmitEvent} event - Form submit event\n */\n onSubmit(event) {\n this.removeInlineErrors()\n this.hideSummary()\n this.resetTitle()\n if (!this.validate()) {\n event.preventDefault()\n this.updateTitle()\n this.showSummary()\n this.showInlineErrors()\n }\n }\n\n showInlineErrors() {\n for (const error of this.errors) {\n this.showInlineError(error)\n }\n }\n\n /**\n * @param {ValidationError} error\n */\n showInlineError(error) {\n const $errorSpan = document.createElement('span')\n $errorSpan.id = `${error.fieldName}-error`\n $errorSpan.classList.add('govuk-error-message')\n $errorSpan.innerHTML = this.escapeHtml(error.message)\n\n const $control = document.querySelector(`#${error.fieldName}`)\n const $fieldset = $control.closest('.govuk-fieldset')\n const $fieldContainer = ($fieldset || $control).closest('.govuk-form-group')\n\n const $label = $fieldContainer.querySelector('label')\n const $legend = $fieldContainer.querySelector('legend')\n\n $fieldContainer.classList.add('govuk-form-group--error')\n\n if ($fieldset && $legend) {\n $legend.after($errorSpan)\n $fieldContainer.setAttribute('aria-invalid', 'true')\n addAttributeValue($fieldset, 'aria-describedby', $errorSpan.id)\n } else if ($label && $control) {\n $label.after($errorSpan)\n $control.setAttribute('aria-invalid', 'true')\n addAttributeValue($control, 'aria-describedby', $errorSpan.id)\n }\n }\n\n removeInlineErrors() {\n for (const error of this.errors) {\n this.removeInlineError(error)\n }\n }\n\n /**\n * @param {ValidationError} error\n */\n removeInlineError(error) {\n const $errorSpan = document.querySelector(`#${error.fieldName}-error`)\n\n const $control = document.querySelector(`#${error.fieldName}`)\n const $fieldset = $control.closest('.govuk-fieldset')\n const $fieldContainer = ($fieldset || $control).closest('.govuk-form-group')\n\n const $label = $fieldContainer.querySelector('label')\n const $legend = $fieldContainer.querySelector('legend')\n\n $errorSpan.remove()\n $fieldContainer.classList.remove('govuk-form-group--error')\n\n if ($fieldset && $legend) {\n $fieldContainer.removeAttribute('aria-invalid')\n removeAttributeValue($fieldset, 'aria-describedby', $errorSpan.id)\n } else if ($label && $control) {\n $control.removeAttribute('aria-invalid')\n removeAttributeValue($control, 'aria-describedby', $errorSpan.id)\n }\n }\n\n /**\n * @param {string} fieldName - Field name\n * @param {ValidationRule[]} rules - Validation rules\n */\n addValidator(fieldName, rules) {\n this.validators.push({\n fieldName,\n rules,\n field: this.$root.elements.namedItem(fieldName)\n })\n }\n\n validate() {\n this.errors = []\n\n /** @type {Validator | null} */\n let validator = null\n\n /** @type {boolean | string} */\n let validatorReturnValue = true\n\n let i\n let j\n\n for (i = 0; i < this.validators.length; i++) {\n validator = this.validators[i]\n for (j = 0; j < validator.rules.length; j++) {\n validatorReturnValue = validator.rules[j].method(\n validator.field,\n validator.rules[j].params\n )\n\n if (\n typeof validatorReturnValue === 'boolean' &&\n !validatorReturnValue\n ) {\n this.errors.push({\n fieldName: validator.fieldName,\n message: validator.rules[j].message\n })\n break\n } else if (typeof validatorReturnValue === 'string') {\n this.errors.push({\n fieldName: validatorReturnValue,\n message: validator.rules[j].message\n })\n break\n }\n }\n }\n return this.errors.length === 0\n }\n\n /**\n * @type {Record<string, string>}\n */\n static entityMap = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;'\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'moj-form-validator'\n\n /**\n * Multi file upload default config\n *\n * @type {FormValidatorConfig}\n */\n static defaults = Object.freeze({\n summary: {\n selector: '.govuk-error-summary'\n }\n })\n\n /**\n * Multi file upload config schema\n *\n * @satisfies {Schema<FormValidatorConfig>}\n */\n static schema = Object.freeze(\n /** @type {const} */ ({\n properties: {\n summary: { type: 'object' }\n }\n })\n )\n}\n\n/**\n * @typedef {object} FormValidatorConfig\n * @property {object} [summary] - Error summary config\n * @property {string} [summary.selector] - Selector for error summary\n * @property {Element | null} [summary.element] - HTML element for error summary\n */\n\n/**\n * @typedef {object} ValidationRule\n * @property {(field: Validator['field'], params: Record<string, Validator['field']>) => boolean | string} method - Validation method\n * @property {string} message - Error message\n * @property {Record<string, Validator['field']>} [params] - Parameters for validation\n */\n\n/**\n * @typedef {object} ValidationError\n * @property {string} fieldName - Name of the field\n * @property {string} message - Validation error message\n */\n\n/**\n * @typedef {object} Validator\n * @property {string} fieldName - Name of the field\n * @property {ValidationRule[]} rules - Validation rules\n * @property {Element | RadioNodeList} field - Form field\n */\n\n/**\n * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'\n */\n"],"names":["removeAttributeValue","$element","attr","value","re","m","getAttribute","removeAttribute","RegExp","match","length","setAttribute","replace","addAttributeValue","test","FormValidator","ConfigurableComponent","constructor","$root","config","$summary","summary","element","document","querySelector","selector","HTMLElement","errors","validators","originalTitle","title","addEventListener","onSubmit","bind","escapeHtml","string","String","name","entityMap","resetTitle","updateTitle","showSummary","innerHTML","getSummaryHtml","classList","remove","focus","html","error","fieldName","message","hideSummary","add","event","removeInlineErrors","validate","preventDefault","showInlineErrors","showInlineError","$errorSpan","createElement","id","$control","$fieldset","closest","$fieldContainer","$label","$legend","after","removeInlineError","addValidator","rules","push","field","elements","namedItem","validator","validatorReturnValue","i","j","method","params","moduleName","defaults","Object","freeze","schema","properties","type"],"mappings":"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"}
@@ -1,49 +1,306 @@
1
- function removeAttributeValue(el, attr, value) {
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 isArray(option) {
21
+ return Array.isArray(option);
22
+ }
23
+ function isObject(option) {
24
+ return !!option && typeof option === 'object' && !isArray(option);
25
+ }
26
+ function formatErrorMessage(Component, message) {
27
+ return `${Component.moduleName}: ${message}`;
28
+ }
29
+
30
+ class GOVUKFrontendError extends Error {
31
+ constructor(...args) {
32
+ super(...args);
33
+ this.name = 'GOVUKFrontendError';
34
+ }
35
+ }
36
+ class SupportError extends GOVUKFrontendError {
37
+ /**
38
+ * Checks if GOV.UK Frontend is supported on this page
39
+ *
40
+ * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
41
+ */
42
+ constructor($scope = document.body) {
43
+ const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
44
+ super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
45
+ this.name = 'SupportError';
46
+ }
47
+ }
48
+ class ConfigError extends GOVUKFrontendError {
49
+ constructor(...args) {
50
+ super(...args);
51
+ this.name = 'ConfigError';
52
+ }
53
+ }
54
+ class ElementError extends GOVUKFrontendError {
55
+ constructor(messageOrOptions) {
56
+ let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
57
+ if (typeof messageOrOptions === 'object') {
58
+ const {
59
+ component,
60
+ identifier,
61
+ element,
62
+ expectedType
63
+ } = messageOrOptions;
64
+ message = identifier;
65
+ message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
66
+ message = formatErrorMessage(component, message);
67
+ }
68
+ super(message);
69
+ this.name = 'ElementError';
70
+ }
71
+ }
72
+ class InitError extends GOVUKFrontendError {
73
+ constructor(componentOrMessage) {
74
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
75
+ super(message);
76
+ this.name = 'InitError';
77
+ }
78
+ }
79
+
80
+ class Component {
81
+ /**
82
+ * Returns the root element of the component
83
+ *
84
+ * @protected
85
+ * @returns {RootElementType} - the root element of component
86
+ */
87
+ get $root() {
88
+ return this._$root;
89
+ }
90
+ constructor($root) {
91
+ this._$root = void 0;
92
+ const childConstructor = this.constructor;
93
+ if (typeof childConstructor.moduleName !== 'string') {
94
+ throw new InitError(`\`moduleName\` not defined in component`);
95
+ }
96
+ if (!($root instanceof childConstructor.elementType)) {
97
+ throw new ElementError({
98
+ element: $root,
99
+ component: childConstructor,
100
+ identifier: 'Root element (`$root`)',
101
+ expectedType: childConstructor.elementType.name
102
+ });
103
+ } else {
104
+ this._$root = $root;
105
+ }
106
+ childConstructor.checkSupport();
107
+ this.checkInitialised();
108
+ const moduleName = childConstructor.moduleName;
109
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
110
+ }
111
+ checkInitialised() {
112
+ const constructor = this.constructor;
113
+ const moduleName = constructor.moduleName;
114
+ if (moduleName && isInitialised(this.$root, moduleName)) {
115
+ throw new InitError(constructor);
116
+ }
117
+ }
118
+ static checkSupport() {
119
+ if (!isSupported()) {
120
+ throw new SupportError();
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * @typedef ChildClass
127
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
128
+ */
129
+
130
+ /**
131
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
132
+ */
133
+ Component.elementType = HTMLElement;
134
+
135
+ const configOverride = Symbol.for('configOverride');
136
+ class ConfigurableComponent extends Component {
137
+ [configOverride](param) {
138
+ return {};
139
+ }
140
+
141
+ /**
142
+ * Returns the root element of the component
143
+ *
144
+ * @protected
145
+ * @returns {ConfigurationType} - the root element of component
146
+ */
147
+ get config() {
148
+ return this._config;
149
+ }
150
+ constructor($root, config) {
151
+ super($root);
152
+ this._config = void 0;
153
+ const childConstructor = this.constructor;
154
+ if (!isObject(childConstructor.defaults)) {
155
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
156
+ }
157
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
158
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
159
+ }
160
+ }
161
+ function normaliseString(value, property) {
162
+ const trimmedValue = value ? value.trim() : '';
163
+ let output;
164
+ let outputType = property == null ? void 0 : property.type;
165
+ if (!outputType) {
166
+ if (['true', 'false'].includes(trimmedValue)) {
167
+ outputType = 'boolean';
168
+ }
169
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
170
+ outputType = 'number';
171
+ }
172
+ }
173
+ switch (outputType) {
174
+ case 'boolean':
175
+ output = trimmedValue === 'true';
176
+ break;
177
+ case 'number':
178
+ output = Number(trimmedValue);
179
+ break;
180
+ default:
181
+ output = value;
182
+ }
183
+ return output;
184
+ }
185
+ function normaliseDataset(Component, dataset) {
186
+ if (!isObject(Component.schema)) {
187
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
188
+ }
189
+ const out = {};
190
+ const entries = Object.entries(Component.schema.properties);
191
+ for (const entry of entries) {
192
+ const [namespace, property] = entry;
193
+ const field = namespace.toString();
194
+ if (field in dataset) {
195
+ out[field] = normaliseString(dataset[field], property);
196
+ }
197
+ if ((property == null ? void 0 : property.type) === 'object') {
198
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
199
+ }
200
+ }
201
+ return out;
202
+ }
203
+ function mergeConfigs(...configObjects) {
204
+ const formattedConfigObject = {};
205
+ for (const configObject of configObjects) {
206
+ for (const key of Object.keys(configObject)) {
207
+ const option = formattedConfigObject[key];
208
+ const override = configObject[key];
209
+ if (isObject(option) && isObject(override)) {
210
+ formattedConfigObject[key] = mergeConfigs(option, override);
211
+ } else {
212
+ formattedConfigObject[key] = override;
213
+ }
214
+ }
215
+ }
216
+ return formattedConfigObject;
217
+ }
218
+ function extractConfigByNamespace(schema, dataset, namespace) {
219
+ const property = schema.properties[namespace];
220
+ if ((property == null ? void 0 : property.type) !== 'object') {
221
+ return;
222
+ }
223
+ const newObject = {
224
+ [namespace]: {}
225
+ };
226
+ for (const [key, value] of Object.entries(dataset)) {
227
+ let current = newObject;
228
+ const keyParts = key.split('.');
229
+ for (const [index, name] of keyParts.entries()) {
230
+ if (isObject(current)) {
231
+ if (index < keyParts.length - 1) {
232
+ if (!isObject(current[name])) {
233
+ current[name] = {};
234
+ }
235
+ current = current[name];
236
+ } else if (key !== namespace) {
237
+ current[name] = normaliseString(value);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ return newObject[namespace];
243
+ }
244
+
245
+ /**
246
+ * @param {Element} $element - Element to remove attribute value from
247
+ * @param {string} attr - Attribute name
248
+ * @param {string} value - Attribute value
249
+ */
250
+ function removeAttributeValue($element, attr, value) {
2
251
  let re, m;
3
- if (el.getAttribute(attr)) {
4
- if (el.getAttribute(attr) === value) {
5
- el.removeAttribute(attr);
252
+ if ($element.getAttribute(attr)) {
253
+ if ($element.getAttribute(attr) === value) {
254
+ $element.removeAttribute(attr);
6
255
  } else {
7
256
  re = new RegExp(`(^|\\s)${value}(\\s|$)`);
8
- m = el.getAttribute(attr).match(re);
257
+ m = $element.getAttribute(attr).match(re);
9
258
  if (m && m.length === 3) {
10
- el.setAttribute(attr, el.getAttribute(attr).replace(re, m[1] && m[2] ? ' ' : ''));
259
+ $element.setAttribute(attr, $element.getAttribute(attr).replace(re, m[1] && m[2] ? ' ' : ''));
11
260
  }
12
261
  }
13
262
  }
14
263
  }
15
- function addAttributeValue(el, attr, value) {
264
+
265
+ /**
266
+ * @param {Element} $element - Element to add attribute value to
267
+ * @param {string} attr - Attribute name
268
+ * @param {string} value - Attribute value
269
+ */
270
+ function addAttributeValue($element, attr, value) {
16
271
  let re;
17
- if (!el.getAttribute(attr)) {
18
- el.setAttribute(attr, value);
272
+ if (!$element.getAttribute(attr)) {
273
+ $element.setAttribute(attr, value);
19
274
  } else {
20
275
  re = new RegExp(`(^|\\s)${value}(\\s|$)`);
21
- if (!re.test(el.getAttribute(attr))) {
22
- el.setAttribute(attr, `${el.getAttribute(attr)} ${value}`);
276
+ if (!re.test($element.getAttribute(attr))) {
277
+ $element.setAttribute(attr, `${$element.getAttribute(attr)} ${value}`);
23
278
  }
24
279
  }
25
280
  }
26
281
 
27
- class FormValidator {
282
+ /**
283
+ * @augments {ConfigurableComponent<FormValidatorConfig, HTMLFormElement>}
284
+ */
285
+ class FormValidator extends ConfigurableComponent {
28
286
  /**
29
- * @param {Element | null} form - HTML element to use for form validator
30
- * @param {FormValidatorConfig} [config] - Button menu config
287
+ * @param {Element | null} $root - HTML element to use for form validator
288
+ * @param {FormValidatorConfig} [config] - Form validator config
31
289
  */
32
- constructor(form, config = {}) {
33
- if (!form || !(form instanceof HTMLFormElement)) {
290
+ constructor($root, config = {}) {
291
+ super($root, config);
292
+ const $summary = this.config.summary.element || document.querySelector(this.config.summary.selector);
293
+ if (!$summary || !($summary instanceof HTMLElement)) {
34
294
  return this;
35
295
  }
36
- this.form = form;
37
- this.errors = [];
38
- this.validators = [];
39
- this.form.addEventListener('submit', this.onSubmit.bind(this));
40
- this.summary = config.summary || document.querySelector('.govuk-error-summary');
296
+ this.$summary = $summary;
297
+ this.errors = /** @type {ValidationError[]} */[];
298
+ this.validators = /** @type {Validator[]} */[];
41
299
  this.originalTitle = document.title;
300
+ this.$root.addEventListener('submit', this.onSubmit.bind(this));
42
301
  }
43
- escapeHtml(string) {
44
- return String(string).replace(/[&<>"'`=/]/g, function fromEntityMap(s) {
45
- return FormValidator.entityMap[s];
46
- });
302
+ escapeHtml(string = '') {
303
+ return String(string).replace(/[&<>"'`=/]/g, name => FormValidator.entityMap[name]);
47
304
  }
48
305
  resetTitle() {
49
306
  document.title = this.originalTitle;
@@ -52,10 +309,10 @@ class FormValidator {
52
309
  document.title = `${this.errors.length} errors - ${document.title}`;
53
310
  }
54
311
  showSummary() {
55
- this.summary.innerHTML = this.getSummaryHtml();
56
- this.summary.classList.remove('moj-hidden');
57
- this.summary.setAttribute('aria-labelledby', 'errorSummary-heading');
58
- this.summary.focus();
312
+ this.$summary.innerHTML = this.getSummaryHtml();
313
+ this.$summary.classList.remove('moj-hidden');
314
+ this.$summary.setAttribute('aria-labelledby', 'errorSummary-heading');
315
+ this.$summary.focus();
59
316
  }
60
317
  getSummaryHtml() {
61
318
  let html = '<h2 id="error-summary-title" class="govuk-error-summary__title">There is a problem</h2>';
@@ -73,9 +330,13 @@ class FormValidator {
73
330
  return html;
74
331
  }
75
332
  hideSummary() {
76
- this.summary.classList.add('moj-hidden');
77
- this.summary.removeAttribute('aria-labelledby');
333
+ this.$summary.classList.add('moj-hidden');
334
+ this.$summary.removeAttribute('aria-labelledby');
78
335
  }
336
+
337
+ /**
338
+ * @param {SubmitEvent} event - Form submit event
339
+ */
79
340
  onSubmit(event) {
80
341
  this.removeInlineErrors();
81
342
  this.hideSummary();
@@ -92,25 +353,29 @@ class FormValidator {
92
353
  this.showInlineError(error);
93
354
  }
94
355
  }
356
+
357
+ /**
358
+ * @param {ValidationError} error
359
+ */
95
360
  showInlineError(error) {
96
- const errorSpan = document.createElement('span');
97
- errorSpan.id = `${error.fieldName}-error`;
98
- errorSpan.classList.add('govuk-error-message');
99
- errorSpan.innerHTML = this.escapeHtml(error.message);
100
- const control = document.querySelector(`#${error.fieldName}`);
101
- const fieldset = control.closest('.govuk-fieldset');
102
- const fieldContainer = (fieldset || control).closest('.govuk-form-group');
103
- const label = fieldContainer.querySelector('label');
104
- const legend = fieldContainer.querySelector('legend');
105
- fieldContainer.classList.add('govuk-form-group--error');
106
- if (fieldset && legend) {
107
- legend.after(errorSpan);
108
- fieldContainer.setAttribute('aria-invalid', 'true');
109
- addAttributeValue(fieldset, 'aria-describedby', errorSpan.id);
110
- } else if (label && control) {
111
- label.after(errorSpan);
112
- control.setAttribute('aria-invalid', 'true');
113
- addAttributeValue(control, 'aria-describedby', errorSpan.id);
361
+ const $errorSpan = document.createElement('span');
362
+ $errorSpan.id = `${error.fieldName}-error`;
363
+ $errorSpan.classList.add('govuk-error-message');
364
+ $errorSpan.innerHTML = this.escapeHtml(error.message);
365
+ const $control = document.querySelector(`#${error.fieldName}`);
366
+ const $fieldset = $control.closest('.govuk-fieldset');
367
+ const $fieldContainer = ($fieldset || $control).closest('.govuk-form-group');
368
+ const $label = $fieldContainer.querySelector('label');
369
+ const $legend = $fieldContainer.querySelector('legend');
370
+ $fieldContainer.classList.add('govuk-form-group--error');
371
+ if ($fieldset && $legend) {
372
+ $legend.after($errorSpan);
373
+ $fieldContainer.setAttribute('aria-invalid', 'true');
374
+ addAttributeValue($fieldset, 'aria-describedby', $errorSpan.id);
375
+ } else if ($label && $control) {
376
+ $label.after($errorSpan);
377
+ $control.setAttribute('aria-invalid', 'true');
378
+ addAttributeValue($control, 'aria-describedby', $errorSpan.id);
114
379
  }
115
380
  }
116
381
  removeInlineErrors() {
@@ -118,33 +383,46 @@ class FormValidator {
118
383
  this.removeInlineError(error);
119
384
  }
120
385
  }
386
+
387
+ /**
388
+ * @param {ValidationError} error
389
+ */
121
390
  removeInlineError(error) {
122
- const errorSpan = document.querySelector(`#${error.fieldName}-error`);
123
- const control = document.querySelector(`#${error.fieldName}`);
124
- const fieldset = control.closest('.govuk-fieldset');
125
- const fieldContainer = (fieldset || control).closest('.govuk-form-group');
126
- const label = fieldContainer.querySelector('label');
127
- const legend = fieldContainer.querySelector('legend');
128
- errorSpan.remove();
129
- fieldContainer.classList.remove('govuk-form-group--error');
130
- if (fieldset && legend) {
131
- fieldContainer.removeAttribute('aria-invalid');
132
- removeAttributeValue(fieldset, 'aria-describedby', errorSpan.id);
133
- } else if (label && control) {
134
- control.removeAttribute('aria-invalid');
135
- removeAttributeValue(control, 'aria-describedby', errorSpan.id);
391
+ const $errorSpan = document.querySelector(`#${error.fieldName}-error`);
392
+ const $control = document.querySelector(`#${error.fieldName}`);
393
+ const $fieldset = $control.closest('.govuk-fieldset');
394
+ const $fieldContainer = ($fieldset || $control).closest('.govuk-form-group');
395
+ const $label = $fieldContainer.querySelector('label');
396
+ const $legend = $fieldContainer.querySelector('legend');
397
+ $errorSpan.remove();
398
+ $fieldContainer.classList.remove('govuk-form-group--error');
399
+ if ($fieldset && $legend) {
400
+ $fieldContainer.removeAttribute('aria-invalid');
401
+ removeAttributeValue($fieldset, 'aria-describedby', $errorSpan.id);
402
+ } else if ($label && $control) {
403
+ $control.removeAttribute('aria-invalid');
404
+ removeAttributeValue($control, 'aria-describedby', $errorSpan.id);
136
405
  }
137
406
  }
407
+
408
+ /**
409
+ * @param {string} fieldName - Field name
410
+ * @param {ValidationRule[]} rules - Validation rules
411
+ */
138
412
  addValidator(fieldName, rules) {
139
413
  this.validators.push({
140
414
  fieldName,
141
415
  rules,
142
- field: this.form.elements[fieldName]
416
+ field: this.$root.elements.namedItem(fieldName)
143
417
  });
144
418
  }
145
419
  validate() {
146
420
  this.errors = [];
421
+
422
+ /** @type {Validator | null} */
147
423
  let validator = null;
424
+
425
+ /** @type {boolean | string} */
148
426
  let validatorReturnValue = true;
149
427
  let i;
150
428
  let j;
@@ -169,11 +447,41 @@ class FormValidator {
169
447
  }
170
448
  return this.errors.length === 0;
171
449
  }
450
+
451
+ /**
452
+ * @type {Record<string, string>}
453
+ */
172
454
  }
173
455
 
174
456
  /**
175
457
  * @typedef {object} FormValidatorConfig
176
- * @property {HTMLElement} [summary] - HTML element to use for error summary
458
+ * @property {object} [summary] - Error summary config
459
+ * @property {string} [summary.selector] - Selector for error summary
460
+ * @property {Element | null} [summary.element] - HTML element for error summary
461
+ */
462
+
463
+ /**
464
+ * @typedef {object} ValidationRule
465
+ * @property {(field: Validator['field'], params: Record<string, Validator['field']>) => boolean | string} method - Validation method
466
+ * @property {string} message - Error message
467
+ * @property {Record<string, Validator['field']>} [params] - Parameters for validation
468
+ */
469
+
470
+ /**
471
+ * @typedef {object} ValidationError
472
+ * @property {string} fieldName - Name of the field
473
+ * @property {string} message - Validation error message
474
+ */
475
+
476
+ /**
477
+ * @typedef {object} Validator
478
+ * @property {string} fieldName - Name of the field
479
+ * @property {ValidationRule[]} rules - Validation rules
480
+ * @property {Element | RadioNodeList} field - Form field
481
+ */
482
+
483
+ /**
484
+ * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'
177
485
  */
178
486
  FormValidator.entityMap = {
179
487
  '&': '&amp;',
@@ -185,6 +493,32 @@ FormValidator.entityMap = {
185
493
  '`': '&#x60;',
186
494
  '=': '&#x3D;'
187
495
  };
496
+ /**
497
+ * Name for the component used when initialising using data-module attributes.
498
+ */
499
+ FormValidator.moduleName = 'moj-form-validator';
500
+ /**
501
+ * Multi file upload default config
502
+ *
503
+ * @type {FormValidatorConfig}
504
+ */
505
+ FormValidator.defaults = Object.freeze({
506
+ summary: {
507
+ selector: '.govuk-error-summary'
508
+ }
509
+ });
510
+ /**
511
+ * Multi file upload config schema
512
+ *
513
+ * @satisfies {Schema<FormValidatorConfig>}
514
+ */
515
+ FormValidator.schema = Object.freeze(/** @type {const} */{
516
+ properties: {
517
+ summary: {
518
+ type: 'object'
519
+ }
520
+ }
521
+ });
188
522
 
189
523
  export { FormValidator };
190
524
  //# sourceMappingURL=form-validator.bundle.mjs.map