From f7f7f783ed7be1914f8789661e6d203fa38ec127 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Wed, 14 Dec 2022 19:20:02 +0000 Subject: [PATCH] Ensure JSDoc blocks for source are complete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All blocks are documented with a description, params, optional return (or throws) types and “this” where instantiated or bound --- src/govuk/common/index.mjs | 27 ++-- src/govuk/components/accordion/accordion.mjs | 83 ++++++++-- src/govuk/components/button/button.mjs | 11 +- src/govuk/components/button/button.test.js | 3 +- .../character-count/character-count.mjs | 3 +- .../components/checkboxes/checkboxes.mjs | 12 +- src/govuk/components/details/details.mjs | 11 +- .../error-summary/error-summary.mjs | 10 +- src/govuk/components/header/header.mjs | 3 +- .../notification-banner.mjs | 5 +- src/govuk/components/radios/radios.mjs | 5 +- src/govuk/components/skip-link/skip-link.mjs | 3 +- src/govuk/components/tabs/tabs.mjs | 144 ++++++++++++++++-- src/govuk/i18n.mjs | 12 +- 14 files changed, 267 insertions(+), 65 deletions(-) diff --git a/src/govuk/common/index.mjs b/src/govuk/common/index.mjs index 6adbee5c4e..fb2b3e8bdb 100644 --- a/src/govuk/common/index.mjs +++ b/src/govuk/common/index.mjs @@ -15,7 +15,7 @@ * * @param {NodeListOf} nodes - NodeList from querySelectorAll() * @param {nodeListIterator} callback - Callback function to run for each node - * @returns {undefined} + * @returns {void} */ export function nodeListForEach (nodes, callback) { if (window.NodeList.prototype.forEach) { @@ -52,7 +52,7 @@ export function generateUniqueID () { * (e.g. {'i18n.showSection': 'Show section'}) and combines them together, with * greatest priority on the LAST item passed in. * - * @returns {object} A flattened object of key-value pairs. + * @returns {Object} A flattened object of key-value pairs. */ export function mergeConfigs (/* configObject1, configObject2, ...configObjects */) { /** @@ -61,16 +61,21 @@ export function mergeConfigs (/* configObject1, configObject2, ...configObjects * each of our objects, nor transform our dataset from a flat list into a * nested object. * - * @param {object} configObject - Deeply nested object - * @returns {object} Flattened object with dot-separated keys + * @param {Object} configObject - Deeply nested object + * @returns {Object} Flattened object with dot-separated keys */ var flattenObject = function (configObject) { // Prepare an empty return object var flattenedObject = {} - // Our flattening function, this is called recursively for each level of - // depth in the object. At each level we prepend the previous level names to - // the key using `prefix`. + /** + * Our flattening function, this is called recursively for each level of + * depth in the object. At each level we prepend the previous level names to + * the key using `prefix`. + * + * @param {Object} obj - Object to flatten + * @param {string} [prefix] - Optional dot-separated prefix + */ var flattenLoop = function (obj, prefix) { // Loop through keys... for (var key in obj) { @@ -118,9 +123,11 @@ export function mergeConfigs (/* configObject1, configObject2, ...configObjects * Extracts keys starting with a particular namespace from a flattened config * object, removing the namespace in the process. * - * @param {object} configObject - The object to extract key-value pairs from. + * @param {Object} configObject - The object to extract key-value pairs from. * @param {string} namespace - The namespace to filter keys with. - * @returns {object} Flattened object with dot-separated key namespace removed + * @returns {Object} Flattened object with dot-separated key namespace removed + * @throws {Error} Config object required + * @throws {Error} Namespace string required */ export function extractConfigByNamespace (configObject, namespace) { // Check we have what we need @@ -155,5 +162,5 @@ export function extractConfigByNamespace (configObject, namespace) { * @param {Element} value - The current node being iterated on * @param {number} index - The current index in the iteration * @param {NodeListOf} nodes - NodeList from querySelectorAll() - * @returns {undefined} + * @returns {void} */ diff --git a/src/govuk/components/accordion/accordion.mjs b/src/govuk/components/accordion/accordion.mjs index 79d1c68158..e78cf078eb 100644 --- a/src/govuk/components/accordion/accordion.mjs +++ b/src/govuk/components/accordion/accordion.mjs @@ -36,6 +36,7 @@ var ACCORDION_TRANSLATIONS = { * @class * @param {HTMLElement} $module - HTML element to use for accordion * @param {AccordionConfig} [config] - Accordion config + * @this {Accordion} */ function Accordion ($module, config) { this.$module = $module @@ -75,7 +76,9 @@ function Accordion ($module, config) { this.sectionContentClass = 'govuk-accordion__section-content' } -// Initialize component +/** + * Initialise component + */ Accordion.prototype.init = function () { // Check for module if (!this.$module) { @@ -90,7 +93,9 @@ Accordion.prototype.init = function () { this.updateShowAllButton(areAllSectionsOpen) } -// Initialise controls and set attributes +/** + * Initialise controls and set attributes + */ Accordion.prototype.initControls = function () { // Create "Show all" button and set attributes this.$showAllButton = document.createElement('button') @@ -123,7 +128,9 @@ Accordion.prototype.initControls = function () { } } -// Initialise section headers +/** + * Initialise section headers + */ Accordion.prototype.initSectionHeaders = function () { // Loop through section headers nodeListForEach(this.$sections, function ($section, i) { @@ -141,6 +148,12 @@ Accordion.prototype.initSectionHeaders = function () { }.bind(this)) } +/** + * Construct section header + * + * @param {HTMLDivElement} $headerWrapper - Section header wrapper + * @param {number} index - Section index + */ Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) { var $span = $headerWrapper.querySelector('.' + this.sectionButtonClass) var $heading = $headerWrapper.querySelector('.' + this.sectionHeadingClass) @@ -237,7 +250,11 @@ Accordion.prototype.constructHeaderMarkup = function ($headerWrapper, index) { $heading.appendChild($button) } -// When a section is opened by the user agent via the 'beforematch' event +/** + * When a section is opened by the user agent via the 'beforematch' event + * + * @param {Event} event - Generic event + */ Accordion.prototype.onBeforeMatch = function (event) { var $section = event.target.closest('.' + this.sectionClass) if ($section) { @@ -245,7 +262,11 @@ Accordion.prototype.onBeforeMatch = function (event) { } } -// When section toggled, set and store state +/** + * When section toggled, set and store state + * + * @param {HTMLElement} $section - Section element + */ Accordion.prototype.onSectionToggle = function ($section) { var expanded = this.isExpanded($section) this.setExpanded(!expanded, $section) @@ -254,7 +275,9 @@ Accordion.prototype.onSectionToggle = function ($section) { this.storeState($section) } -// When Open/Close All toggled, set and store state +/** + * When Open/Close All toggled, set and store state + */ Accordion.prototype.onShowOrHideAllToggle = function () { var $module = this var $sections = this.$sections @@ -269,7 +292,12 @@ Accordion.prototype.onShowOrHideAllToggle = function () { $module.updateShowAllButton(nowExpanded) } -// Set section attributes when opened/closed +/** + * Set section attributes when opened/closed + * + * @param {boolean} expanded - Section expanded + * @param {HTMLElement} $section - Section element + */ Accordion.prototype.setExpanded = function (expanded, $section) { var $icon = $section.querySelector('.' + this.upChevronIconClass) var $showHideText = $section.querySelector('.' + this.sectionShowHideTextClass) @@ -320,12 +348,21 @@ Accordion.prototype.setExpanded = function (expanded, $section) { this.updateShowAllButton(areAllSectionsOpen) } -// Get state of section +/** + * Get state of section + * + * @param {HTMLElement} $section - Section element + * @returns {boolean} True if expanded + */ Accordion.prototype.isExpanded = function ($section) { return $section.classList.contains(this.sectionExpandedClass) } -// Check if all sections are open +/** + * Check if all sections are open + * + * @returns {boolean} True if all sections are open + */ Accordion.prototype.checkIfAllSectionsOpen = function () { // Get a count of all the Accordion sections var sectionsCount = this.$sections.length @@ -336,7 +373,11 @@ Accordion.prototype.checkIfAllSectionsOpen = function () { return areAllSectionsOpen } -// Update "Show all sections" button +/** + * Update "Show all sections" button + * + * @param {boolean} expanded - Section expanded + */ Accordion.prototype.updateShowAllButton = function (expanded) { var $showAllIcon = this.$showAllButton.querySelector('.' + this.upChevronIconClass) var $showAllText = this.$showAllButton.querySelector('.' + this.showAllTextClass) @@ -354,8 +395,12 @@ Accordion.prototype.updateShowAllButton = function (expanded) { } } -// Check for `window.sessionStorage`, and that it actually works. var helper = { + /** + * Check for `window.sessionStorage`, and that it actually works. + * + * @returns {boolean} True if session storage is available + */ checkForSessionStorage: function () { var testString = 'this is the test string' var result @@ -370,7 +415,11 @@ var helper = { } } -// Set the state of the accordions in sessionStorage +/** + * Set the state of the accordions in sessionStorage + * + * @param {HTMLElement} $section - Section element + */ Accordion.prototype.storeState = function ($section) { if (this.browserSupportsSessionStorage) { // We need a unique way of identifying each content in the Accordion. Since @@ -390,7 +439,11 @@ Accordion.prototype.storeState = function ($section) { } } -// Read the state of the accordions from sessionStorage +/** + * Read the state of the accordions from sessionStorage + * + * @param {HTMLElement} $section - Section element + */ Accordion.prototype.setInitialState = function ($section) { if (this.browserSupportsSessionStorage) { var $button = $section.querySelector('.' + this.sectionButtonClass) @@ -409,11 +462,11 @@ Accordion.prototype.setInitialState = function ($section) { /** * Create an element to improve semantics of the section button with punctuation * - * @returns {HTMLSpanElement} DOM element - * * Adding punctuation to the button can also improve its general semantics by dividing its contents * into thematic chunks. * See https://github.com/alphagov/govuk-frontend/issues/2327#issuecomment-922957442 + * + * @returns {HTMLElement} DOM element */ Accordion.prototype.getButtonPunctuationEl = function () { var $punctuationEl = document.createElement('span') diff --git a/src/govuk/components/button/button.mjs b/src/govuk/components/button/button.mjs index c48e7757ac..4ebd831d1f 100644 --- a/src/govuk/components/button/button.mjs +++ b/src/govuk/components/button/button.mjs @@ -10,8 +10,9 @@ var DEBOUNCE_TIMEOUT_IN_SECONDS = 1 * JavaScript enhancements for the Button component * * @class - * @param {HTMLElement} $module - The element this component controls - * @param {ButtonConfig} config - Button config + * @param {HTMLElement} $module - HTML element to use for button + * @param {ButtonConfig} [config] - Button config + * @this {Button} */ function Button ($module, config) { if (!$module) { @@ -51,7 +52,7 @@ Button.prototype.init = function () { * * See https://github.com/alphagov/govuk_elements/pull/272#issuecomment-233028270 * - * @param {KeyboardEvent} event + * @param {KeyboardEvent} event - Keydown event */ Button.prototype.handleKeyDown = function (event) { var target = event.target @@ -69,8 +70,8 @@ Button.prototype.handleKeyDown = function (event) { * stops people accidentally causing multiple form submissions by double * clicking buttons. * - * @param {MouseEvent} event - * @returns {undefined | false} - Returns undefined, or false when debounced + * @param {MouseEvent} event - Mouse click event + * @returns {undefined | false} Returns undefined, or false when debounced */ Button.prototype.debounce = function (event) { // Check the button that was clicked has preventDoubleClick enabled diff --git a/src/govuk/components/button/button.test.js b/src/govuk/components/button/button.test.js index 66d7ce752c..38ba595ff1 100644 --- a/src/govuk/components/button/button.test.js +++ b/src/govuk/components/button/button.test.js @@ -61,7 +61,7 @@ describe('/components/button', () => { * Examples don't do this and we need it to have something to submit * * @param {import('puppeteer').Page} page - Puppeteer page object - * @returns {undefined} + * @returns {Promise} */ function trackClicks (page) { return page.evaluate(() => { @@ -82,6 +82,7 @@ describe('/components/button', () => { /** * Gets the number of times the form was submitted * + * @param {import('puppeteer').Page} page - Puppeteer page object * @returns {number} Number of times the form was submitted */ function getClicksCount (page) { diff --git a/src/govuk/components/character-count/character-count.mjs b/src/govuk/components/character-count/character-count.mjs index d27b772c53..d6c78117d1 100644 --- a/src/govuk/components/character-count/character-count.mjs +++ b/src/govuk/components/character-count/character-count.mjs @@ -50,8 +50,9 @@ var CHARACTER_COUNT_TRANSLATIONS = { * of the available characters/words has been entered. * * @class - * @param {HTMLElement} $module - The element this component controls + * @param {HTMLElement} $module - HTML element to use for character count * @param {CharacterCountConfig} [config] - Character count config + * @this {CharacterCount} */ function CharacterCount ($module, config) { if (!$module) { diff --git a/src/govuk/components/checkboxes/checkboxes.mjs b/src/govuk/components/checkboxes/checkboxes.mjs index 4bca9f1e2f..d74a9cb7e7 100644 --- a/src/govuk/components/checkboxes/checkboxes.mjs +++ b/src/govuk/components/checkboxes/checkboxes.mjs @@ -8,6 +8,7 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs' * * @class * @param {HTMLElement} $module - HTML element to use for checkboxes + * @this {Checkboxes} */ function Checkboxes ($module) { this.$module = $module @@ -15,7 +16,7 @@ function Checkboxes ($module) { } /** - * Initialise Checkboxes + * Initialise component * * Checkboxes can be associated with a 'conditionally revealed' content block – * for example, a checkbox for 'Phone' could reveal an additional form field for @@ -62,11 +63,12 @@ Checkboxes.prototype.init = function () { // for example if they are added to the page dynamically, so sync now too. this.syncAllConditionalReveals() + // Handle events $module.addEventListener('click', this.handleClick.bind(this)) } /** - * Sync the conditional reveal states for all inputs in this $module. + * Sync the conditional reveal states for all checkboxes in this $module. */ Checkboxes.prototype.syncAllConditionalReveals = function () { nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this)) @@ -96,6 +98,8 @@ Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) { * * Find any other checkbox inputs with the same name value, and uncheck them. * This is useful for when a “None of these" checkbox is checked. + * + * @param {HTMLElement} $input - Checkbox input */ Checkboxes.prototype.unCheckAllInputsExcept = function ($input) { var allInputsWithSameName = document.querySelectorAll('input[type="checkbox"][name="' + $input.name + '"]') @@ -110,11 +114,13 @@ Checkboxes.prototype.unCheckAllInputsExcept = function ($input) { } /** - * Uncheck exclusive inputs + * Uncheck exclusive checkboxes * * Find any checkbox inputs with the same name value and the 'exclusive' behaviour, * and uncheck them. This helps prevent someone checking both a regular checkbox and a * "None of these" checkbox in the same fieldset. + * + * @param {HTMLInputElement} $input - Checkbox input */ Checkboxes.prototype.unCheckExclusiveInputs = function ($input) { var allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll( diff --git a/src/govuk/components/details/details.mjs b/src/govuk/components/details/details.mjs index cc44150df1..ebe84ba523 100644 --- a/src/govuk/components/details/details.mjs +++ b/src/govuk/components/details/details.mjs @@ -16,11 +16,15 @@ var KEY_SPACE = 32 * * @class * @param {HTMLElement} $module - HTML element to use for details + * @this {Details} */ function Details ($module) { this.$module = $module } +/** + * Initialise component + */ Details.prototype.init = function () { if (!this.$module) { return @@ -36,6 +40,9 @@ Details.prototype.init = function () { this.polyfillDetails() } +/** + * Polyfill component in older browsers + */ Details.prototype.polyfillDetails = function () { var $module = this.$module @@ -144,6 +151,6 @@ export default Details /** * @callback polyfillHandleInputsCallback - * @param {KeyboardEvent} event - Keyboard event - * @returns {undefined} + * @param {UIEvent} event - Keyboard or mouse event + * @returns {void} */ diff --git a/src/govuk/components/error-summary/error-summary.mjs b/src/govuk/components/error-summary/error-summary.mjs index bcc02f246c..016c425ded 100644 --- a/src/govuk/components/error-summary/error-summary.mjs +++ b/src/govuk/components/error-summary/error-summary.mjs @@ -10,8 +10,9 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs' * Takes focus on initialisation for accessible announcement, unless disabled in configuration. * * @class - * @param {HTMLElement} $module - The element this component controls - * @param {ErrorSummaryConfig} config - Error summary config + * @param {HTMLElement} $module - HTML element to use for error summary + * @param {ErrorSummaryConfig} [config] - Error summary config + * @this {ErrorSummary} */ function ErrorSummary ($module, config) { // Some consuming code may not be passing a module, @@ -39,6 +40,9 @@ function ErrorSummary ($module, config) { ) } +/** + * Initialise component + */ ErrorSummary.prototype.init = function () { var $module = this.$module if (!$module) { @@ -97,7 +101,7 @@ ErrorSummary.prototype.handleClick = function (event) { * NVDA (as tested in 2018.3.2) - without this only the field type is announced * (e.g. "Edit, has autocomplete"). * - * @param {HTMLElement} $target - Event target + * @param {EventTarget} $target - Event target * @returns {boolean} True if the target was able to be focussed */ ErrorSummary.prototype.focusTarget = function ($target) { diff --git a/src/govuk/components/header/header.mjs b/src/govuk/components/header/header.mjs index f00b2979fa..41bcc95dc4 100644 --- a/src/govuk/components/header/header.mjs +++ b/src/govuk/components/header/header.mjs @@ -7,6 +7,7 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs' * * @class * @param {HTMLElement} $module - HTML element to use for header + * @this {Header} */ function Header ($module) { this.$module = $module @@ -28,7 +29,7 @@ function Header ($module) { } /** - * Initialise header + * Initialise component * * Check for the presence of the header, menu and menu button – if any are * missing then there's nothing to do so return early. diff --git a/src/govuk/components/notification-banner/notification-banner.mjs b/src/govuk/components/notification-banner/notification-banner.mjs index 1fa57400a7..b26df99c7d 100644 --- a/src/govuk/components/notification-banner/notification-banner.mjs +++ b/src/govuk/components/notification-banner/notification-banner.mjs @@ -7,7 +7,8 @@ import '../../vendor/polyfills/Event.mjs' // addEventListener, event.target norm * * @class * @param {HTMLElement} $module - HTML element to use for notification banner - * @param {NotificationBannerConfig} config - Notification banner config + * @param {NotificationBannerConfig} [config] - Notification banner config + * @this {NotificationBanner} */ function NotificationBanner ($module, config) { this.$module = $module @@ -23,7 +24,7 @@ function NotificationBanner ($module, config) { } /** - * Initialise the component + * Initialise component */ NotificationBanner.prototype.init = function () { var $module = this.$module diff --git a/src/govuk/components/radios/radios.mjs b/src/govuk/components/radios/radios.mjs index 6391fe8635..1d9f32855d 100644 --- a/src/govuk/components/radios/radios.mjs +++ b/src/govuk/components/radios/radios.mjs @@ -8,6 +8,7 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs' * * @class * @param {HTMLElement} $module - HTML element to use for radios + * @this {Radios} */ function Radios ($module) { this.$module = $module @@ -15,7 +16,7 @@ function Radios ($module) { } /** - * Initialise Radios + * Initialise component * * Radios can be associated with a 'conditionally revealed' content block – for * example, a radio for 'Phone' could reveal an additional form field for the @@ -67,7 +68,7 @@ Radios.prototype.init = function () { } /** - * Sync the conditional reveal states for all inputs in this $module. + * Sync the conditional reveal states for all radio buttons in this $module. */ Radios.prototype.syncAllConditionalReveals = function () { nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this)) diff --git a/src/govuk/components/skip-link/skip-link.mjs b/src/govuk/components/skip-link/skip-link.mjs index 28e9b771f1..4df0f27141 100644 --- a/src/govuk/components/skip-link/skip-link.mjs +++ b/src/govuk/components/skip-link/skip-link.mjs @@ -7,6 +7,7 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs' * * @class * @param {HTMLElement} $module - HTML element to use for skip link + * @this {SkipLink} */ function SkipLink ($module) { this.$module = $module @@ -15,7 +16,7 @@ function SkipLink ($module) { } /** - * Initialise the component + * Initialise component */ SkipLink.prototype.init = function () { // Check for module diff --git a/src/govuk/components/tabs/tabs.mjs b/src/govuk/components/tabs/tabs.mjs index 510d8a1ae5..7ba3209a40 100644 --- a/src/govuk/components/tabs/tabs.mjs +++ b/src/govuk/components/tabs/tabs.mjs @@ -10,6 +10,7 @@ import '../../vendor/polyfills/Function/prototype/bind.mjs' * * @class * @param {HTMLElement} $module - HTML element to use for tabs + * @this {Tabs} */ function Tabs ($module) { this.$module = $module @@ -19,6 +20,9 @@ function Tabs ($module) { this.jsHiddenClass = 'govuk-tabs__panel--hidden' } +/** + * Initialise component + */ Tabs.prototype.init = function () { if (typeof window.matchMedia === 'function') { this.setupResponsiveChecks() @@ -27,12 +31,18 @@ Tabs.prototype.init = function () { } } +/** + * Setup viewport resize check + */ Tabs.prototype.setupResponsiveChecks = function () { this.mql = window.matchMedia('(min-width: 40.0625em)') this.mql.addListener(this.checkMode.bind(this)) this.checkMode() } +/** + * Setup or teardown handler for viewport resize check + */ Tabs.prototype.checkMode = function () { if (this.mql.matches) { this.setup() @@ -41,6 +51,9 @@ Tabs.prototype.checkMode = function () { } } +/** + * Setup tab component + */ Tabs.prototype.setup = function () { var $module = this.$module var $tabs = this.$tabs @@ -82,6 +95,9 @@ Tabs.prototype.setup = function () { window.addEventListener('hashchange', $module.boundOnHashChange, true) } +/** + * Teardown tab component + */ Tabs.prototype.teardown = function () { var $module = this.$module var $tabs = this.$tabs @@ -111,7 +127,13 @@ Tabs.prototype.teardown = function () { window.removeEventListener('hashchange', $module.boundOnHashChange, true) } -Tabs.prototype.onHashChange = function (e) { +/** + * Handle hashchange event + * + * @param {HashChangeEvent} event - Hash change event + * @returns {void | undefined} Returns void, or undefined when prevented + */ +Tabs.prototype.onHashChange = function (event) { var hash = window.location.hash var $tabWithHash = this.getTab(hash) if (!$tabWithHash) { @@ -132,20 +154,41 @@ Tabs.prototype.onHashChange = function (e) { $tabWithHash.focus() } +/** + * Hide panel for tab link + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.hideTab = function ($tab) { this.unhighlightTab($tab) this.hidePanel($tab) } +/** + * Show panel for tab link + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.showTab = function ($tab) { this.highlightTab($tab) this.showPanel($tab) } +/** + * Get tab link by hash + * + * @param {string} hash - Hash fragment including # + * @returns {HTMLAnchorElement | null} Tab link + */ Tabs.prototype.getTab = function (hash) { return this.$module.querySelector('.govuk-tabs__tab[href="' + hash + '"]') } +/** + * Set tab link and panel attributes + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.setAttributes = function ($tab) { // set tab attributes var panelId = this.getHref($tab).slice(1) @@ -162,6 +205,11 @@ Tabs.prototype.setAttributes = function ($tab) { $panel.classList.add(this.jsHiddenClass) } +/** + * Unset tab link and panel attributes + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.unsetAttributes = function ($tab) { // unset tab attributes $tab.removeAttribute('id') @@ -177,19 +225,33 @@ Tabs.prototype.unsetAttributes = function ($tab) { $panel.classList.remove(this.jsHiddenClass) } -Tabs.prototype.onTabClick = function (e) { - if (!e.target.classList.contains('govuk-tabs__tab')) { - // Allow events on child DOM elements to bubble up to tab parent +/** + * Handle tab link clicks + * + * @param {MouseEvent} event - Mouse click event + * @returns {void | false} Returns void, or false within tab link + */ +Tabs.prototype.onTabClick = function (event) { + if (!event.target.classList.contains('govuk-tabs__tab')) { + // Allow events on child DOM elements to bubble up to tab parent return false } - e.preventDefault() - var $newTab = e.target + event.preventDefault() + var $newTab = event.target var $currentTab = this.getCurrentTab() this.hideTab($currentTab) this.showTab($newTab) this.createHistoryEntry($newTab) } +/** + * Update browser URL hash fragment for tab + * + * - Allows back/forward to navigate tabs + * - Avoids page jump when hash changes + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.createHistoryEntry = function ($tab) { var $panel = this.getPanel($tab) @@ -202,21 +264,32 @@ Tabs.prototype.createHistoryEntry = function ($tab) { $panel.id = id } -Tabs.prototype.onTabKeydown = function (e) { - switch (e.keyCode) { +/** + * Handle tab keydown event + * + * - Press right/down arrow for next tab + * - Press left/up arrow for previous tab + * + * @param {KeyboardEvent} event - Keydown event + */ +Tabs.prototype.onTabKeydown = function (event) { + switch (event.keyCode) { case this.keys.left: case this.keys.up: this.activatePreviousTab() - e.preventDefault() + event.preventDefault() break case this.keys.right: case this.keys.down: this.activateNextTab() - e.preventDefault() + event.preventDefault() break } } +/** + * Activate next tab + */ Tabs.prototype.activateNextTab = function () { var currentTab = this.getCurrentTab() var nextTabListItem = currentTab.parentNode.nextElementSibling @@ -231,6 +304,9 @@ Tabs.prototype.activateNextTab = function () { } } +/** + * Activate previous tab + */ Tabs.prototype.activatePreviousTab = function () { var currentTab = this.getCurrentTab() var previousTabListItem = currentTab.parentNode.previousElementSibling @@ -245,40 +321,78 @@ Tabs.prototype.activatePreviousTab = function () { } } +/** + * Get tab panel for tab link + * + * @param {HTMLAnchorElement} $tab - Tab link + * @returns {HTMLDivElement} Tab panel + */ Tabs.prototype.getPanel = function ($tab) { var $panel = this.$module.querySelector(this.getHref($tab)) return $panel } +/** + * Show tab panel for tab link + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.showPanel = function ($tab) { var $panel = this.getPanel($tab) $panel.classList.remove(this.jsHiddenClass) } -Tabs.prototype.hidePanel = function (tab) { - var $panel = this.getPanel(tab) +/** + * Hide tab panel for tab link + * + * @param {HTMLAnchorElement} $tab - Tab link + */ +Tabs.prototype.hidePanel = function ($tab) { + var $panel = this.getPanel($tab) $panel.classList.add(this.jsHiddenClass) } +/** + * Unset 'selected' state for tab link + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.unhighlightTab = function ($tab) { $tab.setAttribute('aria-selected', 'false') $tab.parentNode.classList.remove('govuk-tabs__list-item--selected') $tab.setAttribute('tabindex', '-1') } +/** + * Set 'selected' state for tab link + * + * @param {HTMLAnchorElement} $tab - Tab link + */ Tabs.prototype.highlightTab = function ($tab) { $tab.setAttribute('aria-selected', 'true') $tab.parentNode.classList.add('govuk-tabs__list-item--selected') $tab.setAttribute('tabindex', '0') } +/** + * Get current tab link + * + * @returns {HTMLAnchorElement | undefined} Tab link + */ Tabs.prototype.getCurrentTab = function () { return this.$module.querySelector('.govuk-tabs__list-item--selected .govuk-tabs__tab') } -// this is because IE doesn't always return the actual value but a relative full path -// should be a utility function most prob -// http://labs.thesedays.com/blog/2010/01/08/getting-the-href-value-with-jquery-in-ie/ +/** + * Get link hash fragment for href attribute + * + * this is because IE doesn't always return the actual value but a relative full path + * should be a utility function most prob + * {@link http://labs.thesedays.com/blog/2010/01/08/getting-the-href-value-with-jquery-in-ie/} + * + * @param {HTMLAnchorElement} $tab - Tab link + * @returns {string} Hash fragment including # + */ Tabs.prototype.getHref = function ($tab) { var href = $tab.getAttribute('href') var hash = href.slice(href.indexOf('#'), href.length) diff --git a/src/govuk/i18n.mjs b/src/govuk/i18n.mjs index ee4035bd9e..90665773e8 100644 --- a/src/govuk/i18n.mjs +++ b/src/govuk/i18n.mjs @@ -6,7 +6,8 @@ * @private * @param {TranslationsFlattened} translations - Key-value pairs of the translation strings to use. * @param {object} [config] - Configuration options for the function. - * @param {string} config.locale - An overriding locale for the PluralRules functionality. + * @param {string} [config.locale] - An overriding locale for the PluralRules functionality. + * @this {I18n} */ export function I18n (translations, config) { // Make list of translations available throughout function @@ -21,8 +22,10 @@ export function I18n (translations, config) { * returns the appropriate string. * * @param {string} lookupKey - The lookup key of the string to use. - * @param {object} options - Any options passed with the translation string, e.g: for string interpolation. + * @param {Object} [options] - Any options passed with the translation string, e.g: for string interpolation. * @returns {string} The appropriate translation string. + * @throws {Error} Lookup key required + * @throws {Error} Options required for `${}` placeholders */ I18n.prototype.t = function (lookupKey, options) { if (!lookupKey) { @@ -64,7 +67,7 @@ I18n.prototype.t = function (lookupKey, options) { * with the provided data * * @param {string} translationString - The translation string - * @param {object} options - Any options passed with the translation string, e.g: for string interpolation. + * @param {Object} options - Any options passed with the translation string, e.g: for string interpolation. * @returns {string} The translation string to output, with ${} placeholders replaced */ I18n.prototype.replacePlaceholders = function (translationString, options) { @@ -137,6 +140,7 @@ I18n.prototype.hasIntlNumberFormatSupport = function () { * @param {string} lookupKey - The lookup key of the string to use. * @param {number} count - Number used to determine which pluralisation to use. * @returns {PluralRule} The suffix associated with the correct pluralisation for this locale. + * @throws {Error} Plural form `.other` required when preferred plural form is missing */ I18n.prototype.getPluralSuffix = function (lookupKey, count) { // Validate that the number is actually a number. @@ -376,5 +380,5 @@ I18n.pluralRules = { * Translated messages (flattened) * * @private - * @typedef {Object | {}} TranslationsFlattened + * @typedef {Object} TranslationsFlattened */