Skip to content

Commit

Permalink
feat: enable drop from header to popover
Browse files Browse the repository at this point in the history
  • Loading branch information
dimovpetar committed Jan 18, 2024
1 parent c8999f5 commit 9655827
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 55 deletions.
164 changes: 110 additions & 54 deletions packages/main/src/TabContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ class TabContainer extends UI5Element {
}
}

async _onTabStripClick(e: Event) {
_onTabStripClick(e: Event) {
const tab = getTab(e.target as HTMLElement);
if (!tab || tab._realTab.disabled) {
return;
Expand All @@ -489,23 +489,43 @@ class TabContainer extends UI5Element {
return;
}

if (!tab._realTab._hasOwnContent && tab._realTab.tabs.length) {
this._setPopoverItems(tab._realTab.subTabs);
this.responsivePopover = await this._respPopover();

if (this.responsivePopover.opened) {
this.responsivePopover.close();
} else {
this._setPopoverInitialFocus();
}

this.responsivePopover.showAt(tab._realTab.getTabInStripDomRef()!);
if (this._isSingleClickTab(tab)) {
this._togglePopover(tab, tab._realTab.subTabs);
return;
}

this._onHeaderItemSelect(tab);
}

_isSingleClickTab(tab: Tab) {
return !tab._realTab._hasOwnContent && tab._realTab.tabs.length;
}

async _togglePopover(opener: HTMLElement, items: Array<ITab>, setInitialFocus = false) {
this.responsivePopover = await this._respPopover();

if (this.responsivePopover.isOpen()) {
this.responsivePopover.close();
} else {
this._showPopoverAt(opener, items, setInitialFocus);
}
}

async _showPopoverAt(opener: HTMLElement, items: Array<ITab>, setInitialFocus = false) {
this._setPopoverItems(items);
this.responsivePopover = await this._respPopover();

if (this.responsivePopover.isOpen() && this.responsivePopover._opener !== opener) {
this.responsivePopover.close();
}

if (setInitialFocus) {
this._setPopoverInitialFocus();
}

this.responsivePopover.showAt(opener);
}

getPopoverOpenerItems(opener?: HTMLElement) {
const _opener = opener || this.responsivePopover?._opener;

Expand All @@ -528,7 +548,7 @@ class TabContainer extends UI5Element {
return (_opener as Tab)._realTab.subTabs;
}

async _onTabExpandButtonClick(e: Event) {
_onTabExpandButtonClick(e: Event) {
e.stopPropagation();
e.preventDefault();

Expand All @@ -550,15 +570,7 @@ class TabContainer extends UI5Element {
return;
}

this._setPopoverItems(tabInstance.subTabs);
this.responsivePopover = await this._respPopover();

if (this.responsivePopover.isOpen()) {
this.responsivePopover.close();
} else {
this._setPopoverInitialFocus();
}
this.responsivePopover.showAt(button);
this._togglePopover(button, tabInstance.subTabs, true);
}

_setPopoverInitialFocus() {
Expand Down Expand Up @@ -765,15 +777,15 @@ class TabContainer extends UI5Element {
return slideUp(element).promise();
}

async _onOverflowClick(e: Event) {
_onOverflowClick(e: Event) {
if ((e.target as HTMLElement).classList.contains("ui5-tc__overflow")) {
// the empty area in the overflow was clicked
return;
}

const overflow = e.currentTarget as HTMLElement;
const isEndOverflow = overflow.classList.contains("ui5-tc__overflow--end");
this._setPopoverItems(this.getPopoverOpenerItems(<Button>overflow.querySelector("[ui5-button]")));
// this._setPopoverItems(this.getPopoverOpenerItems(<Button>overflow.querySelector("[ui5-button]")));

let opener;
if (isEndOverflow) {
Expand All @@ -782,13 +794,7 @@ class TabContainer extends UI5Element {
opener = this.startOverflowButton[0] || this._getStartOverflowBtnDOM();
}

this.responsivePopover = await this._respPopover();
if (this.responsivePopover.opened) {
this.responsivePopover.close();
} else {
this._setPopoverInitialFocus();
this.responsivePopover.showAt(opener);
}
this._togglePopover(opener, this.getPopoverOpenerItems(<Button>overflow.querySelector("[ui5-button]")), true);
}

_addStyleIndent(tabs: Array<ITab>) {
Expand All @@ -810,15 +816,15 @@ class TabContainer extends UI5Element {
});
}

async _onOverflowKeyDown(e: KeyboardEvent) {
_onOverflowKeyDown(e: KeyboardEvent) {
const overflow = e.currentTarget as HTMLElement;
const isEndOverflow = overflow.classList.contains("ui5-tc__overflow--end");
const isStartOverflow = overflow.classList.contains("ui5-tc__overflow--start");

if (isDown(e) || (isStartOverflow && isLeft(e)) || (isEndOverflow && isRight(e))) {
e.stopPropagation();
e.preventDefault();
await this._onOverflowClick(e);
this._onOverflowClick(e);
}
}

Expand Down Expand Up @@ -1189,33 +1195,52 @@ class TabContainer extends UI5Element {
}

_onHeaderDragOver(e: DragEvent) {
const draggedElement = (e.target as HTMLElement)?.closest(`[role="tab"]`);
let dropTarget: HTMLElement | null;
const dropIndicator = this.dropIndicatorDOM;
if (!draggedElement) {
dropIndicator.target = "";
return;
}
let opener;
let showPopover = false;

// the tab past this point qualifies to be dropped.
// calling prevent default allows the drop event to fire later
e.preventDefault();
if (e.target === this._getEndOverflowBtnDOM()) {
dropTarget = this._getEndOverflowBtnDOM()!;
opener = dropTarget;
showPopover = true;
} else {
dropTarget = (e.target as HTMLElement)?.closest(`[role="tab"]`);
// the tab past this point qualifies to be dropped.
// calling prevent default allows the drop event to fire later
e.preventDefault();

const tabs = [...this._getTabStrip().querySelectorAll<HTMLElement>(`[role="tab"]:not([hidden])`)];
const found = getElementAtCoordinate(
tabs,
e.clientX,
Orientation.Horizontal,
);
const tabs = [...this._getTabStrip().querySelectorAll<HTMLElement>(`[role="tab"]:not([hidden])`)];
const found = getElementAtCoordinate(
tabs,
e.clientX,
Orientation.Horizontal,
);

if (!found) {
return;
if (!found) {
return;
}

opener = getTab(found.closestElement) as Tab;

// draw drop indicator
dropIndicator.show();
dropIndicator.target = found.closestElement.id;
dropIndicator.placement = found.dropPlacement;

if (opener._realTab.subTabs.length) {
showPopover = true;
}
}

// draw drop indicator
this.dropIndicatorDOM.show();
if (!dropTarget) {
dropIndicator.target = "";
return;
}

dropIndicator.target = found.closestElement.id;
dropIndicator.placement = found.dropPlacement;
if (showPopover) {
this._showPopoverAt(opener, this.getPopoverOpenerItems(opener));
}
}

_onHeaderDrop(e: DragEvent) {
Expand Down Expand Up @@ -1259,7 +1284,8 @@ class TabContainer extends UI5Element {
index: droppedTabIndex,
},
destination: {
element: this, // todo: support nesting
// eslint-disable-next-line no-warning-comments
element: this, // TODO: support nesting
index: targetTabIndex,
dropPlacement: result.dropPlacement,
},
Expand All @@ -1270,6 +1296,36 @@ class TabContainer extends UI5Element {
(this.responsivePopover?.querySelector("[ui5-list]") as List).dropIndicatorDOM.hide();
}

_onPopoverDrop(e: DragEvent) {
// const listItem = e.target;
// TODO: Only handle drop from header ot list, avoid handling if item-reorder event from the list is fired
// TODO: or let the list handle drop from another container?
if (!e.dataTransfer) {
return;
}

const id = e.dataTransfer.getData("text/plain");
const tabs = [...this._getTabStrip().querySelectorAll<HTMLElement>(`[role="tab"]`)] as Array<Tab>;
const droppedTab = tabs.find(item => item.id === id)!._realTab;
const droppedTabIndex = this.items.indexOf(droppedTab);

let dropIn;
const targetTabIndex = this.items.indexOf(((e.target as HTMLElement)!.closest("[ui5-li-custom]") as Tab)._realTab);
const dropPlacement = DropPlacement.After;

this.fireEvent("tab-reorder", {
source: {
element: droppedTab,
index: droppedTabIndex,
},
destination: {
element: dropIn || this, // TODO: support nesting
index: targetTabIndex,
dropPlacement,
},
});
}

_onReorderItemsInPopover(e: CustomEvent<ListItemsReorderEventDetail>) {
const { source, destination } = e.detail;
const droppedTab = (source.element as unknown as Tab)._realTab; // TODO: store _realTab reference as custom data
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/TabContainerPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
separators="None"
@ui5-item-click="{{_onOverflowListItemClick}}"
@ui5-item-reorder="{{_onReorderItemsInPopover}}"
@drop="{{_onPopoverDrop}}"
>
{{#each _popoverItems}}
{{overflowPresentation}}
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/util/DragAndDropHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Orientation from "../types/Orientation.js";

const getElementAtCoordinate = (elements: Array<HTMLElement>, point: number, layoutOrientation: Orientation) => {
let shortestDist = Number.POSITIVE_INFINITY,
closestElement: Element | null = null;
closestElement: HTMLElement | null = null;

// determine which element is most closest to the point
for (let i = 0; i < elements.length; i++) {
Expand Down

0 comments on commit 9655827

Please sign in to comment.