Skip to content

Commit

Permalink
refactor(ui5-tabcontainer): simplify popover logic (#8249)
Browse files Browse the repository at this point in the history
Preparation to handle drop operations.
  • Loading branch information
dimovpetar authored Feb 12, 2024
1 parent 6faf061 commit 53601c3
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 75 deletions.
6 changes: 3 additions & 3 deletions packages/main/src/Tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class Tab extends UI5Element implements ITab, ITabbable {
@property({ type: Boolean })
forcedSelected!: boolean;

@property({ type: Object })
@property({ type: Object, defaultValue: null })
realTabReference!: Tab;

@property({ type: Boolean })
Expand Down Expand Up @@ -230,15 +230,15 @@ class Tab extends UI5Element implements ITab, ITabbable {
}

get isOnSelectedTabPath(): boolean {
return this.realTabReference === this || this.tabs.some(subTab => subTab.isOnSelectedTabPath);
return this.selected || this.tabs.some(subTab => subTab.isOnSelectedTabPath);
}

get _effectiveSlotName() {
return this.isOnSelectedTabPath ? this._individualSlot : `disabled-${this._individualSlot}`;
}

get _defaultSlotName() {
return this.realTabReference === this ? "" : "disabled-slot";
return this.selected ? "" : "disabled-slot";
}

get hasOwnContent() {
Expand Down
202 changes: 135 additions & 67 deletions packages/main/src/TabContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import { getScopedVarName } from "@ui5/webcomponents-base/dist/CustomElementsScope.js";
import "@ui5/webcomponents-icons/dist/slim-arrow-up.js";
import "@ui5/webcomponents-icons/dist/slim-arrow-down.js";
import arraysAreEqual from "@ui5/webcomponents-base/dist/util/arraysAreEqual.js";
import {
TABCONTAINER_PREVIOUS_ICON_ACC_NAME,
TABCONTAINER_NEXT_ICON_ACC_NAME,
Expand Down Expand Up @@ -87,6 +88,8 @@ interface ITab extends UI5Element {
forcedStyle?: Record<string, any>;
}

type TabContainerPopoverOwner = "start-overflow" | "end-overflow" | Tab;

const tabStyles: Array<StyleData> = [];
const staticAreaTabStyles: Array<StyleData> = [];
const PAGE_UP_DOWN_SIZE = 5;
Expand Down Expand Up @@ -298,7 +301,7 @@ class TabContainer extends UI5Element {
_endOverflowText!: string;

@property({ type: Object, multiple: true })
_overflowItems!: Array<ITab>;
_popoverItemsFlat!: Array<ITab>;

@property({ validator: Integer, noAttribute: true })
_width?: number;
Expand All @@ -316,7 +319,7 @@ class TabContainer extends UI5Element {
individualSlots: true,
invalidateOnChildChange: {
properties: true,
slots: false,
slots: true,
},
})
items!: Array<ITab>;
Expand All @@ -342,7 +345,7 @@ class TabContainer extends UI5Element {
startOverflowButton!: Array<IButton>;

_itemNavigation: ItemNavigation;
_allItemsAndSubItems?: Array<ITab>;
_itemsFlat?: Array<ITab>;
responsivePopover?: ResponsivePopover;
_handleResizeBound: () => void;

Expand All @@ -369,18 +372,18 @@ class TabContainer extends UI5Element {
}

onBeforeRendering() {
this._allItemsAndSubItems = this._getAllSubItems(this.items);
if (!this._allItemsAndSubItems.length) {
this._itemsFlat = this._flatten(this.items);
if (!this._itemsFlat.length) {
return;
}

// update selected tab
const selectedTabs = this._allItemsAndSubItems.filter(tab => tab.selected) as Array<Tab>;
const selectedTabs = this._itemsFlat.filter(tab => tab.selected) as Array<Tab>;
if (selectedTabs.length) {
this._selectedTab.forcedSelected = false;
this._selectedTab = selectedTabs[0];
} else {
this._selectedTab = this._allItemsAndSubItems[0] as Tab;
this._selectedTab = this._itemsFlat[0] as Tab;
this._selectedTab.forcedSelected = true;
}

Expand All @@ -406,6 +409,16 @@ class TabContainer extends UI5Element {
const focusStart = this._getRootTab(this._selectedTab);
this._itemNavigation.setCurrentItem(focusStart);
}

if (this.responsivePopover?.opened) {
const popoverItems = this._getPopoverItemsFor(this._getPopoverOwner(this.responsivePopover._opener!));

if (popoverItems.length) {
this._setPopoverItems(popoverItems);
} else {
this.responsivePopover.close();
}
}
}

onEnterDOM() {
Expand Down Expand Up @@ -437,12 +450,10 @@ class TabContainer extends UI5Element {
tab.forcedMixedMode = this.mixedMode;
tab.forcedPosinset = index + 1;
tab.forcedSetsize = arr.length;
tab.realTabReference = this._selectedTab;
tab.isTopLevelTab = items.some(i => i === tab);
walk(items, _tab => {
_tab.realTabReference = tab.realTabReference;
});
});

this._setIndentLevels(items);
}

_onHeaderFocusin(e: FocusEvent) {
Expand All @@ -468,17 +479,8 @@ class TabContainer extends UI5Element {
}

if (!tab.realTabReference.hasOwnContent && tab.realTabReference.tabs.length) {
this._overflowItems = tab.realTabReference.subTabs;
this._addStyleIndent(this._overflowItems);

this.responsivePopover = await this._respPopover();
if (this.responsivePopover.opened) {
this.responsivePopover.close();
} else {
this._setPopoverInitialFocus();
}
await this._togglePopover(tab);

this.responsivePopover.showAt(tab.realTabReference.getTabInStripDomRef()!);
return;
}

Expand All @@ -489,34 +491,32 @@ class TabContainer extends UI5Element {
e.stopPropagation();
e.preventDefault();

let button = e.target as HTMLElement;
let tabInstance = (button as TabContainerExpandButton).tab;
let tabInstance: Tab;

if (isTabInStrip(e.target as HTMLElement)) {
tabInstance = e.target as Tab;
} else {
tabInstance = (e.target as TabContainerExpandButton).tab;
}

let opener = e.target as HTMLElement;

if (tabInstance) {
tabInstance.focus();
}

if (e.type === "keydown" && !(e.target as Tab).realTabReference.isSingleClickArea) {
button = (e.target as Tab).querySelectorAll<HTMLElement>(".ui5-tab-expand-button")[0];
opener = (e.target as Tab).querySelector<TabContainerExpandButton>(".ui5-tab-expand-button [ui5-button]")!;
tabInstance = (e.target as Tab).realTabReference;
}

// if clicked between the expand button and the tab
if (!tabInstance) {
this._onHeaderItemSelect(button.parentElement as HTMLElement);
this._onHeaderItemSelect(opener.parentElement as HTMLElement);
return;
}

this._overflowItems = tabInstance.subTabs;
this._addStyleIndent(this._overflowItems);

this.responsivePopover = await this._respPopover();
if (this.responsivePopover.isOpen()) {
this.responsivePopover.close();
} else {
this._setPopoverInitialFocus();
}
this.responsivePopover.showAt(button);
await this._togglePopover(opener, true);
}

_setPopoverInitialFocus() {
Expand Down Expand Up @@ -618,36 +618,45 @@ class TabContainer extends UI5Element {
* @default []
*/
get allItems() : Array<ITab> {
return this._getAllSubItems(this.items);
return this._flatten(this.items);
}

_getAllSubItems(items: Array<ITab>, result: Array<ITab> = [], level = 1) {
_setIndentLevels(items: Array<ITab>, level = 1) {
items.forEach(item => {
if (item.hasAttribute("ui5-tab") || item.hasAttribute("ui5-tab-separator")) {
item.forcedLevel = level;
result.push(item);

if (item.subTabs) {
this._getAllSubItems(item.subTabs, result, level + 1);
this._setIndentLevels(item.subTabs, level + 1);
}
}
});
}

_flatten(items: Array<ITab>) {
const result: Array<ITab> = [];

walk(items, item => {
if (item.hasAttribute("ui5-tab") || item.hasAttribute("ui5-tab-separator")) {
result.push(item);
}
});

return result;
}

_onItemSelect(selectedTabId: string) {
const previousTab = this._selectedTab;
const selectedTabIndex = this._allItemsAndSubItems!.findIndex(item => item.__id === selectedTabId);
const selectedTab = this._allItemsAndSubItems![selectedTabIndex] as Tab;
const selectedTabIndex = this._itemsFlat!.findIndex(item => item.__id === selectedTabId);
const selectedTab = this._itemsFlat![selectedTabIndex] as Tab;

const selectionSuccessful = this.selectTab(selectedTab, selectedTabIndex);
if (!selectionSuccessful) {
return;
}

// update selected property on all items
this._allItemsAndSubItems!.forEach((item, index) => {
this._itemsFlat!.forEach((item, index) => {
const selected = selectedTabIndex === index;
item.selected = selected;

Expand Down Expand Up @@ -731,45 +740,30 @@ class TabContainer extends UI5Element {

const overflow = e.currentTarget as HTMLElement;
const isEndOverflow = overflow.classList.contains("ui5-tc__overflow--end");
const overflowAttr = isEndOverflow ? "end-overflow" : "start-overflow";

this._overflowItems = this.items.filter(item => {
const stripRef = item.getTabInStripDomRef();

return stripRef && stripRef.hasAttribute(overflowAttr);
});

this._addStyleIndent(this._overflowItems);

let opener;

if (isEndOverflow) {
opener = this.overflowButton[0] || this._getEndOverflowBtnDOM();
} else {
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);
}
await this._togglePopover(opener, true);
}

_addStyleIndent(tabs: Array<ITab>) {
const extraIndent = this._getAllSubItems(tabs)
_addStyleIndent(itemsFlat: Array<ITab>) {
const extraIndent = itemsFlat
.filter(tab => !tab.isSeparator)
.some(tab => tab.design !== SemanticColor.Default && tab.design !== SemanticColor.Neutral);

walk(tabs, tab => {
let level = tab.forcedLevel! - 1;
itemsFlat.forEach(item => {
let level = item.forcedLevel! - 1;

if (tab.isSeparator) {
if (item.isSeparator) {
level += 1;
}

tab.forcedStyle = {
item.forcedStyle = {
[getScopedVarName("--_ui5-tab-indentation-level")]: level,
[getScopedVarName("--_ui5-tab-extra-indent")]: extraIndent ? 1 : null,
};
Expand Down Expand Up @@ -1106,6 +1100,80 @@ class TabContainer extends UI5Element {
return this.items.filter((item): item is Tab => !item.isSeparator);
}

_getPopoverOwner(opener: HTMLElement): TabContainerPopoverOwner {
if (opener === this._getStartOverflowBtnDOM() || opener.slot === "startOverflowButton") {
return "start-overflow";
}

if (opener === this._getEndOverflowBtnDOM() || opener.slot === "overflowButton") {
return "end-overflow";
}

if (opener instanceof Button) {
return (opener as TabContainerExpandButton).tab;
}

return (opener as Tab);
}

_getPopoverItemsFor(targetOwner: TabContainerPopoverOwner) {
if (targetOwner === "start-overflow") {
return this.items.filter(item => {
const stripRef = item.getTabInStripDomRef();

return stripRef && stripRef.hasAttribute("start-overflow");
});
}

if (targetOwner === "end-overflow") {
return this.items.filter(item => {
const stripRef = item.getTabInStripDomRef();

return stripRef && stripRef.hasAttribute("end-overflow");
});
}

if (isTabInStrip(targetOwner)) {
return targetOwner.realTabReference.subTabs;
}

return targetOwner.subTabs;
}

_setPopoverItems(items: Array<ITab>) {
const newItemsFlat = this._flatten(items);

if (!arraysAreEqual(this._popoverItemsFlat, newItemsFlat)) {
this._popoverItemsFlat = newItemsFlat;
this._addStyleIndent(this._popoverItemsFlat);
}
}

async _togglePopover(opener: HTMLElement, setInitialFocus = false) {
this.responsivePopover = await this._respPopover();

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

async _showPopoverAt(opener: HTMLElement, setInitialFocus = false) {
this._setPopoverItems(this._getPopoverItemsFor(this._getPopoverOwner(opener)));
this.responsivePopover = await this._respPopover();

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

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

this.responsivePopover.showAt(opener);
}

get hasSubTabs(): boolean {
const tabs = this._getTabs();

Expand Down Expand Up @@ -1233,11 +1301,11 @@ class TabContainer extends UI5Element {
}
}

const isTabDiv = (el: HTMLElement) => el.localName === "div" && el.getAttribute("role") === "tab";
const isTabInStrip = (el: HTMLElement) => el.localName === "div" && el.getAttribute("role") === "tab";

const getTab = (el: HTMLElement | null) => {
while (el) {
if (isTabDiv(el)) {
if (isTabInStrip(el)) {
return el as Tab;
}

Expand Down
Loading

0 comments on commit 53601c3

Please sign in to comment.