diff --git a/pages/views/drafts/esl-popup.njk b/pages/views/drafts/esl-popup.njk index 3bf1e9873..d6faa10b6 100644 --- a/pages/views/drafts/esl-popup.njk +++ b/pages/views/drafts/esl-popup.njk @@ -26,17 +26,19 @@ tags: drafts

Lorem

ipsum

dot

color

- + Trigger B

Lorem

ipsum

dot

color

- Trigger B + Trigger B

Lorem

ipsum

dot

color

Trigger C

Lorem

ipsum

dot

color

Trigger D (top) - Trigger D (right) + Trigger D (bottom) + Trigger D (left) + Trigger D (right) Exadel Smart Library Popup content
diff --git a/src/modules/esl-popup/core/calcPosition.ts b/src/modules/esl-popup/core/calcPosition.ts index d79a6e935..375074c08 100644 --- a/src/modules/esl-popup/core/calcPosition.ts +++ b/src/modules/esl-popup/core/calcPosition.ts @@ -29,9 +29,17 @@ export interface ElementRect extends ObjectRect { cy: number; } +export interface IntersetionRatioRect { + top?: number; + left?: number; + right?: number; + bottom?: number; +} + export interface PopupPositionConfig { position: PositionType; behavior: string; + intersectionRatio: IntersetionRatioRect, element: DOMRect; inner: ElementRect; outer: ObjectRect; @@ -95,22 +103,22 @@ function fitByMajorAxis(cfg: PopupPositionConfig, rect: BasicRect, arrow: ArrowP switch (cfg.position) { case 'bottom': - if (cfg.outer.bottom < rect.bottom) { + if (cfg.intersectionRatio.bottom || cfg.outer.bottom < rect.bottom) { oppositeOnMajor('bottom', cfg.inner.top, -cfg.element.height, rect, arrow); } break; case 'left': - if (rect.left < cfg.outer.left) { + if (cfg.intersectionRatio.left || rect.left < cfg.outer.left) { oppositeOnMajor('left', cfg.inner.right, cfg.element.width, rect, arrow); } break; case 'right': - if (cfg.outer.right < rect.right) { + if (cfg.intersectionRatio.right || cfg.outer.right < rect.right) { oppositeOnMajor('right', cfg.inner.left, -cfg.element.width, rect, arrow); } break; default: - if (rect.top < cfg.outer.top) { + if (cfg.intersectionRatio.top || rect.top < cfg.outer.top) { oppositeOnMajor('top', cfg.inner.bottom, cfg.element.height, rect, arrow); } } diff --git a/src/modules/esl-popup/core/esl-popup.ts b/src/modules/esl-popup/core/esl-popup.ts index b75dca292..916e76009 100644 --- a/src/modules/esl-popup/core/esl-popup.ts +++ b/src/modules/esl-popup/core/esl-popup.ts @@ -9,7 +9,9 @@ import {listScrollParents} from './listScrollParents'; import {calcPopupPosition, resizeRect} from './calcPosition'; import type {ToggleableActionParams} from '../../esl-toggleable/core'; -import type {PositionType} from './calcPosition'; +import type {PositionType, IntersetionRatioRect} from './calcPosition'; + +const INTERSECTION_LIMIT_FOR_ADJACENT_AXIS = 0.7; export interface PopupActionParams extends ToggleableActionParams { /** popup position relative to trigger */ @@ -37,6 +39,7 @@ export class ESLPopup extends ESLToggleable { protected _offsetWindow: number; protected _deferredUpdatePosition = rafDecorator(() => this._updatePosition()); protected _activatorObserver: ActivatorObserver; + protected _intersectionRatio: IntersetionRatioRect = {}; @attr({defaultValue: 'top'}) public position: PositionType; @attr({defaultValue: 'fit'}) public behavior: string; @@ -71,6 +74,14 @@ export class ESLPopup extends ESLToggleable { window.removeEventListener('scroll', this._deferredUpdatePosition); } + protected get _isPositioningAlongHorizontal() { + return ['left', 'right'].includes(this.position); + } + + protected get _isPositioningAlongVertical() { + return ['top', 'bottom'].includes(this.position); + } + // TODO: move to utilities protected get _windowWidth() { // return document.documentElement.clientWidth || document.body.clientWidth; @@ -122,10 +133,36 @@ export class ESLPopup extends ESLToggleable { this.activator && this._removeActivatorObserver(this.activator); } + protected _checkIntersectionForAdjacentAxis(isAdjacentAxis: boolean, intersectionRatio: number) { + if (isAdjacentAxis && intersectionRatio < INTERSECTION_LIMIT_FOR_ADJACENT_AXIS) { + this.hide(); + } + } + @bind protected onActivatorIntersection(entries: IntersectionObserverEntry[], observer: IntersectionObserver) { - if (!entries[0].isIntersecting) { + const entry = entries[0]; + this._intersectionRatio = {}; + if (!entry.isIntersecting) { this.hide(); + return; + } + + if (entry.intersectionRect.top !== entry.boundingClientRect.top) { + this._intersectionRatio.top = entry.intersectionRect.height / entry.boundingClientRect.height; + this._checkIntersectionForAdjacentAxis(this._isPositioningAlongHorizontal, this._intersectionRatio.top); + } + if (entry.intersectionRect.bottom !== entry.boundingClientRect.bottom) { + this._intersectionRatio.bottom = entry.intersectionRect.height / entry.boundingClientRect.height; + this._checkIntersectionForAdjacentAxis(this._isPositioningAlongHorizontal, this._intersectionRatio.bottom); + } + if (entry.intersectionRect.left !== entry.boundingClientRect.left) { + this._intersectionRatio.left = entry.intersectionRect.width / entry.boundingClientRect.width; + this._checkIntersectionForAdjacentAxis(this._isPositioningAlongVertical, this._intersectionRatio.left); + } + if (entry.intersectionRect.right !== entry.boundingClientRect.right) { + this._intersectionRatio.right = entry.intersectionRect.width / entry.boundingClientRect.width; + this._checkIntersectionForAdjacentAxis(this._isPositioningAlongVertical, this._intersectionRatio.right); } } @@ -147,7 +184,7 @@ export class ESLPopup extends ESLToggleable { const options = { rootMargin: '0px', - threshold: [0, 1] + threshold: [...Array(9).keys()].map((x) => x/8) } as IntersectionObserverInit; const observer = new IntersectionObserver(this.onActivatorIntersection, options); @@ -197,6 +234,7 @@ export class ESLPopup extends ESLToggleable { const config = { position: this.position, behavior: this.behavior, + intersectionRatio: this._intersectionRatio, element: popupRect, trigger, inner: resizeRect(trigger, innerMargin),