From 6be51338181d403c43291cb87713496f3bb2a308 Mon Sep 17 00:00:00 2001 From: Dmitry Shovchko Date: Wed, 6 Nov 2024 15:53:50 +0200 Subject: [PATCH 1/4] feat(esl-popup): add position-origin attribute --- .../esl-popup/core/esl-popup-position.ts | 66 ++++++++++++++----- src/modules/esl-popup/core/esl-popup.ts | 11 +++- 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/modules/esl-popup/core/esl-popup-position.ts b/src/modules/esl-popup/core/esl-popup-position.ts index d6021d4952..bdf5a448dc 100644 --- a/src/modules/esl-popup/core/esl-popup-position.ts +++ b/src/modules/esl-popup/core/esl-popup-position.ts @@ -3,6 +3,7 @@ import {Rect} from '../../esl-utils/dom/rect'; import type {Point} from '../../esl-utils/dom/point'; export type PositionType = 'top' | 'bottom' | 'left' | 'right'; +export type PositionOriginType = 'inner' | 'outer'; export interface PopupPositionValue { placedAt: PositionType; @@ -19,6 +20,7 @@ export interface IntersectionRatioRect { export interface PopupPositionConfig { position: PositionType; + hasInnerOrigin: boolean; behavior: string; marginArrow: number; offsetArrowRatio: number; @@ -71,21 +73,21 @@ function calcPopupPositionByMinorAxis(cfg: PopupPositionConfig, centerPosition: * @param cfg - popup position config * */ function calcPopupBasicRect(cfg: PopupPositionConfig): Rect { - const {position, inner, element} = cfg; + const {position, inner, element, hasInnerOrigin} = cfg; let x = isOnHorizontalAxis(position) ? 0 : calcPopupPositionByMinorAxis(cfg, inner.cx, 'width'); let y = isOnHorizontalAxis(position) ? calcPopupPositionByMinorAxis(cfg, inner.cy, 'height') : 0; - switch (cfg.position) { + switch (position) { case 'left': - x = inner.x - element.width; + x = (hasInnerOrigin ? inner.right : inner.x) - element.width; break; case 'right': - x = inner.right; + x = hasInnerOrigin ? inner.x : inner.right; break; case 'bottom': - y = inner.bottom; + y = hasInnerOrigin ? inner.y : inner.bottom; break; default: - y = inner.y - element.height; + y = (hasInnerOrigin ? inner.bottom : inner.y) - element.height; break; } return new Rect(x, y, element.width, element.height); @@ -141,19 +143,51 @@ function fitOnMajorAxis(cfg: PopupPositionConfig, value: PopupPositionValue): Po * @returns updated popup position value * */ function adjustAlongMajorAxis(cfg: PopupPositionConfig, value: PopupPositionValue): PopupPositionValue { - let {popup, placedAt} = value; + const popup = isStartingSide(cfg.position) + ? adjustForStartingSide(cfg, value.popup) + : adjustForEndingSide(cfg, value.popup); + const placedAt = getOppositePosition(cfg.position); + return {...value, popup, placedAt}; +} + +/** + * Updates popup rect to fit on major axis in case positioning on the starting side. + * @param cfg - popup position config + * @param popup - popup rect + * @returns updated popup rect + * */ +function adjustForStartingSide(cfg: PopupPositionConfig, popup: Rect): Rect { + const {position, inner, hasInnerOrigin} = cfg; let {x, y} = popup; - if (isStartingSide(cfg.position)) { - x = cfg.position === 'left' ? cfg.inner.right : x; - y = cfg.position === 'top' ? cfg.inner.bottom : y; - } else { - x = cfg.position === 'right' ? cfg.inner.x - popup.width : x; - y = cfg.position === 'bottom' ? cfg.inner.y - popup.height : y; + switch (position) { + case 'left': + x = hasInnerOrigin ? inner.x : inner.right; + break; + case 'top': + y = hasInnerOrigin ? inner.y : inner.bottom; + break; } - popup = new Rect(x, y, popup.width, popup.height); - placedAt = getOppositePosition(cfg.position); + return new Rect(x, y, popup.width, popup.height); +} - return {...value, popup, placedAt}; +/** + * Updates popup rect to fit on major axis in case positioning on the ending side. + * @param cfg - popup position config + * @param popup - popup rect + * @returns updated popup rect + * */ +function adjustForEndingSide(cfg: PopupPositionConfig, popup: Rect): Rect { + const {position, inner, hasInnerOrigin} = cfg; + let {x, y} = popup; + switch (position) { + case 'right': + x = (hasInnerOrigin ? inner.right : inner.x) - popup.width; + break; + case 'bottom': + y = (hasInnerOrigin ? inner.bottom : inner.y) - popup.height; + break; + } + return new Rect(x, y, popup.width, popup.height); } /** diff --git a/src/modules/esl-popup/core/esl-popup.ts b/src/modules/esl-popup/core/esl-popup.ts index 007047dd72..32576f0e19 100644 --- a/src/modules/esl-popup/core/esl-popup.ts +++ b/src/modules/esl-popup/core/esl-popup.ts @@ -12,7 +12,7 @@ import {calcPopupPosition, isOnHorizontalAxis} from './esl-popup-position'; import {ESLPopupPlaceholder} from './esl-popup-placeholder'; import type {ESLToggleableActionParams} from '../../esl-toggleable/core'; -import type {PositionType, IntersectionRatioRect} from './esl-popup-position'; +import type {PositionType, PositionOriginType, IntersectionRatioRect} from './esl-popup-position'; const INTERSECTION_LIMIT_FOR_ADJACENT_AXIS = 0.7; const DEFAULT_OFFSET_ARROW = 50; @@ -20,6 +20,8 @@ const DEFAULT_OFFSET_ARROW = 50; export interface ESLPopupActionParams extends ESLToggleableActionParams { /** popup position relative to trigger */ position?: PositionType; + /** clarification of the popup position, whether it should start on the outside of trigger or the inside of trigger */ + positionOrigin?: PositionOriginType; /** popup behavior if it does not fit in the window */ behavior?: string; /** Disable hiding the popup depending on the visibility of the activator */ @@ -74,6 +76,9 @@ export class ESLPopup extends ESLToggleable { */ @attr({defaultValue: 'top'}) public position: PositionType; + /** From which side of the trigger starts the positioning of the popup: 'inner', 'outer' ('outer' by default) */ + @attr({defaultValue: 'outer'}) public positionOrigin: PositionOriginType; + /** Popup behavior if it does not fit in the window ('fit' by default) */ @attr({defaultValue: 'fit'}) public behavior: string; @@ -202,6 +207,7 @@ export class ESLPopup extends ESLToggleable { // TODO: change flow to use merged params unless attribute state is used in CSS Object.assign(this, copyDefinedKeys({ position: params.position, + positionOrigin: params.positionOrigin, behavior: params.behavior, container: params.container, marginArrow: params.marginArrow, @@ -401,10 +407,11 @@ export class ESLPopup extends ESLToggleable { const triggerRect = Rect.from(this.activator).shift(window.scrollX, window.scrollY); const {containerRect} = this; - const innerMargin = this._offsetTrigger + arrowRect.width / 2; + const innerMargin = this._offsetTrigger + (this.positionOrigin === 'inner' ? 0 : arrowRect.width / 2); const config = { position: this.position, + hasInnerOrigin: this.positionOrigin === 'inner', behavior: this.behavior, marginArrow: this.marginArrow, offsetArrowRatio: this.offsetArrowRatio, From 2d7dc2e1e93d732d3cef149c6423c468225195e8 Mon Sep 17 00:00:00 2001 From: Dmitry Shovchko Date: Wed, 6 Nov 2024 15:55:11 +0200 Subject: [PATCH 2/4] chore(site): update esl-popup game with position-origin attribute --- site/views/draft/popup-game.njk | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/site/views/draft/popup-game.njk b/site/views/draft/popup-game.njk index c5c2e9f2d6..28847ea6e0 100644 --- a/site/views/draft/popup-game.njk +++ b/site/views/draft/popup-game.njk @@ -25,7 +25,7 @@ aside: @@ -36,20 +36,21 @@ aside: id="game-popup" class="popup-game popup-h" position="top" + position-origin="outer" container=".uip-preview-inner" dir="ltr" margin-arrow="35">