Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

Commit

Permalink
feat: change dropdown side according to if it is too high or too low
Browse files Browse the repository at this point in the history
  • Loading branch information
Raspincel committed Feb 7, 2024
1 parent 6362584 commit 7acb533
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 156 deletions.
92 changes: 92 additions & 0 deletions src/web-components/who-is-online/components/dropdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,96 @@ describe('who-is-online-dropdown', () => {

expect(element()?.['selected']).toBe(mockParticipants[0].id);
});

describe('repositionDropdown', () => {
test('should call reposition methods if is open', () => {
const el = createEl({ position: 'bottom', participants: mockParticipants });
el['open'] = true;

el['repositionInVerticalDirection'] = jest.fn();
el['repositionInHorizontalDirection'] = jest.fn();

el['repositionDropdown']();

expect(el['repositionInVerticalDirection']).toHaveBeenCalled();
expect(el['repositionInHorizontalDirection']).toHaveBeenCalled();
});

test('should do nothing if is not open', () => {
const el = createEl({ position: 'bottom', participants: mockParticipants });
el['open'] = false;

el['repositionInVerticalDirection'] = jest.fn();
el['repositionInHorizontalDirection'] = jest.fn();

el['repositionDropdown']();

expect(el['repositionInVerticalDirection']).not.toHaveBeenCalled();
expect(el['repositionInHorizontalDirection']).not.toHaveBeenCalled();
});
});

/**
* private repositionInVerticalDirection = () => {
const { bottom, top, height } = this.parentElement.getBoundingClientRect();
const windowVerticalMidpoint = window.innerHeight / 2;
const dropdownVerticalMidpoint = top + height / 2;
if (dropdownVerticalMidpoint > windowVerticalMidpoint) {
this.dropdownList.style.setProperty('bottom', `${window.innerHeight - top + 8}px`);
this.dropdownList.style.setProperty('top', '');
return;
}
this.dropdownList.style.setProperty('top', `${bottom + 8}px`);
this.dropdownList.style.setProperty('bottom', '');
};
*/
describe('repositionInVerticalDirection', () => {
beforeEach(() => {
document.body.innerHTML = '';
window.innerHeight = 1000;
});

test('should set bottom and top styles when dropdownVerticalMidpoint is greater than windowVerticalMidpoint', async () => {
const el = createEl({ position: 'bottom', participants: mockParticipants });

await sleep();

const parentTop = 692;
const parentBottom = 740;
const windowHeight = 1000;

const parentElement = el.parentElement as HTMLElement;
parentElement.style.top = `${parentTop}px`;
parentElement.style.bottom = `${parentBottom}px`;

parentElement.style.height = '40px';

el['repositionInVerticalDirection']();

expect(el['dropdownList'].style.bottom).toBe(`${windowHeight - parentTop}px`);
expect(el['dropdownList'].style.top).toBe('');
});

test('should set top and bottom styles when dropdownVerticalMidpoint is less than windowVerticalMidpoint', async () => {
const el = createEl({ position: 'bottom', participants: mockParticipants });

await sleep();

const parentTop = 100;
const parentBottom = 140;
const windowHeight = 1000;

const parentElement = el.parentElement as HTMLElement;
parentElement.style.top = `${parentTop}px`;
parentElement.style.bottom = `${parentBottom}px`;
parentElement.style.height = '1000px';

el['repositionInVerticalDirection']();

expect(el['dropdownList'].style.bottom).toBe(`${windowHeight - parentTop}px`);
expect(el['dropdownList'].style.top).toBe('');
});
});
});
189 changes: 41 additions & 148 deletions src/web-components/who-is-online/components/dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ import { WebComponentsBase } from '../../base';
import importStyle from '../../base/utils/importStyle';
import { dropdownStyle } from '../css';

import {
Following,
WIODropdownOptions,
PositionOptions,
TooltipData,
VerticalSide,
HorizontalSide,
} from './types';
import { Following, WIODropdownOptions, TooltipData, VerticalSide, HorizontalSide } from './types';

