@ministryofjustice/frontend 5.0.0 → 5.1.0

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 (112) hide show
  1. package/moj/all.bundle.js +1549 -1062
  2. package/moj/all.bundle.js.map +1 -1
  3. package/moj/all.bundle.mjs +1845 -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.bundle.js +151 -83
  86. package/moj/components/sortable-table/sortable-table.bundle.js.map +1 -1
  87. package/moj/components/sortable-table/sortable-table.bundle.mjs +390 -78
  88. package/moj/components/sortable-table/sortable-table.bundle.mjs.map +1 -1
  89. package/moj/components/sortable-table/sortable-table.mjs +149 -79
  90. package/moj/components/sortable-table/sortable-table.mjs.map +1 -1
  91. package/moj/core/_all.scss +3 -0
  92. package/moj/core/_all.scss.map +1 -0
  93. package/moj/core/_moj-frontend-properties.scss +7 -0
  94. package/moj/core/_moj-frontend-properties.scss.map +1 -0
  95. package/moj/filters/prototype-kit-13-filters.js +4 -3
  96. package/moj/helpers.bundle.js +22 -77
  97. package/moj/helpers.bundle.js.map +1 -1
  98. package/moj/helpers.bundle.mjs +23 -74
  99. package/moj/helpers.bundle.mjs.map +1 -1
  100. package/moj/helpers.mjs +23 -74
  101. package/moj/helpers.mjs.map +1 -1
  102. package/moj/moj-frontend.min.css +1 -1
  103. package/moj/moj-frontend.min.css.map +1 -1
  104. package/moj/moj-frontend.min.js +1 -1
  105. package/moj/moj-frontend.min.js.map +1 -1
  106. package/package.json +1 -1
  107. package/moj/version.bundle.js +0 -12
  108. package/moj/version.bundle.js.map +0 -1
  109. package/moj/version.bundle.mjs +0 -4
  110. package/moj/version.bundle.mjs.map +0 -1
  111. package/moj/version.mjs +0 -4
  112. package/moj/version.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"rich-text-editor.bundle.js","sources":["../../../../src/moj/components/rich-text-editor/rich-text-editor.mjs"],"sourcesContent":["export class RichTextEditor {\n /**\n * @param {RichTextEditorConfig} options\n */\n constructor(options = {}) {\n const { textarea } = options\n\n if (\n !textarea ||\n !textarea.parentElement ||\n !(textarea instanceof HTMLTextAreaElement) ||\n !('contentEditable' in document.documentElement)\n ) {\n return this\n }\n\n options.toolbar = options.toolbar || {\n bold: false,\n italic: false,\n underline: false,\n bullets: true,\n numbers: true\n }\n\n this.textarea = textarea\n this.container = this.textarea.parentElement\n this.options = options\n\n if (this.container.hasAttribute('data-rich-text-editor-init')) {\n return this\n }\n\n this.container.setAttribute('data-rich-text-editor-init', '')\n\n this.createToolbar()\n this.hideDefault()\n this.configureToolbar()\n\n this.keys = {\n left: 37,\n right: 39,\n up: 38,\n down: 40\n }\n\n this.content.addEventListener('input', this.onEditorInput.bind(this))\n\n this.container\n .querySelector('label')\n .addEventListener('click', this.onLabelClick.bind(this))\n\n this.toolbar.addEventListener('keydown', this.onToolbarKeydown.bind(this))\n }\n\n onToolbarKeydown(event) {\n let focusableButton\n switch (event.keyCode) {\n case this.keys.right:\n case this.keys.down: {\n focusableButton = this.buttons.find(\n (button) => button.getAttribute('tabindex') === '0'\n )\n const nextButton = focusableButton.nextElementSibling\n if (nextButton instanceof HTMLButtonElement) {\n nextButton.focus()\n focusableButton.setAttribute('tabindex', '-1')\n nextButton.setAttribute('tabindex', '0')\n }\n break\n }\n case this.keys.left:\n case this.keys.up: {\n focusableButton = this.buttons.find(\n (button) => button.getAttribute('tabindex') === '0'\n )\n const previousButton = focusableButton.previousElementSibling\n if (previousButton instanceof HTMLButtonElement) {\n previousButton.focus()\n focusableButton.setAttribute('tabindex', '-1')\n previousButton.setAttribute('tabindex', '0')\n }\n break\n }\n }\n }\n\n getToolbarHtml() {\n let html = ''\n\n html += '<div class=\"moj-rich-text-editor__toolbar\" role=\"toolbar\">'\n\n if (this.options.toolbar.bold) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--bold\" type=\"button\" data-command=\"bold\"><span class=\"govuk-visually-hidden\">Bold</span></button>'\n }\n\n if (this.options.toolbar.italic) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--italic\" type=\"button\" data-command=\"italic\"><span class=\"govuk-visually-hidden\">Italic</span></button>'\n }\n\n if (this.options.toolbar.underline) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--underline\" type=\"button\" data-command=\"underline\"><span class=\"govuk-visually-hidden\">Underline</span></button>'\n }\n\n if (this.options.toolbar.bullets) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--unordered-list\" type=\"button\" data-command=\"insertUnorderedList\"><span class=\"govuk-visually-hidden\">Unordered list</span></button>'\n }\n\n if (this.options.toolbar.numbers) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--ordered-list\" type=\"button\" data-command=\"insertOrderedList\"><span class=\"govuk-visually-hidden\">Ordered list</span></button>'\n }\n\n html += '</div>'\n return html\n }\n\n getEnhancedHtml() {\n return `${this.getToolbarHtml()}<div class=\"govuk-textarea moj-rich-text-editor__content\" contenteditable=\"true\" spellcheck=\"false\"></div>`\n }\n\n hideDefault() {\n this.textarea.classList.add('govuk-visually-hidden')\n this.textarea.setAttribute('aria-hidden', 'true')\n this.textarea.setAttribute('tabindex', '-1')\n }\n\n createToolbar() {\n this.toolbar = document.createElement('div')\n this.toolbar.className = 'moj-rich-text-editor'\n this.toolbar.innerHTML = this.getEnhancedHtml()\n this.container.append(this.toolbar)\n\n this.content = /** @type {HTMLDivElement} */ (\n this.container.querySelector('.moj-rich-text-editor__content')\n )\n\n this.content.innerHTML = this.$textarea.value\n }\n\n configureToolbar() {\n this.buttons = Array.from(\n /** @type {NodeListOf<HTMLButtonElement>} */\n (this.container.querySelectorAll('.moj-rich-text-editor__toolbar-button'))\n )\n\n this.buttons.forEach((button, index) => {\n button.setAttribute('tabindex', !index ? '0' : '-1')\n button.addEventListener('click', this.onButtonClick.bind(this))\n })\n }\n\n onButtonClick(event) {\n if (!(event.currentTarget instanceof HTMLElement)) {\n return\n }\n\n document.execCommand(\n event.currentTarget.getAttribute('data-command'),\n false,\n undefined\n )\n }\n\n getContent() {\n return this.content.innerHTML\n }\n\n onEditorInput() {\n this.updateTextarea()\n }\n\n updateTextarea() {\n document.execCommand('defaultParagraphSeparator', false, 'p')\n this.textarea.value = this.getContent()\n }\n\n onLabelClick(event) {\n event.preventDefault()\n this.content.focus()\n }\n}\n"],"names":["RichTextEditor","constructor","options","textarea","parentElement","HTMLTextAreaElement","document","documentElement","toolbar","bold","italic","underline","bullets","numbers","container","hasAttribute","setAttribute","createToolbar","hideDefault","configureToolbar","keys","left","right","up","down","content","addEventListener","onEditorInput","bind","querySelector","onLabelClick","onToolbarKeydown","event","focusableButton","keyCode","buttons","find","button","getAttribute","nextButton","nextElementSibling","HTMLButtonElement","focus","previousButton","previousElementSibling","getToolbarHtml","html","getEnhancedHtml","classList","add","createElement","className","innerHTML","append","$textarea","value","Array","from","querySelectorAll","forEach","index","onButtonClick","currentTarget","HTMLElement","execCommand","undefined","getContent","updateTextarea","preventDefault"],"mappings":"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"}
