Skip to content

Commit 6d6d232

Browse files
[O2B-1077] Add margins between popover and screen edges (#1297)
* [O2B-1077] Add margins between popover and screen edges * Fix linter
1 parent a70e745 commit 6d6d232

2 files changed

Lines changed: 55 additions & 29 deletions

File tree

lib/public/components/common/popover/PopoverEngine.js

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,26 @@
1616
*
1717
* Object able to project a bounding box 2D data to only one of its dimensions
1818
*
19-
* @property {(subject: BoundingBox) => number} getPosition return the position of the subject
20-
* @property {(subject: BoundingBox, position: number) => void} setPosition set the position of the subject to a given value
21-
* @property {(subject: BoundingBox) => number} getSize return the size of the subject
22-
* @property {(subject: BoundingBox, size: number) => void} setSize set the size of the subject to a given value
19+
* @template T
20+
*
21+
* @property {(subject: {left: T, top: T} | {x: T, y: T}) => T} getPosition return the position of the subject
22+
* @property {(subject: {left: T, top: T} | {x: T, y: T}, position: T) => void} setPosition set the position of the subject to a given value
23+
* @property {(subject: {width: T, height: T}) => t} getSize return the size of the subject
24+
* @property {(subject: {width: T, height: T}, size: T) => void} setSize set the size of the subject to a given value
2325
*/
2426

2527
/**
2628
* @type BoundingBoxProjector
2729
*/
2830
const verticalProjector = Object.freeze({
29-
getPosition: (subject) => subject.top,
31+
getPosition: (subject) => 'top' in subject ? subject.top : subject.y,
3032

3133
setPosition: (subject, position) => {
32-
subject.top = position;
34+
if ('top' in subject) {
35+
subject.top = position;
36+
} else {
37+
subject.y = position;
38+
}
3339
},
3440

3541
getSize: (subject) => subject.height,
@@ -43,10 +49,14 @@ const verticalProjector = Object.freeze({
4349
* @type BoundingBoxProjector
4450
*/
4551
const horizontalProjector = Object.freeze({
46-
getPosition: (subject) => subject.left,
52+
getPosition: (subject) => 'left' in subject ? subject.left : subject.x,
4753

4854
setPosition: (subject, position) => {
49-
subject.left = position;
55+
if ('left' in subject) {
56+
subject.left = position;
57+
} else {
58+
subject.y = position;
59+
}
5060
},
5161

5262
getSize: (subject) => subject.width,
@@ -129,32 +139,31 @@ class PopoverEngine {
129139
* @param {HTMLElement} trigger the bounding box of the trigger
130140
* @param {HTMLElement} popover the bounding box of the popover
131141
* @param {BoundingBox} displayBoundingBox the bounding box of the display zone, representing the limits where the popover may be drawn
132-
* @param {MainAxisPopoverPosition} mainAxisPosition the position of the popover along the main axis
133-
* @param {BoundingBoxProjector} mainAxisProjector the main axis projector
134-
* @param {CrossAxisPopoverPosition} crossAxisPosition the position of the popover along the cross axis
135-
* @param {BoundingBoxProjector} crossAxisProjector the cross axis projector
142+
* @param {{x: number, y: number}} displayZoneMargins the margins to apply at the edge of the display zone
143+
* @param {{main: MainAxisPopoverPosition, cross: CrossAxisPopoverPosition}} position the position of the popover relative to the trigger
144+
* @param {{main: BoundingBoxProjector, cross: BoundingBoxProjector}} projectors the axis projectors
136145
*/
137146
constructor(
138147
trigger,
139148
popover,
140149
displayBoundingBox,
141-
mainAxisPosition,
142-
mainAxisProjector,
143-
crossAxisPosition,
144-
crossAxisProjector,
150+
displayZoneMargins,
151+
position,
152+
projectors,
145153
) {
146154
this._popover = popover;
147155

148156
this._triggerBoundingBox = trigger.getBoundingClientRect();
149157
this._popoverBoundingBox = popover.getBoundingClientRect();
150158

151-
this._mainAxisProjector = mainAxisProjector;
152-
this._crossAxisProjector = crossAxisProjector;
159+
this._mainAxisPosition = position.main;
160+
this._crossAxisPosition = position.cross;
153161

154-
this._mainAxisPosition = mainAxisPosition;
155-
this._crossAxisPosition = crossAxisPosition;
162+
this._mainAxisProjector = projectors.main;
163+
this._crossAxisProjector = projectors.cross;
156164

157165
this._displayBoundingBox = displayBoundingBox;
166+
this._displayZoneMargins = displayZoneMargins;
158167
}
159168

160169
/**
@@ -245,7 +254,7 @@ class PopoverEngine {
245254
mainAxisPosition = this._triggerEndMainAxis;
246255
}
247256

248-
// Popover is placed absolutely relatively to the document, and its position should be offseted by the window scroll
257+
// Popover is placed absolutely relatively to the document, and its position should be offset by the window scroll
249258
mainAxisPosition += this._mainAxisProjector.getPosition(this._offsets);
250259

251260
this._mainAxisProjector.setPosition(this._popover.style, `${mainAxisPosition}px`);
@@ -270,7 +279,8 @@ class PopoverEngine {
270279
targetStartCrossAxis += this._crossAxisProjector.getPosition(this._offsets);
271280

272281
const crossAxisPosition = Math.max(
273-
0,
282+
this._crossAxisProjector.getPosition(this._displayBoundingBox)
283+
+ this._crossAxisProjector.getPosition(this._displayZoneMargins),
274284
Math.min(
275285
this._availableSpaceInCrossAxis - this._popoverSizeCrossAxis,
276286
targetStartCrossAxis,
@@ -296,7 +306,8 @@ class PopoverEngine {
296306
* @private
297307
*/
298308
get _availableSpaceInMainAxis() {
299-
return this._mainAxisProjector.getSize(this._displayBoundingBox);
309+
return this._mainAxisProjector.getSize(this._displayBoundingBox)
310+
- this._mainAxisProjector.getPosition(this._displayZoneMargins);
300311
}
301312

302313
/**
@@ -306,7 +317,8 @@ class PopoverEngine {
306317
* @private
307318
*/
308319
get _availableSpaceInCrossAxis() {
309-
return this._crossAxisProjector.getSize(this._displayBoundingBox);
320+
return this._crossAxisProjector.getSize(this._displayBoundingBox)
321+
- this._crossAxisProjector.getPosition(this._displayZoneMargins) * 2;
310322
}
311323

312324
/**
@@ -406,10 +418,11 @@ class PopoverEngine {
406418
* @param {HTMLElement} trigger the bounding box of the trigger
407419
* @param {HTMLElement} popover the bounding box of the popover
408420
* @param {BoundingBox} displayBoundingBox the bounding box of the display zone, representing the limits where the popover may be drawn
421+
* @param {{x: number, y: number}} displayZoneMargins the margins to apply at the edge of the display zone
409422
* @param {PopoverAnchor} anchor the anchor to place the popover
410423
* @return {PopoverEngine} the created popover engine
411424
*/
412-
export const createPopoverEngine = (trigger, popover, displayBoundingBox, anchor) => {
425+
export const createPopoverEngine = (trigger, popover, displayBoundingBox, displayZoneMargins, anchor) => {
413426
// Top and Bottom are the 6 first anchors
414427
const mainAxisProjector = anchor / 6 < 1 ? verticalProjector : horizontalProjector;
415428
const crossAxisProjector = anchor / 6 < 1 ? horizontalProjector : verticalProjector;
@@ -424,9 +437,8 @@ export const createPopoverEngine = (trigger, popover, displayBoundingBox, anchor
424437
trigger,
425438
popover,
426439
displayBoundingBox,
427-
mainAxisPosition,
428-
mainAxisProjector,
429-
crossAxisPosition,
430-
crossAxisProjector,
440+
displayZoneMargins,
441+
{ main: mainAxisPosition, cross: crossAxisPosition },
442+
{ main: mainAxisProjector, cross: crossAxisProjector },
431443
);
432444
};

lib/public/components/common/popover/popover.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ import { createPortal } from '../../../utilities/createPortal.js';
1616
import { getUniqueId } from '../../../utilities/getUniqueId.js';
1717
import { createPopoverEngine } from './PopoverEngine.js';
1818

19+
/**
20+
* Default margin to apply at the edge of the display zone
21+
*
22+
* @type {{left: number, top: number}}
23+
*/
24+
const DEFAULT_DISPLAY_ZONE_MARGIN = { left: 15, top: 15 };
25+
1926
/**
2027
* @callback onTriggerNodeChange Function called when the trigger node change (this bound to the popover component instance)
2128
*
@@ -60,6 +67,7 @@ import { createPopoverEngine } from './PopoverEngine.js';
6067
* @property {popoverVisibilityChangeCallback} [onVisibilityChange] function called when the visibility changes
6168
* @property {popoverShowConditionCallback} [showCondition] function called before showing the popover
6269
* @property {string[]|string} [popoverClass] css classes to apply to the popover (in addition to the default ones)
70+
* @property {{x: number, y: number}} displayZoneMargins the margins to apply at the edges to display zone
6371
*/
6472

6573
/**
@@ -125,6 +133,7 @@ class PopoverComponent {
125133
left: 0,
126134
top: 0,
127135
},
136+
this._displayZoneMargins,
128137
this._anchor,
129138
);
130139
engine.reset();
@@ -268,13 +277,18 @@ class PopoverComponent {
268277
},
269278
showCondition,
270279
popoverClass = [],
280+
displayZoneMargins = {},
271281
} = configuration;
272282
this._anchor = anchor;
273283
this._onTriggerNodeChange = onTriggerNodeChange.bind(this);
274284
this._onPopoverNodeChange = onPopoverNodeChange.bind(this);
275285
this._onVisibilityChange = onVisibilityChange.bind(this);
276286
this._showCondition = showCondition ? showCondition.bind(this) : () => true;
277287
this._popoverClasses = Array.isArray(popoverClass) ? popoverClass : [popoverClass];
288+
this._displayZoneMargins = {
289+
...DEFAULT_DISPLAY_ZONE_MARGIN,
290+
...displayZoneMargins,
291+
};
278292
}
279293
}
280294

0 commit comments

Comments
 (0)