/* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import { AbstractLayout, aria, EnumObject, Event, EventHandler, HtmlComponent, InitModelOf, scout, SimpleTab, SimpleTabAreaEventMap, SimpleTabAreaLayout, SimpleTabAreaModel, SimpleTabOverflowMenu, SimpleTabView, TabbableCoordinator, TabbableItem, Widget, widgets } from '../index'; export type SimpleTabAreaPosition = EnumObject; export type SimpleTabAreaDisplayStyle = EnumObject; export class SimpleTabArea extends Widget implements SimpleTabAreaModel { declare model: SimpleTabAreaModel; declare eventMap: SimpleTabAreaEventMap; declare self: SimpleTabArea; static Position = { TOP: 'top', BOTTOM: 'bottom', RIGHT: 'right', LEFT: 'left' } as const; static DisplayStyle = { DEFAULT: 'default', SPREAD_EVEN: 'spreadEven' } as const; position: SimpleTabAreaPosition; displayStyle: SimpleTabAreaDisplayStyle; tabs: SimpleTab[]; tabbableCoordinator: TabbableCoordinator; selectOnFocus = true; protected _selectedViewTab: SimpleTab; protected _tabClickHandler: EventHandler>>; constructor() { super(); this.position = SimpleTabArea.Position.TOP; this.displayStyle = SimpleTabArea.DisplayStyle.DEFAULT; this.tabs = []; this._selectedViewTab = null; this._tabClickHandler = this._onTabClick.bind(this); this._addWidgetProperties(['tabs']); } protected override _init(model: InitModelOf) { super._init(model); this.tabbableCoordinator = scout.create(TabbableCoordinator, {parent: this}); if (this.selectOnFocus) { this.tabbableCoordinator.on('propertyChange:currentItem', event => { if (event.newValue instanceof SimpleTab) { this.selectTab(event.newValue); } }); } this._setPosition(this.position); } protected override _render() { this.$container = this.$parent.appendDiv('simple-tab-area'); aria.role(this.$container, 'tablist'); this.htmlComp = HtmlComponent.install(this.$container, this.session); this.htmlComp.setLayout(this._createLayout()); } protected _createLayout(): AbstractLayout { return new SimpleTabAreaLayout(this); } protected override _renderProperties() { super._renderProperties(); this._renderPosition(); this._renderDisplayStyle(); this._renderTabs(); } setPosition(position: SimpleTabAreaPosition) { this.setProperty('position', position); } protected _setPosition(position: SimpleTabAreaPosition) { this._setProperty('position', position); this.tabbableCoordinator.setOrientation(scout.isOneOf(this.position, SimpleTabArea.Position.TOP, SimpleTabArea.Position.BOTTOM) ? 'horizontal' : 'vertical'); } protected _renderPosition() { const positions: SimpleTabAreaPosition[] = [SimpleTabArea.Position.TOP, SimpleTabArea.Position.BOTTOM, SimpleTabArea.Position.LEFT, SimpleTabArea.Position.RIGHT]; positions.forEach(position => this.$container.toggleClass(this._cssClassForPosition(position), this.position === position)); this.invalidateLayoutTree(); } protected _cssClassForPosition(position: SimpleTabAreaPosition) { return 'position-' + position; } setDisplayStyle(displayStyle: SimpleTabAreaDisplayStyle) { this.setProperty('displayStyle', displayStyle); } protected _renderDisplayStyle() { this.$container.toggleClass('spread-even', this.displayStyle === SimpleTabArea.DisplayStyle.SPREAD_EVEN); this.invalidateLayoutTree(); } protected _renderTabs() { // reverse since tab.renderAfter() called without sibling=true argument (see _renderTab) // will _prepend_ themselves into the container. this.tabs.slice().reverse().forEach(tab => this._renderTab(tab)); widgets.updateFirstLastMarker(this.tabs); } protected _renderTab(tab: SimpleTab) { tab.renderAfter(this.$container); } protected override _renderVisible() { this.$container.setVisible(this.visible && this.tabs.length > 0); this.invalidateLayoutTree(); } protected _onTabClick(event: Event>) { this.selectTab(event.source); } getTabs(): SimpleTab[] { return this.tabs; } /** * @param true to also return the visible tabs that are currently overflown. Default is false. */ getVisibleTabs(includeOverflown = false): SimpleTab[] { return this.tabs.filter(tab => { let visible = tab.visible; if (!includeOverflown) { visible &&= !tab.overflown; } return visible; }); } selectTab(viewTab: SimpleTab) { if (this._selectedViewTab === viewTab) { return; } this.deselectTab(this._selectedViewTab); this._selectedViewTab = viewTab; if (viewTab) { if (this.selectOnFocus) { this.tabbableCoordinator.setCurrentItem(viewTab); } // Select the new view tab. viewTab.select(); } this.trigger('tabSelect', {viewTab}); if (viewTab?.overflown) { this.invalidateLayoutTree(); } } deselectTab(viewTab: SimpleTab) { if (!viewTab) { return; } if (this._selectedViewTab !== viewTab) { return; } this._selectedViewTab.deselect(); } getSelectedTab(): SimpleTab { return this._selectedViewTab; } addTab(tab: SimpleTab, sibling?: SimpleTab) { let insertPosition = -1; if (sibling) { insertPosition = this.tabs.indexOf(sibling); } this.tabs.splice(insertPosition + 1, 0, tab); tab.on('click', this._tabClickHandler); this._updateTabbableItems(); if (this.rendered) { this._renderVisible(); tab.renderAfter(this.$container, sibling); widgets.updateFirstLastMarker(this.getTabs()); this.invalidateLayoutTree(); } } destroyTab(tab: SimpleTab) { let index = this.tabs.indexOf(tab); if (index < 0) { return; } this.tabs.splice(index, 1); tab.destroy(); tab.off('click', this._tabClickHandler); this._updateTabbableItems(); if (this.rendered) { this._renderVisible(); widgets.updateFirstLastMarker(this.getTabs()); this.invalidateLayoutTree(); } } /** * @internal */ _updateTabbableItems() { let items: TabbableItem[] = [...this.tabs]; let overflowTab = this.findChild(SimpleTabOverflowMenu); if (overflowTab) { items.push(overflowTab); } this.tabbableCoordinator.setItems(items); } }