UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

352 lines (351 loc) • 12.5 kB
/** * DevExtreme (esm/__internal/ui/stepper/stepper.js) * Version: 25.1.3 * Build date: Wed Jun 25 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 messageLocalization from "../../../common/core/localization/message"; import registerComponent from "../../../core/component_registrator"; import $ from "../../../core/renderer"; import { Deferred } from "../../../core/utils/deferred"; import { isDefined } from "../../../core/utils/type"; import { BindableTemplate } from "../../core/templates/m_bindable_template"; import { getImageContainer } from "../../core/utils/m_icon"; import CollectionWidgetAsync from "../../ui/collection/m_collection_widget.async"; import Connector from "../../ui/stepper/connector"; import StepperItem, { STEP_COMPLETED_CLASS, STEP_INVALID_ICON, STEP_VALID_ICON } from "../../ui/stepper/stepper_item"; export const STEPPER_CLASS = "dx-stepper"; export const STEP_LIST_CLASS = "dx-step-list"; export const STEP_CLASS = "dx-step"; export const STEP_SELECTED_CLASS = "dx-step-selected"; export const STEPPER_HORIZONTAL_ORIENTATION_CLASS = "dx-stepper-horizontal"; export const STEPPER_VERTICAL_ORIENTATION_CLASS = "dx-stepper-vertical"; export const STEP_INDICATOR_CLASS = "dx-step-indicator"; export const STEP_TEXT_CLASS = "dx-step-text"; export const STEP_CAPTION_CLASS = "dx-step-caption"; export const STEP_LABEL_CLASS = "dx-step-label"; export const STEP_OPTIONAL_MARK_CLASS = "dx-step-optional-mark"; export const STEPPER_ITEM_DATA_KEY = "dxStepperItemData"; export const ORIENTATION = { horizontal: "horizontal", vertical: "vertical" }; class Stepper extends CollectionWidgetAsync { _getDefaultOptions() { return _extends({}, super._getDefaultOptions(), { orientation: "horizontal", linear: true, selectionMode: "single", selectOnFocus: true, activeStateEnabled: true, hoverStateEnabled: true, focusStateEnabled: true, loopItemFocus: false, selectionRequired: true, hintExpr: data => data ? data.hint : void 0, _itemAttributes: { role: "tab" } }) } _supportedKeys() { const defaultHandlers = super._supportedKeys(); const { linear: linear, selectOnFocus: selectOnFocus } = this.option(); return _extends({}, defaultHandlers, { home: linear && selectOnFocus ? defaultHandlers.leftArrow : defaultHandlers.home, end: linear && selectOnFocus ? defaultHandlers.rightArrow : defaultHandlers.end }) } _getStepIcon(data) { const { isValid: isValid, icon: icon } = data; if (false === isValid) { return STEP_INVALID_ICON } if (true === isValid) { return STEP_VALID_ICON } return icon } _getStepIndicator(data) { const { text: text } = data; const $indicatorElement = $("<div>").addClass("dx-step-indicator"); const iconName = this._getStepIcon(data); const $indicatorContent = getImageContainer(iconName) ?? $("<div>").addClass("dx-step-text").text(text ?? ""); $indicatorElement.append($indicatorContent); return $indicatorElement } _getStepLabel(data) { const { label: label } = data; if (isDefined(label)) { return $("<div>").addClass("dx-step-label").text(label) } return $() } _getStepOptionalMark(data) { const { optional: optional } = data; if (optional) { const optionalMarkText = messageLocalization.format("dxStepper-optionalMark"); return $("<div>").addClass("dx-step-optional-mark").text(optionalMarkText) } return $() } _getStepCaption(data) { const $stepLabel = this._getStepLabel(data); const $stepOptionalMark = this._getStepOptionalMark(data); if ($stepLabel.length || $stepOptionalMark.length) { const $stepCaption = $("<div>").addClass("dx-step-caption"); $stepCaption.append($stepLabel).append($stepOptionalMark); return $stepCaption } return $() } _prepareDefaultItemTemplate(data, $container) { const $stepIndicator = this._getStepIndicator(data); const $stepLabel = this._getStepCaption(data); $container.append($stepIndicator).append($stepLabel) } _initTemplates() { super._initTemplates(); this._templateManager.addDefaultTemplates({ item: new BindableTemplate((($container, data) => { this._prepareDefaultItemTemplate(data, $container) }), ["text", "icon", "label", "isValid", "optional"], this.option("integrationOptions.watchMethod")) }) } _createItemByTemplate(itemTemplate, renderArgs) { const { itemData: itemData, index: index } = renderArgs; return super._createItemByTemplate(itemTemplate, _extends({}, renderArgs, { itemData: _extends({ text: `${index+1}` }, itemData) })) } _getItemInstance($item) { return StepperItem.getInstance($item) } _renderItem(index, itemData, $container, $itemToReplace) { const $itemFrame = super._renderItem(index, itemData, $container, $itemToReplace); this._getItemInstance($itemFrame).updateInvalidClass(itemData.isValid); return $itemFrame } _postprocessRenderItem(args) { super._postprocessRenderItem(args); const { selectedIndex: selectedIndex = 0 } = this.option(); const itemInstance = this._getItemInstance(args.itemElement); itemInstance.changeCompleted(args.itemIndex < selectedIndex) } _itemClass() { return "dx-step" } _itemContainer() { return this._$stepsContainer } _selectedItemClass() { return "dx-step-selected" } _isItemSelected(index) { const { items: items = [], selectedItem: selectedItem } = this.option(); return selectedItem === items[index] } _itemDataKey() { return "dxStepperItemData" } _init() { super._init(); this.setAria("role", "tablist"); this._appendStepsContainer() } _initMarkup() { $(this.element()).addClass("dx-stepper"); this._renderConnector(); this._toggleOrientationClass(); this._setAriaOrientation(); super._initMarkup() } _renderConnector() { if (this._connector) { return } const { orientation: orientation } = this.option(); this._connector = this._createComponent($("<div>"), Connector, { orientation: orientation, size: this._getConnectorSize(), value: this._getConnectorValue() }); $(this.element()).prepend(this._connector.$element()) } _getConnectorSize() { const { items: items = [] } = this.option(); const itemRatio = 100 / (items.length || 1); return 100 - itemRatio } _getConnectorValue() { const { items: items = [], selectedIndex: selectedIndex = 0 } = this.option(); const segmentsCount = items.length - 1; const itemRatio = 100 / Math.max(segmentsCount, 1); return selectedIndex * itemRatio } _appendStepsContainer() { this._$stepsContainer = $("<div>").addClass("dx-step-list"); $(this.element()).append(this._$stepsContainer) } _setAriaOrientation() { const { orientation: orientation } = this.option(); this.setAria("orientation", orientation) } _toggleOrientationClass() { $(this.element()).toggleClass("dx-stepper-horizontal", this._isHorizontalOrientation()).toggleClass("dx-stepper-vertical", !this._isHorizontalOrientation()) } _isHorizontalOrientation() { const { orientation: orientation } = this.option(); return orientation === ORIENTATION.horizontal } _shouldPreventItemEvent(itemElement) { const itemIndex = this._editStrategy.getIndex(itemElement); const { linear: linear, selectedIndex: selectedIndex = 0 } = this.option(); return !!linear && Math.abs(selectedIndex - itemIndex) > 1 } _itemClickHandler(e, args, config) { if (!this._shouldPreventItemEvent(e.currentTarget)) { super._itemClickHandler(e, args, config) } } _itemPointerDownHandler(e) { if (!this._shouldPreventItemEvent(e.currentTarget)) { super._itemPointerDownHandler(e) } } _itemSelectHandler(e) { if (!this._shouldPreventItemEvent(e.currentTarget)) { super._itemSelectHandler(e) } } _hover($el, $previous) { const $hoverTarget = this._findHoverTarget($el); if ($hoverTarget && this._shouldPreventItemEvent($hoverTarget)) { return } super._hover($el, $previous) } _focusOutHandler(e) { this._clearFocusedItem(); super._focusOutHandler(e) } _clearFocusedItem() { this.option("focusedElement", null) } _processChangeCompletedItems() { const itemElements = this._itemElements(); if (!itemElements.length) { return } const $lastCompletedElement = itemElements.filter(`.${STEP_COMPLETED_CLASS}`).last(); const lastCompletedIndex = this._editStrategy.getIndex($lastCompletedElement); const { selectedIndex: selectedIndex = 0 } = this.option(); const startIndex = Math.min(lastCompletedIndex + 1, selectedIndex); const endIndex = Math.max(lastCompletedIndex + 1, selectedIndex); const isCompleted = lastCompletedIndex < selectedIndex; for (let i = startIndex; i < endIndex; i += 1) { const itemInstance = this._getItemInstance($(itemElements[i])); itemInstance.changeCompleted(isCompleted) } } _postProcessSyncSelection() { this._connector.option("value", this._getConnectorValue()); this._processChangeCompletedItems() } _syncSelectionOptions(byOption) { super._syncSelectionOptions(byOption).done((() => { this._postProcessSyncSelection() })); return Deferred().resolve().promise() } _itemOptionChanged(item, property, value, prevValue) { switch (property) { case "isValid": { const itemIndex = this._getIndexByItem(item); const $item = $(this._itemElements()[itemIndex]); const itemInstance = this._getItemInstance($item); itemInstance.updateInvalidClass(value); super._itemOptionChanged(item, property, value, prevValue); break } default: super._itemOptionChanged(item, property, value, prevValue) } } _optionChanged(args) { const { name: name, value: value } = args; switch (name) { case "orientation": this._toggleOrientationClass(); this._setAriaOrientation(); this._connector.option(name, value); break; case "linear": break; case "hintExpr": this._invalidate(); break; default: super._optionChanged(args) } } } Stepper.ItemClass = StepperItem; registerComponent("dxStepper", Stepper); export default Stepper;