devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
447 lines (444 loc) • 18.3 kB
JavaScript
/**
* DevExtreme (cjs/__internal/ui/m_tile_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 _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 _iterator = require("../../core/utils/iterator");
var _size = require("../../core/utils/size");
var _type = require("../../core/utils/type");
var _window = require("../../core/utils/window");
var _m_collection_widget = _interopRequireDefault(require("../ui/collection/m_collection_widget.edit"));
var _m_scroll_view = _interopRequireDefault(require("../ui/scroll_view/m_scroll_view"));
var _m_support = _interopRequireDefault(require("../core/utils/m_support"));
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 TILEVIEW_CLASS = "dx-tileview";
const TILEVIEW_CONTAINER_CLASS = "dx-tileview-wrapper";
const TILEVIEW_ITEM_CLASS = "dx-tile";
const TILEVIEW_ITEM_SELECTOR = ".dx-tile";
const TILEVIEW_ITEM_DATA_KEY = "dxTileData";
const CONFIGS = {
horizontal: {
itemMainRatio: "widthRatio",
itemCrossRatio: "heightRatio",
baseItemMainDimension: "baseItemWidth",
baseItemCrossDimension: "baseItemHeight",
mainDimension: "width",
crossDimension: "height",
mainPosition: "left",
crossPosition: "top"
},
vertical: {
itemMainRatio: "heightRatio",
itemCrossRatio: "widthRatio",
baseItemMainDimension: "baseItemHeight",
baseItemCrossDimension: "baseItemWidth",
mainDimension: "height",
crossDimension: "width",
mainPosition: "top",
crossPosition: "left"
}
};
class TileView extends _m_collection_widget.default {
_getDefaultOptions() {
return _extends({}, super._getDefaultOptions(), {
items: null,
direction: "horizontal",
hoverStateEnabled: true,
showScrollbar: "never",
height: 500,
baseItemWidth: 100,
baseItemHeight: 100,
itemMargin: 20,
activeStateEnabled: true,
indicateLoading: true
})
}
_defaultOptionsRules() {
return super._defaultOptionsRules().concat([{
device: () => "desktop" === _devices.default.real().deviceType && !_devices.default.isSimulator(),
options: {
focusStateEnabled: true
}
}, {
device: () => _m_support.default.nativeScrolling,
options: {
showScrollbar: "onScroll"
}
}])
}
_itemClass() {
return "dx-tile"
}
_itemDataKey() {
return "dxTileData"
}
_itemContainer() {
return this._$container
}
_init() {
super._init();
this._activeStateUnit = ".dx-tile";
this.$element().addClass("dx-tileview");
this._initScrollView()
}
_dataSourceLoadingChangedHandler(isLoading) {
const scrollView = this._scrollView;
if (!(null !== scrollView && void 0 !== scrollView && scrollView.startLoading)) {
return
}
if (isLoading && this.option("indicateLoading")) {
scrollView.startLoading()
} else {
scrollView.finishLoading()
}
}
_hideLoadingIfLoadIndicationOff() {
if (!this.option("indicateLoading")) {
this._dataSourceLoadingChangedHandler(false)
}
}
_initScrollView() {
const {
width: width,
height: height,
direction: direction,
showScrollbar: showScrollbar
} = this.option();
this._scrollView = this._createComponent(this.$element(), _m_scroll_view.default, {
direction: direction,
width: width,
height: height,
scrollByContent: true,
useKeyboard: false,
showScrollbar: showScrollbar
});
this._$container = (0, _renderer.default)(this._scrollView.content());
this._$container.addClass("dx-tileview-wrapper");
this._scrollView.option("onUpdated", this._renderGeometry.bind(this))
}
_initMarkup() {
super._initMarkup();
(0, _common.deferRender)((() => {
this._cellsPerDimension = 1;
this._renderGeometry();
this._updateScrollView();
this._fireContentReadyAction()
}))
}
_updateScrollView() {
this._scrollView.option("direction", this.option("direction"));
this._scrollView.update();
this._indicateLoadingIfAlreadyStarted()
}
_indicateLoadingIfAlreadyStarted() {
if (this._isDataSourceLoading()) {
this._dataSourceLoadingChangedHandler(true)
}
}
_renderGeometry() {
const {
direction: direction
} = this.option();
this._config = CONFIGS[direction];
const items = this.option("items") || [];
const config = this._config;
const itemMargin = this.option("itemMargin");
const maxItemCrossRatio = Math.max.apply(Math, (0, _iterator.map)(items || [], (item => Math.round(item[config.itemCrossRatio] || 1))));
let crossDimensionValue;
if (_window.hasWindow) {
crossDimensionValue = ("width" === config.crossDimension ? _size.getWidth : _size.getHeight)(this.$element())
} else {
crossDimensionValue = parseInt(this.$element().get(0).style[config.crossDimension])
}
this._cellsPerDimension = Math.floor(crossDimensionValue / (this.option(config.baseItemCrossDimension) + itemMargin));
this._cellsPerDimension = Math.max(this._cellsPerDimension, maxItemCrossRatio);
this._cells = [];
this._cells.push(new Array(this._cellsPerDimension));
this._arrangeItems(items);
this._renderContentSize(config, itemMargin)
}
_renderContentSize(config, itemMargin) {
const {
mainDimension: mainDimension,
baseItemMainDimension: baseItemMainDimension
} = config;
if ((0, _window.hasWindow)()) {
const actualContentSize = this._cells.length * this.option(baseItemMainDimension) + (this._cells.length + 1) * itemMargin;
const elementSize = ("width" === mainDimension ? _size.getWidth : _size.getHeight)(this.$element());
("width" === mainDimension ? _size.setWidth : _size.setHeight)(this._$container, Math.max(actualContentSize, elementSize))
}
}
_arrangeItems(items) {
const config = this._config;
const {
itemMainRatio: itemMainRatio
} = config;
const {
itemCrossRatio: itemCrossRatio
} = config;
const {
mainPosition: mainPosition
} = config;
this._itemsPositions = [];
(0, _iterator.each)(items, ((index, item) => {
const currentItem = {};
currentItem[itemMainRatio] = item[itemMainRatio] || 1;
currentItem[itemCrossRatio] = item[itemCrossRatio] || 1;
currentItem.index = index;
currentItem[itemMainRatio] = currentItem[itemMainRatio] <= 0 ? 0 : Math.round(currentItem[config.itemMainRatio]);
currentItem[itemCrossRatio] = currentItem[itemCrossRatio] <= 0 ? 0 : Math.round(currentItem[config.itemCrossRatio]);
const itemPosition = this._getItemPosition(currentItem);
if (-1 === itemPosition[mainPosition]) {
itemPosition[mainPosition] = this._cells.push(new Array(this._cellsPerDimension)) - 1
}
this._occupyCells(currentItem, itemPosition);
this._arrangeItem(currentItem, itemPosition);
this._itemsPositions.push(itemPosition)
}))
}
_refreshActiveDescendant() {}
_getItemPosition(item) {
const config = this._config;
const {
mainPosition: mainPosition
} = config;
const {
crossPosition: crossPosition
} = config;
const position = {};
position[mainPosition] = -1;
position[crossPosition] = 0;
for (let main = 0; main < this._cells.length; main++) {
for (let cross = 0; cross < this._cellsPerDimension; cross++) {
if (this._itemFit(main, cross, item)) {
position[mainPosition] = main;
position[crossPosition] = cross;
break
}
}
if (position[mainPosition] > -1) {
break
}
}
return position
}
_itemFit(mainPosition, crossPosition, item) {
let result = true;
const config = this._config;
const itemRatioMain = item[config.itemMainRatio];
const itemRatioCross = item[config.itemCrossRatio];
if (crossPosition + itemRatioCross > this._cellsPerDimension) {
return false
}
for (let main = mainPosition; main < mainPosition + itemRatioMain; main++) {
for (let cross = crossPosition; cross < crossPosition + itemRatioCross; cross++) {
if (this._cells.length - 1 < main) {
this._cells.push(new Array(this._cellsPerDimension))
} else if (void 0 !== this._cells[main][cross]) {
result = false;
break
}
}
}
return result
}
_occupyCells(item, itemPosition) {
const config = this._config;
const itemPositionMain = itemPosition[config.mainPosition];
const itemPositionCross = itemPosition[config.crossPosition];
const itemRatioMain = item[config.itemMainRatio];
const itemRatioCross = item[config.itemCrossRatio];
for (let main = itemPositionMain; main < itemPositionMain + itemRatioMain; main++) {
for (let cross = itemPositionCross; cross < itemPositionCross + itemRatioCross; cross++) {
this._cells[main][cross] = item.index
}
}
}
_arrangeItem(item, itemPosition) {
const config = this._config;
const itemPositionMain = itemPosition[config.mainPosition];
const itemPositionCross = itemPosition[config.crossPosition];
const itemRatioMain = item[config.itemMainRatio];
const itemRatioCross = item[config.itemCrossRatio];
const baseItemCross = this.option(config.baseItemCrossDimension);
const baseItemMain = this.option(config.baseItemMainDimension);
const itemMargin = this.option("itemMargin");
const cssProps = {
display: itemRatioMain <= 0 || itemRatioCross <= 0 ? "none" : ""
};
const mainDimension = itemRatioMain * baseItemMain + (itemRatioMain - 1) * itemMargin;
const crossDimension = itemRatioCross * baseItemCross + (itemRatioCross - 1) * itemMargin;
cssProps[config.mainDimension] = mainDimension < 0 ? 0 : mainDimension;
cssProps[config.crossDimension] = crossDimension < 0 ? 0 : crossDimension;
cssProps[config.mainPosition] = itemPositionMain * baseItemMain + (itemPositionMain + 1) * itemMargin;
cssProps[config.crossPosition] = itemPositionCross * baseItemCross + (itemPositionCross + 1) * itemMargin;
if (this.option("rtlEnabled")) {
const offsetCorrection = (0, _size.getWidth)(this._$container);
const baseItemWidth = this.option("baseItemWidth");
const itemPositionX = itemPosition.left;
const offsetPosition = itemPositionX * baseItemWidth;
const itemBaseOffset = baseItemWidth + itemMargin;
const itemWidth = itemBaseOffset * item.widthRatio;
const subItemMargins = itemPositionX * itemMargin;
cssProps.left = offsetCorrection - (offsetPosition + itemWidth + subItemMargins)
}
this._itemElements().eq(item.index).css(cssProps)
}
_moveFocus(location) {
const FOCUS_LEFT = this.option("rtlEnabled") ? "right" : "left";
const FOCUS_RIGHT = this.option("rtlEnabled") ? "left" : "right";
const {
direction: direction,
focusedElement: focusedElement
} = this.option();
const horizontalDirection = "horizontal" === direction;
const cells = this._cells;
const index = (0, _renderer.default)(focusedElement).index();
let targetCol = this._itemsPositions[index].left;
let targetRow = this._itemsPositions[index].top;
const colCount = (horizontalDirection ? cells : cells[0]).length;
const rowCount = (horizontalDirection ? cells[0] : cells).length;
const getCell = function(col, row) {
if (horizontalDirection) {
return cells[col][row]
}
return cells[row][col]
};
switch (location) {
case "pageup":
case "up":
while (targetRow > 0 && index === getCell(targetCol, targetRow)) {
targetRow--
}
if (targetRow < 0) {
targetRow = 0
}
break;
case "pagedown":
case "down":
while (targetRow < rowCount && index === getCell(targetCol, targetRow)) {
targetRow++
}
if (targetRow === rowCount) {
targetRow = rowCount - 1
}
break;
case FOCUS_RIGHT:
while (targetCol < colCount && index === getCell(targetCol, targetRow)) {
targetCol++
}
if (targetCol === colCount) {
targetCol = colCount - 1
}
break;
case FOCUS_LEFT:
while (targetCol >= 0 && index === getCell(targetCol, targetRow)) {
targetCol--
}
if (targetCol < 0) {
targetCol = 0
}
break;
default:
super._moveFocus.apply(this, arguments);
return
}
const newTargetIndex = getCell(targetCol, targetRow);
if (!(0, _type.isDefined)(newTargetIndex)) {
return
}
const $newTarget = this._itemElements().eq(newTargetIndex);
this.option("focusedElement", (0, _element.getPublicElement)($newTarget));
this._scrollToItem($newTarget)
}
_scrollToItem($itemElement) {
if (!$itemElement.length) {
return
}
const config = this._config;
const outerMainGetter = "width" === config.mainDimension ? _size.getOuterWidth : _size.getOuterHeight;
const itemMargin = this.option("itemMargin");
const itemPosition = $itemElement.position()[config.mainPosition];
const itemDimension = outerMainGetter($itemElement);
const itemTail = itemPosition + itemDimension;
const scrollPosition = this.scrollPosition();
const clientWidth = outerMainGetter(this.$element());
if (scrollPosition <= itemPosition && itemTail <= scrollPosition + clientWidth) {
return
}
if (scrollPosition > itemPosition) {
this._scrollView.scrollTo(itemPosition - itemMargin)
} else {
this._scrollView.scrollTo(itemPosition + itemDimension - clientWidth + itemMargin)
}
}
_optionChanged(args) {
switch (args.name) {
case "items":
super._optionChanged(args);
this._renderGeometry();
this._updateScrollView();
break;
case "showScrollbar":
this._initScrollView();
break;
case "disabled":
this._scrollView.option("disabled", args.value);
super._optionChanged(args);
break;
case "baseItemWidth":
case "baseItemHeight":
case "itemMargin":
this._renderGeometry();
break;
case "width":
case "height":
super._optionChanged(args);
this._renderGeometry();
this._scrollView.option(args.name, args.value);
this._updateScrollView();
break;
case "direction":
this._renderGeometry();
this._updateScrollView();
break;
case "indicateLoading":
this._hideLoadingIfLoadIndicationOff();
break;
default:
super._optionChanged(args)
}
}
scrollPosition() {
return this._scrollView.scrollOffset()[this._config.mainPosition]
}
}(0, _component_registrator.default)("dxTileView", TileView);
var _default = exports.default = TileView;