UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

443 lines (442 loc) • 16.8 kB
/** * DevExtreme (ui/pivot/ui.pivot_tabs.js) * Version: 18.2.18 * Build date: Tue Oct 18 2022 * * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; var $ = require("../../core/renderer"), eventsEngine = require("../../events/core/events_engine"), when = require("../../core/utils/deferred").when, fx = require("../../animation/fx"), swipeEvents = require("../../events/swipe"), translator = require("../../animation/translator"), eventUtils = require("../../events/utils"), extend = require("../../core/utils/extend").extend, each = require("../../core/utils/iterator").each, CollectionWidget = require("../collection/ui.collection_widget.edit"), config = require("../../core/config"), BindableTemplate = require("../widget/bindable_template"); var PIVOT_TABS_CLASS = "dx-pivottabs", PIVOT_TAB_CLASS = "dx-pivottabs-tab", PIVOT_TAB_SELECTED_CLASS = "dx-pivottabs-tab-selected", PIVOT_GHOST_TAB_CLASS = "dx-pivottabs-ghosttab", PIVOT_TAB_DATA_KEY = "dxPivotTabData", PIVOT_TAB_MOVE_DURATION = 200, PIVOT_TAB_MOVE_EASING = "cubic-bezier(.40, .80, .60, 1)"; var animation = { moveTo: function($tab, position, completeAction) { return fx.animate($tab, { type: "slide", to: { left: position }, duration: PIVOT_TAB_MOVE_DURATION, easing: PIVOT_TAB_MOVE_EASING, complete: completeAction }) }, slideAppear: function($tab, position) { return fx.animate($tab, { type: "slide", to: { left: position, opacity: 1 }, duration: PIVOT_TAB_MOVE_DURATION, easing: PIVOT_TAB_MOVE_EASING }) }, slideDisappear: function($tab, position) { return fx.animate($tab, { type: "slide", to: { left: position, opacity: 0 }, duration: PIVOT_TAB_MOVE_DURATION, easing: PIVOT_TAB_MOVE_EASING }) }, complete: function(elements) { if (!elements) { return } each(elements, function(_, $element) { fx.stop($element, true) }) }, stop: function(elements) { if (!elements) { return } each(elements, function(_, $element) { fx.stop($element) }) } }; var PivotTabs = CollectionWidget.inherit({ _getDefaultOptions: function() { return extend(this.callBase(), { selectedIndex: 0, onPrepare: null, onUpdatePosition: null, onRollback: null, focusStateEnabled: false, selectionMode: "single", selectionRequired: true, swipeEnabled: true }) }, _itemClass: function() { return PIVOT_TAB_CLASS }, _itemDataKey: function() { return PIVOT_TAB_DATA_KEY }, _itemContainer: function() { return this.$element() }, _elementWidth: function() { if (!this._elementWidthCache) { this._elementWidthCache = this.$element().width() } return this._elementWidthCache }, _clearElementWidthCache: function() { delete this._elementWidthCache }, _itemWidths: function() { if (!this._itemWidthsCache) { var $tabs = this._itemElements(), widths = []; $tabs.each(function() { widths.push($(this).outerWidth()) }); this._itemWidthsCache = widths } return this._itemWidthsCache }, _init: function() { this.callBase(); this._initGhostTab(); this._initSwipeHandlers(); this._initActions() }, _dimensionChanged: function() { this._clearElementWidthCache(); this._cleanPositionCache(); this._updateTabsPositions() }, _initGhostTab: function() { this._$ghostTab = $("<div>").addClass(PIVOT_GHOST_TAB_CLASS) }, _initActions: function() { this._updatePositionAction = this._createActionByOption("onUpdatePosition"); this._rollbackAction = this._createActionByOption("onRollback"); this._prepareAction = this._createActionByOption("onPrepare") }, _initTemplates: function() { this.callBase(); this._defaultTemplates.item = new BindableTemplate(function($container, data) { var text = data && data.title ? data.title : String(data); $container.empty(); $container.append($("<span>").text(text)) }, ["title"], this.option("integrationOptions.watchMethod")) }, _render: function() { this.$element().addClass(PIVOT_TABS_CLASS); this.callBase(); this._calculateMaxOffsets(this._getSelectedItemIndices()); this._updateTabsPositions(); this._renderGhostTab() }, _renderGhostTab: function() { this._itemContainer().append(this._$ghostTab); this._toggleGhostTab(false) }, _toggleGhostTab: function(visible) { var $ghostTab = this._$ghostTab; if (visible) { this._updateGhostTabContent(); $ghostTab.css("opacity", 1) } else { $ghostTab.css("opacity", 0) } }, _isGhostTabVisible: function() { return "1" === this._$ghostTab.css("opacity") }, _updateGhostTabContent: function(prevIndex) { prevIndex = void 0 === prevIndex ? this._previousIndex() : prevIndex; var $ghostTab = this._$ghostTab, $items = this._itemElements(); $ghostTab.html($items.eq(prevIndex).html()) }, _updateTabsPositions: function(offset) { offset = this._applyOffsetBoundaries(offset); var isPrevSwipeHandled = this.option("rtlEnabled") ^ offset > 0 && 0 !== offset, tabPositions = this._calculateTabPositions(isPrevSwipeHandled ? "replace" : "append"); this._moveTabs(tabPositions, offset); this._toggleGhostTab(isPrevSwipeHandled) }, _moveTabs: function(positions, offset) { offset = offset || 0; var $tabs = this._allTabElements(); $tabs.each(function(index) { translator.move($(this), { left: positions[index] + offset }) }) }, _applyOffsetBoundaries: function(offset) { offset = offset || 0; var maxOffset = offset > 0 ? this._maxRightOffset : this._maxLeftOffset; return offset * maxOffset }, _animateRollback: function() { var that = this, $tabs = this._itemElements(), positions = this._calculateTabPositions("prepend"); if (this._isGhostTabVisible()) { this._swapGhostWithTab($tabs.eq(this._previousIndex())); animation.moveTo(this._$ghostTab, positions[this._indexBoundary()], function() { that._toggleGhostTab(false) }) } $tabs.each(function(index) { animation.moveTo($(this), positions[index]) }) }, _animateComplete: function(newIndex, currentIndex) { var $tabs = this._itemElements(), isPrevSwipeHandled = this._isGhostTabVisible(); $tabs.eq(currentIndex).removeClass(PIVOT_TAB_SELECTED_CLASS); if (isPrevSwipeHandled) { this._animateIndexDecreasing(newIndex) } else { this._animateIndexIncreasing(newIndex) } $tabs.eq(newIndex).addClass(PIVOT_TAB_SELECTED_CLASS) }, _animateIndexDecreasing: function(newIndex) { var $tabs = this._itemElements(), positions = this._calculateTabPositions("append", newIndex), animations = []; $tabs.each(function(index) { animations.push(animation.moveTo($(this), positions[index])) }); animations.push(animation.slideDisappear(this._$ghostTab, positions[this._indexBoundary()])); return when.apply($, animations) }, _animateIndexIncreasing: function(newIndex) { var that = this, $tabs = this._itemElements(), positions = this._calculateTabPositions("prepend", newIndex), previousIndex = this._previousIndex(newIndex), $prevTab = $tabs.eq(previousIndex), prevTabPosition = translator.locate($prevTab).left, rtl = this.option("rtlEnabled"), bound = rtl ? this._elementWidth() - this._itemWidths()[previousIndex] : 0, isNextSwipeHandled = (prevTabPosition - bound) * this._getRTLSignCorrection() < 0, animations = []; if (!isNextSwipeHandled) { this._moveTabs(this._calculateTabPositions("append", previousIndex)) } this._updateGhostTabContent(previousIndex); this._swapGhostWithTab($tabs.eq(previousIndex)); $tabs.each(function(index) { var $tab = $(this), newPosition = positions[index]; animations.push(index === previousIndex ? animation.slideAppear($tab, newPosition) : animation.moveTo($tab, newPosition)) }); animations.push(animation.moveTo(this._$ghostTab, positions[this._indexBoundary()], function() { that._toggleGhostTab(false) })); return when.apply($, animations) }, _swapGhostWithTab: function($tab) { var $ghostTab = this._$ghostTab, lastTabPosition = translator.locate($tab).left, lastTabOpacity = $tab.css("opacity"); translator.move($tab, { left: translator.locate($ghostTab).left }); $tab.css("opacity", $ghostTab.css("opacity")); translator.move($ghostTab, { left: lastTabPosition }); $ghostTab.css("opacity", lastTabOpacity) }, _calculateTabPositions: function(ghostPosition, index) { index = void 0 === index ? this.option("selectedIndex") : index; var mark = index + ghostPosition; if (this._calculatedPositionsMark !== mark) { this._calculatedPositions = this._calculateTabPositionsImpl(index, ghostPosition); this._calculatedPositionsMark = mark } return this._calculatedPositions }, _calculateTabPositionsImpl: function(currentIndex, ghostPosition) { var prevIndex = this._normalizeIndex(currentIndex - 1), widths = this._itemWidths(); var rtl = this.option("rtlEnabled"), signCorrection = this._getRTLSignCorrection(), tabsContainerWidth = this._elementWidth(), nextPosition = rtl ? tabsContainerWidth : 0, positions = []; var calculateTabPosition = function(currentIndex, width) { var rtlOffset = rtl * width; positions.splice(currentIndex, 0, nextPosition - rtlOffset); nextPosition += width * signCorrection }; each(widths.slice(currentIndex), calculateTabPosition); each(widths.slice(0, currentIndex), calculateTabPosition); switch (ghostPosition) { case "replace": var lastTabPosition = positions[prevIndex]; positions.splice(prevIndex, 1, rtl ? tabsContainerWidth : -widths[prevIndex]); positions.push(lastTabPosition); break; case "prepend": positions.push(rtl ? tabsContainerWidth : -widths[prevIndex]); break; case "append": positions.push(nextPosition - widths[currentIndex] * rtl) } return positions }, _allTabElements: function() { return this._itemContainer().find("." + PIVOT_TAB_CLASS + ", ." + PIVOT_GHOST_TAB_CLASS) }, _initSwipeHandlers: function() { var $element = this.$element(); eventsEngine.on($element, eventUtils.addNamespace(swipeEvents.start, this.NAME), { itemSizeFunc: this._elementWidth.bind(this) }, this._swipeStartHandler.bind(this)); eventsEngine.on($element, eventUtils.addNamespace(swipeEvents.swipe, this.NAME), this._swipeUpdateHandler.bind(this)); eventsEngine.on($element, eventUtils.addNamespace(swipeEvents.end, this.NAME), this._swipeEndHandler.bind(this)) }, _swipeStartHandler: function(e) { this._prepareAnimation(); this._prepareAction(); e.maxLeftOffset = 1; e.maxRightOffset = 1; if (config().designMode || this.option("disabled") || !this.option("swipeEnabled") || this._indexBoundary() <= 1) { e.cancel = true } else { this._swipeGestureRunning = true } }, _prepareAnimation: function() { this._stopAnimation() }, _stopAnimation: function() { animation.complete(this._allTabElements()) }, _swipeUpdateHandler: function(e) { var offset = e.offset; this._updateTabsPositions(offset); this._updatePositionAction({ offset: offset }) }, _swipeEndHandler: function(e) { var targetOffset = e.targetOffset * this._getRTLSignCorrection(); if (0 === targetOffset) { this._animateRollback(); this._rollbackAction() } else { var newIndex = this._normalizeIndex(this.option("selectedIndex") - targetOffset); this.option("selectedIndex", newIndex) } this._swipeGestureRunning = false }, _previousIndex: function(atIndex) { atIndex = void 0 === atIndex ? this.option("selectedIndex") : atIndex; return this._normalizeIndex(atIndex - 1) }, _normalizeIndex: function(index) { var boundary = this._indexBoundary(); if (index < 0) { index = boundary + index } if (index >= boundary) { index -= boundary } return index }, _indexBoundary: function() { return this.option("items").length }, _renderSelection: function(current) { this._itemElements().eq(current).addClass(PIVOT_TAB_SELECTED_CLASS) }, _updateSelection: function(addedItems, removedItems) { var newIndex = addedItems[0], oldIndex = removedItems[0]; this._calculateMaxOffsets(newIndex); if (!this._swipeGestureRunning) { this._prepareAnimation() } if (this._itemElements().length) { this._animateComplete(newIndex, oldIndex) } }, _calculateMaxOffsets: function(index) { var currentTabWidth = this._itemWidths()[index], prevTabWidth = this._itemWidths()[this._previousIndex(index)], rtl = this.option("rtlEnabled"); this._maxLeftOffset = rtl ? prevTabWidth : currentTabWidth; this._maxRightOffset = rtl ? currentTabWidth : prevTabWidth }, _getRTLSignCorrection: function() { return this.option("rtlEnabled") ? -1 : 1 }, _visibilityChanged: function(visible) { if (visible) { this._dimensionChanged() } }, _clean: function() { animation.stop(this._allTabElements()); clearTimeout(this._resizeEventTimer); this._clearElementWidthCache(); this._cleanPositionCache(); this.callBase() }, _cleanPositionCache: function() { delete this._itemWidthsCache; delete this._calculatedPositionsMark }, _optionChanged: function(args) { switch (args.name) { case "items": case "rtlEnabled": this._cleanPositionCache(); this.callBase(args); break; case "onPrepare": case "swipeEnabled": break; case "onUpdatePosition": case "onRollback": this._initActions(); break; default: this.callBase(args) } }, prepare: function() { this._prepareAnimation() }, updatePosition: function(offset) { this._updateTabsPositions(offset) }, rollback: function() { this._animateRollback() } }); module.exports = PivotTabs;