@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":"sortable-table.bundle.js","sources":["../../../../src/moj/components/sortable-table/sortable-table.mjs"],"sourcesContent":["export class SortableTable {\n constructor(params) {\n const table = params.table\n const head = table?.querySelector('thead')\n const body = table?.querySelector('tbody')\n\n if (!table || !(table instanceof HTMLElement) || !head || !body) {\n return this\n }\n\n this.table = table\n this.head = head\n this.body = body\n\n if (this.table.hasAttribute('data-moj-sortable-table-init')) {\n return this\n }\n\n this.table.setAttribute('data-moj-sortable-table-init', '')\n\n this.headings = this.head\n ? Array.from(this.head.querySelectorAll('th'))\n : []\n\n this.setupOptions(params)\n this.createHeadingButtons()\n this.createStatusBox()\n this.initialiseSortedColumn()\n\n this.head.addEventListener('click', this.onSortButtonClick.bind(this))\n }\n\n setupOptions(params) {\n params = params || {}\n this.statusMessage =\n params.statusMessage || 'Sort by %heading% (%direction%)'\n this.ascendingText = params.ascendingText || 'ascending'\n this.descendingText = params.descendingText || 'descending'\n }\n\n createHeadingButtons() {\n for (const heading of this.headings) {\n if (heading.hasAttribute('aria-sort')) {\n this.createHeadingButton(heading)\n }\n }\n }\n\n createHeadingButton(heading) {\n const index = this.headings.indexOf(heading)\n const button = document.createElement('button')\n\n button.setAttribute('type', 'button')\n button.setAttribute('data-index', `${index}`)\n button.textContent = heading.textContent\n\n heading.textContent = ''\n heading.appendChild(button)\n }\n\n createStatusBox() {\n this.status = document.createElement('div')\n\n this.status.setAttribute('aria-atomic', 'true')\n this.status.setAttribute('aria-live', 'polite')\n this.status.setAttribute('class', 'govuk-visually-hidden')\n this.status.setAttribute('role', 'status')\n\n this.table.insertAdjacentElement('afterend', this.status)\n }\n\n initialiseSortedColumn() {\n const rows = this.getTableRowsArray()\n\n const heading = this.table.querySelector('th[aria-sort]')\n const sortButton = heading?.querySelector('button')\n const sortDirection = heading?.getAttribute('aria-sort')\n const columnNumber = Number.parseInt(\n sortButton?.getAttribute('data-index') ?? '0',\n 10\n )\n\n if (\n !heading ||\n !sortButton ||\n !(sortDirection === 'ascending' || sortDirection === 'descending')\n ) {\n return\n }\n\n const sortedRows = this.sort(rows, columnNumber, sortDirection)\n this.addRows(sortedRows)\n }\n\n onSortButtonClick(event) {\n const button = event.target\n\n if (\n !button ||\n !(button instanceof HTMLButtonElement) ||\n !button.parentElement\n ) {\n return\n }\n\n const heading = button.parentElement\n const sortDirection = heading.getAttribute('aria-sort')\n const columnNumber = Number.parseInt(\n button?.getAttribute('data-index') ?? '0',\n 10\n )\n\n const newSortDirection =\n sortDirection === 'none' || sortDirection === 'descending'\n ? 'ascending'\n : 'descending'\n\n const rows = this.getTableRowsArray()\n const sortedRows = this.sort(rows, columnNumber, newSortDirection)\n\n this.addRows(sortedRows)\n this.removeButtonStates()\n this.updateButtonState(button, newSortDirection)\n }\n\n updateButtonState(button, direction) {\n if (!(direction === 'ascending' || direction === 'descending')) {\n return\n }\n\n button.parentElement.setAttribute('aria-sort', direction)\n let message = this.statusMessage\n message = message.replace(/%heading%/, button.textContent)\n message = message.replace(/%direction%/, this[`${direction}Text`])\n this.status.textContent = message\n }\n\n removeButtonStates() {\n for (const heading of this.headings) {\n heading.setAttribute('aria-sort', 'none')\n }\n }\n\n addRows(rows) {\n for (const row of rows) {\n this.body.append(row)\n }\n }\n\n getTableRowsArray() {\n return Array.from(this.body.querySelectorAll('tr'))\n }\n\n sort(rows, columnNumber, sortDirection) {\n return rows.sort((rowA, rowB) => {\n const tdA = rowA.querySelectorAll('td, th')[columnNumber]\n const tdB = rowB.querySelectorAll('td, th')[columnNumber]\n\n if (\n !tdA ||\n !tdB ||\n !(tdA instanceof HTMLElement) ||\n !(tdB instanceof HTMLElement)\n ) {\n return 0\n }\n\n const valueA =\n sortDirection === 'ascending'\n ? this.getCellValue(tdA)\n : this.getCellValue(tdB)\n\n const valueB =\n sortDirection === 'ascending'\n ? this.getCellValue(tdB)\n : this.getCellValue(tdA)\n\n return !(typeof valueA === 'number' && typeof valueB === 'number')\n ? valueA.toString().localeCompare(valueB.toString())\n : valueA - valueB\n })\n }\n\n getCellValue(cell) {\n const val = cell.getAttribute('data-sort-value') || cell.innerHTML\n const valAsNumber = Number(val)\n\n return Number.isFinite(valAsNumber)\n ? valAsNumber // Exclude invalid numbers, infinity etc\n : val\n }\n}\n"],"names":["SortableTable","constructor","params","table","head","querySelector","body","HTMLElement","hasAttribute","setAttribute","headings","Array","from","querySelectorAll","setupOptions","createHeadingButtons","createStatusBox","initialiseSortedColumn","addEventListener","onSortButtonClick","bind","statusMessage","ascendingText","descendingText","heading","createHeadingButton","index","indexOf","button","document","createElement","textContent","appendChild","status","insertAdjacentElement","_sortButton$getAttrib","rows","getTableRowsArray","sortButton","sortDirection","getAttribute","columnNumber","Number","parseInt","sortedRows","sort","addRows","event","_button$getAttribute","target","HTMLButtonElement","parentElement","newSortDirection","removeButtonStates","updateButtonState","direction","message","replace","row","append","rowA","rowB","tdA","tdB","valueA","getCellValue","valueB","toString","localeCompare","cell","val","innerHTML","valAsNumber","isFinite"],"mappings":"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"}
1
+ {"version":3,"file":"sortable-table.bundle.js","sources":["../../../../src/moj/components/sortable-table/sortable-table.mjs"],"sourcesContent":["import { ConfigurableComponent } from 'govuk-frontend'\n\n/**\n * @augments {ConfigurableComponent<SortableTableConfig>}\n */\nexport class SortableTable extends ConfigurableComponent {\n /**\n * @param {Element | null} $root - HTML element to use for sortable table\n * @param {SortableTableConfig} [config] - Sortable table config\n */\n constructor($root, config = {}) {\n super($root, config)\n\n const $head = $root?.querySelector('thead')\n const $body = $root?.querySelector('tbody')\n\n if (!$head || !$body) {\n return this\n }\n\n this.$head = $head\n this.$body = $body\n this.$caption = this.$root.querySelector('caption')\n\n this.$upArrow = `<svg width=\"22\" height=\"22\" focusable=\"false\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 22 22\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M6.5625 15.5L11 6.63125L15.4375 15.5H6.5625Z\" fill=\"currentColor\"/>\n</svg>`\n this.$downArrow = `<svg width=\"22\" height=\"22\" focusable=\"false\" aria-hidden=\"true\" role=\"img\" vviewBox=\"0 0 22 22\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M15.4375 7L11 15.8687L6.5625 7L15.4375 7Z\" fill=\"currentColor\"/>\n</svg>`\n this.$upDownArrow = `<svg width=\"22\" height=\"22\" focusable=\"false\" aria-hidden=\"true\" role=\"img\" vviewBox=\"0 0 22 22\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z\" fill=\"currentColor\"/>\n<path d=\"M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z\" fill=\"currentColor\"/>\n</svg>`\n\n this.$headings = this.$head\n ? Array.from(this.$head.querySelectorAll('th'))\n : []\n\n this.createHeadingButtons()\n this.updateCaption()\n this.updateDirectionIndicators()\n this.createStatusBox()\n this.initialiseSortedColumn()\n\n this.$head.addEventListener('click', this.onSortButtonClick.bind(this))\n }\n\n createHeadingButtons() {\n for (const $heading of this.$headings) {\n if ($heading.hasAttribute('aria-sort')) {\n this.createHeadingButton($heading)\n }\n }\n }\n\n /**\n * @param {HTMLTableCellElement} $heading\n */\n createHeadingButton($heading) {\n const index = this.$headings.indexOf($heading)\n const $button = document.createElement('button')\n\n $button.setAttribute('type', 'button')\n $button.setAttribute('data-index', `${index}`)\n $button.textContent = $heading.textContent\n\n $heading.textContent = ''\n $heading.appendChild($button)\n }\n\n createStatusBox() {\n this.$status = document.createElement('div')\n\n this.$status.setAttribute('aria-atomic', 'true')\n this.$status.setAttribute('aria-live', 'polite')\n this.$status.setAttribute('class', 'govuk-visually-hidden')\n this.$status.setAttribute('role', 'status')\n\n this.$root.insertAdjacentElement('afterend', this.$status)\n }\n\n initialiseSortedColumn() {\n const $rows = this.getTableRowsArray()\n\n const $heading = this.$root.querySelector(\n 'th[aria-sort=\"ascending\"], th[aria-sort=\"descending\"]'\n )\n const $sortButton = $heading?.querySelector('button')\n const sortDirection = $heading?.getAttribute('aria-sort')\n\n const columnNumber = Number.parseInt(\n $sortButton?.getAttribute('data-index') ?? '0',\n 10\n )\n\n if (\n !$heading ||\n !$sortButton ||\n !(sortDirection === 'ascending' || sortDirection === 'descending')\n ) {\n return\n }\n\n const $sortedRows = this.sort($rows, columnNumber, sortDirection)\n this.addRows($sortedRows)\n }\n\n /**\n * @param {MouseEvent} event - Click event\n */\n onSortButtonClick(event) {\n const $target = /** @type {HTMLElement} */ (event.target)\n const $button = $target.closest('button')\n\n if (\n !$button ||\n !($button instanceof HTMLButtonElement) ||\n !$button.parentElement\n ) {\n return\n }\n\n const $heading = $button.parentElement\n const sortDirection = $heading.getAttribute('aria-sort')\n\n const columnNumber = Number.parseInt(\n $button?.getAttribute('data-index') ?? '0',\n 10\n )\n\n const newSortDirection =\n sortDirection === 'none' || sortDirection === 'descending'\n ? 'ascending'\n : 'descending'\n\n const $rows = this.getTableRowsArray()\n const $sortedRows = this.sort($rows, columnNumber, newSortDirection)\n\n this.addRows($sortedRows)\n this.removeButtonStates()\n this.updateButtonState($button, newSortDirection)\n this.updateDirectionIndicators()\n }\n\n updateCaption() {\n if (!this.$caption) {\n return\n }\n\n let assistiveText = this.$caption.querySelector('.govuk-visually-hidden')\n if (assistiveText) {\n return\n }\n\n assistiveText = document.createElement('span')\n assistiveText.classList.add('govuk-visually-hidden')\n assistiveText.textContent = ' (column headers with buttons are sortable).'\n\n this.$caption.appendChild(assistiveText)\n }\n\n /**\n * @param {HTMLButtonElement} $button\n * @param {string} direction\n */\n updateButtonState($button, direction) {\n if (!(direction === 'ascending' || direction === 'descending')) {\n return\n }\n\n $button.parentElement.setAttribute('aria-sort', direction)\n let message = this.config.statusMessage\n message = message.replace(/%heading%/, $button.textContent)\n message = message.replace(/%direction%/, this.config[`${direction}Text`])\n this.$status.textContent = message\n }\n\n updateDirectionIndicators() {\n for (const $heading of this.$headings) {\n const $button = /** @type {HTMLButtonElement} */ (\n $heading.querySelector('button')\n )\n if ($heading.hasAttribute('aria-sort') && $button) {\n const direction = $heading.getAttribute('aria-sort')\n $button.querySelector('svg')?.remove()\n\n switch (direction) {\n case 'ascending':\n $button.insertAdjacentHTML('beforeend', this.$upArrow)\n break\n case 'descending':\n $button.insertAdjacentHTML('beforeend', this.$downArrow)\n break\n default:\n $button.insertAdjacentHTML('beforeend', this.$upDownArrow)\n }\n }\n }\n }\n\n removeButtonStates() {\n for (const $heading of this.$headings) {\n $heading.setAttribute('aria-sort', 'none')\n }\n }\n\n /**\n * @param {HTMLTableRowElement[]} $rows\n */\n addRows($rows) {\n for (const $row of $rows) {\n this.$body.append($row)\n }\n }\n\n getTableRowsArray() {\n return Array.from(this.$body.querySelectorAll('tr'))\n }\n\n /**\n * @param {HTMLTableRowElement[]} $rows\n * @param {number} columnNumber\n * @param {string} sortDirection\n */\n sort($rows, columnNumber, sortDirection) {\n return $rows.sort(($rowA, $rowB) => {\n const $tdA = $rowA.querySelectorAll('td, th')[columnNumber]\n const $tdB = $rowB.querySelectorAll('td, th')[columnNumber]\n\n if (\n !$tdA ||\n !$tdB ||\n !($tdA instanceof HTMLElement) ||\n !($tdB instanceof HTMLElement)\n ) {\n return 0\n }\n\n const valueA =\n sortDirection === 'ascending'\n ? this.getCellValue($tdA)\n : this.getCellValue($tdB)\n\n const valueB =\n sortDirection === 'ascending'\n ? this.getCellValue($tdB)\n : this.getCellValue($tdA)\n\n return !(typeof valueA === 'number' && typeof valueB === 'number')\n ? valueA.toString().localeCompare(valueB.toString())\n : valueA - valueB\n })\n }\n\n /**\n * @param {HTMLElement} $cell\n */\n getCellValue($cell) {\n const val = $cell.getAttribute('data-sort-value') || $cell.innerHTML\n const valAsNumber = Number(val)\n\n return Number.isFinite(valAsNumber)\n ? valAsNumber // Exclude invalid numbers, infinity etc\n : val\n }\n\n /**\n * Name for the component used when initialising using data-module attributes.\n */\n static moduleName = 'moj-sortable-table'\n\n /**\n * Sortable table config\n *\n * @type {SortableTableConfig}\n */\n static defaults = Object.freeze({\n statusMessage: 'Sort by %heading% (%direction%)',\n ascendingText: 'ascending',\n descendingText: 'descending'\n })\n\n /**\n * Sortable table config schema\n *\n * @satisfies {Schema<SortableTableConfig>}\n */\n static schema = Object.freeze(\n /** @type {const} */ ({\n properties: {\n statusMessage: { type: 'string' },\n ascendingText: { type: 'string' },\n descendingText: { type: 'string' }\n }\n })\n )\n}\n\n/**\n * Sortable table config\n *\n * @typedef {object} SortableTableConfig\n * @property {string} [statusMessage] - Status message\n * @property {string} [ascendingText] - Ascending text\n * @property {string} [descendingText] - Descending text\n */\n\n/**\n * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'\n */\n"],"names":["SortableTable","ConfigurableComponent","constructor","$root","config","$head","querySelector","$body","$caption","$upArrow","$downArrow","$upDownArrow","$headings","Array","from","querySelectorAll","createHeadingButtons","updateCaption","updateDirectionIndicators","createStatusBox","initialiseSortedColumn","addEventListener","onSortButtonClick","bind","$heading","hasAttribute","createHeadingButton","index","indexOf","$button","document","createElement","setAttribute","textContent","appendChild","$status","insertAdjacentElement","_$sortButton$getAttri","$rows","getTableRowsArray","$sortButton","sortDirection","getAttribute","columnNumber","Number","parseInt","$sortedRows","sort","addRows","event","_$button$getAttribute","$target","target","closest","HTMLButtonElement","parentElement","newSortDirection","removeButtonStates","updateButtonState","assistiveText","classList","add","direction","message","statusMessage","replace","_$button$querySelecto","remove","insertAdjacentHTML","$row","append","$rowA","$rowB","$tdA","$tdB","HTMLElement","valueA","getCellValue","valueB","toString","localeCompare","$cell","val","innerHTML","valAsNumber","isFinite","moduleName","defaults","Object","freeze","ascendingText","descendingText","schema","properties","type"],"mappings":"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"}
@@ -1,126 +1,487 @@
1
- class SortableTable {
2
- constructor(params) {
3
- const table = params.table;
4
- const head = table == null ? void 0 : table.querySelector('thead');
5
- const body = table == null ? void 0 : table.querySelector('tbody');
6
- if (!table || !(table instanceof HTMLElement) || !head || !body) {
7
- return this;
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';
8
168
  }
9
- this.table = table;
10
- this.head = head;
11
- this.body = body;
12
- if (this.table.hasAttribute('data-moj-sortable-table-init')) {
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<SortableTableConfig>}
247
+ */
248
+ class SortableTable extends ConfigurableComponent {
249
+ /**
250
+ * @param {Element | null} $root - HTML element to use for sortable table
251
+ * @param {SortableTableConfig} [config] - Sortable table config
252
+ */
253
+ constructor($root, config = {}) {
254
+ super($root, config);
255
+ const $head = $root == null ? void 0 : $root.querySelector('thead');
256
+ const $body = $root == null ? void 0 : $root.querySelector('tbody');
257
+ if (!$head || !$body) {
13
258
  return this;
14
259
  }
15
- this.table.setAttribute('data-moj-sortable-table-init', '');
16
- this.headings = this.head ? Array.from(this.head.querySelectorAll('th')) : [];
17
- this.setupOptions(params);
260
+ this.$head = $head;
261
+ this.$body = $body;
262
+ this.$caption = this.$root.querySelector('caption');
263
+ this.$upArrow = `<svg width="22" height="22" focusable="false" aria-hidden="true" role="img" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
264
+ <path d="M6.5625 15.5L11 6.63125L15.4375 15.5H6.5625Z" fill="currentColor"/>
265
+ </svg>`;
266
+ this.$downArrow = `<svg width="22" height="22" focusable="false" aria-hidden="true" role="img" vviewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
267
+ <path d="M15.4375 7L11 15.8687L6.5625 7L15.4375 7Z" fill="currentColor"/>
268
+ </svg>`;
269
+ this.$upDownArrow = `<svg width="22" height="22" focusable="false" aria-hidden="true" role="img" vviewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
270
+ <path d="M8.1875 9.5L10.9609 3.95703L13.7344 9.5H8.1875Z" fill="currentColor"/>
271
+ <path d="M13.7344 12.0781L10.9609 17.6211L8.1875 12.0781H13.7344Z" fill="currentColor"/>
272
+ </svg>`;
273
+ this.$headings = this.$head ? Array.from(this.$head.querySelectorAll('th')) : [];
18
274
  this.createHeadingButtons();
275
+ this.updateCaption();
276
+ this.updateDirectionIndicators();
19
277
  this.createStatusBox();
20
278
  this.initialiseSortedColumn();
21
- this.head.addEventListener('click', this.onSortButtonClick.bind(this));
22
- }
23
- setupOptions(params) {
24
- params = params || {};
25
- this.statusMessage = params.statusMessage || 'Sort by %heading% (%direction%)';
26
- this.ascendingText = params.ascendingText || 'ascending';
27
- this.descendingText = params.descendingText || 'descending';
279
+ this.$head.addEventListener('click', this.onSortButtonClick.bind(this));
28
280
  }
29
281
  createHeadingButtons() {
30
- for (const heading of this.headings) {
31
- if (heading.hasAttribute('aria-sort')) {
32
- this.createHeadingButton(heading);
282
+ for (const $heading of this.$headings) {
283
+ if ($heading.hasAttribute('aria-sort')) {
284
+ this.createHeadingButton($heading);
33
285
  }
34
286
  }
35
287
  }
36
- createHeadingButton(heading) {
37
- const index = this.headings.indexOf(heading);
38
- const button = document.createElement('button');
39
- button.setAttribute('type', 'button');
40
- button.setAttribute('data-index', `${index}`);
41
- button.textContent = heading.textContent;
42
- heading.textContent = '';
43
- heading.appendChild(button);
288
+
289
+ /**
290
+ * @param {HTMLTableCellElement} $heading
291
+ */
292
+ createHeadingButton($heading) {
293
+ const index = this.$headings.indexOf($heading);
294
+ const $button = document.createElement('button');
295
+ $button.setAttribute('type', 'button');
296
+ $button.setAttribute('data-index', `${index}`);
297
+ $button.textContent = $heading.textContent;
298
+ $heading.textContent = '';
299
+ $heading.appendChild($button);
44
300
  }
45
301
  createStatusBox() {
46
- this.status = document.createElement('div');
47
- this.status.setAttribute('aria-atomic', 'true');
48
- this.status.setAttribute('aria-live', 'polite');
49
- this.status.setAttribute('class', 'govuk-visually-hidden');
50
- this.status.setAttribute('role', 'status');
51
- this.table.insertAdjacentElement('afterend', this.status);
302
+ this.$status = document.createElement('div');
303
+ this.$status.setAttribute('aria-atomic', 'true');
304
+ this.$status.setAttribute('aria-live', 'polite');
305
+ this.$status.setAttribute('class', 'govuk-visually-hidden');
306
+ this.$status.setAttribute('role', 'status');
307
+ this.$root.insertAdjacentElement('afterend', this.$status);
52
308
  }
53
309
  initialiseSortedColumn() {
54
- var _sortButton$getAttrib;
55
- const rows = this.getTableRowsArray();
56
- const heading = this.table.querySelector('th[aria-sort]');
57
- const sortButton = heading == null ? void 0 : heading.querySelector('button');
58
- const sortDirection = heading == null ? void 0 : heading.getAttribute('aria-sort');
59
- const columnNumber = Number.parseInt((_sortButton$getAttrib = sortButton == null ? void 0 : sortButton.getAttribute('data-index')) != null ? _sortButton$getAttrib : '0', 10);
60
- if (!heading || !sortButton || !(sortDirection === 'ascending' || sortDirection === 'descending')) {
310
+ var _$sortButton$getAttri;
311
+ const $rows = this.getTableRowsArray();
312
+ const $heading = this.$root.querySelector('th[aria-sort="ascending"], th[aria-sort="descending"]');
313
+ const $sortButton = $heading == null ? void 0 : $heading.querySelector('button');
314
+ const sortDirection = $heading == null ? void 0 : $heading.getAttribute('aria-sort');
315
+ const columnNumber = Number.parseInt((_$sortButton$getAttri = $sortButton == null ? void 0 : $sortButton.getAttribute('data-index')) != null ? _$sortButton$getAttri : '0', 10);
316
+ if (!$heading || !$sortButton || !(sortDirection === 'ascending' || sortDirection === 'descending')) {
61
317
  return;
62
318
  }
63
- const sortedRows = this.sort(rows, columnNumber, sortDirection);
64
- this.addRows(sortedRows);
319
+ const $sortedRows = this.sort($rows, columnNumber, sortDirection);
320
+ this.addRows($sortedRows);
65
321
  }
322
+
323
+ /**
324
+ * @param {MouseEvent} event - Click event
325
+ */
66
326
  onSortButtonClick(event) {
67
- var _button$getAttribute;
68
- const button = event.target;
69
- if (!button || !(button instanceof HTMLButtonElement) || !button.parentElement) {
327
+ var _$button$getAttribute;
328
+ const $target = /** @type {HTMLElement} */event.target;
329
+ const $button = $target.closest('button');
330
+ if (!$button || !($button instanceof HTMLButtonElement) || !$button.parentElement) {
70
331
  return;
71
332
  }
72
- const heading = button.parentElement;
73
- const sortDirection = heading.getAttribute('aria-sort');
74
- const columnNumber = Number.parseInt((_button$getAttribute = button == null ? void 0 : button.getAttribute('data-index')) != null ? _button$getAttribute : '0', 10);
333
+ const $heading = $button.parentElement;
334
+ const sortDirection = $heading.getAttribute('aria-sort');
335
+ const columnNumber = Number.parseInt((_$button$getAttribute = $button == null ? void 0 : $button.getAttribute('data-index')) != null ? _$button$getAttribute : '0', 10);
75
336
  const newSortDirection = sortDirection === 'none' || sortDirection === 'descending' ? 'ascending' : 'descending';
76
- const rows = this.getTableRowsArray();
77
- const sortedRows = this.sort(rows, columnNumber, newSortDirection);
78
- this.addRows(sortedRows);
337
+ const $rows = this.getTableRowsArray();
338
+ const $sortedRows = this.sort($rows, columnNumber, newSortDirection);
339
+ this.addRows($sortedRows);
79
340
  this.removeButtonStates();
80
- this.updateButtonState(button, newSortDirection);
341
+ this.updateButtonState($button, newSortDirection);
342
+ this.updateDirectionIndicators();
343
+ }
344
+ updateCaption() {
345
+ if (!this.$caption) {
346
+ return;
347
+ }
348
+ let assistiveText = this.$caption.querySelector('.govuk-visually-hidden');
349
+ if (assistiveText) {
350
+ return;
351
+ }
352
+ assistiveText = document.createElement('span');
353
+ assistiveText.classList.add('govuk-visually-hidden');
354
+ assistiveText.textContent = ' (column headers with buttons are sortable).';
355
+ this.$caption.appendChild(assistiveText);
81
356
  }
82
- updateButtonState(button, direction) {
357
+
358
+ /**
359
+ * @param {HTMLButtonElement} $button
360
+ * @param {string} direction
361
+ */
362
+ updateButtonState($button, direction) {
83
363
  if (!(direction === 'ascending' || direction === 'descending')) {
84
364
  return;
85
365
  }
86
- button.parentElement.setAttribute('aria-sort', direction);
87
- let message = this.statusMessage;
88
- message = message.replace(/%heading%/, button.textContent);
89
- message = message.replace(/%direction%/, this[`${direction}Text`]);
90
- this.status.textContent = message;
366
+ $button.parentElement.setAttribute('aria-sort', direction);
367
+ let message = this.config.statusMessage;
368
+ message = message.replace(/%heading%/, $button.textContent);
369
+ message = message.replace(/%direction%/, this.config[`${direction}Text`]);
370
+ this.$status.textContent = message;
371
+ }
372
+ updateDirectionIndicators() {
373
+ for (const $heading of this.$headings) {
374
+ const $button = /** @type {HTMLButtonElement} */
375
+ $heading.querySelector('button');
376
+ if ($heading.hasAttribute('aria-sort') && $button) {
377
+ var _$button$querySelecto;
378
+ const direction = $heading.getAttribute('aria-sort');
379
+ (_$button$querySelecto = $button.querySelector('svg')) == null || _$button$querySelecto.remove();
380
+ switch (direction) {
381
+ case 'ascending':
382
+ $button.insertAdjacentHTML('beforeend', this.$upArrow);
383
+ break;
384
+ case 'descending':
385
+ $button.insertAdjacentHTML('beforeend', this.$downArrow);
386
+ break;
387
+ default:
388
+ $button.insertAdjacentHTML('beforeend', this.$upDownArrow);
389
+ }
390
+ }
391
+ }
91
392
  }
92
393
  removeButtonStates() {
93
- for (const heading of this.headings) {
94
- heading.setAttribute('aria-sort', 'none');
394
+ for (const $heading of this.$headings) {
395
+ $heading.setAttribute('aria-sort', 'none');
95
396
  }
96
397
  }
97
- addRows(rows) {
98
- for (const row of rows) {
99
- this.body.append(row);
398
+
399
+ /**
400
+ * @param {HTMLTableRowElement[]} $rows
401
+ */
402
+ addRows($rows) {
403
+ for (const $row of $rows) {
404
+ this.$body.append($row);
100
405
  }
101
406
  }
102
407
  getTableRowsArray() {
103
- return Array.from(this.body.querySelectorAll('tr'));
408
+ return Array.from(this.$body.querySelectorAll('tr'));
104
409
  }
105
- sort(rows, columnNumber, sortDirection) {
106
- return rows.sort((rowA, rowB) => {
107
- const tdA = rowA.querySelectorAll('td, th')[columnNumber];
108
- const tdB = rowB.querySelectorAll('td, th')[columnNumber];
109
- if (!tdA || !tdB || !(tdA instanceof HTMLElement) || !(tdB instanceof HTMLElement)) {
410
+
411
+ /**
412
+ * @param {HTMLTableRowElement[]} $rows
413
+ * @param {number} columnNumber
414
+ * @param {string} sortDirection
415
+ */
416
+ sort($rows, columnNumber, sortDirection) {
417
+ return $rows.sort(($rowA, $rowB) => {
418
+ const $tdA = $rowA.querySelectorAll('td, th')[columnNumber];
419
+ const $tdB = $rowB.querySelectorAll('td, th')[columnNumber];
420
+ if (!$tdA || !$tdB || !($tdA instanceof HTMLElement) || !($tdB instanceof HTMLElement)) {
110
421
  return 0;
111
422
  }
112
- const valueA = sortDirection === 'ascending' ? this.getCellValue(tdA) : this.getCellValue(tdB);
113
- const valueB = sortDirection === 'ascending' ? this.getCellValue(tdB) : this.getCellValue(tdA);
423
+ const valueA = sortDirection === 'ascending' ? this.getCellValue($tdA) : this.getCellValue($tdB);
424
+ const valueB = sortDirection === 'ascending' ? this.getCellValue($tdB) : this.getCellValue($tdA);
114
425
  return !(typeof valueA === 'number' && typeof valueB === 'number') ? valueA.toString().localeCompare(valueB.toString()) : valueA - valueB;
115
426
  });
116
427
  }
117
- getCellValue(cell) {
118
- const val = cell.getAttribute('data-sort-value') || cell.innerHTML;
428
+
429
+ /**
430
+ * @param {HTMLElement} $cell
431
+ */
432
+ getCellValue($cell) {
433
+ const val = $cell.getAttribute('data-sort-value') || $cell.innerHTML;
119
434
  const valAsNumber = Number(val);
120
435
  return Number.isFinite(valAsNumber) ? valAsNumber // Exclude invalid numbers, infinity etc
121
436
  : val;
122
437
  }
438
+
439
+ /**
440
+ * Name for the component used when initialising using data-module attributes.
441
+ */
123
442
  }
124
443
 
444
+ /**
445
+ * Sortable table config
446
+ *
447
+ * @typedef {object} SortableTableConfig
448
+ * @property {string} [statusMessage] - Status message
449
+ * @property {string} [ascendingText] - Ascending text
450
+ * @property {string} [descendingText] - Descending text
451
+ */
452
+
453
+ /**
454
+ * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'
455
+ */
456
+ SortableTable.moduleName = 'moj-sortable-table';
457
+ /**
458
+ * Sortable table config
459
+ *
460
+ * @type {SortableTableConfig}
461
+ */
462
+ SortableTable.defaults = Object.freeze({
463
+ statusMessage: 'Sort by %heading% (%direction%)',
464
+ ascendingText: 'ascending',
465
+ descendingText: 'descending'
466
+ });
467
+ /**
468
+ * Sortable table config schema
469
+ *
470
+ * @satisfies {Schema<SortableTableConfig>}
471
+ */
472
+ SortableTable.schema = Object.freeze(/** @type {const} */{
473
+ properties: {
474
+ statusMessage: {
475
+ type: 'string'
476
+ },
477
+ ascendingText: {
478
+ type: 'string'
479
+ },
480
+ descendingText: {
481
+ type: 'string'
482
+ }
483
+ }
484
+ });
485
+
125
486
  export { SortableTable };
126
487
  //# sourceMappingURL=sortable-table.bundle.mjs.map