const WebComponentsBaseElement = WebComponentsBase(LitElement);
const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, dropdownStyle];
Expand All @@ -30,15 +23,14 @@ export class WhoIsOnlineDropdown extends WebComponentsBaseElement {
declare participants: Participant[];
private textColorValues: number[];
declare selected: string;
private originalPosition: VerticalSide;
private menu: HTMLElement;
private dropdownContent: HTMLElement;
private host: HTMLElement;
declare disableDropdown: boolean;
declare showSeeMoreTooltip: boolean;
declare showParticipantTooltip: boolean;
declare following: Following;
declare localParticipantJoinedPresence: boolean;
declare dropdownList: HTMLElement;

private animationFrame: number;

static properties = {
open: { type: Boolean },
Expand All @@ -51,6 +43,7 @@ export class WhoIsOnlineDropdown extends WebComponentsBaseElement {
showSeeMoreTooltip: { type: Boolean },
showParticipantTooltip: { type: Boolean },
localParticipantJoinedPresence: { type: Boolean },
dropdownList: { type: Object },
};

constructor() {
Expand All @@ -67,13 +60,8 @@ export class WhoIsOnlineDropdown extends WebComponentsBaseElement {
this.shadowRoot.querySelector('.who-is-online__extras-dropdown').scrollTop = 0;
importStyle.call(this, 'who-is-online');

const dropdownList = this.shadowRoot.querySelector('.dropdown-list') as HTMLElement;
const { right, bottom } = this.parentElement.getBoundingClientRect();
dropdownList.style.setProperty('right', `${window.innerWidth - right}px`);
dropdownList.style.setProperty('top', `${bottom + 5}px`);
dropdownList.style.setProperty('z-index', '10');

dropdownList.style.position = 'fixed';
this.dropdownList = this.shadowRoot.querySelector('.dropdown-list') as HTMLElement;
this.dropdownList.style.position = 'fixed';
}

protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
Expand Down Expand Up @@ -238,140 +226,12 @@ export class WhoIsOnlineDropdown extends WebComponentsBaseElement {
);
}

private setMenu() {
if (!this.menu) {
this.menu = this.shadowRoot.querySelector('.who-is-online__extras-dropdown');
const options = {
rootMargin: '0px',
threshold: 1.0,
};

const intersectionObserver = new IntersectionObserver(this.adjustPosition, options);
const resizeObserver = new ResizeObserver(this.adjustPosition);
const target = this.menu;
intersectionObserver.observe(target);
resizeObserver.observe(this.scrollableParent ?? document.body);
}
}

private get scrollableParent() {
let elementWithOverflow: HTMLElement;

if (!this.host) {
this.host = (this.getRootNode() as ShadowRoot).host as HTMLElement;
}

let nextElement = this.host;

while (!elementWithOverflow) {
const parent = nextElement?.parentElement;

const hasOverflow = this.isScrollable(parent);

if (hasOverflow) {
elementWithOverflow = parent;
break;
}

nextElement = parent;

if (!nextElement) break;
}

return elementWithOverflow;
}

private isScrollable(element: HTMLElement): boolean {
if (!element) return false;

const hasScrollableContent = element.scrollHeight > element.clientHeight;
const overflowYStyle = window.getComputedStyle(element).overflowY;
const overflowXStyle = window.getComputedStyle(element).overflowX;
const isOverflowYHidden = overflowYStyle.indexOf('hidden') !== -1;
const isOverflowXHidden = overflowXStyle.indexOf('hidden') !== -1;

return hasScrollableContent && !isOverflowYHidden && !isOverflowXHidden;
}

private get dropdownBounds() {
if (!this.dropdownContent) {
this.dropdownContent = this.shadowRoot.querySelector('.dropdown-content');
}

const bounds = this.dropdownContent.getBoundingClientRect();

const { y, height } = this.menu.getBoundingClientRect();

return {
top: y,
bottom: y + height + 4,
height: height + 4,
contentY: bounds.y,
};
}

private shouldUseOriginalVertical() {
const { height, contentY } = this.dropdownBounds;
const { innerHeight } = window;
const bottom = contentY + height;

if (this.originalPosition.includes('bottom')) {
return height + bottom < innerHeight;
}

return contentY - height > 0;
}

private positionAction(): PositionOptions {
const { top, bottom } = this.dropdownBounds;
const { innerHeight } = window;

const isOutsideWindowBottom = bottom > innerHeight;
const isOutsideWindowTop = top < 0;

if (
(isOutsideWindowBottom && this.position.includes('bottom')) ||
(isOutsideWindowTop && this.position.includes('top'))
) {
return PositionOptions['CALCULATE-NEW'];
}

if (!isOutsideWindowBottom && !isOutsideWindowTop && this.shouldUseOriginalVertical()) {
return PositionOptions['USE-ORIGINAL'];
}

return PositionOptions['DO-NOTHING'];
}

private adjustPosition = () => {
const { top, bottom } = this.dropdownBounds;
const { innerHeight } = window;

const action = this.positionAction();

if (action === PositionOptions['DO-NOTHING']) return;

if (action === PositionOptions['USE-ORIGINAL']) {
const originalVertical = this.originalPosition.split('-')[0];
this.position = this.position.replace(/top|bottom/, originalVertical) as VerticalSide;
return;
}

const newSide = innerHeight - bottom > top ? 'bottom' : 'top';
const previousSide = this.position.split('-')[0];
const newPosition = this.position.replace(previousSide, newSide) as VerticalSide;

this.position = newPosition;
};

private toggle() {
if (!this.originalPosition) this.originalPosition = this.position;
this.setMenu();
this.open = !this.open;

if (!this.open) return;
this.selected = '';
setTimeout(() => this.adjustPosition());
this.repositionDropdown();
}

private get menuClasses() {
Expand All @@ -393,6 +253,39 @@ export class WhoIsOnlineDropdown extends WebComponentsBaseElement {
></superviz-tooltip>`;
};

private repositionDropdown = () => {
if (!this.open) {
window.cancelAnimationFrame(this.animationFrame);
return;
}

this.repositionInVerticalDirection();
this.repositionInHorizontalDirection();

this.animationFrame = window.requestAnimationFrame(this.repositionDropdown);
};

private repositionInVerticalDirection = () => {
const { bottom, top, height } = this.parentElement.getBoundingClientRect();
const windowVerticalMidpoint = window.innerHeight / 2;
const dropdownVerticalMidpoint = top + height / 2;

if (dropdownVerticalMidpoint > windowVerticalMidpoint) {
this.dropdownList.style.setProperty('bottom', `${window.innerHeight - top + 8}px`);
this.dropdownList.style.setProperty('top', '');
return;
}

this.dropdownList.style.setProperty('top', `${bottom + 8}px`);
this.dropdownList.style.setProperty('bottom', '');
};

private repositionInHorizontalDirection = () => {
const { right, left } = this.parentElement.getBoundingClientRect();
this.dropdownList.style.setProperty('right', `${window.innerWidth - right}px`);
this.dropdownList.style.setProperty('left', `${left}px`);
};

protected render() {
return html`
<div class="dropdown">
Expand Down
6 changes: 0 additions & 6 deletions src/web-components/who-is-online/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ export interface Options {
slotIndex: number;
}

export enum PositionOptions {
'DO-NOTHING',
'USE-ORIGINAL',
'CALCULATE-NEW',
}

export interface LocalParticipantData {
id: string;
color: string;
Expand Down
6 changes: 4 additions & 2 deletions src/web-components/who-is-online/css/dropdown.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ export const dropdownStyle = css`
position: relative;
display: flex;
flex-direction: column;
z-index: 10;
}
.dropdown-list > div {
padding: 4px;
min-width: 216px;
width: 216px;
box-sizing: border-box;
}
.who-is-online__extras-dropdown {
Expand Down Expand Up @@ -179,7 +181,7 @@ export const dropdownStyle = css`
}
.dropdown-list > div {
min-width: 192px;
width: 192px;
}
.menu--top {
Expand Down

0 comments on commit 7acb533

Please sign in to comment.