devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
498 lines (495 loc) • 19.9 kB
JavaScript
/**
* DevExtreme (cjs/__internal/ui/m_multi_view.js)
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _translator2 = require("../../common/core/animation/translator");
var _swipeable = _interopRequireDefault(require("../../common/core/events/gesture/swipeable"));
var _visibility_change = require("../../common/core/events/visibility_change");
var _message = _interopRequireDefault(require("../../common/core/localization/message"));
var _component_registrator = _interopRequireDefault(require("../../core/component_registrator"));
var _devices = _interopRequireDefault(require("../../core/devices"));
var _element = require("../../core/element");
var _renderer = _interopRequireDefault(require("../../core/renderer"));
var _common = require("../../core/utils/common");
var _deferred = require("../../core/utils/deferred");
var _math = require("../../core/utils/math");
var _size = require("../../core/utils/size");
var _type = require("../../core/utils/type");
var _uiCollection_widget = _interopRequireDefault(require("../../ui/collection/ui.collection_widget.live_update"));
var _m_multi_view = require("./multi_view/m_multi_view.animation");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
default: e
}
}
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function(n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) {
({}).hasOwnProperty.call(t, r) && (n[r] = t[r])
}
}
return n
}, _extends.apply(null, arguments)
}
const MULTIVIEW_CLASS = "dx-multiview";
const MULTIVIEW_WRAPPER_CLASS = "dx-multiview-wrapper";
const MULTIVIEW_ITEM_CONTAINER_CLASS = "dx-multiview-item-container";
const MULTIVIEW_ITEM_CLASS = "dx-multiview-item";
const MULTIVIEW_ITEM_HIDDEN_CLASS = "dx-multiview-item-hidden";
const MULTIVIEW_ITEM_DATA_KEY = "dxMultiViewItemData";
const MULTIVIEW_ANIMATION_DURATION = 200;
const toNumber = value => +value;
const position = $element => (0, _translator2.locate)($element).left;
class MultiView extends _uiCollection_widget.default {
_supportedKeys() {
return _extends({}, super._supportedKeys(), {
pageUp: _common.noop,
pageDown: _common.noop
})
}
_getDefaultOptions() {
return _extends({}, super._getDefaultOptions(), {
selectedIndex: 0,
swipeEnabled: true,
animationEnabled: true,
loop: false,
deferRendering: true,
loopItemFocus: false,
selectOnFocus: true,
selectionMode: "single",
selectionRequired: true,
selectByClick: false
})
}
_defaultOptionsRules() {
return super._defaultOptionsRules().concat([{
device: () => "desktop" === _devices.default.real().deviceType && !_devices.default.isSimulator(),
options: {
focusStateEnabled: true
}
}])
}
_itemClass() {
return "dx-multiview-item"
}
_itemDataKey() {
return "dxMultiViewItemData"
}
_itemContainer() {
return this._$itemContainer
}
_itemElements() {
return this._itemContainer().children(this._itemSelector())
}
_itemWidth() {
if (!this._itemWidthValue) {
this._itemWidthValue = (0, _size.getWidth)(this._$wrapper)
}
return this._itemWidthValue
}
_clearItemWidthCache() {
delete this._itemWidthValue
}
_itemsCount() {
return this.option("items").length
}
_isAllItemsHidden() {
const {
items: items
} = this.option();
return items.every(((_, index) => !this._isItemVisible(index)))
}
_normalizeIndex(index, direction) {
let loop = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : true;
const count = this._itemsCount();
if (this._isAllItemsHidden()) {
return
}
if (index < 0) {
index += count
}
if (index >= count) {
index -= count
}
const step = direction > 0 ? -1 : 1;
const lastNotLoopedIndex = -1 === step ? 0 : count - 1;
while (!this._isItemVisible(index) && (loop || index !== lastNotLoopedIndex)) {
index = (index + step + count) % count
}
return index
}
_getRTLSignCorrection() {
return this.option("rtlEnabled") ? -1 : 1
}
_init() {
super._init.apply(this, arguments);
this._activeStateUnit = ".dx-multiview-item";
const $element = this.$element();
$element.addClass("dx-multiview");
this._$wrapper = (0, _renderer.default)("<div>").addClass("dx-multiview-wrapper");
this._$wrapper.appendTo($element);
this._$itemContainer = (0, _renderer.default)("<div>").addClass("dx-multiview-item-container");
this._$itemContainer.appendTo(this._$wrapper);
this.option("loopItemFocus", this.option("loop"));
this._findBoundaryIndices();
this._initSwipeable()
}
_ensureSelectedItemIsVisible() {
const {
loop: loop,
selectedIndex: currentSelectedIndex
} = this.option();
if (this._isItemVisible(currentSelectedIndex)) {
return
}
if (this._isAllItemsHidden()) {
this.option("selectedIndex", 0);
return
}
const direction = -1 * this._getRTLSignCorrection();
let newSelectedIndex = this._normalizeIndex(currentSelectedIndex, direction, loop);
if (newSelectedIndex === currentSelectedIndex) {
newSelectedIndex = this._normalizeIndex(currentSelectedIndex, -direction, loop)
}
this.option("selectedIndex", newSelectedIndex)
}
_initMarkup() {
this._deferredItems = [];
super._initMarkup();
this._ensureSelectedItemIsVisible();
const selectedItemIndices = this._getSelectedItemIndices();
this._updateItemsVisibility(selectedItemIndices[0]);
this._setElementAria();
this._setItemsAria()
}
_afterItemElementDeleted($item, deletedActionArgs) {
super._afterItemElementDeleted($item, deletedActionArgs);
if (this._deferredItems) {
this._deferredItems.splice(deletedActionArgs.itemIndex, 1)
}
}
_beforeItemElementInserted(change) {
super._beforeItemElementInserted.apply(this, arguments);
if (this._deferredItems) {
this._deferredItems.splice(change.index, 0, null)
}
}
_executeItemRenderAction(index, itemData, itemElement) {
index = (this.option("items") || []).indexOf(itemData);
super._executeItemRenderAction(index, itemData, itemElement)
}
_renderItemContent(args) {
const renderContentDeferred = (0, _deferred.Deferred)();
const that = this;
const deferred = (0, _deferred.Deferred)();
deferred.done((() => {
const $itemContent = super._renderItemContent.call(that, args);
renderContentDeferred.resolve($itemContent)
}));
this._deferredItems[args.index] = deferred;
this.option("deferRendering") || deferred.resolve();
return renderContentDeferred.promise()
}
_render() {
super._render();
(0, _common.deferRender)((() => {
const selectedItemIndices = this._getSelectedItemIndices();
this._updateItems(selectedItemIndices[0])
}))
}
_getElementAria() {
return {
role: "group",
roledescription: _message.default.format("dxMultiView-elementAriaRoleDescription"),
label: _message.default.format("dxMultiView-elementAriaLabel")
}
}
_setElementAria() {
const aria = this._getElementAria();
this.setAria(aria, this.$element())
}
_setItemsAria() {
const $itemElements = this._itemElements();
const itemsCount = this._itemsCount();
$itemElements.each(((itemIndex, item) => {
const aria = this._getItemAria({
itemIndex: itemIndex,
itemsCount: itemsCount
});
this.setAria(aria, (0, _renderer.default)(item))
}))
}
_getItemAria(args) {
const {
itemIndex: itemIndex,
itemsCount: itemsCount
} = args;
const aria = {
role: "group",
roledescription: _message.default.format("dxMultiView-itemAriaRoleDescription"),
label: _message.default.format("dxMultiView-itemAriaLabel", itemIndex + 1, itemsCount)
};
return aria
}
_updateItems(selectedIndex, newIndex) {
this._updateItemsPosition(selectedIndex, newIndex);
this._updateItemsVisibility(selectedIndex, newIndex)
}
_modifyByChanges() {
super._modifyByChanges.apply(this, arguments);
const selectedItemIndices = this._getSelectedItemIndices();
this._updateItemsVisibility(selectedItemIndices[0])
}
_updateItemsPosition(selectedIndex, newIndex) {
const $itemElements = this._itemElements();
const positionSign = (0, _type.isDefined)(newIndex) ? -this._animationDirection(newIndex, selectedIndex) : void 0;
const $selectedItem = $itemElements.eq(selectedIndex);
_m_multi_view._translator.move($selectedItem, 0);
if ((0, _type.isDefined)(newIndex)) {
_m_multi_view._translator.move($itemElements.eq(newIndex), 100 * positionSign + "%")
}
}
_isItemVisible(index) {
var _this$option$index;
return (null === (_this$option$index = this.option("items")[index]) || void 0 === _this$option$index ? void 0 : _this$option$index.visible) ?? true
}
_updateItemsVisibility(selectedIndex, newIndex) {
const $itemElements = this._itemElements();
$itemElements.each(((itemIndex, item) => {
const $item = (0, _renderer.default)(item);
const isHidden = itemIndex !== selectedIndex && itemIndex !== newIndex;
if (!isHidden) {
this._renderSpecificItem(itemIndex)
}
$item.toggleClass("dx-multiview-item-hidden", isHidden);
this.setAria("hidden", isHidden || void 0, $item)
}))
}
_renderSpecificItem(index) {
const $item = this._itemElements().eq(index);
const hasItemContent = $item.find(this._itemContentClass()).length > 0;
if ((0, _type.isDefined)(index) && !hasItemContent) {
this._deferredItems[index].resolve();
(0, _visibility_change.triggerResizeEvent)($item)
}
}
_refreshItem($item, item) {
super._refreshItem($item, item);
this._updateItemsVisibility(this.option("selectedIndex"))
}
_setAriaSelectionAttribute() {}
_updateSelection(addedSelection, removedSelection) {
const newIndex = addedSelection[0];
const prevIndex = removedSelection[0];
_m_multi_view.animation.complete(this._$itemContainer);
this._updateItems(prevIndex, newIndex);
const animationDirection = this._animationDirection(newIndex, prevIndex);
this._animateItemContainer(animationDirection * this._itemWidth(), (() => {
_m_multi_view._translator.move(this._$itemContainer, 0);
this._updateItems(newIndex);
(0, _size.getWidth)(this._$itemContainer)
}))
}
_animateItemContainer(position, completeCallback) {
const duration = this.option("animationEnabled") ? 200 : 0;
_m_multi_view.animation.moveTo(this._$itemContainer, position, duration, completeCallback)
}
_animationDirection(newIndex, prevIndex) {
const containerPosition = position(this._$itemContainer);
const indexDifference = (prevIndex - newIndex) * this._getRTLSignCorrection() * this._getItemFocusLoopSignCorrection();
const isSwipePresent = 0 !== containerPosition;
const directionSignVariable = isSwipePresent ? containerPosition : indexDifference;
return (0, _math.sign)(directionSignVariable)
}
_getSwipeDisabledState() {
return !this.option("swipeEnabled") || this._itemsCount() <= 1
}
_initSwipeable() {
this._createComponent(this.$element(), _swipeable.default, {
disabled: this._getSwipeDisabledState(),
elastic: false,
itemSizeFunc: this._itemWidth.bind(this),
onStart: args => this._swipeStartHandler(args.event),
onUpdated: args => this._swipeUpdateHandler(args.event),
onEnd: args => this._swipeEndHandler(args.event)
})
}
_findBoundaryIndices() {
const items = this.option("items");
let firstIndex;
let lastIndex;
items.forEach(((item, index) => {
const isDisabled = Boolean(null === item || void 0 === item ? void 0 : item.disabled);
const isVisible = this._isItemVisible(index);
if (!isDisabled && isVisible) {
firstIndex ?? (firstIndex = index);
lastIndex = index
}
}));
this._boundaryIndices = {
firstAvailableIndex: firstIndex ?? 0,
lastAvailableIndex: lastIndex ?? items.length - 1,
firstTrueIndex: 0,
lastTrueIndex: items.length - 1
}
}
_swipeStartHandler(e) {
_m_multi_view.animation.complete(this._$itemContainer);
const selectedIndex = this.option("selectedIndex");
const loop = this.option("loop");
const {
firstAvailableIndex: firstAvailableIndex,
lastAvailableIndex: lastAvailableIndex
} = this._boundaryIndices;
const rtl = this.option("rtlEnabled");
e.maxLeftOffset = toNumber(loop || (rtl ? selectedIndex > firstAvailableIndex : selectedIndex < lastAvailableIndex));
e.maxRightOffset = toNumber(loop || (rtl ? selectedIndex < lastAvailableIndex : selectedIndex > firstAvailableIndex))
}
_swipeUpdateHandler(e) {
const {
offset: offset
} = e;
const swipeDirection = (0, _math.sign)(offset) * this._getRTLSignCorrection();
const selectedIndex = this.option("selectedIndex");
const newIndex = this._normalizeIndex(selectedIndex - swipeDirection, swipeDirection);
if (selectedIndex === newIndex) {
return
}
_m_multi_view._translator.move(this._$itemContainer, offset * this._itemWidth());
this._updateItems(selectedIndex, newIndex)
}
_findNextAvailableIndex(index, offset) {
const {
items: items,
loop: loop
} = this.option();
const {
firstAvailableIndex: firstAvailableIndex,
lastAvailableIndex: lastAvailableIndex,
firstTrueIndex: firstTrueIndex,
lastTrueIndex: lastTrueIndex
} = this._boundaryIndices;
const isFirstActive = [firstTrueIndex, firstAvailableIndex].includes(index);
const isLastActive = [lastTrueIndex, lastAvailableIndex].includes(index);
if (loop) {
if (isFirstActive && offset < 0) {
return lastAvailableIndex
}
if (isLastActive && offset > 0) {
return firstAvailableIndex
}
}
for (let i = index + offset; i >= firstAvailableIndex && i <= lastAvailableIndex; i += offset) {
const isDisabled = Boolean(items[i].disabled);
const isVisible = this._isItemVisible(i);
if (!isDisabled && isVisible) {
return i
}
}
return index
}
_postprocessSwipe(args) {}
_swipeEndHandler(e) {
const targetOffset = e.targetOffset * this._getRTLSignCorrection();
if (targetOffset) {
const newSelectedIndex = this._findNextAvailableIndex(this.option("selectedIndex"), -targetOffset);
this.selectItem(newSelectedIndex).fail((() => {
this._animateItemContainer(0, _common.noop)
})).done((() => {
this._postprocessSwipe({
swipedTabsIndex: newSelectedIndex
})
}));
const $selectedElement = this.itemElements().filter(".dx-item-selected");
this.option("focusStateEnabled") && this.option("focusedElement", (0, _element.getPublicElement)($selectedElement))
} else {
this._animateItemContainer(0, _common.noop)
}
}
_getItemFocusLoopSignCorrection() {
return this._itemFocusLooped ? -1 : 1
}
_moveFocus() {
super._moveFocus.apply(this, arguments);
this._itemFocusLooped = false
}
_prevItem($items) {
const $result = super._prevItem.apply(this, arguments);
this._itemFocusLooped = $result.is($items.last());
return $result
}
_nextItem($items) {
const $result = super._nextItem.apply(this, arguments);
this._itemFocusLooped = $result.is($items.first());
return $result
}
_dimensionChanged() {
this._clearItemWidthCache()
}
_visibilityChanged(visible) {
if (visible) {
this._dimensionChanged()
}
}
_updateSwipeDisabledState() {
const disabled = this._getSwipeDisabledState();
_swipeable.default.getInstance(this.$element()).option("disabled", disabled)
}
_dispose() {
delete this._boundaryIndices;
super._dispose()
}
_itemOptionChanged(item, property) {
super._itemOptionChanged(...arguments);
const {
selectedItem: selectedItem
} = this.option();
if ("visible" === property && item === selectedItem) {
this._ensureSelectedItemIsVisible()
}
}
_optionChanged(args) {
const {
value: value
} = args;
switch (args.name) {
case "loop":
this.option("loopItemFocus", value);
break;
case "animationEnabled":
break;
case "swipeEnabled":
this._updateSwipeDisabledState();
break;
case "deferRendering":
this._invalidate();
break;
case "items":
this._updateSwipeDisabledState();
this._findBoundaryIndices();
super._optionChanged(args);
break;
case "selectedIndex":
if (this._isItemVisible(value)) {
super._optionChanged(args)
} else {
this._ensureSelectedItemIsVisible()
}
break;
default:
super._optionChanged(args)
}
}
}(0, _component_registrator.default)("dxMultiView", MultiView);
var _default = exports.default = MultiView;