Skip to content

Commit 320bb1d

Browse files
authored
Merge branch 'main' into feat/color-field
2 parents 905d426 + e90ccda commit 320bb1d

File tree

14 files changed

+308
-145
lines changed

14 files changed

+308
-145
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@genexus/chameleon-controls-library",
3-
"version": "6.23.0",
3+
"version": "6.25.0",
44
"description": "chameleon - A library of white-label, highly-customizable and reusable web components",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.js",
@@ -17,8 +17,9 @@
1717
"loader/"
1818
],
1919
"scripts": {
20-
"build": "npm run build.monaco && npm run build.light && npm run build.extras",
20+
"build": "npm run build.monaco && npm run build.light && npm run build.extras && npm run build.showcase",
2121
"build.light": "stencil build --docs --react",
22+
"build.showcase": "stencil build",
2223
"build.extras": "tsc --project tsconfig-collection.json && node copy-files.mjs",
2324
"build.monaco": "vite build",
2425
"generate": "stencil generate",
@@ -78,7 +79,7 @@
7879
"vite": "^5.2.10"
7980
},
8081
"peerDependencies": {
81-
"lit": "^3.3.0"
82+
"lit": "^3.3.1"
8283
},
8384
"husky": {
8485
"hooks": {

src/components/combo-box/combo-box.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { GxImageMultiStateStart } from "../../common/types";
2929
import { isMobileDevice, tokenMap } from "../../common/utils";
3030
import { ChPopoverCustomEvent, GxImageMultiState } from "../../components";
3131
import { focusComposedPath } from "../common/helpers";
32-
import { ChPopoverAlign } from "../popover/types";
32+
import { ChPopoverAlign, PopoverClosedInfo } from "../popover/types";
3333
import { filterSubModel } from "./helpers";
3434
import { computeComboBoxItemImage, getComboBoxImages } from "./item-images";
3535
import { findNextSelectedIndex, findSelectedIndex } from "./navigation";
@@ -679,7 +679,7 @@ export class ChComboBoxRender
679679
}
680680
};
681681

682-
#handlePopoverClose = (event: ChPopoverCustomEvent<any>) => {
682+
#handlePopoverClose = (event: ChPopoverCustomEvent<PopoverClosedInfo>) => {
683683
event.stopPropagation();
684684

685685
// The focus must return to the Host when the popover is closed using the
@@ -692,8 +692,13 @@ export class ChComboBoxRender
692692
// in the ch-popover
693693

