Skip to content

Commit 50f11ec

Browse files
feat(ui5-popover): implement resizable popover (#12623)
* chore: add test page * chore: improve test page * chore: render the resize corner icon * chore: add ResizeHandlePlacement * chore: add some resizing structure * chore: calc resizeHandlePlacement * chore: improve calculations * chore: handle min sizes * fix: resize icon placing in rtl mode * fix: resize positioning when centered * fix: fix re-resizing * fix: clicking outside the resize handle icon * fix: lint error * chore: rename props * chore: add tests * chore: fix tests * chore: improve tests * chore: improve tests * chore: override styles like in the Dialog * chore: add public sample * chore: fix typo * chore: improve public sample * chore: address code comments * chore: lower the flickering during resize * chore: fix resizeHandlePlacement in RTL * chore: improve documentation * chore: move resize logic into separate PopoverResize class * chore: address code comments * chore: add more tests * chore: fix the tests * chore: check if there is an opener * chore: address code comments * chore: change release version and improve code * chore: address code comments
1 parent b7e185f commit 50f11ec

16 files changed

Lines changed: 2417 additions & 35 deletions

File tree

packages/main/cypress/specs/PopoverResize.cy.tsx

Lines changed: 1526 additions & 0 deletions
Large diffs are not rendered by default.

packages/main/src/Popover.ts

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
44
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
55
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
66
import { isIOS } from "@ui5/webcomponents-base/dist/Device.js";
7-
import { getClosedPopupParent } from "@ui5/webcomponents-base/dist/util/PopupUtils.js";
7+
import { isClickInRect, getClosedPopupParent } from "@ui5/webcomponents-base/dist/util/PopupUtils.js";
88
import clamp from "@ui5/webcomponents-base/dist/util/clamp.js";
99
import DOMReferenceConverter from "@ui5/webcomponents-base/dist/converters/DOMReference.js";
1010
import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js";
@@ -13,6 +13,8 @@ import PopoverPlacement from "./types/PopoverPlacement.js";
1313
import PopoverVerticalAlign from "./types/PopoverVerticalAlign.js";
1414
import PopoverHorizontalAlign from "./types/PopoverHorizontalAlign.js";
1515
import { addOpenedPopover, removeOpenedPopover } from "./popup-utils/PopoverRegistry.js";
16+
import PopoverResize from "./PopoverResize.js";
17+
import type { ResizeHandlePlacement } from "./PopoverResize.js";
1618

1719
// Template
1820
import PopoverTemplate from "./PopoverTemplate.js";
@@ -135,7 +137,7 @@ class Popover extends Popup {
135137

136138
/**
137139
* Defines whether the component should close when
138-
* clicking/tapping outside of the popover.
140+
* clicking/tapping outside the popover.
139141
* If enabled, it blocks any interaction with the background.
140142
* @default false
141143
* @public
@@ -161,6 +163,16 @@ class Popover extends Popup {
161163
@property({ type: Boolean })
162164
allowTargetOverlap = false;
163165

166+
/**
167+
* Determines whether the component is resizable.
168+
* **Note:** This property is effective only on desktop devices.
169+
* @default false
170+
* @public
171+
* @since 2.19.0
172+
*/
173+
@property({ type: Boolean })
174+
resizable = false;
175+
164176
/**
165177
* Sets the X translation of the arrow
166178
* @private
@@ -188,6 +200,9 @@ class Popover extends Popup {
188200
@property({ type: Number, noAttribute: true })
189201
_maxWidth?: number;
190202

203+
@property({ noAttribute: true })
204+
_resizeHandlePlacement?: `${ResizeHandlePlacement}`;
205+
191206
/**
192207
* Defines the header HTML Element.
193208
* @public
@@ -211,12 +226,19 @@ class Popover extends Popup {
211226
_width?: string;
212227
_height?: string;
213228

229+
_popoverResize: PopoverResize;
230+
231+
_initialWidth?: string;
232+
_initialHeight?: string;
233+
214234
static get VIEWPORT_MARGIN() {
215235
return 10; // px
216236
}
217237

218238
constructor() {
219239
super();
240+
241+
this._popoverResize = new PopoverResize(this);
220242
}
221243

222244
/**
@@ -262,11 +284,26 @@ class Popover extends Popup {
262284
return;
263285
}
264286

287+
this._initialWidth = this.style.width;
288+
this._initialHeight = this.style.height;
289+
265290
this._openerRect = opener.getBoundingClientRect();
266291

267292
await super.openPopup();
268293
}
269294

295+
closePopup(escPressed = false, preventRegistryUpdate = false, preventFocusRestore = false) : void {
296+
Object.assign(this.style, {
297+
width: this._initialWidth,
298+
height: this._initialHeight,
299+
});
300+
301+
this._popoverResize.reset();
302+
delete this._resizeHandlePlacement;
303+
304+
super.closePopup(escPressed, preventRegistryUpdate, preventFocusRestore);
305+
}
306+
270307
isOpenerClicked(e: MouseEvent) {
271308
const target = e.target as HTMLElement;
272309
const opener = this.getOpenerHTMLElement(this.opener);
@@ -286,6 +323,17 @@ class Popover extends Popup {
286323
return e.composedPath().indexOf(opener) > -1;
287324
}
288325

326+
isClicked(e: MouseEvent) {
327+
if (this._showResizeHandle) {
328+
const resizeHandle = this.shadowRoot!.querySelector(".ui5-popover-resize-handle");
329+
if (resizeHandle === e.composedPath()[0]) {
330+
return true;
331+
}
332+
}
333+
334+
return isClickInRect(e, this.getBoundingClientRect());
335+
}
336+
289337
/**
290338
* Override for the _addOpenedPopup hook, which would otherwise just call addOpenedPopup(this)
291339
* @private
@@ -378,8 +426,16 @@ class Popover extends Popup {
378426
}
379427
}
380428

429+
get _viewportMargin() {
430+
return Popover.VIEWPORT_MARGIN;
431+
}
432+
381433
reposition() {
382434
this._show();
435+
436+
if (this.resizable) {
437+
this._resizeHandlePlacement = this._popoverResize.getResizeHandlePlacement();
438+
}
383439
}
384440

385441
async _show() {
@@ -462,6 +518,10 @@ class Popover extends Popup {
462518
left: `${left}px`,
463519
});
464520

521+
if (this._popoverResize.isResized) {
522+
return;
523+
}
524+
465525
if (this.horizontalAlign === PopoverHorizontalAlign.Stretch && this._width) {
466526
this.style.width = this._width;
467527
}
@@ -553,12 +613,14 @@ class Popover extends Popup {
553613
const isVertical = actualPlacement === PopoverActualPlacement.Top
554614
|| actualPlacement === PopoverActualPlacement.Bottom;
555615

556-
if (this.horizontalAlign === PopoverHorizontalAlign.Stretch && isVertical) {
557-
popoverSize.width = targetRect.width;
558-
this._width = `${targetRect.width}px`;
559-
} else if (this.verticalAlign === PopoverVerticalAlign.Stretch && !isVertical) {
560-
popoverSize.height = targetRect.height;
561-
this._height = `${targetRect.height}px`;
616+
if (!this._popoverResize.isResized) {
617+
if (this.horizontalAlign === PopoverHorizontalAlign.Stretch && isVertical) {
618+
popoverSize.width = targetRect.width;
619+
this._width = `${targetRect.width}px`;
620+
} else if (this.verticalAlign === PopoverVerticalAlign.Stretch && !isVertical) {
621+
popoverSize.height = targetRect.height;
622+
this._height = `${targetRect.height}px`;
623+
}
562624
}
563625

564626
const arrowOffset = this.hideArrow ? 0 : ARROW_SIZE;
@@ -642,6 +704,10 @@ class Popover extends Popup {
642704
};
643705
}
644706

707+
get isVertical() : boolean {
708+
return this.placement === PopoverPlacement.Top || this.placement === PopoverPlacement.Bottom;
709+
}
710+
645711
getRTLCorrectionLeft() {
646712
return parseFloat(window.getComputedStyle(this).left) - this.getBoundingClientRect().left;
647713
}
@@ -725,7 +791,6 @@ class Popover extends Popup {
725791

726792
getActualPlacement(targetRect: DOMRect): `${PopoverActualPlacement}` {
727793
const placement = this.placement;
728-
const isVertical = placement === PopoverPlacement.Top || placement === PopoverPlacement.Bottom;
729794
const popoverSize = this.getPopoverSize(!this.allowTargetOverlap);
730795

731796
let actualPlacement: PopoverActualPlacement = PopoverActualPlacement.Right;
@@ -749,7 +814,7 @@ class Popover extends Popup {
749814
let clientHeight = document.documentElement.clientHeight;
750815
let popoverHeight = popoverSize.height;
751816

752-
if (isVertical) {
817+
if (this.isVertical) {
753818
popoverHeight += this.hideArrow ? 0 : ARROW_SIZE;
754819
clientHeight -= Popover.VIEWPORT_MARGIN;
755820
}
@@ -790,6 +855,7 @@ class Popover extends Popup {
790855
case PopoverActualHorizontalAlign.Center:
791856
case PopoverActualHorizontalAlign.Stretch:
792857
left = targetRect.left - (popoverSize.width - targetRect.width) / 2;
858+
left = this._popoverResize.getCorrectedLeft(left);
793859
break;
794860
case PopoverActualHorizontalAlign.Left:
795861
left = targetRect.left;
@@ -809,6 +875,7 @@ class Popover extends Popup {
809875
case PopoverVerticalAlign.Center:
810876
case PopoverVerticalAlign.Stretch:
811877
top = targetRect.top - (popoverSize.height - targetRect.height) / 2;
878+
top = this._popoverResize.getCorrectedTop(top);
812879
break;
813880
case PopoverVerticalAlign.Top:
814881
top = targetRect.top;
@@ -849,6 +916,11 @@ class Popover extends Popup {
849916
get classes() {
850917
const allClasses = super.classes;
851918
allClasses.root["ui5-popover-root"] = true;
919+
allClasses.root["ui5-popover-rtl"] = this.isRtl;
920+
921+
if (this.resizable) {
922+
this._popoverResize.setCorrectResizeHandleClass(allClasses);
923+
}
852924

853925
return allClasses;
854926
}
@@ -884,6 +956,19 @@ class Popover extends Popup {
884956
return PopoverActualHorizontalAlign.Center;
885957
}
886958
}
959+
960+
get _showResizeHandle() {
961+
return this.resizable && this.onDesktop;
962+
}
963+
964+
get resizeHandlePlacement() {
965+
return this._resizeHandlePlacement;
966+
}
967+
968+
_onResizeMouseDown(e: MouseEvent) {
969+
this._popoverResize.onResizeMouseDown(e);
970+
this._resizeHandlePlacement = this._popoverResize.getResizeHandlePlacement();
971+
}
887972
}
888973

889974
const instanceOfPopover = (object: any): object is Popover => {
@@ -894,4 +979,4 @@ Popover.define();
894979

895980
export default Popover;
896981

897-
export { instanceOfPopover };
982+
export { instanceOfPopover, PopoverActualPlacement, PopoverActualHorizontalAlign };

0 commit comments

Comments
 (0)