UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

416 lines (415 loc) • 14.9 kB
/** * DevExtreme (esm/ui/tabs.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import $ from "../core/renderer"; import eventsEngine from "../events/core/events_engine"; import devices from "../core/devices"; import registerComponent from "../core/component_registrator"; import Button from "./button"; import { render } from "./widget/utils.ink_ripple"; import { addNamespace } from "../events/utils/index"; import { extend } from "../core/utils/extend"; import { isPlainObject } from "../core/utils/type"; import pointerEvents from "../events/pointer"; import { each } from "../core/utils/iterator"; import TabsItem from "./tabs/item"; import { TABS_EXPANDED_CLASS } from "./tabs/constants"; import { isMaterial, current as currentTheme } from "./themes"; import holdEvent from "../events/hold"; import Scrollable from "./scroll_view/ui.scrollable"; import { default as CollectionWidget } from "./collection/ui.collection_widget.live_update"; import { getImageContainer } from "../core/utils/icon"; import { BindableTemplate } from "../core/templates/bindable_template"; import { Deferred, when } from "../core/utils/deferred"; var TABS_CLASS = "dx-tabs"; var TABS_WRAPPER_CLASS = "dx-tabs-wrapper"; var TABS_STRETCHED_CLASS = "dx-tabs-stretched"; var TABS_SCROLLABLE_CLASS = "dx-tabs-scrollable"; var TABS_NAV_BUTTONS_CLASS = "dx-tabs-nav-buttons"; var OVERFLOW_HIDDEN_CLASS = "dx-overflow-hidden"; var TABS_ITEM_CLASS = "dx-tab"; var TABS_ITEM_SELECTED_CLASS = "dx-tab-selected"; var TABS_NAV_BUTTON_CLASS = "dx-tabs-nav-button"; var TABS_LEFT_NAV_BUTTON_CLASS = "dx-tabs-nav-button-left"; var TABS_RIGHT_NAV_BUTTON_CLASS = "dx-tabs-nav-button-right"; var TABS_ITEM_TEXT_CLASS = "dx-tab-text"; var TABS_ITEM_DATA_KEY = "dxTabData"; var BUTTON_NEXT_ICON = "chevronnext"; var BUTTON_PREV_ICON = "chevronprev"; var FEEDBACK_HIDE_TIMEOUT = 100; var FEEDBACK_DURATION_INTERVAL = 5; var FEEDBACK_SCROLL_TIMEOUT = 300; var TAB_OFFSET = 30; var Tabs = CollectionWidget.inherit({ _activeStateUnit: "." + TABS_ITEM_CLASS, _getDefaultOptions: function() { return extend(this.callBase(), { hoverStateEnabled: true, showNavButtons: true, scrollByContent: true, scrollingEnabled: true, selectionMode: "single", activeStateEnabled: true, selectionRequired: false, selectOnFocus: true, loopItemFocus: false, useInkRipple: false, badgeExpr: function(data) { return data ? data.badge : void 0 }, _itemAttributes: { role: "tab" } }) }, _defaultOptionsRules: function() { var themeName = currentTheme(); return this.callBase().concat([{ device: function() { return "desktop" !== devices.real().deviceType }, options: { showNavButtons: false } }, { device: { deviceType: "desktop" }, options: { scrollByContent: false } }, { device: function() { return "desktop" === devices.real().deviceType && !devices.isSimulator() }, options: { focusStateEnabled: true } }, { device: function() { return isMaterial(themeName) }, options: { useInkRipple: true, selectOnFocus: false } }]) }, _init: function() { this.callBase(); this.setAria("role", "tablist"); this.$element().addClass(TABS_CLASS); this._renderWrapper(); this._renderMultiple(); this._feedbackHideTimeout = FEEDBACK_HIDE_TIMEOUT }, _initTemplates: function() { this.callBase(); this._templateManager.addDefaultTemplates({ item: new BindableTemplate(function($container, data) { if (isPlainObject(data)) { this._prepareDefaultItemTemplate(data, $container) } else { $container.text(String(data)) } var $iconElement = getImageContainer(data.icon); $iconElement && $iconElement.prependTo($container); $container.wrapInner($("<span>").addClass(TABS_ITEM_TEXT_CLASS)) }.bind(this), ["text", "html", "icon"], this.option("integrationOptions.watchMethod")) }) }, _createItemByTemplate: function(itemTemplate, renderArgs) { var { itemData: itemData, container: container, index: index } = renderArgs; this._deferredTemplates[index] = new Deferred; return itemTemplate.render({ model: itemData, container: container, index: index, onRendered: () => this._deferredTemplates[index].resolve() }) }, _itemClass: function() { return TABS_ITEM_CLASS }, _selectedItemClass: function() { return TABS_ITEM_SELECTED_CLASS }, _itemDataKey: function() { return TABS_ITEM_DATA_KEY }, _initMarkup: function() { this._deferredTemplates = []; this.callBase(); this.option("useInkRipple") && this._renderInkRipple(); this.$element().addClass(OVERFLOW_HIDDEN_CLASS) }, _render: function() { this.callBase(); this._deferRenderScrolling() }, _deferRenderScrolling() { when.apply(this, this._deferredTemplates).done(() => this._renderScrolling()) }, _renderScrolling: function() { var removeClasses = [TABS_STRETCHED_CLASS, TABS_EXPANDED_CLASS, OVERFLOW_HIDDEN_CLASS]; this.$element().removeClass(removeClasses.join(" ")); if (this.option("scrollingEnabled") && this._isItemsWidthExceeded()) { if (!this._scrollable) { this._renderScrollable(); this._renderNavButtons() } this._scrollable.update(); this._updateNavButtonsVisibility(); if (this.option("rtlEnabled")) { this._scrollable.scrollTo({ left: this._scrollable.scrollWidth() - this._scrollable.clientWidth() }) } this._scrollToItem(this.option("selectedItem")) } if (!(this.option("scrollingEnabled") && this._isItemsWidthExceeded())) { this._cleanScrolling(); if (this._needStretchItems() && !this._isItemsWidthExceeded()) { this.$element().addClass(TABS_STRETCHED_CLASS) } this.$element().removeClass(TABS_NAV_BUTTONS_CLASS).addClass(TABS_EXPANDED_CLASS) } }, _isItemsWidthExceeded: function() { var tabItemsWidth = this._getSummaryItemsWidth(this._getVisibleItems(), true); return tabItemsWidth - 1 > this.$element().width() }, _needStretchItems: function() { var $visibleItems = this._getVisibleItems(); var elementWidth = this.$element().width(); var itemsWidth = []; each($visibleItems, (_, item) => { itemsWidth.push($(item).outerWidth(true)) }); var maxTabWidth = Math.max.apply(null, itemsWidth); return maxTabWidth > elementWidth / $visibleItems.length }, _cleanNavButtons: function() { if (!this._leftButton || !this._rightButton) { return } this._leftButton.$element().remove(); this._rightButton.$element().remove(); this._leftButton = null; this._rightButton = null }, _cleanScrolling: function() { if (!this._scrollable) { return } this._$wrapper.appendTo(this.$element()); this._scrollable.$element().remove(); this._scrollable = null; this._cleanNavButtons() }, _renderInkRipple: function() { this._inkRipple = render() }, _toggleActiveState: function($element, value, e) { this.callBase.apply(this, arguments); if (!this._inkRipple) { return } var config = { element: $element, event: e }; if (value) { this._inkRipple.showWave(config) } else { this._inkRipple.hideWave(config) } }, _renderMultiple: function() { if ("multiple" === this.option("selectionMode")) { this.option("selectOnFocus", false) } }, _renderWrapper: function() { this._$wrapper = $("<div>").addClass(TABS_WRAPPER_CLASS); this.$element().append(this._$wrapper) }, _itemContainer: function() { return this._$wrapper }, _renderScrollable: function() { var $itemContainer = this.$element().wrapInner($("<div>").addClass(TABS_SCROLLABLE_CLASS)).children(); this._scrollable = this._createComponent($itemContainer, Scrollable, { direction: "horizontal", showScrollbar: false, useKeyboard: false, useNative: false, scrollByContent: this.option("scrollByContent"), onScroll: this._updateNavButtonsVisibility.bind(this) }); this.$element().append(this._scrollable.$element()) }, _scrollToItem: function(itemData) { if (!this._scrollable) { return } var $item = this._editStrategy.getItemElement(itemData); this._scrollable.scrollToElement($item) }, _renderNavButtons: function() { this.$element().toggleClass(TABS_NAV_BUTTONS_CLASS, this.option("showNavButtons")); if (!this.option("showNavButtons")) { return } var rtlEnabled = this.option("rtlEnabled"); this._leftButton = this._createNavButton(-TAB_OFFSET, rtlEnabled ? BUTTON_NEXT_ICON : BUTTON_PREV_ICON); var $leftButton = this._leftButton.$element(); $leftButton.addClass(TABS_LEFT_NAV_BUTTON_CLASS); this.$element().prepend($leftButton); this._rightButton = this._createNavButton(TAB_OFFSET, rtlEnabled ? BUTTON_PREV_ICON : BUTTON_NEXT_ICON); var $rightButton = this._rightButton.$element(); $rightButton.addClass(TABS_RIGHT_NAV_BUTTON_CLASS); this.$element().append($rightButton) }, _updateNavButtonsVisibility: function() { this._leftButton && this._leftButton.option("disabled", this._scrollable.scrollLeft() <= 0); this._rightButton && this._rightButton.option("disabled", this._scrollable.scrollLeft() >= Math.round(this._scrollable.scrollWidth() - this._scrollable.clientWidth())) }, _updateScrollPosition: function(offset, duration) { this._scrollable.update(); this._scrollable.scrollBy(offset / duration) }, _createNavButton: function(offset, icon) { var that = this; var holdAction = that._createAction((function() { that._holdInterval = setInterval((function() { that._updateScrollPosition(offset, FEEDBACK_DURATION_INTERVAL) }), FEEDBACK_DURATION_INTERVAL) })); var holdEventName = addNamespace(holdEvent.name, "dxNavButton"); var pointerUpEventName = addNamespace(pointerEvents.up, "dxNavButton"); var pointerOutEventName = addNamespace(pointerEvents.out, "dxNavButton"); var navButton = this._createComponent($("<div>").addClass(TABS_NAV_BUTTON_CLASS), Button, { focusStateEnabled: false, icon: icon, onClick: function() { that._updateScrollPosition(offset, 1) }, integrationOptions: {} }); var $navButton = navButton.$element(); eventsEngine.on($navButton, holdEventName, { timeout: FEEDBACK_SCROLL_TIMEOUT }, function(e) { holdAction({ event: e }) }.bind(this)); eventsEngine.on($navButton, pointerUpEventName, (function() { that._clearInterval() })); eventsEngine.on($navButton, pointerOutEventName, (function() { that._clearInterval() })); return navButton }, _clearInterval: function() { if (this._holdInterval) { clearInterval(this._holdInterval) } }, _updateSelection: function(addedSelection) { this._scrollable && this._scrollable.scrollToElement(this.itemElements().eq(addedSelection[0]), { left: 1, right: 1 }) }, _visibilityChanged: function(visible) { if (visible) { this._dimensionChanged() } }, _dimensionChanged: function() { this._renderScrolling() }, _itemSelectHandler: function(e) { if ("single" === this.option("selectionMode") && this.isItemSelected(e.currentTarget)) { return } this.callBase(e) }, _clean: function() { this._deferredTemplates = []; this._cleanScrolling(); this.callBase() }, _optionChanged: function(args) { switch (args.name) { case "useInkRipple": case "scrollingEnabled": case "showNavButtons": this._invalidate(); break; case "scrollByContent": this._scrollable && this._scrollable.option(args.name, args.value); break; case "width": this.callBase(args); this._dimensionChanged(); break; case "selectionMode": this._renderMultiple(); this.callBase(args); break; case "badgeExpr": this._invalidate(); break; default: this.callBase(args) } }, _afterItemElementInserted() { this.callBase(); this._deferRenderScrolling() }, _afterItemElementDeleted($item, deletedActionArgs) { this.callBase($item, deletedActionArgs); this._renderScrolling() } }); Tabs.ItemClass = TabsItem; registerComponent("dxTabs", Tabs); export default Tabs;