694694
// Return the focus to the control if the popover was closed with the
695-
// escape key or by clicking again the combo-box
696-
if (focusComposedPath().includes(this.el)) {
695+
// escape key or by clicking again the combo-box. Don't return the focus if
696+
// the popover was closed because it is no longer visible, because it will
697+
// provoke a layout shift
698+
if (
699+
event.detail.reason !== "popover-no-longer-visible" &&
700+
focusComposedPath().includes(this.el)
701+
) {
697702
this.#shouldFocusTheComboBox = true;
698703
}
699704

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Gets the width and height of the document's viewport.
3+
* This uses the `window.innerWidth` and `window.innerHeight` properties to get
4+
* the current size of the viewport. These values represent the width and
5+
* height of the visible area, excluding any scrollbars.
6+
*
7+
* IMPORTANT: This function must not use `getBoundingClientRect` or similar
8+
* methods that depend on the DOM layout, and not in the actual viewport size.
9+
*
10+
* @returns An object containing the width and height of the document's
11+
* viewport.
12+
*/
13+
export const getDocumentSizes = () => ({
14+
width: window.innerWidth,
15+
height: window.innerHeight
16+
});

src/components/popover/popover.tsx

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ import {
1919
subscribeToRTLChanges,
2020
unsubscribeToRTLChanges
2121
} from "../../common/utils";
22+
import { getDocumentSizes } from "./get-document-sizes";
2223
import {
2324
ChPopoverAlign,
2425
ChPopoverResizeElement,
2526
ChPopoverSizeMatch,
26-
PopoverActionElement
27+
PopoverActionElement,
28+
PopoverClosedInfo
2729
} from "./types";
2830
import { fromPxToNumber, setResponsiveAlignment } from "./utils";
2931

@@ -506,8 +508,26 @@ export class ChPopover {
506508
*
507509
* This event can be prevented (`preventDefault()`), interrupting the
508510
* `ch-popover`'s closing.
511+
*
512+
* The `reason` property of the event provides more information about
513+
* the cause of the closing:
514+
* - `"click-outside"`: The popover is being closed because the user clicked
515+
* outside the popover when using `closeOnClickOutside === true` and
516+
* `mode === "manual"`.
517+
*
518+
* - `"escape-key"`: The popover is being closed because the user pressed the
519+
* "Escape" key when using `closeOnClickOutside === true` and
520+
* `mode === "manual"`.
521+
*
522+
* - `"popover-no-longer-visible"`: The popover is being closed because it
523+
* is no longer visible.
524+
*
525+
* - `"toggle"`: The popover is being closed by the native toggle behavior
526+
* of popover. It can be produced by the user clicking the `actionElement`,
527+
* pressing the "Enter" or "Space" keys on the `actionElement`, pressing
528+
* the "Escape" key or other. Used when `mode === "auto"`.
509529
*/
510-
@Event() popoverClosed: EventEmitter;
530+
@Event() popoverClosed: EventEmitter<PopoverClosedInfo>;
511531

512532
#showPopover = () => {
513533
this.el.showPopover();
@@ -520,11 +540,14 @@ export class ChPopover {
520540
};
521541

522542
// TODO: Add unit tests for this feature
523-
#closePopoverIfNotDefaultPrevented = (event: Event) => {
524-
const eventInfo = this.popoverClosed.emit();
543+
#closePopoverIfNotDefaultPrevented = (
544+
reason: PopoverClosedInfo,
545+
event?: Event
546+
) => {
547+
const eventInfo = this.popoverClosed.emit(reason);
525548

526549
if (eventInfo.defaultPrevented) {
527-
event.preventDefault();
550+
event?.preventDefault();
528551
return;
529552
}
530553

@@ -541,13 +564,16 @@ export class ChPopover {
541564
// determine if the popover should be closed
542565
!composedPath.includes(this.actionElement)
543566
) {
544-
this.#closePopoverIfNotDefaultPrevented(event);
567+
this.#closePopoverIfNotDefaultPrevented(
568+
{ reason: "click-outside" },
569+
event
570+
);
545571
}
546572
};
547573

548574
#handlePopoverCloseOnEscapeKey = (event: KeyboardEvent) => {
549575
if (event.code === KEY_CODES.ESCAPE) {
550-
this.#closePopoverIfNotDefaultPrevented(event);
576+
this.#closePopoverIfNotDefaultPrevented({ reason: "escape-key" }, event);
551577
}
552578
};
553579

@@ -664,6 +690,13 @@ export class ChPopover {
664690
passive: true
665691
})
666692
);
693+
694+
// We must observe the actual viewport size, because observing the
695+
// document.body does not work when the browser's window is resized
696+
// and the body has scrollbars.
697+
// Also, passive mode is not supported in the "resize" event, because this
698+
// event can not be prevented
699+
window.addEventListener("resize", this.#updatePositionRAF);
667700
};
668701

