UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

539 lines (538 loc) • 19.7 kB
/** * DevExtreme (esm/__internal/ui/drawer/m_drawer.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/ */ import _extends from "@babel/runtime/helpers/esm/extends"; import { fx } from "../../../common/core/animation"; import { name as CLICK_EVENT_NAME } from "../../../common/core/events/click"; import eventsEngine from "../../../common/core/events/core/events_engine"; import { triggerResizeEvent } from "../../../common/core/events/visibility_change"; import registerComponent from "../../../core/component_registrator"; import { getPublicElement } from "../../../core/element"; import $ from "../../../core/renderer"; import { EmptyTemplate } from "../../../core/templates/empty_template"; import { Deferred, when } from "../../../core/utils/deferred"; import { getBoundingRect } from "../../../core/utils/position"; import { isDefined, isFunction } from "../../../core/utils/type"; import { hasWindow } from "../../../core/utils/window"; import Widget from "../../core/widget/widget"; import { animation } from "../../ui/drawer/m_drawer.animation"; import OverlapStrategy from "../../ui/drawer/m_drawer.rendering.strategy.overlap"; import PushStrategy from "../../ui/drawer/m_drawer.rendering.strategy.push"; import ShrinkStrategy from "../../ui/drawer/m_drawer.rendering.strategy.shrink"; const DRAWER_CLASS = "dx-drawer"; const DRAWER_WRAPPER_CLASS = "dx-drawer-wrapper"; const DRAWER_PANEL_CONTENT_CLASS = "dx-drawer-panel-content"; const DRAWER_PANEL_CONTENT_HIDDEN_CLASS = "dx-drawer-panel-content-hidden"; const DRAWER_VIEW_CONTENT_CLASS = "dx-drawer-content"; const DRAWER_SHADER_CLASS = "dx-drawer-shader"; const INVISIBLE_STATE_CLASS = "dx-state-invisible"; const OPENED_STATE_CLASS = "dx-drawer-opened"; const ANONYMOUS_TEMPLATE_NAME = "content"; const PANEL_TEMPLATE_NAME = "panel"; class Drawer extends Widget { _getDefaultOptions() { return _extends({}, super._getDefaultOptions(), { position: "left", opened: false, minSize: null, maxSize: null, shading: false, template: "panel", openedStateMode: "shrink", revealMode: "slide", animationEnabled: true, animationDuration: 400, closeOnOutsideClick: false, contentTemplate: "content" }) } _init() { super._init(); this._initStrategy(); this.$element().addClass("dx-drawer"); this._whenAnimationCompleted = void 0; this._whenPanelContentRendered = void 0; this._whenPanelContentRefreshed = void 0; this._$wrapper = $("<div>").addClass("dx-drawer-wrapper"); this._$viewContentWrapper = $("<div>").addClass("dx-drawer-content"); this._$wrapper.append(this._$viewContentWrapper); this.$element().append(this._$wrapper) } _initStrategy() { const { openedStateMode: openedStateMode } = this.option(); switch (openedStateMode) { case "push": default: this._strategy = new PushStrategy(this); break; case "shrink": this._strategy = new ShrinkStrategy(this); break; case "overlap": this._strategy = new OverlapStrategy(this) } } _getAnonymousTemplateName() { return "content" } _initTemplates() { const defaultTemplates = {}; defaultTemplates.panel = new EmptyTemplate; defaultTemplates.content = new EmptyTemplate; this._templateManager.addDefaultTemplates(defaultTemplates); super._initTemplates() } _viewContentWrapperClickHandler(e) { let closeOnOutsideClick = this.option("closeOnOutsideClick"); if (isFunction(closeOnOutsideClick)) { closeOnOutsideClick = closeOnOutsideClick(e) } if (closeOnOutsideClick && this.option("opened")) { this.stopAnimations(); if (this.option("shading")) { e.preventDefault() } this.hide() } } _initMarkup() { super._initMarkup(); const { opened: opened } = this.option(); this._toggleOpenedStateClass(opened); this._renderPanelContentWrapper(); this._refreshOpenedStateModeClass(); this._refreshRevealModeClass(); this._renderShader(); this._refreshPositionClass(); this._whenPanelContentRendered = Deferred(); this._strategy.renderPanelContent(this._whenPanelContentRendered); this._strategy.onPanelContentRendered(); this._renderViewContent(); eventsEngine.off(this._$viewContentWrapper, CLICK_EVENT_NAME); eventsEngine.on(this._$viewContentWrapper, CLICK_EVENT_NAME, this._viewContentWrapperClickHandler.bind(this)); this._refreshWrapperChildrenOrder() } _render() { var _this$_whenPanelConte; this._initMinMaxSize(); super._render(); null === (_this$_whenPanelConte = this._whenPanelContentRendered) || void 0 === _this$_whenPanelConte || _this$_whenPanelConte.always((() => { this._initMinMaxSize(); const { revealMode: revealMode, opened: opened } = this.option(); this._strategy.refreshPanelElementSize("slide" === revealMode); this._renderPosition(opened, true); this._removePanelManualPosition() })) } _removePanelManualPosition() { if (this._$panelContentWrapper.attr("manualposition")) { this._$panelContentWrapper.removeAttr("manualPosition"); this._$panelContentWrapper.css({ position: "", top: "", left: "", right: "", bottom: "" }) } } _togglePanelContentHiddenClass() { const callback = () => { const { minSize: minSize, opened: opened } = this.option(); const shouldBeSet = minSize ? false : !opened; this._$panelContentWrapper.toggleClass("dx-drawer-panel-content-hidden", shouldBeSet) }; if (this._whenAnimationCompleted && !this.option("opened")) { when(this._whenAnimationCompleted).done(callback) } else { callback() } } _renderPanelContentWrapper() { const { openedStateMode: openedStateMode, opened: opened, minSize: minSize } = this.option(); this._$panelContentWrapper = $("<div>").addClass("dx-drawer-panel-content"); this._togglePanelContentHiddenClass(); const position = this.calcTargetPosition(); if ("push" === openedStateMode && ["top", "bottom"].includes(position)) { this._$panelContentWrapper.addClass("dx-drawer-panel-content-push-top-or-bottom") } if ("overlap" !== openedStateMode && !opened && !minSize) { this._$panelContentWrapper.attr("manualposition", true); this._$panelContentWrapper.css({ position: "absolute", top: "-10000px", left: "-10000px", right: "auto", bottom: "auto" }) } this._$wrapper.append(this._$panelContentWrapper) } _refreshOpenedStateModeClass(prevOpenedStateMode) { if (prevOpenedStateMode) { this.$element().removeClass(`dx-drawer-${prevOpenedStateMode}`) } const { openedStateMode: openedStateMode } = this.option(); this.$element().addClass(`dx-drawer-${openedStateMode}`) } _refreshPositionClass() { this.$element().removeClass(["left", "right", "top", "bottom"].map((position => `dx-drawer-${position}`)).join(" ")).addClass(`dx-drawer-${this.calcTargetPosition()}`) } _refreshWrapperChildrenOrder() { const position = this.calcTargetPosition(); if (this._strategy.isViewContentFirst(position, this.option("rtlEnabled"))) { this._$wrapper.prepend(this._$viewContentWrapper) } else { this._$wrapper.prepend(this._$panelContentWrapper) } } _refreshRevealModeClass(prevRevealMode) { if (prevRevealMode) { this.$element().removeClass(`dx-drawer-${prevRevealMode}`) } const { revealMode: revealMode } = this.option(); this.$element().addClass(`dx-drawer-${revealMode}`) } _renderViewContent() { const contentTemplateOption = this.option("contentTemplate"); const contentTemplate = this._getTemplate(contentTemplateOption); if (contentTemplate) { const $viewTemplate = contentTemplate.render({ container: this.viewContent(), noModel: true, transclude: this._templateManager.anonymousTemplateName === contentTemplateOption }); if ($viewTemplate.hasClass("ng-scope")) { $(this._$viewContentWrapper).children().not(".dx-drawer-shader").replaceWith($viewTemplate) } } } _renderShader() { this._$shader = this._$shader || $("<div>").addClass("dx-drawer-shader"); this._$shader.appendTo(this.viewContent()); const { opened: opened } = this.option(); this._toggleShaderVisibility(opened) } _initSize() { this._initMinMaxSize() } _initMinMaxSize() { const realPanelSize = this.isHorizontalDirection() ? this.getRealPanelWidth() : this.getRealPanelHeight(); const { maxSize: maxSize, minSize: minSize } = this.option(); this._maxSize = maxSize || realPanelSize; this._minSize = minSize || 0 } calcTargetPosition() { const { position: position, rtlEnabled: rtlEnabled } = this.option(); if ("before" === position) { return rtlEnabled ? "right" : "left" } if ("after" === position) { return rtlEnabled ? "left" : "right" } return position } getOverlayTarget() { return this._$wrapper } getOverlay() { return this._overlay } getMaxSize() { return this._maxSize } getMinSize() { return this._minSize } getRealPanelWidth() { if (hasWindow()) { const { templateSize: templateSize } = this.option(); if (isDefined(templateSize)) { return templateSize } return getBoundingRect(this._getPanelTemplateElement()).width } return 0 } getRealPanelHeight() { if (hasWindow()) { const { templateSize: templateSize } = this.option(); if (isDefined(templateSize)) { return templateSize } return getBoundingRect(this._getPanelTemplateElement()).height } return 0 } _getPanelTemplateElement() { const $panelContent = this._strategy.getPanelContent(); let $result = $panelContent; if ($panelContent.children().length) { $result = $panelContent.children().eq(0); if ($panelContent.hasClass("dx-overlay-content") && $result.hasClass("dx-template-wrapper") && $result.children().length) { $result = $result.children().eq(0) } } return $result.get(0) } getElementHeight($element) { const $children = $element.children(); return $children.length ? getBoundingRect($children.eq(0).get(0)).height : getBoundingRect($element.get(0)).height } isHorizontalDirection() { const position = this.calcTargetPosition(); return "left" === position || "right" === position } stopAnimations(jumpToEnd) { fx.stop(this._$shader, jumpToEnd); fx.stop($(this.content()), jumpToEnd); fx.stop($(this.viewContent()), jumpToEnd); const overlay = this.getOverlay(); if (overlay) { fx.stop($(overlay.$content()), jumpToEnd) } } setZIndex(zIndex) { this._$shader.css("zIndex", zIndex - 1); this._$panelContentWrapper.css("zIndex", zIndex) } resizeContent() { this.resizeViewContent } resizeViewContent() { triggerResizeEvent(this.viewContent()) } _isInvertedPosition() { const position = this.calcTargetPosition(); return "right" === position || "bottom" === position } _renderPosition(isDrawerOpened, disableAnimation, jumpToEnd) { this.stopAnimations(jumpToEnd); this._whenAnimationCompleted = Deferred(); let { animationEnabled: animationEnabled } = this.option(); if (true === disableAnimation) { animationEnabled = false } if (!animationEnabled) { this._whenAnimationCompleted.resolve() } if (!hasWindow()) { return } $(this.viewContent()).css("paddingLeft", 0); $(this.viewContent()).css("paddingRight", 0); $(this.viewContent()).css("paddingTop", 0); $(this.viewContent()).css("paddingBottom", 0); if (isDrawerOpened) { this._toggleShaderVisibility(isDrawerOpened) } this._strategy.renderPosition(animationEnabled, this.option("animationDuration")) } _animationCompleteHandler() { var _this$_whenAnimationC; this.resizeViewContent(); null === (_this$_whenAnimationC = this._whenAnimationCompleted) || void 0 === _this$_whenAnimationC || _this$_whenAnimationC.resolve() } _getPositionCorrection() { return this._isInvertedPosition() ? -1 : 1 } _dispose() { animation.complete($(this.viewContent())); super._dispose() } _visibilityChanged(visible) { if (visible) { this._dimensionChanged() } } _dimensionChanged() { this._initMinMaxSize(); const { revealMode: revealMode } = this.option(); this._strategy.refreshPanelElementSize("slide" === revealMode); this._renderPosition(this.option("opened"), true) } _toggleShaderVisibility(visible) { if (this.option("shading")) { this._$shader.toggleClass("dx-state-invisible", !visible); this._$shader.css("visibility", visible ? "visible" : "hidden") } else { this._$shader.toggleClass("dx-state-invisible", true) } } _toggleOpenedStateClass(opened) { this.$element().toggleClass("dx-drawer-opened", opened) } _refreshPanel() { $(this.viewContent()).css("left", 0); $(this.viewContent()).css("transform", "translate(0px, 0px)"); $(this.viewContent()).removeClass("dx-theme-background-color"); this._removePanelContentWrapper(); this._removeOverlay(); this._renderPanelContentWrapper(); this._refreshWrapperChildrenOrder(); this._whenPanelContentRefreshed = Deferred(); this._strategy.renderPanelContent(this._whenPanelContentRefreshed); this._strategy.onPanelContentRendered(); if (hasWindow()) { this._whenPanelContentRefreshed.always((() => { const { revealMode: revealMode } = this.option(); this._strategy.refreshPanelElementSize("slide" === revealMode); this._renderPosition(this.option("opened"), true, true); this._removePanelManualPosition() })) } } _clean() { this._cleanFocusState(); this._removePanelContentWrapper(); this._removeOverlay() } _removePanelContentWrapper() { if (this._$panelContentWrapper) { this._$panelContentWrapper.remove() } } _removeOverlay() { if (this._overlay) { this._overlay.dispose(); delete this._overlay; delete this._$panelContentWrapper } } _optionChanged(args) { switch (args.name) { case "width": super._optionChanged(args); this._dimensionChanged(); break; case "opened": this._renderPosition(this.option("opened")); this._toggleOpenedStateClass(args.value); this._togglePanelContentHiddenClass(); break; case "position": this._refreshPositionClass(); this._refreshWrapperChildrenOrder(); this._invalidate(); break; case "contentTemplate": case "template": this._invalidate(); break; case "openedStateMode": this._initStrategy(); this._refreshOpenedStateModeClass(args.previousValue); this._refreshPanel(); break; case "minSize": this._initMinMaxSize(); this._renderPosition(this.option("opened"), true); this._togglePanelContentHiddenClass(); break; case "maxSize": this._initMinMaxSize(); this._renderPosition(this.option("opened"), true); break; case "revealMode": this._refreshRevealModeClass(args.previousValue); this._refreshPanel(); break; case "shading": { const { opened: opened } = this.option(); this._toggleShaderVisibility(opened); break } case "animationEnabled": case "animationDuration": case "closeOnOutsideClick": break; default: super._optionChanged(args) } } content() { return getPublicElement(this._$panelContentWrapper) } viewContent() { return getPublicElement(this._$viewContentWrapper) } show() { return this.toggle(true) } hide() { return this.toggle(false) } toggle(opened) { var _this$_whenAnimationC2; const targetOpened = void 0 === opened ? !this.option("opened") : opened; this.option("opened", targetOpened); return null === (_this$_whenAnimationC2 = this._whenAnimationCompleted) || void 0 === _this$_whenAnimationC2 ? void 0 : _this$_whenAnimationC2.promise() } } registerComponent("dxDrawer", Drawer); export default Drawer;