diff --git a/packages/components/src/components/popover/component.tsx b/packages/components/src/components/popover/component.tsx index 17b07a317a..97801fbf6d 100644 --- a/packages/components/src/components/popover/component.tsx +++ b/packages/components/src/components/popover/component.tsx @@ -1,11 +1,10 @@ -import { arrow, computePosition, flip, MiddlewareData, offset, Placement, shift } from '@floating-ui/dom'; import { Component, h, Host, JSX, Prop, State, Watch } from '@stencil/core'; import { AlignPropType, validateAlign } from '../../types/props/align'; import { ShowPropType, validateShow } from '../../types/props/show'; import { getDocument } from '../../utils/dev.utils'; -import { processEnv } from '../../utils/reuse'; import { API, States } from './types'; +import { alignFloatingElements } from '../../utils/align-floating-elements'; /** * @slot - Der Inhalt des Popover. @@ -21,73 +20,19 @@ export class KolPopover implements API { private triggerElement?: HTMLElement | null; private host?: HTMLElement; - /* Popover functions */ - private alignPopover = (callBack?: () => unknown): void => { - setTimeout(() => { - if (processEnv !== 'test' && this.triggerElement && this.popoverElement) { - const trigger = this.triggerElement; - const popoverEl = this.popoverElement; - const arrowEl = this.arrowElement; - - const middleware = [offset(arrowEl?.offsetHeight ?? 10), flip(), shift()]; - if (arrowEl) { - middleware.push(arrow({ element: arrowEl })); - } + private async showPopover(): Promise { + this.addListenersToBody(); - void computePosition(trigger, popoverEl, { - placement: this.state._align, - middleware: middleware, - }).then(({ x, y, middlewareData, placement }) => { - this.setPosition(x, y, middlewareData, placement, callBack); - }); - } - }); - }; - private setPosition(x: number, y: number, middlewareData: MiddlewareData, placement: Placement, callBack?: () => unknown) { - if (this.popoverElement) { - const oldPos = { - left: this.popoverElement.style.left, - top: this.popoverElement.style.top, - }; - Object.assign(this.popoverElement.style, { - left: `${x}px`, - top: `${y}px`, + if (this.triggerElement && this.popoverElement) { + await alignFloatingElements({ + align: this._align, + referenceElement: this.triggerElement, + arrowElement: this.arrowElement, + floatingElement: this.popoverElement, }); - - if (this.arrowElement && middlewareData.arrow) { - switch (placement) { - case 'top': - this.arrowElement.style.inset = `100% auto auto ${middlewareData.arrow.x || 0}px`; - this.arrowElement.style.translate = '0 -50%'; - break; - case 'right': - this.arrowElement.style.inset = `${middlewareData.arrow.y || 0}px 100% auto auto`; - this.arrowElement.style.translate = '50% 0'; - break; - case 'bottom': - this.arrowElement.style.inset = `auto auto 100% ${middlewareData.arrow.x || 0}px`; - this.arrowElement.style.translate = '0 50%'; - break; - case 'left': - this.arrowElement.style.inset = `${middlewareData.arrow.y || 0}px auto auto 100%`; - this.arrowElement.style.translate = '-50% 0'; - break; - } - } - if (oldPos.left !== this.popoverElement.style.left || oldPos.top !== this.popoverElement.style.top) { - this.alignPopover(callBack); - } else if (typeof callBack === 'function') { - callBack(); - } + this.state = { ...this.state, _visible: true }; } } - private showPopover = (): void => { - this.addListenersToBody(); - - this.alignPopover(() => { - this.state = { ...this.state, _visible: true }; - }); - }; private hidePopover(): void { this.state = { ...this.state, @@ -111,13 +56,21 @@ export class KolPopover implements API { const body = getDocument().body; body.addEventListener('keyup', this.hidePopoverByEscape); body.addEventListener('click', this.hidePopoverByClickOutside); - document.scrollingElement?.addEventListener('scroll', this.showPopover, { passive: true }); + document.scrollingElement?.addEventListener( + 'scroll', + () => { + void this.showPopover(); + }, + { passive: true } + ); } private removeListenersToBody(): void { const body = getDocument().body; body.removeEventListener('keyup', this.hidePopoverByEscape); body.removeEventListener('click', this.hidePopoverByClickOutside); - document.scrollingElement?.removeEventListener('scroll', this.showPopover); + document.scrollingElement?.removeEventListener('scroll', () => { + void this.showPopover(); + }); } /* catchElement functions */ @@ -170,7 +123,7 @@ export class KolPopover implements API { @Watch('_show') public validateShow(value?: ShowPropType): void { validateShow(this, value); - if (value) this.showPopover(); + if (value) void this.showPopover(); } public componentWillLoad(): void { diff --git a/packages/components/src/components/tooltip/component.tsx b/packages/components/src/components/tooltip/component.tsx index 9c68113cf2..6fe147d31d 100644 --- a/packages/components/src/components/tooltip/component.tsx +++ b/packages/components/src/components/tooltip/component.tsx @@ -1,4 +1,4 @@ -import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'; +import { autoUpdate } from '@floating-ui/dom'; import { Component, Element, h, Host, JSX, Prop, State, Watch } from '@stencil/core'; import { AlignPropType, validateAlign } from '../../types/props/align'; @@ -6,9 +6,9 @@ import { IdPropType, validateId } from '../../types/props/id'; import { LabelPropType, validateLabel } from '../../types/props/label'; import { getDocument, nonce } from '../../utils/dev.utils'; import { hideOverlay, showOverlay } from '../../utils/overlay'; -import { processEnv } from '../../utils/reuse'; import { API, States } from './types'; import { AccessKeyPropType, validateAccessKey } from '../../types/props/access-key'; +import { alignFloatingElements } from '../../utils/align-floating-elements'; @Component({ tag: 'kol-tooltip-wc', @@ -24,45 +24,16 @@ export class KolTooltip implements API { private cleanupAutoPositioning?: () => void; - private alignTooltip = (): void => { - if (processEnv !== 'test' && this.previousSibling /* SSR instanceof HTMLElement */ && this.tooltipElement /* SSR instanceof HTMLElement */) { - const target = this.previousSibling; - const tooltipEl = this.tooltipElement; - const arrowEl = this.arrowElement; - - const middleware = [offset(arrowEl?.offsetHeight ?? 10), flip(), shift()]; - if (arrowEl) { - middleware.push(arrow({ element: arrowEl })); - } - - void computePosition(target, tooltipEl, { - placement: this.state._align, - middleware: middleware, - }).then(({ x, y, middlewareData, placement }) => { - Object.assign(tooltipEl.style, { - left: `${x}px`, - top: `${y}px`, - visibility: 'visible', - }); - - if (arrowEl) { - if (middlewareData.arrow?.x) { - Object.assign(arrowEl.style, { - left: `${middlewareData.arrow.x}px`, - top: placement === 'bottom' ? `${-arrowEl.offsetHeight / 2}px` : '', - bottom: placement === 'top' ? `${-arrowEl.offsetHeight / 2}px` : '', - }); - } else if (middlewareData.arrow?.y) { - Object.assign(arrowEl.style, { - left: placement === 'right' ? `${-arrowEl.offsetWidth / 2}px` : '', - right: placement === 'left' ? `${-arrowEl.offsetWidth / 2}px` : '', - top: `${middlewareData.arrow.y}px`, - }); - } - } + private async alignTooltip(): Promise { + if (this.tooltipElement && this.previousSibling) { + await alignFloatingElements({ + align: this._align, + referenceElement: this.previousSibling, + arrowElement: this.arrowElement, + floatingElement: this.tooltipElement, }); } - }; + } private showTooltip = (): void => { if (this.previousSibling && this.tooltipElement /* SSR instanceof HTMLElement */) { @@ -72,7 +43,9 @@ export class KolTooltip implements API { const target = this.previousSibling; const tooltipEl = this.tooltipElement; - this.cleanupAutoPositioning = autoUpdate(target, tooltipEl, this.alignTooltip); + this.cleanupAutoPositioning = autoUpdate(target, tooltipEl, () => { + void this.alignTooltip(); + }); } }; diff --git a/packages/components/src/utils/align-floating-elements.ts b/packages/components/src/utils/align-floating-elements.ts new file mode 100644 index 0000000000..152d77ee54 --- /dev/null +++ b/packages/components/src/utils/align-floating-elements.ts @@ -0,0 +1,45 @@ +import { processEnv } from './reuse'; +import { arrow, computePosition, flip, offset, shift } from '@floating-ui/dom'; +import { AlignPropType } from '../types/props/align'; + +type Arguments = { + floatingElement: HTMLElement; + referenceElement: Element; + arrowElement?: HTMLElement; + align?: AlignPropType; +}; +export const alignFloatingElements = async ({ floatingElement, referenceElement, arrowElement, align = 'top' }: Arguments) => { + if (processEnv !== 'test') { + const middleware = [offset(arrowElement?.offsetHeight ?? 10), flip(), shift()]; + if (arrowElement) { + middleware.push(arrow({ element: arrowElement })); + } + + const { x, y, middlewareData, placement } = await computePosition(referenceElement, floatingElement, { + placement: align, + middleware: middleware, + }); + + Object.assign(floatingElement.style, { + left: `${x}px`, + top: `${y}px`, + visibility: 'visible', + }); + + if (arrowElement) { + if (middlewareData.arrow?.x) { + Object.assign(arrowElement.style, { + left: `${middlewareData.arrow.x}px`, + top: placement === 'bottom' ? `${-arrowElement.offsetHeight / 2}px` : '', + bottom: placement === 'top' ? `${-arrowElement.offsetHeight / 2}px` : '', + }); + } else if (middlewareData.arrow?.y) { + Object.assign(arrowElement.style, { + left: placement === 'right' ? `${-arrowElement.offsetWidth / 2}px` : '', + right: placement === 'left' ? `${-arrowElement.offsetWidth / 2}px` : '', + top: `${middlewareData.arrow.y}px`, + }); + } + } + } +};