Skip to content

Commit

Permalink
Merge pull request #870 from OutSystems/ROU-4664
Browse files Browse the repository at this point in the history
ROU-4664: Prevent dom move when inside popup
  • Loading branch information
BenOsodrac authored Dec 7, 2023
2 parents 90d1b6a + 4b7342d commit cc7eb40
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 28 deletions.
7 changes: 6 additions & 1 deletion dist/OutSystemsUI.css
Original file line number Diff line number Diff line change
Expand Up @@ -12699,7 +12699,7 @@ html[data-uieditorversion^="1"] .wizard-wrapper-item{
font:normal normal normal 20px/1 FontAwesome;
height:100%;
pointer-events:none;
position:absolute;
position:fixed;
right:16px;
top:0;
-webkit-transition:-webkit-transform 200ms ease-in-out;
Expand Down Expand Up @@ -12940,6 +12940,11 @@ html[data-uieditorversion^="1"] .wizard-wrapper-item{
color:var(--color-neutral-6);
pointer-events:none;
}
.osui-dropdown-serverside--is-inside-popup .osui-dropdown-serverside__balloon-wrapper{
top:calc(var(--osui-dropdown-ss-top) + var(--osui-dropdown-ss-input-height) + 4px);
position:fixed;
overflow:visible;
}
.osui-dropdown-serverside--not-valid .osui-dropdown-serverside__selected-values-wrapper{
border-color:var(--color-error);
}
Expand Down
5 changes: 5 additions & 0 deletions dist/OutSystemsUI.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ declare namespace OSFramework.OSUI.GlobalEnum {
MainContent = "main-content",
MenuLinks = "app-menu-links",
Placeholder = "ph",
Popup = "popup-dialog",
SkipContent = "skip-nav"
}
enum CSSSelectors {
Expand Down Expand Up @@ -319,6 +320,7 @@ declare namespace OSFramework.OSUI.GlobalEnum {
Prefix = "on",
Resize = "resize",
Scroll = "scroll",
ScrollEnd = "scrollend",
TouchEnd = "touchend",
TouchMove = "touchmove",
TouchStart = "touchstart",
Expand Down Expand Up @@ -1035,6 +1037,7 @@ declare namespace OSFramework.OSUI.Helper {
static GetElementById(id: string): HTMLElement;
static GetElementByUniqueId(uniqueId: string): HTMLElement;
static GetFocusableElements(element: HTMLElement): HTMLElement[];
static IsInsidePopupWidget(element: HTMLElement): boolean;
static Move(element: HTMLElement, target: HTMLElement): void;
static SetInputValue(inputElem: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, value: string): void;
static TagSelector(element: HTMLElement, htmlTag: string): HTMLElement | undefined;
Expand Down Expand Up @@ -1934,6 +1937,7 @@ declare namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
private _hasA11yEnabled;
private _intersectionObserver;
private _isBlocked;
private _isInsidePopup;
private _isOpen;
private _platformEventOnToggleCallback;
private _selectValuesWrapper;
Expand Down Expand Up @@ -2033,6 +2037,7 @@ declare namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide.Enum {
BalloonWrapper = "osui-dropdown-serverside__balloon-wrapper",
ErrorMessage = "osui-dropdown-serverside-error-message",
IsDisabled = "osui-dropdown-serverside--is-disabled",
IsInsidePopup = "osui-dropdown-serverside--is-inside-popup",
IsOpened = "osui-dropdown-serverside--is-opened",
IsVisible = "osui-dropdown-serverside-visible",
NotValid = "osui-dropdown-serverside--not-valid",
Expand Down
55 changes: 44 additions & 11 deletions dist/OutSystemsUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ var OSFramework;
CssClassElements["MainContent"] = "main-content";
CssClassElements["MenuLinks"] = "app-menu-links";
CssClassElements["Placeholder"] = "ph";
CssClassElements["Popup"] = "popup-dialog";
CssClassElements["SkipContent"] = "skip-nav";
})(CssClassElements = GlobalEnum.CssClassElements || (GlobalEnum.CssClassElements = {}));
let CSSSelectors;
Expand Down Expand Up @@ -395,6 +396,7 @@ var OSFramework;
HTMLEvent["Prefix"] = "on";
HTMLEvent["Resize"] = "resize";
HTMLEvent["Scroll"] = "scroll";
HTMLEvent["ScrollEnd"] = "scrollend";
HTMLEvent["TouchEnd"] = "touchend";
HTMLEvent["TouchMove"] = "touchmove";
HTMLEvent["TouchStart"] = "touchstart";
Expand Down Expand Up @@ -2789,6 +2791,12 @@ var OSFramework;
const _filteredElements = Array.from(_focusableElems).filter((element) => element.getAttribute(OSUI.Constants.FocusTrapIgnoreAttr) !== 'true');
return [..._filteredElements];
}
static IsInsidePopupWidget(element) {
const _popup = document.querySelector(OSUI.Constants.Dot + OSUI.GlobalEnum.CssClassElements.Popup);
if (_popup && element) {
return _popup.contains(element);
}
}
static Move(element, target) {
if (element && target) {
target.appendChild(element);
Expand Down Expand Up @@ -2860,11 +2868,26 @@ var OSFramework;
}
}
static _scrollToInvalidInput(element, isSmooth, elementParentClass) {
const browser = OSFramework.OSUI.Helper.DeviceInfo.GetBrowser();
OutSystems.OSUI.Utils.ScrollToElement(element.id, isSmooth, 0, elementParentClass);
if (browser === OSUI.GlobalEnum.Browser.safari || OSFramework.OSUI.Helper.DeviceInfo.IsIos) {
if (isSmooth) {
console.warn('Due to the unsupported scrollend event on Safari/iOS, the smooth transition is disabled and the invalid input will be focused directly.');
}
element.focus();
}
else {
const activeScreenElement = Helper.Dom.ClassSelector(document.body, OSUI.GlobalEnum.CssClassElements.ActiveScreen);
const focusOnScrollEnd = () => {
element.focus();
activeScreenElement.removeEventListener(OSUI.GlobalEnum.HTMLEvent.ScrollEnd, focusOnScrollEnd);
};
activeScreenElement.addEventListener(OSUI.GlobalEnum.HTMLEvent.ScrollEnd, focusOnScrollEnd);
}
}
static _searchElementId(element, isSmooth, elementParentClass) {
const elementToSearch = element.parentElement;
if (elementToSearch.id !== '') {
if (elementToSearch.id !== OSUI.Constants.EmptyString) {
this._scrollToInvalidInput(elementToSearch, isSmooth, elementParentClass);
}
else {
Expand All @@ -2876,10 +2899,12 @@ var OSFramework;
errorCode: OutSystems.OSUI.ErrorCodes.Utilities.FailGetInvalidInput,
callback: () => {
let element = document.body;
if (elementId !== '') {
if (elementId !== OSUI.Constants.EmptyString) {
element = Helper.Dom.GetElementById(elementId);
}
this._checkInvalidInputs(element, isSmooth, elementParentClass);
Helper.AsyncInvocation(() => {
this._checkInvalidInputs(element, isSmooth, elementParentClass);
});
},
});
return result;
Expand Down Expand Up @@ -4462,12 +4487,6 @@ var OSFramework;
var BottomSheet;
(function (BottomSheet_1) {
class BottomSheet extends Patterns.AbstractPattern {
get gestureEventInstance() {
return this._gestureEventInstance;
}
get hasGestureEvents() {
return this._hasGestureEvents;
}
constructor(uniqueId, configs) {
super(uniqueId, new BottomSheet_1.BottomSheetConfig(configs));
this._isOpen = false;
Expand All @@ -4480,6 +4499,12 @@ var OSFramework;
},
};
}
get gestureEventInstance() {
return this._gestureEventInstance;
}
get hasGestureEvents() {
return this._hasGestureEvents;
}
_handleFocusBehavior() {
const opts = {
focusTargetElement: this._parentSelf,
Expand Down Expand Up @@ -5404,7 +5429,8 @@ var OSFramework;
throw new Error(`${OSUI.ErrorCodes.Dropdown.HasNoImplementation.code}: ${OSUI.ErrorCodes.Dropdown.HasNoImplementation.message}`);
}
_moveBallonElement() {
OSUI.Helper.Dom.Move(this._balloonWrapperElement, this._activeScreenElement);
const balloon = document.adoptNode(this._balloonWrapperElement);
this._activeScreenElement.appendChild(balloon);
}
_onBodyClick(_eventType, event) {
if (this._isOpen === false) {
Expand Down Expand Up @@ -5802,7 +5828,13 @@ var OSFramework;
this.setA11YProperties();
this._setUpEvents();
this._setCssClasses();
this._moveBallonElement();
this._isInsidePopup = OSUI.Helper.Dom.IsInsidePopupWidget(this.selfElement);
if (this._isInsidePopup) {
OSUI.Helper.Dom.Styles.AddClass(this.selfElement, ServerSide.Enum.CssClass.IsInsidePopup);
}
else {
this._moveBallonElement();
}
this._setBalloonCoordinates();
}
unsetCallbacks() {
Expand Down Expand Up @@ -6036,6 +6068,7 @@ var OSFramework;
CssClass["BalloonWrapper"] = "osui-dropdown-serverside__balloon-wrapper";
CssClass["ErrorMessage"] = "osui-dropdown-serverside-error-message";
CssClass["IsDisabled"] = "osui-dropdown-serverside--is-disabled";
CssClass["IsInsidePopup"] = "osui-dropdown-serverside--is-inside-popup";
CssClass["IsOpened"] = "osui-dropdown-serverside--is-opened";
CssClass["IsVisible"] = "osui-dropdown-serverside-visible";
CssClass["NotValid"] = "osui-dropdown-serverside--not-valid";
Expand Down
1 change: 1 addition & 0 deletions src/scripts/OSFramework/OSUI/GlobalEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace OSFramework.OSUI.GlobalEnum {
MainContent = 'main-content',
MenuLinks = 'app-menu-links',
Placeholder = 'ph',
Popup = 'popup-dialog',
SkipContent = 'skip-nav',
}

Expand Down
23 changes: 23 additions & 0 deletions src/scripts/OSFramework/OSUI/Helper/Dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,29 @@ namespace OSFramework.OSUI.Helper {
return [..._filteredElements] as HTMLElement[];
}

/**
* Method to check if element is inside a Popup widget
*
* @static
* @param {HTMLElement} element
* @return {*} {boolean}
* @memberof Dom
*/
public static IsInsidePopupWidget(element: HTMLElement): boolean {
const _popup = document.querySelectorAll(Constants.Dot + GlobalEnum.CssClassElements.Popup);
let _isInsidePopup = false;

if (_popup.length > 0 && element) {
_popup.forEach((popup) => {
if (popup.contains(element)) {
_isInsidePopup = true;
}
});
}

return _isInsidePopup;
}

/**
* Moves a given HTML element to target position.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
private _intersectionObserver: IntersectionObserver;
// Store a Flag property that will control if the dropdown is blocked (like it's under closing animation)
private _isBlocked = false;
// Store if Dropdown is being used inside a popup widget
private _isInsidePopup: boolean;
// Store the Element State, by default is closed!
private _isOpen = false;
// Platform OnClose Callback
Expand Down Expand Up @@ -507,9 +509,8 @@ namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
this._selfElementBoundingClientRect.x + this._selfElementBoundingClientRect.width &&
selfElement.y === this._selfElementBoundingClientRect.y)
)

// Store the new selElement coordinates
this._selfElementBoundingClientRect.x = selfElement.x;
// Store the new selElement coordinates
this._selfElementBoundingClientRect.x = selfElement.x;
this._selfElementBoundingClientRect.y = selfElement.y;

// Set Css inline variables
Expand Down Expand Up @@ -649,8 +650,8 @@ namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
Event.DOMEvents.Listeners.Type.BodyOnClick,
this._eventOnBodyClick
);
if(Helper.DeviceInfo.IsPhone === false) {

if (Helper.DeviceInfo.IsPhone === false) {
// Add the ScreenScroll callback that will be used to update the balloon coodinates
Event.DOMEvents.Listeners.GlobalListenerManager.Instance.addHandler(
Event.DOMEvents.Listeners.Type.ScreenOnScroll,
Expand Down Expand Up @@ -730,7 +731,7 @@ namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
this._eventOnBodyClick
);

if(Helper.DeviceInfo.IsPhone === false) {
if (Helper.DeviceInfo.IsPhone === false) {
Event.DOMEvents.Listeners.GlobalListenerManager.Instance.removeHandler(
Event.DOMEvents.Listeners.Type.ScreenOnScroll,
this._eventOnScreenScroll
Expand Down Expand Up @@ -971,8 +972,20 @@ namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
this._setUpEvents();
// Add CSS classes
this._setCssClasses();
// Ensure that the Move only happens after HTML elements has been set!
this._moveBallonElement();

// Check if the dropdown is placed inside a Popup Widget
this._isInsidePopup = Helper.Dom.IsInsidePopupWidget(this.selfElement);

if (this._isInsidePopup) {
/* If it is inside, then do not perform the MoveElement and instead add a class to change some CSS properties.
This is done due to the changes in recat-dom recent version, where listeners are all placed on the reactContainer, making any widget with events to loose
its context when its moved inside a Popup, that is placed outside the reactContainer*/
Helper.Dom.Styles.AddClass(this.selfElement, Enum.CssClass.IsInsidePopup);
} else {
// Ensure that the Move only happens after HTML elements has been set!
this._moveBallonElement();
}

// Set the balloon coordinates
this._setBalloonCoordinates();
}
Expand Down Expand Up @@ -1275,4 +1288,4 @@ namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide.Enum {
BalloonWrapper = 'osui-dropdown-serverside__balloon-wrapper',
ErrorMessage = 'osui-dropdown-serverside-error-message',
IsDisabled = 'osui-dropdown-serverside--is-disabled',
IsInsidePopup = 'osui-dropdown-serverside--is-inside-popup',
IsOpened = 'osui-dropdown-serverside--is-opened',
IsVisible = 'osui-dropdown-serverside-visible',
NotValid = 'osui-dropdown-serverside--not-valid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ $balloonMobileTopMargin: 5vh;
overflow: hidden;
// --osui-dropdown-ss-thresholdanimateval is the value used as a threshold to animate down the balloon
transform: translateY(calc(-1 * var(--osui-dropdown-ss-thresholdanimateval)));
transition:
opacity 250ms ease,
transform 300ms ease-in-out;
transition: opacity 250ms ease, transform 300ms ease-in-out;

// Service Studio Preview
& {
Expand Down Expand Up @@ -297,6 +295,19 @@ $balloonMobileTopMargin: 5vh;
}
}

// When inside Popup widget
&--is-inside-popup {
.osui-dropdown-serverside__balloon-wrapper {
top: calc(var(--osui-dropdown-ss-top) + var(--osui-dropdown-ss-input-height) + 4px);
position: fixed;
overflow: visible;

&.osui-dropdown-serverside--is-opened {
z-index: calc(var(--osui-popup-layer) + var(--layer-local-tier-1));
}
}
}

// When it's not valid
&--not-valid {
.osui-dropdown-serverside__selected-values-wrapper {
Expand Down Expand Up @@ -344,10 +355,10 @@ $balloonMobileTopMargin: 5vh;
}
}

// Inside Popup
body:has(.popup-dialog):has(.osui-dropdown-serverside--is-opened) {
.osui-dropdown-serverside__balloon-wrapper.osui-dropdown-serverside--is-opened {
z-index: calc(var(--osui-popup-layer) + var(--layer-local-tier-1));
// Inside Form & Popup
.form {
.osui-dropdown-serverside--is-inside-popup input[data-input] {
margin-bottom: 0;
}
}

Expand Down

0 comments on commit cc7eb40

Please sign in to comment.