669702
#updatePositionRAF = () => {
@@ -672,7 +705,7 @@ export class ChPopover {
672705

673706
#updatePosition = () => {
674707
// - - - - - - - - - - - - - DOM read operations - - - - - - - - - - - - -
675-
const documentRect = document.documentElement.getBoundingClientRect();
708+
const documentRect = getDocumentSizes();
676709
const actionRect = this.actionElement.getBoundingClientRect();
677710
const popoverScrollSizes = {
678711
width: this.el.scrollWidth,
@@ -701,15 +734,15 @@ export class ChPopover {
701734
};
702735

703736
#getActionInlineStartPosition = (
704-
documentRect: DOMRect,
737+
documentRect: { width: number; height: number },
705738
actionRect: DOMRect
706739
) =>
707740
this.#isRTLDirection
708741
? documentRect.width - (actionRect.left + actionRect.width)
709742
: actionRect.left;
710743

711744
#setResponsiveAlignment = (
712-
documentRect: DOMRect,
745+
documentRect: { width: number; height: number },
713746
actionRect: DOMRect,
714747
actionInlineStart: number,
715748
popoverScrollSizes: { width: number; height: number },
@@ -743,13 +776,15 @@ export class ChPopover {
743776
// TODO: Add e2e tests for this
744777
try {
745778
if (maxInlineSizeCustomVarValue.endsWith("px")) {
746-
actualPopoverWidth = Number(
747-
maxInlineSizeCustomVarValue.replace("px", "").trim()
779+
actualPopoverWidth = Math.min(
780+
actualPopoverWidth,
781+
Number(maxInlineSizeCustomVarValue.replace("px", "").trim())
748782
);
749783
}
750784
if (maxBlockSizeCustomVarValue.endsWith("px")) {
751-
actualPopoverHeight = Number(
752-
maxBlockSizeCustomVarValue.replace("px", "").trim()
785+
actualPopoverHeight = Math.min(
786+
actualPopoverHeight,
787+
Number(maxBlockSizeCustomVarValue.replace("px", "").trim())
753788
);
754789
}
755790
} catch {
@@ -809,6 +844,17 @@ export class ChPopover {
809844
// Inline size
810845
if (inlineOverflow < 0) {
811846
const newMaxInlineSize = popoverWidth + inlineOverflow;
847+
848+
// TODO: Add e2e tests for this
849+
// TODO: We must implement a property to configure the behavior of these
850+
// kinds of situations
851+
// Close the popover if it won't be visible.
852+
if (newMaxInlineSize <= PRECISION_TO_AVOID_FLOATING_POINT_ERRORS) {
853+
return this.#closePopoverIfNotDefaultPrevented({
854+
reason: "popover-no-longer-visible"
855+
});
856+
}
857+
812858
setProperty(this.el, POPOVER_FORCED_MAX_INLINE_SIZE, newMaxInlineSize);
813859
}
814860
// Check if the forced inline size is no longer needed
@@ -823,6 +869,17 @@ export class ChPopover {
823869
// Block size
824870
if (blockOverflow < 0) {
825871
const newMaxBlockSize = popoverHeight + blockOverflow;
872+
873+
// TODO: Add e2e tests for this
874+
// TODO: We must implement a property to configure the behavior of these
875+
// kinds of situations
876+
// Close the popover if it won't be visible.
877+
if (newMaxBlockSize <= PRECISION_TO_AVOID_FLOATING_POINT_ERRORS) {
878+
return this.#closePopoverIfNotDefaultPrevented({
879+
reason: "popover-no-longer-visible"
880+
});
881+
}
882+
826883
setProperty(this.el, POPOVER_FORCED_MAX_BLOCK_SIZE, newMaxBlockSize);
827884
}
828885
// Check if the forced block size is no longer needed
@@ -890,6 +947,7 @@ export class ChPopover {
890947
capture: true
891948
})
892949
);
950+
window.removeEventListener("resize", this.#updatePositionRAF);
893951

894952
// Delete references for root nodes to any avoid memory leak
895953
this.#rootNodes = undefined;
@@ -903,7 +961,18 @@ export class ChPopover {
903961
if (willBeOpen) {
904962
eventInfo = this.popoverOpened.emit();
905963
} else {
906-
eventInfo = this.popoverClosed.emit();
964+
// If the popover is already closed, don't emit the event again
965+
// This can happen when:
966+
// - The "Escape" key is pressed in mode === "manual"
967+
// - The user clicks outside the popover in mode === "manual"
968+
// - The show property is changed to false externally
969+
// - The user scrolls the window and the popover is no longer visible
970+
// TODO: Add e2e tests for this
971+
if (!this.show) {
972+
return;
973+
}
974+
975+
eventInfo = this.popoverClosed.emit({ reason: "toggle" });
907976
}
908977

909978
// TODO: Add unit tests for this feature
@@ -1215,7 +1284,7 @@ export class ChPopover {
12151284
}
12161285

12171286
if (this.#adjustAlignment) {
1218-
const documentRect = document.documentElement.getBoundingClientRect();
1287+
const documentRect = getDocumentSizes();
12191288
const actionRect = this.actionElement.getBoundingClientRect();
12201289
const popoverScrollSizes = {
12211290
width: this.el.scrollWidth,
@@ -1285,6 +1354,7 @@ export class ChPopover {
12851354
? this.#handleMouseDown
12861355
: null
12871356
}
1357+
// TODO: Add beforetoggle listener to properly support preventing the natural toggle
12881358
// TODO: Should we add this event with popover="manual"???
12891359
// TODO: Check if the actionElement is an instance of Button to add this handler
12901360
onToggle={this.#handlePopoverToggle}

src/components/popover/readme.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ relative to an element, but placed on the top layer using `position: fixed`.
3030

3131
## Events
3232

33-
| Event | Description | Type |
34-
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
35-
| `popoverClosed` | Emitted when the popover is closed by an user interaction. This event can be prevented (`preventDefault()`), interrupting the `ch-popover`'s closing. | `CustomEvent<any>` |
36-
| `popoverOpened` | Emitted when the popover is opened by an user interaction. This event can be prevented (`preventDefault()`), interrupting the ch-popover's opening. | `CustomEvent<any>` |
33+
| Event | Description | Type |
34+
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
35+
| `popoverClosed` | Emitted when the popover is closed by an user interaction. This event can be prevented (`preventDefault()`), interrupting the `ch-popover`'s closing. The `reason` property of the event provides more information about the cause of the closing: - `"click-outside"`: The popover is being closed because the user clicked outside the popover when using `closeOnClickOutside === true` and `mode === "manual"`. - `"escape-key"`: The popover is being closed because the user pressed the "Escape" key when using `closeOnClickOutside === true` and `mode === "manual"`. - `"popover-no-longer-visible"`: The popover is being closed because it is no longer visible. - `"toggle"`: The popover is being closed by the native toggle behavior of popover. It can be produced by the user clicking the `actionElement`, pressing the "Enter" or "Space" keys on the `actionElement`, pressing the "Escape" key or other. Used when `mode === "auto"`. | `CustomEvent<{ reason: "click-outside" \| "escape-key" \| "popover-no-longer-visible" \| "toggle"; }>` |
36+
| `popoverOpened` | Emitted when the popover is opened by an user interaction. This event can be prevented (`preventDefault()`), interrupting the ch-popover's opening. | `CustomEvent<any>` |
3737

3838

3939
## Shadow Parts

src/components/popover/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,11 @@ export type ChPopoverSizeMatch =
2323
| "action-element-as-minimum";
2424

2525
export type ChPopoverPositionTry = "flip-block" | "flip-inline" | "none";
26+
27+
export type PopoverClosedInfo = {
28+
reason:
29+
| "click-outside"
30+
| "escape-key"
31+
| "popover-no-longer-visible"
32+
| "toggle";
33+
};

src/components/popover/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const getAlignmentValue = (
135135
};
136136

137137
export const setResponsiveAlignment = (
138-
documentRect: DOMRect,
138+
documentRect: { width: number; height: number },
139139
actionRect: DOMRect,
140140
actionInlineStart: number,
141141
popoverWidth: number,

0 commit comments

Comments
 (0)