Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🚀 ESL Popup] add position-origin attribute #2747

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions site/views/draft/popup-game.njk
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ aside:
<esl-d-popup-game trigger="::find(.game-popup-trigger)">
<esl-trigger
target="#game-popup"
track-click="not"
track-click="not"
track-hover
hover-hide-delay="250"
class="game-popup-trigger">
Expand All @@ -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">
<div class="popup-content">
<div>
<p>Space exploration has always captured the imagination of humanity.</p>

<details>
<summary>Early History</summary>
<p>Since the dawn of time, humans have looked up at the stars with wonder. Early civilizations like the Babylonians, Greeks, and Chinese made significant contributions to our understanding of the cosmos.
<p>The 20th century marked the beginning of modern space exploration with the launch of Sputnik 1 by the Soviet Union in 1957, followed by Yuri Gagarin's historic flight in 1961, making him the first human in space.
<p>NASA's Apollo program achieved a monumental milestone in 1969 when Neil Armstrong and Buzz Aldrin walked on the moon, a feat watched by millions around the globe.</p>
</details>

<details>
<summary>Modern Missions</summary>
<p>In recent decades, space exploration has seen significant advancements with the development of the International Space Station (ISS), a collaborative effort involving multiple countries. The ISS serves as a hub for scientific research and international cooperation.
Expand Down Expand Up @@ -100,6 +101,14 @@ aside:
<option value="left">Left</option>
<option value="right">Right</option>
</uip-select-setting>
<uip-select-setting label="Position origin" target="esl-popup" attribute="position-origin">
<option value="outer">Outer</option>
<option value="inner">Inner</option>
</uip-select-setting>
<uip-select-setting label="Arrow" target="esl-popup" attribute="class" mode="append">
<option value="">Enable</option>
<option value="disable-arrow">Disable</option>
</uip-select-setting>
<uip-select-setting label="Orientation" target="esl-popup" attribute="class" mode="append">
<option value="popup-v">Vertical</option>
<option value="popup-h">Horizontal</option>
Expand Down
6 changes: 5 additions & 1 deletion src/modules/esl-popup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,17 @@ You can also add additional classes and styles when activating and displaying th

- `position` - popup position relative to the trigger (currently supported: 'top', 'bottom', 'left', 'right' ) ('top' by default)

- `position-origin` <i class="badge badge-sup badge-warning">beta</i> - this attribute specifies from which side of the trigger grows popup to achieve the desired position ('outer' by default). Available options:
- `outer` - popup grows from the outside of the trigger relative to the positioning direction and cannot overlap the trigger
- `inner` - popup grows from the inside of the trigger relative to the positioning direction and will overlap the trigger

- `behavior` - popup behavior if it does not fit in the window ('fit' by default). Available options:
- `fit` - default, the popup will always be positioned in the right place. Position dynamically updates so it will
always be visible
- `fit-major` - same as fit, but position dynamically updates only on major axes. Looks like a flip in relation to the trigger
- `fit-minor` - same as fit, but position dynamically updates only on minor axes. Looks like alignment to the arrow
- `none`, empty or unsupported value - will not be prevented from overflowing clipping boundaries, such as the viewport

- `container` - defines container element ([ESLTraversingQuery](../esl-traversing-query/README.md) selector) to determine bounds of popup visibility (window by default)

- `disable-activator-observation` (boolean) - disable hiding the popup depending on the visibility of the activator
Expand Down
66 changes: 50 additions & 16 deletions src/modules/esl-popup/core/esl-popup-position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,6 +20,7 @@ export interface IntersectionRatioRect {

export interface PopupPositionConfig {
position: PositionType;
hasInnerOrigin: boolean;
behavior: string;
marginArrow: number;
offsetArrowRatio: number;
Expand Down Expand Up @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: probably we can use left/right key and some generic function for invertion in future

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);
Expand Down Expand Up @@ -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);
}

/**
Expand Down
11 changes: 9 additions & 2 deletions src/modules/esl-popup/core/esl-popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ 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;

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 */
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Copy link
Collaborator

@ala-n ala-n Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need? Practically there is no limitation to have arrow for inner behaviour

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a limitation. This is just a position adjustment.


const config = {
position: this.position,
hasInnerOrigin: this.positionOrigin === 'inner',
behavior: this.behavior,
marginArrow: this.marginArrow,
offsetArrowRatio: this.offsetArrowRatio,
Expand Down