{"version":3,"file":"error-summary.mjs","sources":["../../../../src/govuk/components/error-summary/error-summary.mjs"],"sourcesContent":["import { ConfigurableComponent } from '../../common/configuration.mjs'\nimport { setFocus } from '../../common/index.mjs'\n\n/**\n * Error summary component\n *\n * Takes focus on initialisation for accessible announcement, unless disabled in\n * configuration.\n *\n * @preserve\n * @augments ConfigurableComponent<ErrorSummaryConfig>\n */\nexport class ErrorSummary extends ConfigurableComponent {\n  /**\n   * @param {Element | null} $root - HTML element to use for error summary\n   * @param {ErrorSummaryConfig} [config] - Error summary config\n   */\n  constructor($root, config = {}) {\n    super($root, config)\n\n    /**\n     * Focus the error summary\n     */\n    if (!this.config.disableAutoFocus) {\n      setFocus(this.$root)\n    }\n\n    this.$root.addEventListener('click', (event) => this.handleClick(event))\n  }\n\n  /**\n   * Click event handler\n   *\n   * @private\n   * @param {MouseEvent} event - Click event\n   */\n  handleClick(event) {\n    const $target = event.target\n    if ($target && this.focusTarget($target)) {\n      event.preventDefault()\n    }\n  }\n\n  /**\n   * Focus the target element\n   *\n   * By default, the browser will scroll the target into view. Because our\n   * labels or legends appear above the input, this means the user will be\n   * presented with an input without any context, as the label or legend will be\n   * off the top of the screen.\n   *\n   * Manually handling the click event, scrolling the question into view and\n   * then focussing the element solves this.\n   *\n   * This also results in the label and/or legend being announced correctly in\n   * NVDA (as tested in 2018.3.2) - without this only the field type is\n   * announced (e.g. \"Edit, has autocomplete\").\n   *\n   * @private\n   * @param {EventTarget} $target - Event target\n   * @returns {boolean} True if the target was able to be focussed\n   */\n  focusTarget($target) {\n    // If the element that was clicked was not a link, return early\n    if (!($target instanceof HTMLAnchorElement)) {\n      return false\n    }\n\n    const inputId = $target.hash.replace('#', '')\n    if (!inputId) {\n      return false\n    }\n\n    const $input = document.getElementById(inputId)\n    if (!$input) {\n      return false\n    }\n\n    const $legendOrLabel = this.getAssociatedLegendOrLabel($input)\n    if (!$legendOrLabel) {\n      return false\n    }\n\n    // Scroll the legend or label into view *before* calling focus on the input\n    // to avoid extra scrolling in browsers that don't support `preventScroll`\n    // (which at time of writing is most of them...)\n    $legendOrLabel.scrollIntoView()\n    $input.focus({ preventScroll: true })\n\n    return true\n  }\n\n  /**\n   * Get associated legend or label\n   *\n   * Returns the first element that exists from this list:\n   *\n   * - The `<legend>` associated with the closest `<fieldset>` ancestor, as long\n   *   as the top of it is no more than half a viewport height away from the\n   *   bottom of the input\n   * - The first `<label>` that is associated with the input using for=\"inputId\"\n   * - The closest parent `<label>`\n   *\n   * @private\n   * @param {Element} $input - The input\n   * @returns {Element | null} Associated legend or label, or null if no\n   *   associated legend or label can be found\n   */\n  getAssociatedLegendOrLabel($input) {\n    const $fieldset = $input.closest('fieldset')\n\n    if ($fieldset) {\n      const $legends = $fieldset.getElementsByTagName('legend')\n\n      if ($legends.length) {\n        const $candidateLegend = $legends[0]\n\n        // If the input type is radio or checkbox, always use the legend if\n        // there is one.\n        if (\n          $input instanceof HTMLInputElement &&\n          ($input.type === 'checkbox' || $input.type === 'radio')\n        ) {\n          return $candidateLegend\n        }\n\n        // For other input types, only scroll to the fieldset’s legend (instead\n        // of the label associated with the input) if the input would end up in\n        // the top half of the screen.\n        //\n        // This should avoid situations where the input either ends up off the\n        // screen, or obscured by a software keyboard.\n        const legendTop = $candidateLegend.getBoundingClientRect().top\n        const inputRect = $input.getBoundingClientRect()\n\n        // If the browser doesn't support Element.getBoundingClientRect().height\n        // or window.innerHeight (like IE8), bail and just link to the label.\n        if (inputRect.height && window.innerHeight) {\n          const inputBottom = inputRect.top + inputRect.height\n\n          if (inputBottom - legendTop < window.innerHeight / 2) {\n            return $candidateLegend\n          }\n        }\n      }\n    }\n\n    return (\n      document.querySelector(`label[for='${$input.getAttribute('id')}']`) ??\n      $input.closest('label')\n    )\n  }\n\n  /**\n   * Name for the component used when initialising using data-module attributes.\n   */\n  static moduleName = 'govuk-error-summary'\n\n  /**\n   * Error summary default config\n   *\n   * @see {@link ErrorSummaryConfig}\n   * @constant\n   * @type {ErrorSummaryConfig}\n   */\n  static defaults = Object.freeze({\n    disableAutoFocus: false\n  })\n\n  /**\n   * Error summary config schema\n   *\n   * @constant\n   * @satisfies {Schema<ErrorSummaryConfig>}\n   */\n  static schema = Object.freeze({\n    properties: {\n      disableAutoFocus: { type: 'boolean' }\n    }\n  })\n}\n\n/**\n * Error summary config\n *\n * @typedef {object} ErrorSummaryConfig\n * @property {boolean} [disableAutoFocus=false] - If set to `true` the error\n *   summary will not be focussed when the page loads.\n */\n\n/**\n * @import { Schema } from '../../common/configuration.mjs'\n */\n"],"names":["ErrorSummary","ConfigurableComponent","constructor","$root","config","disableAutoFocus","setFocus","addEventListener","event","handleClick","$target","target","focusTarget","preventDefault","HTMLAnchorElement","inputId","hash","replace","$input","document","getElementById","$legendOrLabel","getAssociatedLegendOrLabel","scrollIntoView","focus","preventScroll","_document$querySelect","$fieldset","closest","$legends","getElementsByTagName","length","$candidateLegend","HTMLInputElement","type","legendTop","getBoundingClientRect","top","inputRect","height","window","innerHeight","inputBottom","querySelector","getAttribute","moduleName","defaults","Object","freeze","schema","properties"],"mappings":"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"}