{"version":3,"sources":["components/floating-menu/floating-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,MAAM,MAAM,+BAA+B,CAAC;AACnD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,gCAAgC,EACjC,MAAM,QAAQ,CAAC;AAChB,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,gCAAgC,EAAE,CAAC;AAE7H;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,SAAS,EAAE,gCAAgC,CAAC;IAE5C;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;CACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCD;;GAEG;AACH,uBAAe,cAAe,SAAQ,mBAAyC;IAC7E;;OAEG;IACH,OAAO,CAAC,qBAAqB,CAAuB;IAEpD;;OAEG;IACH,OAAO,CAAC,wBAAwB,CAAuB;IAEvD;;OAEG;IAGH,OAAO,CAAC,eAAe,CAOpB;IAIH,OAAO,CAAC,WAAW,CAQjB;IAEF;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAQ;IAEtD;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAE5C;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAE5C;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,IAAI,kBAAkB,kCAOrB;IAED;;OAEG;IACH,IAAI,SAAS,QAEZ;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,oBAAoB,CA2EnC;IAED,gBAAgB;IAOhB,oBAAoB;IASpB,OAAO,CAAC,iBAAiB,KAAA;IA6BzB;;OAEG;IACH,MAAM,CAAC,aAAa,UAAQ;IAE5B;;OAEG;IACH,MAAM,KAAK,iBAAiB,WAE3B;CACF;AAED,eAAe,cAAc,CAAC","file":"floating-menu.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 { LitElement } from 'lit-element';\nimport HostListener from '../../globals/decorators/host-listener';\nimport FocusMixin from '../../globals/mixins/focus';\nimport HostListenerMixin from '../../globals/mixins/host-listener';\nimport Handle from '../../globals/internal/handle';\nimport {\n  FLOATING_MENU_ALIGNMENT,\n  FLOATING_MENU_DIRECTION,\n  FLOATING_MENU_DIRECTION_GROUP,\n  FLOATING_MENU_POSITION_DIRECTION,\n} from './defs';\nimport BXFloatingMenuTrigger from './floating-menu-trigger';\n\nexport { FLOATING_MENU_ALIGNMENT, FLOATING_MENU_DIRECTION, FLOATING_MENU_DIRECTION_GROUP, FLOATING_MENU_POSITION_DIRECTION };\n\n/**\n * Position of floating menu, or trigger button of floating menu.\n */\nexport interface FloatingMenuPosition {\n  /**\n   * The LTR/RTL direction used for positioning floating menu.\n   */\n  direction: FLOATING_MENU_POSITION_DIRECTION;\n\n  /**\n   * The start position (Left position for LTR, right position for RTL).\n   */\n  start: number;\n\n  /**\n   * The top position.\n   */\n  top: number;\n}\n\n/**\n * Observes resize of the given element with the given resize observer.\n * @param observer The resize observer.\n * @param elem The element to observe the resize.\n */\nconst observeResize = (observer: ResizeObserver, elem: Element) => {\n  observer.observe(elem);\n  return {\n    release() {\n      observer.unobserve(elem);\n      return null;\n    },\n  } as Handle;\n};\n\n/**\n * @param elem The starting element.\n * @param selector The CSS selector.\n * @returns {Element}\n *   The closest ancestor node of the given element that matches the given selector, crossing Shadow DOM boundary.\n */\nconst closestComposed = (elem: Element, selector: string) => {\n  const found = elem.closest(selector);\n  if (found) {\n    return found;\n  }\n  const { host } = elem.getRootNode() as ShadowRoot;\n  if (host) {\n    return closestComposed(host, selector);\n  }\n  return null;\n};\n\n/**\n * Floating menu.\n */\nabstract class BXFloatingMenu extends HostListenerMixin(FocusMixin(LitElement)) {\n  /**\n   * The handle for observing resize of the element containing the trigger button.\n   */\n  private _hObserveResizeParent: Handle | null = null;\n\n  /**\n   * The handle for observing resize of the floating menu container.\n   */\n  private _hObserveResizeContainer: Handle | null = null;\n\n  /**\n   * The `ResizeObserver` instance for observing element resizes for re-positioning floating menu position.\n   */\n  // TODO: Wait for `.d.ts` update to support `ResizeObserver`\n  // @ts-ignore\n  private _resizeObserver = new ResizeObserver(() => {\n    const { container, open, parent, position } = this;\n    if (container && open && parent) {\n      const { direction, start, top } = position;\n      this.style[direction !== FLOATING_MENU_POSITION_DIRECTION.RTL ? 'left' : 'right'] = `${start}px`;\n      this.style.top = `${top}px`;\n    }\n  });\n\n  @HostListener('focusout')\n  // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to\n  private _handleBlur = ({ relatedTarget }: FocusEvent) => {\n    if (!this.contains(relatedTarget as Node)) {\n      const { parent } = this;\n      if (parent) {\n        parent.open = false;\n        HTMLElement.prototype.focus.call(this.parent); // SVGElement in IE11 does not have `.focus()` method\n      }\n    }\n  };\n\n  /**\n   * The DOM element, typically a custom element in this library, launching this floating menu.\n   */\n  protected parent: BXFloatingMenuTrigger | null = null;\n\n  /**\n   * How the menu is aligned to the trigger button.\n   */\n  abstract alignment: FLOATING_MENU_ALIGNMENT;\n\n  /**\n   * The menu direction.\n   */\n  abstract direction: FLOATING_MENU_DIRECTION;\n\n  /**\n   * `true` if the menu should be open.\n   */\n  abstract open: boolean;\n\n  /**\n   * The horizontal/vertical direction with regard to how the menu is aligned to the trigger button.\n   */\n  get alignmentDirection() {\n    return {\n      [FLOATING_MENU_DIRECTION.LEFT]: FLOATING_MENU_DIRECTION_GROUP.VERTICAL,\n      [FLOATING_MENU_DIRECTION.TOP]: FLOATING_MENU_DIRECTION_GROUP.HORIZONTAL,\n      [FLOATING_MENU_DIRECTION.RIGHT]: FLOATING_MENU_DIRECTION_GROUP.VERTICAL,\n      [FLOATING_MENU_DIRECTION.BOTTOM]: FLOATING_MENU_DIRECTION_GROUP.HORIZONTAL,\n    }[this.direction];\n  }\n\n  /**\n   * The DOM element to put this menu into.\n   */\n  get container() {\n    return closestComposed(this, (this.constructor as typeof BXFloatingMenu).selectorContainer) || this.ownerDocument!.body;\n  }\n\n  /**\n   * The position of this floating menu.\n   */\n  get position(): FloatingMenuPosition {\n    const { triggerPosition } = this.parent!;\n    if (!triggerPosition) {\n      throw new TypeError('Missing information of trigger button position.');\n    }\n\n    const { container } = this;\n    const { left: refLeft = 0, top: refTop = 0, right: refRight = 0, bottom: refBottom = 0 } = triggerPosition;\n    const { width, height } = this.getBoundingClientRect();\n    const { left: containerLeft = 0, right: containerRight = 0, top: containerTop = 0 } = container.getBoundingClientRect();\n    const refCenterHorizontal = (refLeft + refRight) / 2;\n    const refCenterVertical = (refTop + refBottom) / 2;\n\n    const containerComputedStyle = container.ownerDocument!.defaultView!.getComputedStyle(container);\n    const positionDirection = containerComputedStyle.getPropertyValue('direction') as FLOATING_MENU_POSITION_DIRECTION;\n    const isRtl = positionDirection === FLOATING_MENU_POSITION_DIRECTION.RTL;\n    const containerStartFromViewport = !isRtl ? containerLeft : container.ownerDocument!.defaultView!.innerWidth - containerRight;\n    const refStartFromContainer = !isRtl ? refLeft - containerLeft : containerRight - refRight;\n    const refEndFromContainer = !isRtl ? refRight - containerLeft : containerRight - refLeft;\n    const refCenterHorizontalFromContainer = !isRtl ? refCenterHorizontal - containerLeft : containerRight - refCenterHorizontal;\n    const refTopFromContainer = refTop - containerTop;\n    const refCenterVerticalFromContainer = refCenterVertical - containerTop;\n\n    if (\n      (container !== this.ownerDocument!.body || containerStartFromViewport !== 0 || containerTop !== 0) &&\n      containerComputedStyle.getPropertyValue('position') === 'static'\n    ) {\n      throw new Error('Floating menu container must not have `position:static`.');\n    }\n\n    const { alignment, alignmentDirection, direction } = this;\n    if (Object.values(FLOATING_MENU_ALIGNMENT).indexOf(alignment) < 0) {\n      throw new Error(`Wrong menu alignment: ${alignment}`);\n    }\n    if (Object.values(FLOATING_MENU_DIRECTION).indexOf(direction) < 0) {\n      throw new Error(`Wrong menu position direction: ${direction}`);\n    }\n\n    const alignmentStart = {\n      [FLOATING_MENU_DIRECTION_GROUP.HORIZONTAL]: {\n        [FLOATING_MENU_ALIGNMENT.START]: () => refStartFromContainer,\n        [FLOATING_MENU_ALIGNMENT.CENTER]: () => refCenterHorizontalFromContainer - width / 2,\n        [FLOATING_MENU_ALIGNMENT.END]: () => refEndFromContainer - width,\n      },\n      [FLOATING_MENU_DIRECTION_GROUP.VERTICAL]: {\n        [FLOATING_MENU_ALIGNMENT.START]: () => refTopFromContainer,\n        [FLOATING_MENU_ALIGNMENT.CENTER]: () => refCenterVerticalFromContainer - height / 2,\n        [FLOATING_MENU_ALIGNMENT.END]: () => refBottom - height,\n      },\n    }[alignmentDirection][alignment]();\n\n    const { start, top } = {\n      [FLOATING_MENU_DIRECTION.LEFT]: () => ({\n        start: refStartFromContainer - width,\n        top: alignmentStart,\n      }),\n      [FLOATING_MENU_DIRECTION.TOP]: () => ({\n        start: alignmentStart,\n        top: refTopFromContainer - height,\n      }),\n      [FLOATING_MENU_DIRECTION.RIGHT]: () => ({\n        start: refEndFromContainer,\n        top: alignmentStart,\n      }),\n      [FLOATING_MENU_DIRECTION.BOTTOM]: () => ({\n        start: alignmentStart,\n        top: refBottom,\n      }),\n    }[direction]();\n\n    return {\n      direction: positionDirection,\n      start,\n      top,\n    };\n  }\n\n  createRenderRoot() {\n    return this.attachShadow({\n      mode: 'open',\n      delegatesFocus: Number((/Safari\\/(\\d+)/.exec(navigator.userAgent) ?? ['', 0])[1]) <= 537,\n    });\n  }\n\n  disconnectedCallback() {\n    if (this._hObserveResizeContainer) {\n      this._hObserveResizeContainer = this._hObserveResizeContainer.release();\n    }\n    if (this._hObserveResizeParent) {\n      this._hObserveResizeParent = this._hObserveResizeParent.release();\n    }\n  }\n\n  updated(changedProperties) {\n    const { container, open, parent } = this;\n    if ((changedProperties.has('alignment') || changedProperties.has('direction') || changedProperties.has('open')) && open) {\n      if (!parent) {\n        this.parent = this.parentElement as BXFloatingMenuTrigger;\n        container.appendChild(this);\n      }\n      // Note: `this.position` cannot be referenced until `this.parent` is set\n      const { direction, start, top } = this.position;\n      this.style[direction !== FLOATING_MENU_POSITION_DIRECTION.RTL ? 'left' : 'right'] = `${start}px`;\n      this.style.top = `${top}px`;\n    }\n    if (changedProperties.has('open')) {\n      if (this._hObserveResizeContainer) {\n        this._hObserveResizeContainer = this._hObserveResizeContainer.release();\n      }\n      if (this._hObserveResizeParent) {\n        this._hObserveResizeParent = this._hObserveResizeParent.release();\n      }\n      if (open) {\n        const { parentElement } = this.parent ?? {};\n        this._hObserveResizeContainer = observeResize(this._resizeObserver, container);\n        if (parentElement) {\n          this._hObserveResizeParent = observeResize(this._resizeObserver, parentElement);\n        }\n      }\n    }\n  }\n\n  /**\n   * A constant indicating that this class is a floating menu.\n   */\n  static FLOATING_MENU = true;\n\n  /**\n   * The CSS selector to find the element to put this floating menu in.\n   */\n  static get selectorContainer() {\n    return '[data-floating-menu-container],bx-modal';\n  }\n}\n\nexport default BXFloatingMenu;\n"]}