{"version":3,"sources":["components/modal/modal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAwC,UAAU,EAAE,MAAM,aAAa,CAAC;AAG/E,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAIpC,OAAO,EAAE,UAAU,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCtB;;;;;;;;GAQG;AACH,cACM,OAAQ,SAAQ,YAA6B;IACjD;;OAEG;IACH,OAAO,CAAC,SAAS,CAAwB;IAEzC;;OAEG;IAEH,OAAO,CAAC,kBAAkB,CAAqB;IAE/C;;OAEG;IAEH,OAAO,CAAC,gBAAgB,CAAqB;IAE7C;;;OAGG;IAGH,OAAO,CAAC,YAAY,CAIlB;IAEF;;;OAGG;IAGH,OAAO,CAAC,WAAW,CA8BjB;IAIF,OAAO,CAAC,cAAc,CAIpB;IAEF;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAiBjC;;OAEG;IAEH,cAAc,SAAM;IAEpB;;OAEG;IAEH,IAAI,UAAS;IAEb;;OAEG;IAEH,IAAI,aAAsB;IAE1B,MAAM;IAoBA,OAAO,CAAC,iBAAiB,KAAA;IAoB/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,MAAM;IAMrB;;OAEG;IACH,MAAM,KAAK,mBAAmB,WAE7B;IAED;;OAEG;IACH,MAAM,KAAK,gBAAgB,WAE1B;IAED;;OAEG;IACH,MAAM,KAAK,oBAAoB,WAE9B;IAED;;;OAGG;IACH,MAAM,KAAK,gBAAgB,WAE1B;IAED;;OAEG;IACH,MAAM,KAAK,UAAU,WAEpB;IAED,MAAM,CAAC,MAAM,MAAU;CACxB;AAED,eAAe,OAAO,CAAC","file":"modal.d.ts","sourcesContent":["/**\n * @license\n *\n * Copyright IBM Corp. 2019, 2020\n *\n * This source code is licensed under the Apache-2.0 license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport settings from 'carbon-components/es/globals/js/settings';\nimport { classMap } from 'lit-html/directives/class-map';\nimport { html, property, query, customElement, LitElement } from 'lit-element';\nimport HostListener from '../../globals/decorators/host-listener';\nimport HostListenerMixin from '../../globals/mixins/host-listener';\nimport { MODAL_SIZE } from './defs';\nimport styles from './modal.scss';\nimport { selectorTabbable } from '../../globals/settings';\n\nexport { MODAL_SIZE };\n\nconst { prefix } = settings;\n\n// eslint-disable-next-line no-bitwise\nconst PRECEDING = Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS;\n// eslint-disable-next-line no-bitwise\nconst FOLLOWING = Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY;\n\n/**\n * Tries to focus on the given elements and bails out if one of them is successful.\n * @param elems The elements.\n * @param reverse `true` to go through the list in reverse order.\n * @returns `true` if one of the attempts is successful, `false` otherwise.\n */\nfunction tryFocusElems(elems: NodeListOf<HTMLElement>, reverse: boolean = false) {\n  if (!reverse) {\n    for (let i = 0; i < elems.length; ++i) {\n      const elem = elems[i];\n      elem.focus();\n      if (elem.ownerDocument!.activeElement === elem) {\n        return true;\n      }\n    }\n  } else {\n    for (let i = elems.length - 1; i >= 0; --i) {\n      const elem = elems[i];\n      elem.focus();\n      if (elem.ownerDocument!.activeElement === elem) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Modal.\n * @element bx-modal\n * @csspart dialog The dialog.\n * @fires bx-modal-beingclosed\n *   The custom event fired before this modal is being closed upon a user gesture.\n *   Cancellation of this event stops the user-initiated action of closing this modal.\n * @fires bx-modal-closed - The custom event fired after this modal is closed upon a user gesture.\n */\n@customElement(`${prefix}-modal`)\nclass BXModal extends HostListenerMixin(LitElement) {\n  /**\n   * The element that had focus before this modal gets open.\n   */\n  private _launcher: Element | null = null;\n\n  /**\n   * Node to track focus going outside of modal content.\n   */\n  @query('#start-sentinel')\n  private _startSentinelNode!: HTMLAnchorElement;\n\n  /**\n   * Node to track focus going outside of modal content.\n   */\n  @query('#end-sentinel')\n  private _endSentinelNode!: HTMLAnchorElement;\n\n  /**\n   * Handles `click` event on this element.\n   * @param event The event.\n   */\n  @HostListener('click')\n  // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to\n  private _handleClick = (event: MouseEvent) => {\n    if (event.composedPath().indexOf(this.shadowRoot!) < 0) {\n      this._handleUserInitiatedClose(event.target);\n    }\n  };\n\n  /**\n   * Handles `blur` event on this element.\n   * @param event The event.\n   */\n  @HostListener('shadowRoot:focusout')\n  // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to\n  private _handleBlur = async ({ target, relatedTarget }: FocusEvent) => {\n    const { open, _startSentinelNode: startSentinelNode, _endSentinelNode: endSentinelNode } = this;\n    const oldContains = target !== this && this.contains(target as Node);\n    const currentContains =\n      relatedTarget !== this &&\n      (this.contains(relatedTarget as Node) ||\n        (this.shadowRoot?.contains(relatedTarget as Node) && relatedTarget !== (endSentinelNode as Node)));\n\n    // Performs focus wrapping if _all_ of the following is met:\n    // * This modal is open\n    // * The viewport still has focus\n    // * Modal body used to have focus but no longer has focus\n    const { selectorTabbable: selectorTabbableForModal } = this.constructor as typeof BXModal;\n    if (open && relatedTarget && oldContains && !currentContains) {\n      const comparisonResult = (target as Node).compareDocumentPosition(relatedTarget as Node);\n      // eslint-disable-next-line no-bitwise\n      if (relatedTarget === startSentinelNode || comparisonResult & PRECEDING) {\n        await (this.constructor as typeof BXModal)._delay();\n        if (!tryFocusElems(this.querySelectorAll(selectorTabbableForModal), true) && relatedTarget !== this) {\n          this.focus();\n        }\n      }\n      // eslint-disable-next-line no-bitwise\n      else if (relatedTarget === endSentinelNode || comparisonResult & FOLLOWING) {\n        await (this.constructor as typeof BXModal)._delay();\n        if (!tryFocusElems(this.querySelectorAll(selectorTabbableForModal))) {\n          this.focus();\n        }\n      }\n    }\n  };\n\n  @HostListener('document:keydown')\n  // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to\n  private _handleKeydown = ({ key, target }: KeyboardEvent) => {\n    if (key === 'Esc' || key === 'Escape') {\n      this._handleUserInitiatedClose(target);\n    }\n  };\n\n  /**\n   * Handles `click` event on the modal container.\n   * @param event The event.\n   */\n  private _handleClickContainer(event: MouseEvent) {\n    if ((event.target as Element).matches((this.constructor as typeof BXModal).selectorCloseButton)) {\n      this._handleUserInitiatedClose(event.target);\n    }\n  }\n\n  /**\n   * Handles user-initiated close request of this modal.\n   * @param triggeredBy The element that triggered this close request.\n   */\n  private _handleUserInitiatedClose(triggeredBy: EventTarget | null) {\n    if (this.open) {\n      const init = {\n        bubbles: true,\n        cancelable: true,\n        composed: true,\n        detail: {\n          triggeredBy,\n        },\n      };\n      if (this.dispatchEvent(new CustomEvent((this.constructor as typeof BXModal).eventBeforeClose, init))) {\n        this.open = false;\n        this.dispatchEvent(new CustomEvent((this.constructor as typeof BXModal).eventClose, init));\n      }\n    }\n  }\n\n  /**\n   * The additional CSS class names for the container <div> of the element.\n   */\n  @property({ attribute: 'container-class' })\n  containerClass = '';\n\n  /**\n   * `true` if the modal should be open.\n   */\n  @property({ type: Boolean, reflect: true })\n  open = false;\n\n  /**\n   * Modal size.\n   */\n  @property({ reflect: true })\n  size = MODAL_SIZE.REGULAR;\n\n  render() {\n    const { size } = this;\n    const containerClass = this.containerClass\n      .split(' ')\n      .filter(Boolean)\n      .reduce((acc, item) => ({ ...acc, [item]: true }), {});\n    const containerClasses = classMap({\n      [`${prefix}--modal-container`]: true,\n      [`${prefix}--modal-container--${size}`]: size,\n      ...containerClass,\n    });\n    return html`\n      <a id=\"start-sentinel\" class=\"${prefix}--visually-hidden\" href=\"javascript:void 0\" role=\"navigation\"></a>\n      <div part=\"dialog\" class=${containerClasses} role=\"dialog\" tabindex=\"-1\" @click=${this._handleClickContainer}>\n        <slot></slot>\n      </div>\n      <a id=\"end-sentinel\" class=\"${prefix}--visually-hidden\" href=\"javascript:void 0\" role=\"navigation\"></a>\n    `;\n  }\n\n  async updated(changedProperties) {\n    if (changedProperties.has('open')) {\n      if (this.open) {\n        this._launcher = this.ownerDocument!.activeElement;\n        const primaryFocusNode = this.querySelector((this.constructor as typeof BXModal).selectorPrimaryFocus);\n        await (this.constructor as typeof BXModal)._delay();\n        if (primaryFocusNode) {\n          // For cases where a `carbon-web-components` component (e.g. `<bx-btn>`) being `primaryFocusNode`,\n          // where its first update/render cycle that makes it focusable happens after `<bx-modal>`'s first update/render cycle\n          (primaryFocusNode as HTMLElement).focus();\n        } else if (!tryFocusElems(this.querySelectorAll((this.constructor as typeof BXModal).selectorTabbable), true)) {\n          this.focus();\n        }\n      } else if (this._launcher && typeof (this._launcher as HTMLElement).focus === 'function') {\n        (this._launcher as HTMLElement).focus();\n        this._launcher = null;\n      }\n    }\n  }\n\n  /**\n   * @param ms The number of milliseconds.\n   * @returns A promise that is resolves after the given milliseconds.\n   */\n  private static _delay(ms: number = 0) {\n    return new Promise(resolve => {\n      setTimeout(resolve, ms);\n    });\n  }\n\n  /**\n   * A selector selecting buttons that should close this modal.\n   */\n  static get selectorCloseButton() {\n    return `[data-modal-close],${prefix}-modal-close-button`;\n  }\n\n  /**\n   * A selector selecting tabbable nodes.\n   */\n  static get selectorTabbable() {\n    return selectorTabbable;\n  }\n\n  /**\n   * A selector selecting the nodes that should be focused when modal gets open.\n   */\n  static get selectorPrimaryFocus() {\n    return `[data-modal-primary-focus],${prefix}-modal-footer ${prefix}-btn[kind=\"primary\"]`;\n  }\n\n  /**\n   * The name of the custom event fired before this modal is being closed upon a user gesture.\n   * Cancellation of this event stops the user-initiated action of closing this modal.\n   */\n  static get eventBeforeClose() {\n    return `${prefix}-modal-beingclosed`;\n  }\n\n  /**\n   * The name of the custom event fired after this modal is closed upon a user gesture.\n   */\n  static get eventClose() {\n    return `${prefix}-modal-closed`;\n  }\n\n  static styles = styles; // `styles` here is a `CSSResult` generated by custom WebPack loader\n}\n\nexport default BXModal;\n"]}