1
+ {"version":3,"file":"rich-text-editor.bundle.js","sources":["../../../../src/moj/components/rich-text-editor/rich-text-editor.mjs"],"sourcesContent":["import { ConfigurableComponent } from 'govuk-frontend'\n\n/**\n * @augments {ConfigurableComponent<RichTextEditorConfig>}\n */\nexport class RichTextEditor extends ConfigurableComponent {\n /**\n * @param {Element | null} $root - HTML element to use for rich text editor\n * @param {RichTextEditorConfig} config\n */\n constructor($root, config = {}) {\n super($root, config)\n\n if (!RichTextEditor.isSupported()) {\n return this\n }\n\n const $textarea = this.$root.querySelector('.govuk-textarea')\n if (!$textarea || !($textarea instanceof HTMLTextAreaElement)) {\n return this\n }\n\n this.$textarea = $textarea\n\n this.createToolbar()\n this.hideDefault()\n this.configureToolbar()\n\n this.keys = {\n left: 37,\n right: 39,\n up: 38,\n down: 40\n }\n\n this.$content.addEventListener('input', this.onEditorInput.bind(this))\n\n this.$root\n .querySelector('label')\n .addEventListener('click', this.onLabelClick.bind(this))\n\n this.$toolbar.addEventListener('keydown', this.onToolbarKeydown.bind(this))\n }\n\n /**\n * @param {KeyboardEvent} event - Click event\n */\n onToolbarKeydown(event) {\n let $focusableButton\n switch (event.keyCode) {\n case this.keys.right:\n case this.keys.down: {\n $focusableButton = this.$buttons.find(\n (button) => button.getAttribute('tabindex') === '0'\n )\n\n if ($focusableButton) {\n const $nextButton = $focusableButton.nextElementSibling\n\n if ($nextButton && $nextButton instanceof HTMLButtonElement) {\n $nextButton.focus()\n $focusableButton.setAttribute('tabindex', '-1')\n $nextButton.setAttribute('tabindex', '0')\n }\n }\n\n break\n }\n\n case this.keys.left:\n case this.keys.up: {\n $focusableButton = this.$buttons.find(\n (button) => button.getAttribute('tabindex') === '0'\n )\n\n if ($focusableButton) {\n const $previousButton = $focusableButton.previousElementSibling\n\n if ($previousButton && $previousButton instanceof HTMLButtonElement) {\n $previousButton.focus()\n $focusableButton.setAttribute('tabindex', '-1')\n $previousButton.setAttribute('tabindex', '0')\n }\n }\n\n break\n }\n }\n }\n\n getToolbarHtml() {\n let html = ''\n\n html += '<div class=\"moj-rich-text-editor__toolbar\" role=\"toolbar\">'\n\n if (this.config.toolbar.bold) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--bold\" type=\"button\" data-command=\"bold\"><span class=\"govuk-visually-hidden\">Bold</span></button>'\n }\n\n if (this.config.toolbar.italic) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--italic\" type=\"button\" data-command=\"italic\"><span class=\"govuk-visually-hidden\">Italic</span></button>'\n }\n\n if (this.config.toolbar.underline) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--underline\" type=\"button\" data-command=\"underline\"><span class=\"govuk-visually-hidden\">Underline</span></button>'\n }\n\n if (this.config.toolbar.bullets) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--unordered-list\" type=\"button\" data-command=\"insertUnorderedList\"><span class=\"govuk-visually-hidden\">Unordered list</span></button>'\n }\n\n if (this.config.toolbar.numbers) {\n html +=\n '<button class=\"moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--ordered-list\" type=\"button\" data-command=\"insertOrderedList\"><span class=\"govuk-visually-hidden\">Ordered list</span></button>'\n }\n\n html += '</div>'\n return html\n }\n\n getEnhancedHtml() {\n return `${this.getToolbarHtml()}<div class=\"govuk-textarea moj-rich-text-editor__content\" contenteditable=\"true\" spellcheck=\"false\"></div>`\n }\n\n hideDefault() {\n this.$textarea.classList.add('govuk-visually-hidden')\n this.$textarea.setAttribute('aria-hidden', 'true')\n this.$textarea.setAttribute('tabindex', '-1')\n }\n\n createToolbar() {\n this.$toolbar = document.createElement('div')\n this.$toolbar.className = 'moj-rich-text-editor'\n this.$toolbar.innerHTML = this.getEnhancedHtml()\n this.$root.append(this.$toolbar)\n\n this.$content = /** @type {HTMLElement} */ (\n this.$root.querySelector('.moj-rich-text-editor__content')\n )\n\n this.$content.innerHTML = this.$textarea.value\n }\n\n configureToolbar() {\n this.$buttons = Array.from(\n /** @type {NodeListOf<HTMLButtonElement>} */\n (this.$root.querySelectorAll('.moj-rich-text-editor__toolbar-button'))\n )\n\n this.$buttons.forEach(($button, index) => {\n $button.setAttribute('tabindex', !index ? '0' : '-1')\n $button.addEventListener('click', this.onButtonClick.bind(this))\n })\n }\n\n /**\n * @param {MouseEvent} event - Click event\n */\n onButtonClick(event) {\n if (!(event.currentTarget instanceof HTMLElement)) {\n return\n }\n\n document.execCommand(\n event.currentTarget.getAttribute('data-command'),\n false,\n undefined\n )\n }\n\n getContent() {\n return this.$content.innerHTML\n }\n\n onEditorInput() {\n this.updateTextarea()\n }\n\n updateTextarea() {\n document.execCommand('defaultParagraphSeparator', false, 'p')\n this.$textarea.value = this.getContent()\n }\n\n /**\n * @param {MouseEvent} event - Click event\n */\n onLabelClick(event) {\n event.preventDefault()\n this.$content.focus()\n }\n\n static isSupported() {\n return 'contentEditable' in document.documentElement\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'moj-rich-text-editor'\n\n /**\n * Rich text editor config\n *\n * @type {RichTextEditorConfig}\n */\n static defaults = Object.freeze({\n toolbar: {\n bold: false,\n italic: false,\n underline: false,\n bullets: true,\n numbers: true\n }\n })\n\n /**\n * Rich text editor config schema\n *\n * @satisfies {Schema<RichTextEditorConfig>}\n */\n static schema = Object.freeze(\n /** @type {const} */ ({\n properties: {\n toolbar: { type: 'object' }\n }\n })\n )\n}\n\n/**\n * Rich text editor config\n *\n * @typedef {object} RichTextEditorConfig\n * @property {RichTextEditorToolbar} [toolbar] - Toolbar options\n */\n\n/**\n * Rich text editor toolbar options\n *\n * @typedef {object} RichTextEditorToolbar\n * @property {boolean} [bold] - Show the bold button\n * @property {boolean} [italic] - Show the italic button\n * @property {boolean} [underline] - Show the underline button\n * @property {boolean} [bullets] - Show the bullets button\n * @property {boolean} [numbers] - Show the numbers button\n */\n\n/**\n * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'\n */\n"],"names":["RichTextEditor","ConfigurableComponent","constructor","$root","config","isSupported","$textarea","querySelector","HTMLTextAreaElement","createToolbar","hideDefault","configureToolbar","keys","left","right","up","down","$content","addEventListener","onEditorInput","bind","onLabelClick","$toolbar","onToolbarKeydown","event","$focusableButton","keyCode","$buttons","find","button","getAttribute","$nextButton","nextElementSibling","HTMLButtonElement","focus","setAttribute","$previousButton","previousElementSibling","getToolbarHtml","html","toolbar","bold","italic","underline","bullets","numbers","getEnhancedHtml","classList","add","document","createElement","className","innerHTML","append","value","Array","from","querySelectorAll","forEach","$button","index","onButtonClick","currentTarget","HTMLElement","execCommand","undefined","getContent","updateTextarea","preventDefault","documentElement","moduleName","defaults","Object","freeze","schema","properties","type"],"mappings":"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"}
@@ -1,28 +1,265 @@
1
- class RichTextEditor {
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
+ * @augments {ConfigurableComponent<RichTextEditorConfig>}
247
+ */
248
+ class RichTextEditor extends ConfigurableComponent {
2
249
  /**
3
- * @param {RichTextEditorConfig} options
250
+ * @param {Element | null} $root - HTML element to use for rich text editor
251
+ * @param {RichTextEditorConfig} config
4
252
  */
5
- constructor(options = {}) {
6
- const {
7
- textarea
8
- } = options;
9
- if (!textarea || !textarea.parentElement || !(textarea instanceof HTMLTextAreaElement) || !('contentEditable' in document.documentElement)) {
253
+ constructor($root, config = {}) {
254
+ super($root, config);
255
+ if (!RichTextEditor.isSupported()) {
10
256
  return this;
11
257
  }
12
- options.toolbar = options.toolbar || {
13
- bold: false,
14
- italic: false,
15
- underline: false,
16
- bullets: true,
17
- numbers: true
18
- };
19
- this.textarea = textarea;
20
- this.container = this.textarea.parentElement;
21
- this.options = options;
22
- if (this.container.hasAttribute('data-rich-text-editor-init')) {
258
+ const $textarea = this.$root.querySelector('.govuk-textarea');
259
+ if (!$textarea || !($textarea instanceof HTMLTextAreaElement)) {
23
260
  return this;
24
261
  }
25
- this.container.setAttribute('data-rich-text-editor-init', '');
262
+ this.$textarea = $textarea;
26
263
  this.createToolbar();
27
264
  this.hideDefault();
28
265
  this.configureToolbar();
@@ -32,34 +269,42 @@ class RichTextEditor {
32
269
  up: 38,
33
270
  down: 40
34
271
  };
35
- this.content.addEventListener('input', this.onEditorInput.bind(this));
36
- this.container.querySelector('label').addEventListener('click', this.onLabelClick.bind(this));
37
- this.toolbar.addEventListener('keydown', this.onToolbarKeydown.bind(this));
272
+ this.$content.addEventListener('input', this.onEditorInput.bind(this));
273
+ this.$root.querySelector('label').addEventListener('click', this.onLabelClick.bind(this));
274
+ this.$toolbar.addEventListener('keydown', this.onToolbarKeydown.bind(this));
38
275
  }
276
+
277
+ /**
278
+ * @param {KeyboardEvent} event - Click event
279
+ */
39
280
  onToolbarKeydown(event) {
40
- let focusableButton;
281
+ let $focusableButton;
41
282
  switch (event.keyCode) {
42
283
  case this.keys.right:
43
284
  case this.keys.down:
44
285
  {
45
- focusableButton = this.buttons.find(button => button.getAttribute('tabindex') === '0');
46
- const nextButton = focusableButton.nextElementSibling;
47
- if (nextButton instanceof HTMLButtonElement) {
48
- nextButton.focus();
49
- focusableButton.setAttribute('tabindex', '-1');
50
- nextButton.setAttribute('tabindex', '0');
286
+ $focusableButton = this.$buttons.find(button => button.getAttribute('tabindex') === '0');
287
+ if ($focusableButton) {
288
+ const $nextButton = $focusableButton.nextElementSibling;
289
+ if ($nextButton && $nextButton instanceof HTMLButtonElement) {
290
+ $nextButton.focus();
291
+ $focusableButton.setAttribute('tabindex', '-1');
292
+ $nextButton.setAttribute('tabindex', '0');
293
+ }
51
294
  }
52
295
  break;
53
296
  }
54
297
  case this.keys.left:
55
298
  case this.keys.up:
56
299
  {
57
- focusableButton = this.buttons.find(button => button.getAttribute('tabindex') === '0');
58
- const previousButton = focusableButton.previousElementSibling;
59
- if (previousButton instanceof HTMLButtonElement) {
60
- previousButton.focus();
61
- focusableButton.setAttribute('tabindex', '-1');
62
- previousButton.setAttribute('tabindex', '0');
300
+ $focusableButton = this.$buttons.find(button => button.getAttribute('tabindex') === '0');
301
+ if ($focusableButton) {
302
+ const $previousButton = $focusableButton.previousElementSibling;
303
+ if ($previousButton && $previousButton instanceof HTMLButtonElement) {
304
+ $previousButton.focus();
305
+ $focusableButton.setAttribute('tabindex', '-1');
306
+ $previousButton.setAttribute('tabindex', '0');
307
+ }
63
308
  }
64
309
  break;
65
310
  }
@@ -68,19 +313,19 @@ class RichTextEditor {
68
313
  getToolbarHtml() {
69
314
  let html = '';
70
315
  html += '<div class="moj-rich-text-editor__toolbar" role="toolbar">';
71
- if (this.options.toolbar.bold) {
316
+ if (this.config.toolbar.bold) {
72
317
  html += '<button class="moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--bold" type="button" data-command="bold"><span class="govuk-visually-hidden">Bold</span></button>';
73
318
  }
74
- if (this.options.toolbar.italic) {
319
+ if (this.config.toolbar.italic) {
75
320
  html += '<button class="moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--italic" type="button" data-command="italic"><span class="govuk-visually-hidden">Italic</span></button>';
76
321
  }
77
- if (this.options.toolbar.underline) {
322
+ if (this.config.toolbar.underline) {
78
323
  html += '<button class="moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--underline" type="button" data-command="underline"><span class="govuk-visually-hidden">Underline</span></button>';
79
324
  }
80
- if (this.options.toolbar.bullets) {
325
+ if (this.config.toolbar.bullets) {
81
326
  html += '<button class="moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--unordered-list" type="button" data-command="insertUnorderedList"><span class="govuk-visually-hidden">Unordered list</span></button>';
82
327
  }
83
- if (this.options.toolbar.numbers) {
328
+ if (this.config.toolbar.numbers) {
84
329
  html += '<button class="moj-rich-text-editor__toolbar-button moj-rich-text-editor__toolbar-button--ordered-list" type="button" data-command="insertOrderedList"><span class="govuk-visually-hidden">Ordered list</span></button>';
85
330
  }
86
331
  html += '</div>';
@@ -90,27 +335,31 @@ class RichTextEditor {
90
335
  return `${this.getToolbarHtml()}<div class="govuk-textarea moj-rich-text-editor__content" contenteditable="true" spellcheck="false"></div>`;
91
336
  }
92
337
  hideDefault() {
93
- this.textarea.classList.add('govuk-visually-hidden');
94
- this.textarea.setAttribute('aria-hidden', 'true');
95
- this.textarea.setAttribute('tabindex', '-1');
338
+ this.$textarea.classList.add('govuk-visually-hidden');
339
+ this.$textarea.setAttribute('aria-hidden', 'true');
340
+ this.$textarea.setAttribute('tabindex', '-1');
96
341
  }
97
342
  createToolbar() {
98
- this.toolbar = document.createElement('div');
99
- this.toolbar.className = 'moj-rich-text-editor';
100
- this.toolbar.innerHTML = this.getEnhancedHtml();
101
- this.container.append(this.toolbar);
102
- this.content = /** @type {HTMLDivElement} */
103
- this.container.querySelector('.moj-rich-text-editor__content');
104
- this.content.innerHTML = this.$textarea.value;
343
+ this.$toolbar = document.createElement('div');
344
+ this.$toolbar.className = 'moj-rich-text-editor';
345
+ this.$toolbar.innerHTML = this.getEnhancedHtml();
346
+ this.$root.append(this.$toolbar);
347
+ this.$content = /** @type {HTMLElement} */
348
+ this.$root.querySelector('.moj-rich-text-editor__content');
349
+ this.$content.innerHTML = this.$textarea.value;
105
350
  }
106
351
  configureToolbar() {
107
- this.buttons = Array.from(/** @type {NodeListOf<HTMLButtonElement>} */
108
- this.container.querySelectorAll('.moj-rich-text-editor__toolbar-button'));
109
- this.buttons.forEach((button, index) => {
110
- button.setAttribute('tabindex', !index ? '0' : '-1');
111
- button.addEventListener('click', this.onButtonClick.bind(this));
352
+ this.$buttons = Array.from(/** @type {NodeListOf<HTMLButtonElement>} */
353
+ this.$root.querySelectorAll('.moj-rich-text-editor__toolbar-button'));
354
+ this.$buttons.forEach(($button, index) => {
355
+ $button.setAttribute('tabindex', !index ? '0' : '-1');
356
+ $button.addEventListener('click', this.onButtonClick.bind(this));
112
357
  });
113
358
  }
359
+
360
+ /**
361
+ * @param {MouseEvent} event - Click event
362
+ */
114
363
  onButtonClick(event) {
115
364
  if (!(event.currentTarget instanceof HTMLElement)) {
116
365
  return;
@@ -118,20 +367,80 @@ class RichTextEditor {
118
367
  document.execCommand(event.currentTarget.getAttribute('data-command'), false, undefined);
119
368
  }
120
369
  getContent() {
121
- return this.content.innerHTML;
370
+ return this.$content.innerHTML;
122
371
  }
123
372
  onEditorInput() {
124
373
  this.updateTextarea();
125
374
  }
126
375
  updateTextarea() {
127
376
  document.execCommand('defaultParagraphSeparator', false, 'p');
128
- this.textarea.value = this.getContent();
377
+ this.$textarea.value = this.getContent();
129
378
  }
379
+
380
+ /**
381
+ * @param {MouseEvent} event - Click event
382
+ */
130
383
  onLabelClick(event) {
131
384
  event.preventDefault();
132
- this.content.focus();
385
+ this.$content.focus();
133
386
  }
387
+ static isSupported() {
388
+ return 'contentEditable' in document.documentElement;
389
+ }
390
+
391
+ /**
392
+ * Name for the component used when initialising using data-module attributes.
393
+ */
134
394
  }
135
395
 
396
+ /**
397
+ * Rich text editor config
398
+ *
399
+ * @typedef {object} RichTextEditorConfig
400
+ * @property {RichTextEditorToolbar} [toolbar] - Toolbar options
401
+ */
402
+
403
+ /**
404
+ * Rich text editor toolbar options
405
+ *
406
+ * @typedef {object} RichTextEditorToolbar
407
+ * @property {boolean} [bold] - Show the bold button
408
+ * @property {boolean} [italic] - Show the italic button
409
+ * @property {boolean} [underline] - Show the underline button
410
+ * @property {boolean} [bullets] - Show the bullets button
411
+ * @property {boolean} [numbers] - Show the numbers button
412
+ */
413
+
414
+ /**
415
+ * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'
416
+ */
417
+ RichTextEditor.moduleName = 'moj-rich-text-editor';
418
+ /**
419
+ * Rich text editor config
420
+ *
421
+ * @type {RichTextEditorConfig}
422
+ */
423
+ RichTextEditor.defaults = Object.freeze({
424
+ toolbar: {
425
+ bold: false,
426
+ italic: false,
427
+ underline: false,
428
+ bullets: true,
429
+ numbers: true
430
+ }
431
+ });
432
+ /**
433
+ * Rich text editor config schema
434
+ *
435
+ * @satisfies {Schema<RichTextEditorConfig>}
436
+ */
437
+ RichTextEditor.schema = Object.freeze(/** @type {const} */{
438
+ properties: {
439
+ toolbar: {
440
+ type: 'object'
441
+ }
442
+ }
443
+ });
444
+
136
445
  export { RichTextEditor };
137
446
  //# sourceMappingURL=rich-text-editor.bundle.mjs.map