devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
268 lines (267 loc) • 11 kB
JavaScript
/**
* DevExtreme (esm/__internal/ui/list/list.edit.decorator.switchable.slide.js)
* Version: 25.2.7
* Build date: Tue May 05 2026
*
* Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
fx
} from "../../../common/core/animation";
import {
locate,
move
} from "../../../common/core/animation/translator";
import {
name as clickEventName
} from "../../../common/core/events/click";
import {
active
} from "../../../common/core/events/core/emitter.feedback";
import eventsEngine from "../../../common/core/events/core/events_engine";
import {
addNamespace
} from "../../../common/core/events/utils";
import messageLocalization from "../../../common/core/localization/message";
import $ from "../../../core/renderer";
import {
noop
} from "../../../core/utils/common";
import {
getOuterWidth,
setWidth
} from "../../../core/utils/size";
import {
current,
isMaterialBased
} from "../../../ui/themes";
import ActionSheet from "../../ui/action_sheet";
import SwitchableEditDecorator from "../../ui/list/list.edit.decorator.switchable";
import {
register as registerDecorator
} from "../../ui/list/list.edit.decorator_registry";
const LIST_EDIT_DECORATOR = "dxListEditDecorator";
const CLICK_EVENT_NAME = addNamespace(clickEventName, LIST_EDIT_DECORATOR);
const ACTIVE_EVENT_NAME = addNamespace(active, LIST_EDIT_DECORATOR);
const SLIDE_MENU_CLASS = "dx-list-slide-menu";
const SLIDE_MENU_WRAPPER_CLASS = "dx-list-slide-menu-wrapper";
const SLIDE_MENU_CONTENT_CLASS = "dx-list-slide-menu-content";
const SLIDE_MENU_BUTTONS_CONTAINER_CLASS = "dx-list-slide-menu-buttons-container";
const SLIDE_MENU_BUTTONS_CLASS = "dx-list-slide-menu-buttons";
const SLIDE_MENU_BUTTON_CLASS = "dx-list-slide-menu-button";
const SLIDE_MENU_BUTTON_MENU_CLASS = "dx-list-slide-menu-button-menu";
const SLIDE_MENU_BUTTON_DELETE_CLASS = "dx-list-slide-menu-button-delete";
const SLIDE_MENU_ANIMATION_DURATION = 400;
const SLIDE_MENU_ANIMATION_EASING = "cubic-bezier(0.075, 0.82, 0.165, 1)";
class SwitchableEditDecoratorSlide extends SwitchableEditDecorator {
_shouldHandleSwipe() {
return true
}
_init() {
super._init();
this._$buttonsContainer = $("<div>").addClass(SLIDE_MENU_BUTTONS_CONTAINER_CLASS);
eventsEngine.on(this._$buttonsContainer, ACTIVE_EVENT_NAME, noop);
this._$buttons = $("<div>").addClass(SLIDE_MENU_BUTTONS_CLASS).appendTo(this._$buttonsContainer);
this._renderMenu();
this._renderDeleteButton()
}
_renderMenu() {
const {
menuItems: menuItems = []
} = this._list.option();
if (!menuItems.length) {
return
}
if (1 === menuItems.length) {
const menuItem = menuItems[0];
this._renderMenuButton(menuItem.text ?? "", e => {
e.stopPropagation();
this._fireAction(menuItem)
})
} else {
const $menu = $("<div>").addClass(SLIDE_MENU_CLASS);
this._menu = this._list._createComponent($menu, ActionSheet, {
showTitle: false,
items: menuItems,
onItemClick: args => {
this._fireAction(args.itemData)
},
integrationOptions: {}
});
$menu.appendTo(this._list.$element());
const $menuButton = this._renderMenuButton(messageLocalization.format("dxListEditDecorator-more"), e => {
e.stopPropagation();
this._menu.show()
});
this._menu.option("target", $menuButton)
}
}
_renderMenuButton(text, action) {
const $menuButton = $("<div>").addClass(SLIDE_MENU_BUTTON_CLASS).addClass(SLIDE_MENU_BUTTON_MENU_CLASS).text(text);
this._$buttons.append($menuButton);
eventsEngine.on($menuButton, CLICK_EVENT_NAME, action);
return $menuButton
}
_renderDeleteButton() {
const {
allowItemDeleting: allowItemDeleting
} = this._list.option();
if (!allowItemDeleting) {
return
}
const $deleteButton = $("<div>").addClass(SLIDE_MENU_BUTTON_CLASS).addClass(SLIDE_MENU_BUTTON_DELETE_CLASS).text(isMaterialBased(current()) ? "" : messageLocalization.format("dxListEditDecorator-delete"));
eventsEngine.on($deleteButton, CLICK_EVENT_NAME, e => {
e.stopPropagation();
this._deleteItem()
});
this._$buttons.append($deleteButton)
}
_fireAction(menuItem) {
this._list._itemEventHandlerByHandler($(this._cachedNode), menuItem.action, {}, {
excludeValidators: ["disabled", "readOnly"]
});
this._cancelDeleteReadyItem()
}
modifyElement(config) {
super.modifyElement(config);
const {
$itemElement: $itemElement
} = config;
$itemElement.addClass(SLIDE_MENU_WRAPPER_CLASS);
const $slideMenuContent = $("<div>").addClass(SLIDE_MENU_CONTENT_CLASS);
$itemElement.wrapInner($slideMenuContent)
}
_getDeleteButtonContainer() {
return this._$buttonsContainer
}
handleClick($itemElement, e) {
if ($(e.target).closest(`.${SLIDE_MENU_CONTENT_CLASS}`).length) {
return super.handleClick($itemElement, e)
}
return false
}
_swipeStartHandler($itemElement) {
this._enablePositioning($itemElement);
this._cacheItemData($itemElement);
this._setPositions(this._getPositions(0))
}
_swipeUpdateHandler($itemElement, e) {
const rtl = this._isRtlEnabled();
const signCorrection = rtl ? -1 : 1;
const isItemReadyToDelete = this._isReadyToDelete($itemElement);
const moveJustStarted = this._getCurrentPositions().content === this._getStartPositions().content;
if (moveJustStarted && !isItemReadyToDelete && e.offset * signCorrection > 0) {
e.cancel = true;
return
}
const offset = this._cachedItemWidth * e.offset;
const startOffset = isItemReadyToDelete ? -this._cachedButtonWidth * signCorrection : 0;
const correctedOffset = (offset + startOffset) * signCorrection;
const percent = correctedOffset < 0 ? Math.abs((offset + startOffset) / this._cachedButtonWidth) : 0;
this._setPositions(this._getPositions(percent))
}
_getStartPositions() {
const rtl = this._isRtlEnabled();
const signCorrection = rtl ? -1 : 1;
return {
content: 0,
buttonsContainer: rtl ? -this._cachedButtonWidth : this._cachedItemWidth,
buttons: -this._cachedButtonWidth * signCorrection
}
}
_getPositions(percent) {
const rtl = this._isRtlEnabled();
const signCorrection = rtl ? -1 : 1;
const startPositions = this._getStartPositions();
return {
content: startPositions.content - percent * this._cachedButtonWidth * signCorrection,
buttonsContainer: startPositions.buttonsContainer - Math.min(percent, 1) * this._cachedButtonWidth * signCorrection,
buttons: startPositions.buttons + Math.min(percent, 1) * this._cachedButtonWidth * signCorrection
}
}
_getCurrentPositions() {
return {
content: locate(this._$cachedContent).left,
buttonsContainer: locate(this._$buttonsContainer).left,
buttons: locate(this._$buttons).left
}
}
_setPositions(positions) {
move(this._$cachedContent, {
left: positions.content
});
move(this._$buttonsContainer, {
left: positions.buttonsContainer
});
move(this._$buttons, {
left: positions.buttons
})
}
_cacheItemData($itemElement) {
var _this$_$cachedContent;
if ($itemElement[0] === this._cachedNode) {
return
}
this._$cachedContent = $itemElement.find(`.${SLIDE_MENU_CONTENT_CLASS}`);
this._cachedItemWidth = getOuterWidth($itemElement);
this._cachedButtonWidth = this._cachedButtonWidth || getOuterWidth(this._$buttons);
setWidth(this._$buttonsContainer, this._cachedButtonWidth);
if (null !== (_this$_$cachedContent = this._$cachedContent) && void 0 !== _this$_$cachedContent && _this$_$cachedContent.length) {
this._cachedNode = $itemElement.get(0)
}
}
_minButtonContainerLeftOffset() {
return this._cachedItemWidth - this._cachedButtonWidth
}
_swipeEndHandler($itemElement, args) {
this._cacheItemData($itemElement);
const signCorrection = this._isRtlEnabled() ? 1 : -1;
const offset = this._cachedItemWidth * args.offset;
const endedAtReadyToDelete = !this._isReadyToDelete($itemElement) && offset * signCorrection > .2 * this._cachedButtonWidth;
const readyToDelete = args.targetOffset === signCorrection && endedAtReadyToDelete;
this._toggleDeleteReady($itemElement, readyToDelete)
}
_enablePositioning($itemElement) {
if (this._$cachedContent) {
fx.stop(this._$cachedContent.get(0), true)
}
super._enablePositioning($itemElement);
this._$buttonsContainer.appendTo($itemElement)
}
_disablePositioning($itemElement) {
super._disablePositioning($itemElement);
this._$buttonsContainer.detach()
}
_animatePrepareDeleteReady() {
return this._animateToPositions(this._getPositions(1))
}
_animateForgetDeleteReady($itemElement) {
this._cacheItemData($itemElement);
return this._animateToPositions(this._getPositions(0))
}
_animateToPositions(positions) {
const currentPosition = this._getCurrentPositions();
const durationTimePart = Math.min(Math.abs(currentPosition.content - positions.content) / this._cachedButtonWidth, 1);
return fx.animate($(this._$cachedContent).get(0), {
from: currentPosition,
to: positions,
easing: SLIDE_MENU_ANIMATION_EASING,
duration: 400 * durationTimePart,
strategy: "frame",
draw: drawPositions => {
this._setPositions(drawPositions)
}
})
}
dispose() {
if (this._menu) {
this._menu.$element().remove()
}
if (this._$buttonsContainer) {
this._$buttonsContainer.remove()
}
super.dispose()
}
}
registerDecorator("menu", "slide", SwitchableEditDecoratorSlide);