UNPKG

@progress/kendo-ui

Version:

This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.

3,008 lines 106 kB
import { t as valueMapperOptions } from "./valueMapper-D6U5gUYU.js";

//#region ../src/kendo.virtuallist.js
const __meta__ = {
	id: "virtuallist",
	name: "VirtualList",
	category: "framework",
	depends: ["data"],
	hidden: true
};
(function($, undefined) {
	const kendo = window.kendo, ui = kendo.ui, encode = kendo.htmlEncode, Widget = ui.Widget, DataBoundWidget = ui.DataBoundWidget, percentageUnitsRegex = /^\d+(\.\d+)?%$/i, LIST_CONTENT = "k-list-content", TABLE_CONTENT = "k-table-body k-table-scroller", HEADER = "k-list-group-sticky-header", TABLE_HEADER = "k-table-group-sticky-header", LIST_ITEM = "k-list-item", TABLE_ITEM = "k-table-row", HEIGHTCONTAINER = "k-height-container", GROUPITEM = "k-list-item-group-label", GROUP_HEADER_ITEM = "k-list-group-item", LIST_UL = "k-list-ul", TABLE = "k-table", SELECTED = "k-selected", FOCUSED = "k-focus", HOVER = "k-hover", CHANGE = "change", CLICK = "click", LISTBOUND = "listBound", ITEMCHANGE = "itemChange", ACTION = "action", ACTIVATE = "activate", DEACTIVATE = "deactivate", GROUP_ROW_SEL = ".k-table-group-row", VIRTUAL_LIST_NS = ".VirtualList";
	function lastFrom(array) {
		return array[array.length - 1];
	}
	function toArray(value) {
		return value instanceof Array ? value : [value];
	}
	function isPrimitive(dataItem) {
		return typeof dataItem === "string" || typeof dataItem === "number" || typeof dataItem === "boolean";
	}
	function getItemCount(screenHeight, listScreens, itemHeight) {
		return Math.ceil(screenHeight * listScreens / itemHeight);
	}
	function appendChild(parent, className, tagName) {
		const element = document.createElement(tagName || "div");
		if (className) {
			element.className = className;
		}
		parent.appendChild(element);
		return element;
	}
	function getDefaultItemHeight(listSize) {
		const mockList = $("<div class=\"k-list " + listSize + " k-virtual-list\">" + "<div class=\"k-list-content\">" + "<ul class=\"k-list-ul\">" + "<li class=\"k-list-item\">" + "<span class=\"k-list-item-text\">test</span>" + "</li>" + "</ul>" + "</div>" + "</div>");
		mockList.css({
			position: "absolute",
			left: "-200000px",
			visibility: "hidden"
		});
		mockList.appendTo(document.body);
		const itemHeight = parseFloat(kendo.getComputedStyles(mockList.find(".k-list-item")[0], ["height"]).height);
		mockList.remove();
		return itemHeight;
	}
	/**
	* Gets the CSS gap value from an existing UL element in the DOM,
	* or creates a temporary one to measure if no UL exists.
	* @param {jQuery|HTMLElement} containerElement - The container element in the DOM (for creating temp UL)
	* @param {jQuery} existingUl - An existing UL element in the DOM (optional)
	* @returns {number} - The gap value in pixels (0 if not found)
	*/
	function getCssGap(containerElement, existingUl) {
		if (existingUl && existingUl.length) {
			let computedStyle = window.getComputedStyle(existingUl[0]);
			let gap = computedStyle.gap || computedStyle.rowGap || "0";
			return parseFloat(gap) || 0;
		}
		const container = containerElement instanceof $ ? containerElement[0] : containerElement;
		if (!container || !container.parentNode) {
			return 0;
		}
		const tempUl = document.createElement("ul");
		tempUl.className = LIST_UL;
		tempUl.style.cssText = "position:absolute;visibility:hidden;left:-9999px;";
		container.appendChild(tempUl);
		const computedStyle = window.getComputedStyle(tempUl);
		const gap = computedStyle.gap || computedStyle.rowGap || "0";
		const gapValue = parseFloat(gap) || 0;
		container.removeChild(tempUl);
		return gapValue;
	}
	function bufferSizes(screenHeight, listScreens, opposite) {
		return {
			down: screenHeight * opposite,
			up: screenHeight * (listScreens - 1 - opposite)
		};
	}
	function listValidator(options, screenHeight) {
		const downThreshold = (options.listScreens - 1 - options.threshold) * screenHeight;
		const upThreshold = options.threshold * screenHeight;
		return function(list, scrollTop, lastScrollTop) {
			if (scrollTop > lastScrollTop) {
				return scrollTop - list.top < downThreshold;
			} else {
				return list.top === 0 || scrollTop - list.top > upThreshold;
			}
		};
	}
	function scrollCallback(element, callback) {
		return function(force) {
			return callback(element.scrollTop, force);
		};
	}
	function syncList(reorder) {
		return function(list, force) {
			reorder(list.items, list.index, force);
			return list;
		};
	}
	function position(element, y) {
		element.style.webkitTransform = "translateY(" + y + "px)";
		element.style.transform = "translateY(" + y + "px)";
	}
	/**
	* Calculates the absolute position (in pixels) of a flat index in a grouped list.
	* Takes into account group headers that appear before each group and CSS gap.
	* @param {number} flatIndex - The flat index of an item (0-based, counting only data items)
	* @param {number} itemHeight - Height of each item in pixels
	* @param {Array} groupRanges - Array of group range objects with startIndex and itemCount
	* @param {number} cssGap - CSS gap between items in pixels (default 0)
	* @returns {number} - The absolute pixel position
	*/
	function getGroupedItemPosition(flatIndex, itemHeight, groupRanges, cssGap) {
		let position = 0;
		cssGap = cssGap || 0;
		const effectiveItemHeight = itemHeight + cssGap;
		for (let i = 0; i < groupRanges.length; i++) {
			const group = groupRanges[i];
			const { startIndex, itemCount } = group;
			if (flatIndex < startIndex + itemCount) {
				if (i > 0) {
					position += effectiveItemHeight;
				}
				position += (flatIndex - startIndex) * effectiveItemHeight;
				break;
			} else {
				if (i > 0) {
					position += effectiveItemHeight;
				}
				position += itemCount * effectiveItemHeight;
			}
		}
		return position;
	}
	/**
	* Calculates the total height of the grouped list including all group headers and CSS gaps.
	* First group uses the sticky header (not inline), so we subtract one header height.
	* @param {number} totalItems - Total number of data items
	* @param {number} itemHeight - Height of each item
	* @param {number} groupCount - Number of groups
	* @param {number} cssGap - CSS gap between items in pixels (default 0)
	* @returns {number} - Total height in pixels
	*/
	function getGroupedTotalHeight(totalItems, itemHeight, groupCount, cssGap) {
		cssGap = cssGap || 0;
		const inlineHeaders = Math.max(0, groupCount - 1);
		const totalElements = totalItems + inlineHeaders;
		const totalGaps = Math.max(0, totalElements - groupCount);
		return totalElements * itemHeight + totalGaps * cssGap;
	}
	/**
	* Builds the group ranges array from the dataSource's group info.
	* For paged/server-side grouping, this builds ranges relative to the current range.
	* @param {Object} dataSource - The Kendo DataSource
	* @param {number} rangeStart - The start index of the current data range (for offset calculation)
	* @param {number} firstGroupIndex - The global index of the first group in this view (0-based, used as fallback)
	* @param {Map} groupValueMap - Optional map of groupValue -> globalGroupIndex for consistent indexing
	* @returns {Array} - Array of { value, startIndex, itemCount, globalGroupIndex }
	*/
	function buildGroupRanges(dataSource, rangeStart, firstGroupIndex, groupValueMap) {
		const groups = dataSource.group() || [];
		if (!groups.length) {
			return [];
		}
		const view = dataSource.view() || [];
		if (!view.length) {
			return [];
		}
		const ranges = [];
		let currentIndex = rangeStart || 0;
		let nextNewGroupIndex = 0;
		if (groupValueMap && groupValueMap.size > 0) {
			for (const idx of groupValueMap.values()) {
				if (idx >= nextNewGroupIndex) {
					nextNewGroupIndex = idx + 1;
				}
			}
		}
		for (const group of view) {
			const groupItems = group.items || [];
			if (!groupItems.length) {
				continue;
			}
			let groupIndex;
			if (groupValueMap) {
				if (groupValueMap.has(group.value)) {
					groupIndex = groupValueMap.get(group.value);
				} else {
					groupIndex = nextNewGroupIndex++;
					groupValueMap.set(group.value, groupIndex);
				}
			} else {
				groupIndex = (firstGroupIndex || 0) + ranges.length;
			}
			ranges.push({
				value: group.value,
				startIndex: currentIndex,
				itemCount: groupItems.length,
				globalGroupIndex: groupIndex
			});
			currentIndex += groupItems.length;
		}
		return ranges;
	}
	function map2(callback, templates) {
		return function(arr1, arr2) {
			for (let i = 0, len = arr1.length; i < len; i++) {
				callback(arr1[i], arr2[i], templates);
				if (arr2[i].item) {
					this.trigger(ITEMCHANGE, {
						item: $(arr1[i]),
						data: arr2[i].item,
						ns: kendo.ui
					});
				}
			}
		};
	}
	function reshift(items, diff) {
		let range;
		if (diff > 0) {
			range = items.splice(0, diff);
			items.push(...range);
		} else {
			range = items.splice(diff, -diff);
			items.unshift(...range);
		}
		return range;
	}
	function render(element, data, templates) {
		const that = this;
		const options = that.options;
		const itemTemplate = data.item ? templates.template : templates.placeholderTemplate;
		const hasColumns = options.columns?.length;
		const altRow = data.index % 2 === 1 ? "k-table-alt-row" : "";
		const iconField = options.iconField;
		const descriptionField = options.descriptionField;
		const actionField = options.actionField;
		const dataItem = data.item;
		const hasIcon = iconField && dataItem && dataItem[iconField];
		const hasDescription = descriptionField && dataItem && dataItem[descriptionField];
		const hasAction = actionField && dataItem && dataItem[actionField];
		element = $(element);
		if (data.index === 0 && this.header && data.group) {
			this.header.html(templates.fixedGroupTemplate(data.group));
		}
		element.attr("data-uid", dataItem ? dataItem.uid : "").attr("data-offset-index", data.index);
		if (hasAction) {
			element.attr("data-action", dataItem[actionField]);
			element.addClass("k-list-item-action");
		} else {
			element.removeAttr("data-action");
			element.removeClass("k-list-item-action");
		}
		if (hasColumns && dataItem) {
			if (altRow.length > 0) {
				element.addClass(altRow);
			} else {
				element.removeClass("k-table-alt-row");
			}
			const renderedColumns = $(renderColumns(options, dataItem, templates));
			kendo.applyStylesFromKendoAttributes(renderedColumns, ["width", "max-width"]);
			element.empty().append(renderedColumns);
		} else {
			element.find("." + GROUPITEM).remove();
			const textContainer = element.find(".k-list-item-text");
			textContainer.html(itemTemplate(dataItem || {}));
			let iconWrapper = element.find(".k-list-item-icon-wrapper");
			if (hasIcon) {
				const iconHtml = kendo.ui.icon({
					icon: dataItem[iconField],
					iconClass: "k-list-item-icon",
					attr: { "aria-hidden": "true" }
				});
				if (iconWrapper.length === 0) {
					textContainer.before(`<span class="k-list-item-icon-wrapper" role="presentation">${iconHtml}</span>`);
				} else {
					iconWrapper.attr("role", "presentation").html(iconHtml);
				}
			} else {
				iconWrapper.remove();
			}
			let descElement = element.find(".k-list-item-description");
			if (hasDescription) {
				if (descElement.length === 0) {
					textContainer.after(`<span class="k-list-item-description">${encode(dataItem[descriptionField])}</span>`);
				} else {
					descElement.html(encode(dataItem[descriptionField]));
				}
			} else {
				descElement.remove();
			}
		}
		element.toggleClass(FOCUSED, data.current);
		element.toggleClass(SELECTED, data.selected);
		element.toggleClass("k-first", data.newGroup);
		element.toggleClass("k-last", data.isLastGroupedItem);
		element.toggleClass("k-loading-item", !dataItem);
		if (data.index !== 0 && data.newGroup) {
			if (hasColumns) {
				$(`<span class="k-table-td k-table-group-td"><span>${templates.groupTemplate(data.group)}</span></span>`).appendTo(element);
			} else {
				$(`<div class="${GROUPITEM}"></div>`).appendTo(element).html(templates.groupTemplate(data.group));
			}
		} else if (data.group && hasColumns) {
			element.append($("<span class=\"k-table-td k-table-spacer-td\"></span>"));
		}
		if (data.top !== undefined) {
			element.css("top", data.top + "px");
		}
	}
	function renderColumns(options, dataItem, templates) {
		let item = "";
		for (let i = 0; i < options.columns.length; i++) {
			const currentWidth = options.columns[i].width;
			const currentWidthInt = parseInt(currentWidth, 10);
			let widthStyle = "";
			if (currentWidth) {
				const widthValue = `${currentWidthInt}${percentageUnitsRegex.test(currentWidth) ? "%" : "px"}`;
				widthStyle = `${kendo.attr("style-width")}="${widthValue}" ${kendo.attr("style-max-width")}="${widthValue}"`;
			}
			item += `<span class='k-table-td' ${widthStyle}>`;
			item += templates["column" + i](dataItem);
			item += "</span>";
		}
		return item;
	}
	function mapChangedItems(selected, itemsToMatch) {
		const itemsLength = itemsToMatch.length;
		const selectedLength = selected.length;
		const changed = [];
		const unchanged = [];
		if (selectedLength) {
			for (let i = 0; i < selectedLength; i++) {
				const dataItem = selected[i];
				let found = false;
				for (let j = 0; j < itemsLength; j++) {
					if (dataItem === itemsToMatch[j]) {
						found = true;
						changed.push({
							index: i,
							item: dataItem
						});
						break;
					}
				}
				if (!found) {
					unchanged.push(dataItem);
				}
			}
		}
		return {
			changed,
			unchanged
		};
	}
	function isActivePromise(promise) {
		return promise && promise.state() !== "resolved";
	}
	const VirtualList = DataBoundWidget.extend({
		init: function(element, options) {
			const that = this;
			const contentClasses = options.columns?.length ? TABLE_CONTENT : LIST_CONTENT;
			that.bound(false);
			that._fetching = false;
			Widget.fn.init.call(that, element, options);
			if (!that.options.itemHeight) {
				that.options.itemHeight = getDefaultItemHeight(options.listSize);
			}
			that._cssGap = 0;
			options = that.options;
			that.element.attr("role", "listbox");
			if (that.options.columns?.length) {
				const contentSelector = "." + contentClasses.split(" ").join(".");
				that.content = that.wrapper = that.element.find(contentSelector);
				if (!that.content.length) {
					that.content = that.wrapper = $(`<div unselectable='on' class='${contentClasses}'></div>`).appendTo(that.element);
				}
				const stickyHeader = $(`<div class="${that._getFixedGroupHeaderClass()}"><span class="k-table-th"></span></div>`);
				that.content.before(stickyHeader);
				that.header = stickyHeader.find(".k-table-th");
			} else {
				that.header = $(`<div class='${that._getFixedGroupHeaderClass()}'></div>`).appendTo(that.element);
				that.content = that.wrapper = $(`<div unselectable='on' class='${contentClasses}'></div>`).appendTo(that.element);
			}
			that.element.children(".k-list-footer, .k-table-footer").appendTo(that.element);
			if (options.ariaLabel) {
				this.element.attr("aria-label", options.ariaLabel);
			} else if (options.ariaLabelledBy) {
				this.element.attr("aria-labelledby", options.ariaLabelledBy);
			}
			that.element.on("mouseenter" + VIRTUAL_LIST_NS, "li:not(.k-loading-item)", function() {
				$(this).addClass(HOVER);
			}).on("mouseleave" + VIRTUAL_LIST_NS, "li", function() {
				$(this).removeClass(HOVER);
			});
			that._values = toArray(that.options.value);
			that._selectedDataItems = [];
			that._selectedIndexes = [];
			that._rangesList = {};
			that._promisesList = [];
			that._optionID = kendo.guid();
			that._templates();
			that.setDataSource(options.dataSource);
			that.content.on("scroll" + VIRTUAL_LIST_NS, kendo.throttle(function() {
				that._renderItems();
				that._triggerListBound();
			}, options.delay));
			that._selectable();
		},
		options: {
			name: "VirtualList",
			autoBind: true,
			delay: 100,
			height: null,
			listScreens: 4,
			threshold: .5,
			itemHeight: null,
			oppositeBuffer: 1,
			type: "flat",
			selectable: false,
			value: [],
			dataValueField: null,
			template: (data) => encode(data),
			placeholderTemplate: () => "loading...",
			groupTemplate: (data) => encode(data),
			fixedGroupTemplate: (data) => encode(data),
			fixedGroupHeader: true,
			mapValueTo: "index",
			valueMapper: null,
			ariaLabel: null,
			ariaLabelledBy: null,
			id: null,
			iconField: null,
			descriptionField: null,
			groupIconField: null,
			actionField: null
		},
		events: [
			CHANGE,
			CLICK,
			LISTBOUND,
			ITEMCHANGE,
			ACTIVATE,
			DEACTIVATE,
			ACTION
		],
		_isTableVariant: function() {
			return !!this.options.columns?.length;
		},
		_getItemClass: function() {
			return this._isTableVariant() ? TABLE_ITEM : LIST_ITEM;
		},
		_getItemHeightStyle: function() {
			return this.options.itemHeight + "px";
		},
		_getHeaderHeight: function() {
			return this.header?.is(":visible") ? this.header.outerHeight() : 0;
		},
		_toggleHeaderVisibility: function(show) {
			const target = this.header.closest(GROUP_ROW_SEL).length ? this.header.closest(GROUP_ROW_SEL) : this.header;
			target[show ? "show" : "hide"]();
		},
		_getHeaderTextSelector: function() {
			return this._isTableVariant() ? ".k-table-th" : ".k-list-item-text";
		},
		_getGroupHeaderClass: function() {
			return this._isTableVariant() ? "k-table-group-row" : GROUP_HEADER_ITEM;
		},
		_getFixedGroupHeaderClass: function() {
			return this._isTableVariant() ? TABLE_HEADER : HEADER;
		},
		setOptions: function(options) {
			Widget.fn.setOptions.call(this, options);
			if (this._selectProxy && this.options.selectable === false) {
				this.element.off(CLICK, "." + this._getItemClass(), this._selectProxy);
			} else if (!this._selectProxy && this.options.selectable) {
				this._selectable();
			}
			this._templates();
			this.refresh();
		},
		items: function() {
			return $(this._items);
		},
		ulElements: function() {
			const that = this;
			if (that.options.type === "flat") {
				return that.ul || $();
			} else {
				return that.content.find("." + LIST_UL);
			}
		},
		destroy: function() {
			this.wrapper.off(VIRTUAL_LIST_NS);
			this.dataSource.unbind(CHANGE, this._refreshHandler);
			Widget.fn.destroy.call(this);
		},
		setDataSource: function(source) {
			const that = this;
			let dataSource = source || {};
			dataSource = Array.isArray(dataSource) ? { data: dataSource } : dataSource;
			dataSource = kendo.data.DataSource.create(dataSource);
			if (that.dataSource) {
				that.dataSource.unbind(CHANGE, that._refreshHandler);
				that._clean();
				that.bound(false);
				that._deferValueSet = true;
				const value = that.value();
				that.value([]);
				that.mute(function() {
					that.value(value);
				});
			} else {
				that._refreshHandler = that.refresh.bind(that);
			}
			that.dataSource = dataSource.bind(CHANGE, that._refreshHandler);
			that.setDSFilter(dataSource.filter());
			if (dataSource.view().length !== 0) {
				that.refresh();
			} else if (that.options.autoBind) {
				dataSource.fetch();
			}
		},
		skip: function() {
			return this.dataSource.currentRangeStart();
		},
		_triggerListBound: function() {
			const that = this;
			const skip = that.skip();
			if (that.bound() && !that._selectingValue && that._skip !== skip) {
				that._skip = skip;
				that.trigger(LISTBOUND);
			}
		},
		_getValues: function(dataItems) {
			const getter = this._valueGetter;
			return $.map(dataItems, (dataItem) => getter(dataItem));
		},
		_highlightSelectedItems: function() {
			for (let i = 0; i < this._selectedDataItems.length; i++) {
				const item = this._getElementByDataItem(this._selectedDataItems[i]);
				if (item.length) {
					item.addClass(SELECTED);
				}
			}
		},
		_focusSelectedInFilteredData: function() {
			const that = this;
			const selectedDataItems = that._selectedDataItems;
			const valueGetter = that._valueGetter;
			let focusIndex = 0;
			if (selectedDataItems.length) {
				const data = that.dataSource.flatView();
				const selectedValue = valueGetter(selectedDataItems[0]);
				for (let i = 0; i < data.length; i++) {
					const item = that.options.type === "group" ? data[i].item : data[i];
					if (item && valueGetter(item) === selectedValue) {
						focusIndex = i;
						break;
					}
				}
			}
			that.focus(focusIndex);
		},
		refresh: function(e) {
			const that = this;
			const action = e?.action;
			const isItemChange = action === "itemchange";
			const filtered = this.isFiltered();
			if (that._mute) {
				return;
			}
			that._deferValueSet = false;
			if (!that._fetching) {
				if (filtered) {
					that._focusSelectedInFilteredData();
				}
				that._createList();
				if (!action && that._values.length && !filtered && !that.options.skipUpdateOnBind && !that._emptySearch) {
					that._selectingValue = true;
					that.bound(true);
					that.value(that._values, true).done(function() {
						that._selectingValue = false;
						that._triggerListBound();
					});
				} else {
					that.bound(true);
					that._highlightSelectedItems();
					that._triggerListBound();
				}
			} else {
				if (that._renderItems) {
					that._renderItems(true);
				}
				that._triggerListBound();
			}
			if (isItemChange || action === "remove") {
				const result = mapChangedItems(that._selectedDataItems, e.items);
				if (result.changed.length) {
					if (isItemChange) {
						that.trigger("selectedItemChange", { items: result.changed });
					} else {
						that.value(that._getValues(result.unchanged));
					}
				}
			}
			that._fetching = false;
		},
		removeAt: function(position) {
			const value = this._values.splice(position, 1)[0];
			return {
				position,
				dataItem: this._removeSelectedDataItem(value)
			};
		},
		_removeSelectedDataItem: function(value) {
			const that = this;
			const valueGetter = that._valueGetter;
			for (const idx in that._selectedDataItems) {
				if (valueGetter(that._selectedDataItems[idx]) === value) {
					that._selectedIndexes.splice(idx, 1);
					return that._selectedDataItems.splice(idx, 1)[0];
				}
			}
		},
		setValue: function(value) {
			this._values = toArray(value);
		},
		value: function(value, _forcePrefetch) {
			const that = this;
			if (value === undefined) {
				return that._values.slice();
			}
			if (value === null) {
				value = [];
			}
			value = toArray(value);
			if (!that._valueDeferred || that._valueDeferred.state() === "resolved") {
				that._valueDeferred = $.Deferred();
			}
			const shouldClear = that.options.selectable === "multiple" && that.select().length && value.length;
			if (shouldClear || !value.length) {
				that.select(-1);
			}
			that._values = value;
			if (that.bound() && !that._mute && !that._deferValueSet || _forcePrefetch) {
				that._prefetchByValue(value);
			}
			return that._valueDeferred;
		},
		_checkValuesOrder: function(value) {
			if (this._removedAddedIndexes?.length === value.length) {
				const newValue = this._removedAddedIndexes.slice();
				this._removedAddedIndexes = null;
				return newValue;
			}
			return value;
		},
		_prefetchByValue: function(value) {
			const that = this;
			const dataView = that._dataView;
			const valueGetter = that._valueGetter;
			const mapValueTo = that.options.mapValueTo;
			const forSelection = [];
			for (let i = 0; i < value.length; i++) {
				for (let idx = 0; idx < dataView.length; idx++) {
					const item = dataView[idx].item;
					if (item) {
						const match = isPrimitive(item) ? value[i] === item : value[i] === valueGetter(item);
						if (match) {
							forSelection.push(dataView[idx].index);
						}
					}
				}
			}
			if (forSelection.length === value.length) {
				that._values = [];
				that.select(forSelection);
				return;
			}
			if (typeof that.options.valueMapper === "function") {
				const callback = mapValueTo === "index" ? that.mapValueToIndex : that.mapValueToDataItem;
				that.options.valueMapper(valueMapperOptions(this.options, value, callback.bind(that)));
			} else {
				if (!that.value()[0]) {
					that.select([-1]);
				} else {
					that._selectingValue = false;
					that._triggerListBound();
				}
			}
		},
		mapValueToIndex: function(indexes) {
			if (indexes === undefined || indexes === -1 || indexes === null) {
				indexes = [];
			} else {
				indexes = toArray(indexes);
			}
			if (!indexes.length) {
				indexes = [-1];
			} else {
				const removed = this._deselect([]).removed;
				if (removed.length) {
					this._triggerChange(removed, []);
				}
			}
			this.select(indexes);
		},
		mapValueToDataItem: function(dataItems) {
			if (dataItems === undefined || dataItems === null) {
				dataItems = [];
			} else {
				dataItems = toArray(dataItems);
			}
			if (!dataItems.length) {
				this.select([-1]);
			} else {
				const removed = $.map(this._selectedDataItems, (item, index) => ({
					index,
					dataItem: item
				}));
				const added = $.map(dataItems, (item, index) => ({
					index,
					dataItem: item
				}));
				this._selectedDataItems = dataItems;
				this._selectedIndexes = [];
				for (let i = 0; i < this._selectedDataItems.length; i++) {
					const item = this._getElementByDataItem(this._selectedDataItems[i]);
					this._selectedIndexes.push(this._getIndecies(item)[0]);
					item.addClass(SELECTED);
				}
				this._triggerChange(removed, added);
				if (this._valueDeferred) {
					this._valueDeferred.resolve();
				}
			}
		},
		deferredRange: function(index) {
			const dataSource = this.dataSource;
			const take = this.itemCount;
			const ranges = this._rangesList;
			const result = $.Deferred();
			const defs = [];
			const low = Math.floor(index / take) * take;
			const high = Math.ceil(index / take) * take;
			const pages = high === low ? [high] : [low, high];
			$.each(pages, (_, skip) => {
				const end = skip + take;
				const existingRange = ranges[skip];
				let deferred;
				if (!existingRange || existingRange.end !== end) {
					deferred = $.Deferred();
					ranges[skip] = {
						end,
						deferred
					};
					dataSource._multiplePrefetch(skip, take, () => {
						deferred.resolve();
					});
				} else {
					deferred = existingRange.deferred;
				}
				defs.push(deferred);
			});
			$.when(...defs).done(() => {
				result.resolve();
			});
			return result;
		},
		prefetch: function(indexes) {
			const that = this;
			const take = this.itemCount;
			const isEmptyList = !that._promisesList.length;
			if (!isActivePromise(that._activeDeferred)) {
				that._activeDeferred = $.Deferred();
				that._promisesList = [];
			}
			$.each(indexes, (_, index) => {
				that._promisesList.push(that.deferredRange(that._getSkip(index, take)));
			});
			if (isEmptyList) {
				$.when(...that._promisesList).done(() => {
					that._promisesList = [];
					that._activeDeferred.resolve();
				});
			}
			return that._activeDeferred;
		},
		_findDataItem: function(view, index) {
			if (this.options.type === "group") {
				for (let i = 0; i < view.length; i++) {
					const group = view[i].items;
					if (group.length <= index) {
						index = index - group.length;
					} else {
						return group[index];
					}
				}
			}
			return view[index];
		},
		_getRange: function(skip, take) {
			return this.dataSource._findRange(skip, Math.min(skip + take, this.dataSource.total()));
		},
		dataItemByIndex: function(index) {
			const that = this;
			const take = that.itemCount;
			const skip = that._getSkip(index, take);
			let view = this._getRange(skip, take);
			if (!that._getRange(skip, take).length) {
				return null;
			}
			if (that.options.type === "group") {
				kendo.ui.progress($(that.wrapper), true);
				that.mute(function() {
					that.dataSource.range(skip, take, () => {
						kendo.ui.progress($(that.wrapper), false);
					});
					view = that.dataSource.view();
				});
			}
			return that._findDataItem(view, [index - skip]);
		},
		selectedDataItems: function() {
			return this._selectedDataItems.slice();
		},
		scrollWith: function(value) {
			this.content.scrollTop(this.content.scrollTop() + value);
		},
		scrollTo: function(y) {
			this.content.scrollTop(y);
		},
		scrollToIndex: function(index) {
			const position = this._getElementPosition ? this._getElementPosition(index) : index * (this.options.itemHeight + this._cssGap);
			this.scrollTo(position);
		},
		focus: function(candidate) {
			const that = this;
			const itemHeight = that.options.itemHeight;
			const id = that._optionID;
			let element;
			let index;
			let current;
			let triggerEvent = true;
			if (candidate === undefined) {
				current = that.content.find("." + FOCUSED);
				return current.length ? current : null;
			}
			if (typeof candidate === "function") {
				const data = that.dataSource.flatView();
				for (let idx = 0; idx < data.length; idx++) {
					if (candidate(data[idx])) {
						candidate = idx;
						break;
					}
				}
			}
			if (candidate instanceof Array) {
				candidate = lastFrom(candidate);
			}
			if (isNaN(candidate)) {
				element = $(candidate);
				index = parseInt($(element).attr("data-offset-index"), 10);
			} else {
				index = candidate;
				element = that._getElementByIndex(index);
			}
			if (index === -1) {
				that.content.find("." + FOCUSED).removeClass(FOCUSED);
				that._focusedIndex = undefined;
				return;
			}
			if (element.length) {
				if (element.hasClass(FOCUSED)) {
					triggerEvent = false;
				}
				const previousIndex = that._focusedIndex;
				if (previousIndex !== undefined) {
					current = that._getElementByIndex(previousIndex);
					current.removeClass(FOCUSED).removeAttr("id");
					if (triggerEvent) {
						that.trigger(DEACTIVATE);
					}
				}
				that._focusedIndex = index;
				element.addClass(FOCUSED).attr("id", id);
				let elementPosition = that._getRenderedElementPosition(element);
				if (elementPosition === 0 && index > 0) {
					elementPosition = that._getElementPosition(index);
				}
				const headerHeight = that._getHeaderHeight();
				const goingUp = previousIndex !== undefined && index < previousIndex;
				const goingDown = previousIndex !== undefined && index > previousIndex;
				const scrollTop = this.content.scrollTop();
				const visibleTop = scrollTop + headerHeight;
				const screenEnd = scrollTop + this._screenHeight;
				const elementBottom = elementPosition + itemHeight;
				let position;
				if (elementPosition < visibleTop && elementBottom > scrollTop) {
					position = "top";
				} else if (elementPosition === screenEnd || elementPosition < screenEnd && screenEnd < elementBottom) {
					position = "bottom";
				} else if (elementPosition >= visibleTop && elementBottom <= screenEnd) {
					position = "inScreen";
				} else {
					position = "outScreen";
				}
				if (position === "top") {
					that.scrollTo(Math.max(0, elementPosition - headerHeight));
				} else if (position === "bottom") {
					that.scrollTo(elementPosition + itemHeight - that._screenHeight);
				} else if (position === "outScreen") {
					if (goingUp) {
						that.scrollTo(Math.max(0, elementPosition - headerHeight));
					} else if (goingDown) {
						that.scrollTo(elementPosition + itemHeight - that._screenHeight);
					} else {
						that.scrollTo(Math.max(0, elementPosition - headerHeight));
					}
				}
				if (triggerEvent) {
					that.trigger(ACTIVATE);
				}
			} else {
				const previousIndex = that._focusedIndex;
				if (previousIndex !== undefined) {
					current = that._getElementByIndex(previousIndex);
					current.removeClass(FOCUSED).removeAttr("id");
				}
				that._focusedIndex = index;
				that.items().removeClass(FOCUSED);
				const elementPosition = that._getElementPosition(index);
				const headerHeight = that._getHeaderHeight();
				const goingUp = previousIndex !== undefined && index < previousIndex;
				const goingDown = previousIndex !== undefined && index > previousIndex;
				if (goingUp) {
					that.scrollTo(Math.max(0, elementPosition - headerHeight));
				} else if (goingDown) {
					that.scrollTo(elementPosition + itemHeight - that._screenHeight);
				} else {
					that.scrollTo(Math.max(0, elementPosition - headerHeight));
				}
			}
		},
		focusIndex: function() {
			return this._focusedIndex;
		},
		focusFirst: function() {
			this.scrollTo(0);
			this.focus(0);
		},
		focusLast: function() {
			const lastIndex = this.dataSource.total();
			this.scrollTo(this.heightContainer.offsetHeight);
			this.focus(lastIndex - 1);
		},
		focusPrev: function() {
			let index = this._focusedIndex;
			if (!isNaN(index) && index > 0) {
				index -= 1;
				this.focus(index);
				const current = this.focus();
				if (current?.hasClass("k-loading-item")) {
					index += 1;
					this.focus(index);
				}
				return index;
			} else {
				index = this.dataSource.total() - 1;
				this.focus(index);
				return index;
			}
		},
		focusNext: function() {
			let index = this._focusedIndex;
			const lastIndex = this.dataSource.total() - 1;
			if (!isNaN(index) && index < lastIndex) {
				index += 1;
				this.focus(index);
				const current = this.focus();
				if (current?.hasClass("k-loading-item")) {
					index -= 1;
					this.focus(index);
				}
				return index;
			} else {
				index = 0;
				this.focus(index);
				return index;
			}
		},
		_triggerChange: function(removed = [], added = []) {
			if (removed.length || added.length) {
				this.trigger(CHANGE, {
					removed,
					added
				});
			}
		},
		select: function(candidate) {
			const that = this;
			const singleSelection = that.options.selectable !== "multiple";
			let prefetchStarted = isActivePromise(that._activeDeferred);
			const filtered = this.isFiltered();
			if (candidate === undefined) {
				return that._selectedIndexes.slice();
			}
			if (!that._selectDeferred || that._selectDeferred.state() === "resolved") {
				that._selectDeferred = $.Deferred();
			}
			let indices = that._getIndecies(candidate);
			const isAlreadySelected = singleSelection && !filtered && lastFrom(indices) === lastFrom(this._selectedIndexes);
			let removed = that._deselectCurrentValues(indices);
			if (removed.length || !indices.length || isAlreadySelected) {
				that._triggerChange(removed);
				if (that._valueDeferred) {
					that._valueDeferred.resolve().promise();
				}
				return that._selectDeferred.resolve().promise();
			}
			if (indices.length === 1 && indices[0] === -1) {
				indices = [];
			}
			const initialIndices = indices;
			const result = that._deselect(indices);
			removed = result.removed;
			indices = result.indices;
			if (singleSelection) {
				prefetchStarted = false;
				if (indices.length) {
					indices = [lastFrom(indices)];
				}
			}
			const done = function() {
				const added = that._select(indices);
				if (initialIndices.length === indices.length || singleSelection) {
					that.focus(indices);
				}
				that._triggerChange(removed, added);
				if (that._valueDeferred) {
					that._valueDeferred.resolve();
				}
				that._selectDeferred.resolve();
			};
			const deferred = that.prefetch(indices);
			if (!prefetchStarted) {
				if (deferred) {
					deferred.done(done);
				} else {
					done();
				}
			}
			return that._selectDeferred.promise();
		},
		bound: function(bound) {
			if (bound === undefined) {
				return this._listCreated;
			}
			this._listCreated = bound;
		},
		mute: function(callback) {
			this._mute = true;
			callback();
			this._mute = false;
		},
		setDSFilter: function(filter) {
			this._lastDSFilter = $.extend({}, filter);
		},
		isFiltered: function() {
			if (!this._lastDSFilter) {
				this.setDSFilter(this.dataSource.filter());
			}
			return !kendo.data.Query.compareFilters(this.dataSource.filter(), this._lastDSFilter);
		},
		skipUpdate: $.noop,
		_getElementByIndex: function(index) {
			return this.items().filter((idx, element) => index === parseInt($(element).attr("data-offset-index"), 10));
		},
		_getElementByDataItem: function(dataItem) {
			const dataView = this._dataView;
			const valueGetter = this._valueGetter;
			let element;
			for (let i = 0; i < dataView.length; i++) {
				const match = dataView[i].item && isPrimitive(dataView[i].item) ? dataView[i].item === dataItem : dataView[i].item && dataItem && valueGetter(dataView[i].item) == valueGetter(dataItem);
				if (match) {
					element = dataView[i];
					break;
				}
			}
			return element ? this._getElementByIndex(element.index) : $();
		},
		_clean: function() {
			this.result = undefined;
			this._lastScrollTop = undefined;
			this._skip = undefined;
			$(this.heightContainer).remove();
			this.heightContainer = undefined;
			if (this.content) {
				this.content.empty();
			}
			this._flatUl = null;
			this.ul = null;
			if (this._groupUlCache) {
				for (const key in this._groupUlCache) {
					const cached = this._groupUlCache[key];
					if (cached?.ul?.parentNode) {
						cached.ul.parentNode.removeChild(cached.ul);
					}
				}
			}
			this._groupContainers = [];
			this._groupUlCache = {};
			this._groupRanges = null;
			this._items = [];
			this._groupValueMap = null;
		},
		_height: function() {
			const hasData = !!this.dataSource.view().length;
			let height = this.options.height;
			const itemHeight = this.options.itemHeight;
			const cssGap = this._cssGap || 0;
			const effectiveItemHeight = itemHeight + cssGap;
			const total = this.dataSource.total();
			if (!hasData) {
				height = 0;
			} else if (height / effectiveItemHeight > total) {
				height = total * effectiveItemHeight;
			}
			return height;
		},
		setScreenHeight: function() {
			const height = this._height();
			this.content.height(height);
			this._screenHeight = height;
		},
		screenHeight: function() {
			return this._screenHeight;
		},
		_getRenderedElementPosition: function(element) {
			if (!element || !element.length) {
				return 0;
			}
			const el = element[0];
			const parentUl = element.closest("ul")[0];
			if (!parentUl) {
				return el.offsetTop;
			}
			const transform = parentUl.style.transform || parentUl.style.webkitTransform || "";
			const match = transform.match(/translateY\((-?\d+(?:\.\d+)?)px\)/);
			const ulTranslateY = match ? parseFloat(match[1]) : 0;
			return ulTranslateY + el.offsetTop;
		},
		_getElementLocation: function(index) {
			const scrollTop = this.content.scrollTop();
			const screenHeight = this._screenHeight;
			const itemHeight = this.options.itemHeight;
			const yPosition = this._getElementPosition(index);
			const yDownPostion = yPosition + itemHeight;
			const screenEnd = scrollTop + screenHeight;
			const headerHeight = this._getHeaderHeight();
			const visibleTop = scrollTop + headerHeight;
			if (yPosition < visibleTop && yDownPostion > scrollTop) {
				return "top";
			} else if (yPosition === screenEnd || yPosition < screenEnd && screenEnd < yDownPostion) {
				return "bottom";
			} else if (yPosition >= visibleTop && yDownPostion <= screenEnd) {
				return "inScreen";
			} else {
				return "outScreen";
			}
		},
		_getElementPosition: function(index) {
			const itemHeight = this.options.itemHeight;
			const groupRanges = this._groupRanges;
			const cssGap = this._cssGap || 0;
			if (groupRanges && groupRanges.length) {
				const lastRange = groupRanges[groupRanges.length - 1];
				const lastKnownIndex = lastRange.startIndex + lastRange.itemCount - 1;
				if (index <= lastKnownIndex) {
					return getGroupedItemPosition(index, itemHeight, groupRanges, cssGap);
				}
				const knownPosition = getGroupedItemPosition(lastKnownIndex, itemHeight, groupRanges, cssGap);
				const itemsBeyond = index - lastKnownIndex;
				const avgItemsPerGroup = lastKnownIndex / groupRanges.length;
				const estimatedNewHeaders = Math.floor(itemsBeyond / avgItemsPerGroup);
				return knownPosition + itemsBeyond * (itemHeight + cssGap) + estimatedNewHeaders * itemHeight;
			}
			return index * (itemHeight + cssGap);
		},
		_templates: function() {
			const options = this.options;
			const templates = {
				template: options.template,
				placeholderTemplate: options.placeholderTemplate,
				groupTemplate: options.groupTemplate,
				fixedGroupTemplate: options.fixedGroupTemplate
			};
			if (options.columns) {
				options.columns.forEach((column, i) => {
					const templateText = column.field ? column.field.toString() : "text";
					const templateFunc = (data) => encode(kendo.getter(templateText)(data));
					templates["column" + i] = column.template || templateFunc;
				});
			}
			for (const key in templates) {
				if (typeof templates[key] !== "function") {
					templates[key] = kendo.template(templates[key] || "");
				}
			}
			this.templates = templates;
		},
		_generateItems: function(element, count) {
			const items = [];
			const itemHeight = this._getItemHeightStyle();
			const itemClass = this._getItemClass();
			while (count-- > 0) {
				const text = document.createElement("span");
				text.className = "k-list-item-text";
				const item = document.createElement("li");
				item.tabIndex = -1;
				item.className = itemClass;
				item.setAttribute("role", "option");
				item.style.height = itemHeight;
				item.style.minHeight = itemHeight;
				item.style.position = "absolute";
				item.style.width = "100%";
				item.appendChild(text);
				element.appendChild(item);
				items.push(item);
			}
			return items;
		},
		_generateGroupId: function(groupIndex, groupValue) {
			const stringValue = groupValue === null || groupValue === undefined ? "" : String(groupValue);
			const safeValue = stringValue.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-_]/g, "");
			return this._optionID + "-group-" + groupIndex + "-" + safeValue;
		},
		_createGroupUl: function(hasHeader, groupValue, groupIndex, firstDataItem) {
			const that = this;
			const options = that.options;
			const itemHeight = that._getItemHeightStyle();
			const ul = document.createElement("ul");
			const groupId = that._generateGroupId(groupIndex, groupValue);
			const groupIconField = options.groupIconField;
			ul.className = that._isTableVariant() ? `${LIST_UL} ${TABLE}` : LIST_UL;
			ul.style.position = "absolute";
			ul.style.width = "100%";
			ul.style.top = "0";
			ul.style.left = "0";
			if (options.id) {
				ul.id = `${options.id}-group-${groupIndex}`;
			}
			ul.setAttribute("role", "group");
			ul.setAttribute("aria-labelledby", groupId);
			if (options.ariaLive) {
				ul.setAttribute("aria-live", options.ariaLive);
			}
			const groupContainer = {
				ul,
				header: null,
				items: [],
				groupValue,
				hasHeader,
				groupId
			};
			if (hasHeader) {
				const header = document.createElement("li");
				header.className = that._getGroupHeaderClass();
				header.setAttribute("role", "presentation");
				header.setAttribute("id", groupId);
				header.style.height = itemHeight;
				header.style.minHeight = itemHeight;
				header.style.position = "relative";
				header.style.transform = "none";
				header.style.overflow = "hidden";
				if (groupIconField && firstDataItem && firstDataItem[groupIconField]) {
					const iconWrapper = document.createElement("span");
					iconWrapper.className = "k-list-item-icon-wrapper";
					iconWrapper.setAttribute("role", "presentation");
					const iconHtml = kendo.ui.icon({
						icon: firstDataItem[groupIconField],
						iconClass: "k-list-group-icon",
						attr: { "aria-hidden": "true" }
					});
					$(iconWrapper).append(iconHtml);
					$(header).append(iconWrapper);
				}
				const headerText = document.createElement("span");
				headerText.className = that._isTableVariant() ? "k-table-th" : "k-list-item-text";
				header.appendChild(headerText);
				ul.appendChild(header);
				groupContainer.header = header;
			}
			return groupContainer;
		},
		_addGroupItems: function(groupContainer, count) {
			const that = this;
			const itemHeight = that._getItemHeightStyle();
			const hasColumns = that._isTableVariant();
			const itemClass = that._getItemClass();
			for (let i = 0; i < count; i++) {
				const item = document.createElement("li");
				item.tabIndex = -1;
				item.className = itemClass;
				item.setAttribute("role", "option");
				item.style.height = itemHeight;
				item.style.minHeight = itemHeight;
				item.style.position = "relative";
				item.style.transform = "none";
				if (!hasColumns) {
					const text = document.createElement("span");
					text.className = "k-list-item-text";
					item.appendChild(text);
				}
				groupContainer.ul.appendChild(item);
				groupContainer.items.push(item);
			}
		},
		_generateGroupedItems: function(container, visibleGroups, itemsPerGroup) {
			const that = this;
			const groupContainers = [];
			const allItems = [];
			container.innerHTML = "";
			for (const groupInfo of visibleGroups) {
				const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.groupIndex);
				const itemCount = Math.min(groupInfo.itemCount, itemsPerGroup);
				that._addGroupItems(groupContainer, itemCount);
				position(groupContainer.ul, groupInfo.top);
				groupContainer.ul.setAttribute("data-group-index", groupInfo.groupIndex);
				groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
				groupContainer.startIndex = groupInfo.startIndex;
				groupContainer.itemCount = itemCount;
				groupContainer.top = groupInfo.top;
				groupContainer.groupIndex = groupInfo.groupIndex;
				container.appendChild(groupContainer.ul);
				groupContainers.push(groupContainer);
				allItems.push(...groupContainer.items);
			}
			return {
				groupContainers,
				allItems
			};
		},
		_saveInitialRanges: function() {
			const ranges = this.dataSource._ranges;
			const deferred = $.Deferred();
			deferred.resolve();
			this._rangesList = {};
			for (let i = 0; i < ranges.length; i++) {
				this._rangesList[ranges[i].start] = {
					end: ranges[i].end,
					deferred
				};
			}
		},
		_createList: function() {
			const that = this;
			const content = that.content.get(0);
			const options = that.options;
			const dataSource = that.dataSource;
			if (that.bound()) {
				that._clean();
			}
			that._saveInitialRanges();
			that._buildValueGetter();
			that.setScreenHeight();
			that.itemCount = getItemCount(that._screenHeight, options.listScreens, options.itemHeight);
			if (that.itemCount > dataSource.total()) {
				that.itemCount = dataSource.total();
			}
			that.options.type = (dataSource.group() || []).length ? "group" : "flat";
			if (that.options.type === "flat") {
				const ul = document.createElement("ul");
				ul.className = that._isTableVariant() ? `${LIST_UL} ${TABLE}` : LIST_UL;
				ul.style.position = "relative";
				that.content.get(0).appendChild(ul);
				that._flatUl = ul;
				if (options.id) {
					ul.id = options.id;
				}
				if (options.ariaLive) {
					ul.setAttribute("aria-live", options.ariaLive);
				}
				that.ul = $(ul);
				that._cssGap = that._isTableVariant() ? 0 : getCssGap(that.content, that.ul);
				that._items = that._generateItems(ul, that.itemCount);
				const totalItems = dataSource.total();
				const effectiveItemHeight = options.itemHeight + that._cssGap;
				const totalHeight = totalItems * effectiveItemHeight;
				that._setHeight(totalHeight);
				that._toggleHeaderVisibility(false);
				that.getter = that._getter(() => {
					that._renderItems(true);
				});
				that._onScroll = (scrollTop, force) => {
					const getList = that._listItems(that.getter);
					return that._fixedHeader(scrollTop, getList(scrollTop, force));
				};
				that._renderItems = that._whenChanged(scrollCallback(content, that._onScroll), syncList(that._reorderList(that._items, render.bind(that))));
			} else {
				that._toggleHeaderVisibility(options.fixedGroupHeader !== false);
				that._groupValueMap = new Map();
				that._cssGap = getCssGap(that.content, null);
				that._groupRanges = buildGroupRanges(dataSource, 0, 0, that._groupValueMap);
				let estimatedTotalGroups = options.groupCount;
				const isServerOperations = dataSource.options.serverGrouping === true;
				if (!estimatedTotalGroups && that._groupRanges.length > 0) {
					if (isServerOperations) {
						estimatedTotalGroups = that._groupRanges.length;
					} else {
						const groupDescriptor = dataSource.group();
						const allData = dataSource.data();
						if (allData?.length && groupDescriptor?.length) {
							const fullGrouped = new kendo.data.Query(allData).group(groupDescriptor).data;
							estimatedTotalGroups = fullGrouped.length;
						} else {
							estimatedTotalGroups = that._groupRanges.length;
						}
					}
				}
				that._totalGroupCount = estimatedTotalGroups || that._groupRanges.length;
				that._initialGroupRanges = that._groupRanges.slice();
				const groupCount = that._totalGroupCount;
				const isServerGrouping = dataSource.options.serverGrouping === true;
				const itemCount = isServerGrouping ? dataSource.total() : dataSource.data().length || dataSource.total();
				const totalHeight = getGroupedTotalHeight(itemCount, options.itemHeight, groupCount, that._cssGap);
				that._setHeight(totalHeight);
				that._initGroupedRendering();
			}
			that._renderItems();
			that._calculateGroupPadding(that._screenHeight);
			that._calculateColumnsHeaderPadding();
		},
		_initGroupedRendering: function() {
			const that = this;
			const content = that.content.get(0);
			that._currentPageStart = 0;
			that._renderedPageStart = null;
			that._groupUls = [];
			that._items = [];
			that.getter = that._groupedGetter(() => {
				that._renderItems(true);
			});
			that._onScroll = (scrollTop, force) => that._handleGroupedScroll(scrollTop, force);
			that._renderItems = that._whenChangedGrouped(scrollCallback(content, that._onScroll), (list, force) => {
				that._renderGroupedPage(list, force);
				return list;
			});
		},
		_handleGroupedScroll: function(scrollTop, force) {
			const that = this;
			const itemHeight = that.options.itemHeight;
			const dataSource = that.dataSource;
			const pageSize = that.itemCount;
			const total = dataSource.total();
			const screenHeight = that._screenHeight || 300;
			const totalGroupCount = that._totalGroupCount || 1;
			const isServerOperations = dataSource.options.serverGrouping === true;
			if (!isServerOperations) {
				return that._buildGroupedPageData(0, scrollTop);
			}
			const totalHeight = total * itemHeight + Math.max(0, totalGroupCount - 1) * itemHeight;
			const pageInfo = that._scrollPositionToPageInfo(scrollTop);
			let pageStart = pageInfo.pageStart;
			const maxPageStart = Math.max(0, total - pageSize);
			const scrollBottom = scrollTop + screenHeight;
			const nearEnd = scrollBottom >= totalHeight - screenHeight * 2;
			if (nearEnd && pageStart < maxPageStart) {
				pageStart = maxPageStart;
			}
			let constrainedPageStart = Math.min(Math.max(0, pageStart), maxPageStart);
			const pageChanged = that._currentPageStart !== constrainedPageStart;
			if (pageChanged || force) {
				if (!dataSource.inRange(constrainedPageStart, pageSize)) {
					if (that._pageDeferred) {
						that._pageDeferred.reject();
					}
					that._pageDeferred = that.deferredRange(constrainedPageStart);
					that._pageDeferred.then(() => {
						that._pageDeferred = null;
						that.mute(() => {
							dataSource.range(constrainedPageStart, pageSize);
						});
						const firstGroupIdx = that._estimateFirstGroupIndex(constrainedPageStart);
						that._groupRanges = buildGroupRanges(dataSource, constrainedPageStart, firstGroupIdx, that._groupValueMap);
						that._updateGroupCountAndHeight();
						that._currentPageStart = constrainedPageStart;
						that._renderItems(true);
					});
					return that._buildPlaceholderPageData(constrainedPageStart, scrollTop);
				}
				if (dataSource.currentRangeStart() !== constrainedPageStart) {
					that.mute(() => {
						dataSource.range(constrainedPageStart, pageSize);
					});
					const firstGroupIdx = that._estimateFirstGroupIndex(constrainedPageStart);
					that._groupRanges = buildGroupRanges(dataSource, constrainedPageStart, firstGroupIdx, that._groupValueMap);
					that._updateGroupCountAndHeight();
					that._currentPageStart = constrainedPageStart;
					const list = that._buildGroupedPageData(constrainedPageStart, scrollTop);
					that._renderGroupedPage(list, true, scrollTop);
					return list;
				}
				that._currentPageStart = constrainedPageStart;
			}
			return that._buildGroupedPageData(constrainedPageStart, scrollTop);
		},
		_getKnownRangeInfo: function(initialRanges, itemHeight) {
			let knownPosition = 0;
			for (let i = 0; i < initialRanges.length; i++) {
				const range = initialRanges[i];
				const headerSlots = i > 0 ? 1 : 0;
				knownPosition += (headerSlots + range.itemCount) * itemHeight;
			}
			const lastRange = initialRanges[initialRanges.length - 1];
			const lastKnownIndex = lastRange.startIndex + lastRange.itemCount - 1;
			const lastKnownGroupIndex = initialRanges.length - 1;
			const avgGroupHeight = knownPosition / initialRanges.length;
			const avgItemsPerGroup = (lastKnownIndex + 1) / initialRanges.length;
			return {
				knownPosition,
				lastKnownIndex,
				lastKnownGroupIndex,
				avgGroupHeight,
				avgItemsPerGroup
			};
		},
		_estimateViewportIndicesBeyondKnown: function(scrollTop, viewportEnd, rangeInfo, total) {
			const { knownPosition, lastKnownGroupIndex, avgGroupHeight, avgItemsPerGroup } = rangeInfo;
			const positionBeyondKnown = scrollTop - knownPosition;
			const groupsBeyondKnown = Math.floor(positionBeyondKnown / avgGroupHeight);
			const targetGroupIndex = lastKnownGroupIndex + 1 + groupsBeyondKnown;
			let viewportStartIndex = Math.floor(targetGroupIndex * avgItemsPerGroup);
			viewportStartIndex = Math.min(viewportStartIndex, total - 1);
			const endPositionBeyondKnown = viewportEnd - knownPosition;
			const endGroupsBeyondKnown = Math.ceil(endPositionBeyondKnown / avgGroupHeight);
			const endGroupIndex = lastKnownGroupIndex + 1 + endGroupsBeyondKnown;
			let viewportEndIndex = Math.floor(endGroupIndex * avgItemsPerGroup);
			viewportEndIndex = Math.min(viewportEndIndex, total - 1);
			return {
				viewportStartIndex,
				viewportEndIndex,
				targetGroupIndex
			};
		},
		_findViewportIndicesInKnownRanges: function(scrollTop, viewportEnd, initialRanges, itemHeight, rangeInfo, total) {
			const { knownPosition, lastKnownGroupIndex, avgGroupHeight, avgItemsPerGroup } = rangeInfo;
			let viewportStartIndex = 0;
			let viewportEndIndex = 0;
			let targetGroupIndex = 0;
			let position = 0;
			for (let i = 0; i < initialRanges.length; i++) {
				const range = initialRanges[i];
				const headerSlots = i > 0 ? 1 : 0;
				const groupHeight = (headerSlots + range.itemCount) * itemHeight;
				const groupEnd = position + groupHeight;
				if (scrollTop >= position && scrollTop < groupEnd) {
					const posInGroup = scrollTop - position;
					const slotsIntoGroup = Math.floor(posInGroup / itemHeight);
					const itemsIntoGroup = Math.max(0, slotsIntoGroup - headerSlots);
					viewportStartIndex = range.startIndex + Math.min(itemsIntoGroup, range.itemCount - 1);
					targetGroupIndex = i;
				}
				if (viewportEnd >= position && viewportEnd < groupEnd) {
					const posInGroup = viewportEnd - position;
					const slotsIntoGroup = Math.ceil(posInGroup / itemHeight);
					const itemsIntoGroup = Math.max(0, slotsIntoGroup - headerSlots);
					viewportEndIndex = range.startIndex + Math.min(itemsIntoGroup, range.itemCount - 1);
				}
				position += groupHeight;
			}
			if (viewportEnd >= knownPosition && viewportEndIndex === 0) {
				const endPositionBeyondKnown = viewportEnd - knownPosition;
				const endGroupsBeyondKnown = Math.ceil(endPositionBeyondKnown / avgGroupHeight);
				const endGroupIndex = lastKnownGroupIndex + 1 + endGroupsBeyondKnown;
				viewportEndIndex = Math.floor(endGroupIndex * avgItemsPerGroup);
				viewportEndIndex = Math.min(viewportEndIndex, total - 1);
			}
			return {
				viewportStartIndex,
				viewportEndIndex,
				targetGroupIndex
			};
		},
		_calculateCenteredPageStart: function(viewportStartIndex, viewportEndIndex, pageSize, total) {
			const viewportItemCount = viewportEndIndex - viewportStartIndex + 1;
			const bufferItems = Math.floor((pageSize - viewportItemCount) / 2);
			const maxPageStart = Math.max(0, total - pageSize);
			let pageStart = Math.max(0, viewportStartIndex - bufferItems);
			pageStart = Math.min(pageStart, maxPageStart);
			const pageEnd = pageStart + pageSize - 1;
			if (viewportEndIndex > pageEnd) {
				pageStart = Math.min(maxPageStart, viewportEndIndex - pageSize + 1);
				pageStart = Math.max(0, pageStart);
			}
			return pageStart;
		},
		_scrollPositionToPageInfo: function(scrollTop) {
			const that = this;
			const itemHeight = that.options.itemHeight;
			const pageSize = that.itemCount;
			const initialRanges = that._initialGroupRanges;
			const screenHeight = that._screenHeight || 300;
			const total = that.dataSource.total();
			if (!initialRanges || !initialRanges.length) {
				const itemIndex = Math.floor(scrollTop / itemHeight);
				const maxPageStart = Math.max(0, total - pageSize);
				let pageStart = Math.max(0, itemIndex - Math.floor(pageSize / 2));
				pageStart = Math.min(pageStart, maxPageStart);
				return {
					pageStart,
					groupIndex: 0
				};
			}
			const rangeInfo = that._getKnownRangeInfo(initialRanges, itemHeight);
			const viewportEnd = scrollTop + screenHeight;
			let result;
			if (scrollTop >= rangeInfo.knownPosition) {
				result = that._estimateViewportIndicesBeyondKnown(scrollTop, viewportEnd, rangeInfo, total);
			} else {
				result = that._findViewportIndicesInKnownRanges(scrollTop, viewportEnd, initialRanges, itemHeight, rangeInfo, total);
			}
			let { viewportStartIndex, viewportEndIndex, targetGroupIndex } = result;
			if (viewportEndIndex === 0 || viewportEndIndex < viewportStartIndex) {
				viewportEndIndex = Math.min(viewportStartIndex + Math.ceil(screenHeight / itemHeight), total - 1);
			}
			const pageStart = that._calculateCenteredPageStart(viewportStartIndex, viewportEndIndex, pageSize, total);
			return {
				pageStart,
				groupIndex: targetGroupIndex
			};
		},
		_updateGroupCountAndHeight: function() {
			const that = this;
			const options = that.options;
			const dataSource = that.dataSource;
			if (!that._groupValueMap || options.type !== "group") {
				return;
			}
			const discoveredGroupCount = that._groupValueMap.size;
			const previousGroupCount = that._totalGroupCount || 0;
			const total = dataSource.total();
			const pageSize = that.itemCount;
			const currentRangeStart = dataSource.currentRangeStart() || 0;
			const isAtEnd = currentRangeStart + pageSize >= total;
			const itemHeight = that.options.itemHeight;
			if (discoveredGroupCount > previousGroupCount || isAtEnd && discoveredGroupCount !== previousGroupCount) {
				that._totalGroupCount = discoveredGroupCount;
				const totalHeight = getGroupedTotalHeight(total, itemHeight, discoveredGroupCount, that._cssGap);
				that._setHeight(totalHeight);
			}
		},
		_estimateFirstGroupIndex: function(startIndex) {
			const initialRanges = this._initialGroupRanges;
			const totalGroups = this._totalGroupCount || 1;
			const total = this.dataSource.total();
			const avgGroupSize = total / totalGroups;
			if (initialRanges && initialRanges.length) {
				const lastRange = initialRanges[initialRanges.length - 1];
				const lastRangeEnd = lastRange.startIndex + lastRange.itemCount;
				if (startIndex < lastRangeEnd) {
					for (let i = 0; i < initialRanges.length; i++) {
						const range = initialRanges[i];
						const rangeEnd = range.startIndex + range.itemCount;
						if (startIndex < rangeEnd) {
							return i;
						}
					}
				}
			}
			return Math.floor(startIndex / avgGroupSize);
		},
		_buildPlaceholderPageData: function(pageStart, scrollTop) {
			const that = this;
			const itemHeight = that.options.itemHeight;
			const screenHeight = that._screenHeight || 400;
			const visibleItemCount = Math.ceil(screenHeight / itemHeight) + 2;
			const startIndex = Math.floor(scrollTop / itemHeight);
			const placeholderTop = startIndex * itemHeight;
			const items = [];
			for (let i = 0; i < visibleItemCount; i++) {
				const itemIndex = startIndex + i;
				items.push({
					item: null,
					group: "Loading...",
					newGroup: i === 0,
					selected: false,
					current: false,
					index: itemIndex,
					top: itemIndex * itemHeight,
					trueGlobalIndex: itemIndex,
					globalGroupIndex: -1,
					localIndex: i,
					viewIndex: 0,
					isPlaceholder: true
				});
			}
			const pageGroups = [{
				viewIndex: 0,
				groupValue: "Loading...",
				startIndex: 0,
				actualGlobalStartIndex: startIndex,
				globalGroupIndex: -1,
				itemCount: visibleItemCount,
				fullGroupItemCount: visibleItemCount,
				hasHeader: false,
				isPartialGroup: false,
				top: placeholderTop,
				height: visibleItemCount * itemHeight,
				itemsBeforeLoad: 0,
				isPlaceholder: true
			}];
			return {
				pageStart,
				pageGroups,
				items,
				isPlaceholder: true
			};
		},
		_buildAllClientGroupInfos: function(view, itemHeight, cssGap) {
			const that = this;
			const allGroupInfos = [];
			let globalItemIndex = 0;
			let cumulativeHeight = 0;
			for (let groupIdx = 0; groupIdx < view.length; groupIdx++) {
				const viewGroup = view[groupIdx];
				const groupValue = viewGroup.value;
				const groupItems = viewGroup.items || [];
				const groupItemCount = groupItems.length;
				if (groupItemCount === 0) {
					continue;
				}
				const hasHeader = groupIdx > 0 || that.options.fixedGroupHeader === false;
				const groupTop = cumulativeHeight;
				const elementCount = groupItemCount + (hasHeader ? 1 : 0);
				const gapsInGroup = Math.max(0, elementCount - 1) * cssGap;
				const visualHeight = elementCount * itemHeight + gapsInGroup;
				const groupBottom = groupTop + visualHeight;
				allGroupInfos.push({
					viewIndex: groupIdx,
					groupValue,
					startIndex: globalItemIndex,
					actualGlobalStartIndex: globalItemIndex,
					globalGroupIndex: groupIdx,
					itemCount: groupItemCount,
					fullGroupItemCount: groupItemCount,
					hasHeader,
					isPartialGroup: false,
					top: groupTop,
					bottom: groupBottom,
					height: visualHeight,
					itemsBeforeLoad: 0,
					groupItems
				});
				cumulativeHeight += visualHeight;
				globalItemIndex += groupItemCount;
			}
			return allGroupInfos;
		},
		_filterVisibleGroupsAndBuildItems: function(allGroupInfos, scrollTop, screenHeight, bufferSize) {
			const that = this;
			const visibleTop = Math.max(0, scrollTop - bufferSize);
			const visibleBottom = scrollTop + screenHeight + bufferSize;
			const pageGroups = [];
			const items = [];
			for (const groupInfo of allGroupInfos) {
				if (groupInfo.bottom >= visibleTop && groupInfo.top <= visibleBottom) {
					pageGroups.push(groupInfo);
					const groupItems = groupInfo.groupItems || [];
					for (let j = 0; j < groupInfo.itemCount; j++) {
						const dataItem = groupItems[j];
						if (!dataItem) {
							continue;
						}
						const itemIndex = groupInfo.startIndex + j;
						const mappedItem = that._itemMapper({
							item: dataItem,
							group: groupInfo.groupValue
						}, itemIndex, that._values.slice());
						mappedItem.trueGlobalIndex = itemIndex;
						mappedItem.globalGroupIndex = groupInfo.globalGroupIndex;
						mappedItem.localIndex = j;
						mappedItem.viewIndex = groupInfo.viewIndex;
						items.push(mappedItem);
					}
				}
				delete groupInfo.groupItems;
			}
			return {
				pageGroups,
				items
			};
		},
		_buildClientGroupedPageData: function(pageStart, scrollTop) {
			const that = this;
			const dataSource = that.dataSource;
			const itemHeight = that.options.itemHeight;
			const cssGap = that._cssGap || 0;
			const groupDescriptor = dataSource.group();
			const allData = dataSource.data();
			if (!allData || !allData.length || !groupDescriptor || !groupDescriptor.length) {
				return {
					pageStart,
					pageGroups: [],
					items: []
				};
			}
			const view = new kendo.data.Query(allData).group(groupDescriptor).data;
			if (!view || !view.length) {
				return {
					pageStart,
					pageGroups: [],
					items: []
				};
			}
			const screenHeight = that._screenHeight || 400;
			const bufferSize = screenHeight;
			const allGroupInfos = that._buildAllClientGroupInfos(view, itemHeight, cssGap);
			const { pageGroups, items } = that._filterVisibleGroupsAndBuildItems(allGroupInfos, scrollTop, screenHeight, bufferSize);
			that._updateFixedHeaderClientGrouped(scrollTop, pageGroups, itemHeight);
			return {
				pageStart,
				pageGroups,
				items
			};
		},
		_calculateSectionHeaderOffset: function(viewIdx, groupRanges, view, pageStart, itemHeight) {
			const that = this;
			let headerOffset = 0;
			for (let i = 0; i < viewIdx; i++) {
				const prevRange = groupRanges[i];
				const prevViewGroup = view[i];
				if (prevRange && prevViewGroup) {
					const prevFirstItemInPage = Math.max(prevRange.startIndex, pageStart);
					const prevItemsBeforeLoad = prevFirstItemInPage - prevRange.startIndex;
					const prevHasHeader = (i > 0 || that.options.fixedGroupHeader === false) && prevItemsBeforeLoad === 0;
					if (prevHasHeader) {
						headerOffset += itemHeight;
					}
				}
			}
			return headerOffset;
		},
		_buildServerGroupSection: function(viewIdx, viewGroup, groupRange, pageStart, pageEnd, itemHeight, headerOffset) {
			const that = this;
			const groupStartIndex = groupRange.startIndex;
			const groupEndIndex = groupRange.startIndex + groupRange.itemCount;
			if (groupEndIndex <= pageStart || groupStartIndex >= pageEnd) {
				return null;
			}
			const firstItemInPage = Math.max(groupStartIndex, pageStart);
			const lastItemInPage = Math.min(groupEndIndex, pageEnd);
			const itemsInPage = lastItemInPage - firstItemInPage;
			const itemOffset = firstItemInPage - groupStartIndex;
			const viewGroupItems = viewGroup.items || [];
			const groupItems = viewGroupItems.slice(itemOffset, itemOffset + itemsInPage);
			const groupItemCount = groupItems.length;
			if (groupItemCount === 0) {
				return null;
			}
			const globalStartIndex = firstItemInPage;
			const globalGroupIndex = groupRange.globalGroupIndex;
			const sectionTop = globalStartIndex * itemHeight;
			const itemsBeforeLoad = globalStartIndex - groupRange.startIndex;
			const isPartialGroup = itemsBeforeLoad > 0;
			const hasHeader = (viewIdx > 0 || that.options.fixedGroupHeader === false) && !isPartialGroup;
			const groupTop = sectionTop + headerOffset;
			const groupHeight = (hasHeader ? itemHeight : 0) + groupItemCount * itemHeight;
			const sectionItems = [];
			for (let j = 0; j < groupItemCount; j++) {
				const dataItem = groupItems[j];
				if (!dataItem) {
					continue;
				}
				const globalIndex = globalStartIndex + j;
				const mappedItem = that._itemMapper({
					item: dataItem,
					group: viewGroup.value
				}, globalIndex, that._values.slice());
				mappedItem.viewIndex = viewIdx;
				mappedItem.globalGroupIndex = globalGroupIndex;
				mappedItem.localIndex = j;
				sectionItems.push(mappedItem);
			}
			const groupInfo = {
				viewIndex: viewIdx,
				groupValue: viewGroup.value,
				startIndex: globalStartIndex,
				actualGlobalStartIndex: groupRange.startIndex,
				globalGroupIndex,
				sectionIndex: viewIdx,
				itemCount: groupItemCount,
				fullGroupItemCount: groupRange.itemCount,
				hasHeader,
				isPartialGroup,
				top: groupTop,
				height: groupHeight,
				itemsBeforeLoad
			};
			return {
				groupInfo,
				sectionItems
			};
		},
		_buildServerGroupedPageData: function(pageStart, scrollTop) {
			const that = this;
			const dataSource = that.dataSource;
			const view = dataSource.view();
			const groupRanges = that._groupRanges || [];
			const itemHeight = that.options.itemHeight;
			const pageSize = that.itemCount;
			const pageEnd = pageStart + pageSize;
			const pageGroups = [];
			const items = [];
			for (let viewIdx = 0; viewIdx < view.length; viewIdx++) {
				const viewGroup = view[viewIdx];
				const groupRange = groupRanges[viewIdx];
				if (!groupRange) {
					continue;
				}
				const headerOffset = that._calculateSectionHeaderOffset(viewIdx, groupRanges, view, pageStart, itemHeight);
				const result = that._buildServerGroupSection(viewIdx, viewGroup, groupRange, pageStart, pageEnd, itemHeight, headerOffset);
				if (result) {
					pageGroups.push(result.groupInfo);
					items.push(...result.sectionItems);
				}
			}
			that._updateFixedHeader(scrollTop, pageGroups);
			return {
				pageStart,
				pageGroups,
				items
			};
		},
		_buildGroupedPageData: function(pageStart, scrollTop) {
			const that = this;
			const isServerGrouping = that.dataSource.options.serverGrouping;
			if (!isServerGrouping) {
				return that._buildClientGroupedPageData(pageStart, scrollTop);
			}
			return that._buildServerGroupedPageData(pageStart, scrollTop);
		},
		_calculateGroupTopPosition: function(startIndex, globalGroupIndex, itemHeight) {
			const headersBefore = Math.max(0, globalGroupIndex - 1);
			return startIndex * itemHeight + headersBefore * itemHeight;
		},
		_updateFixedHeader: function(scrollTop, pageGroups) {
			const that = this;
			if (!pageGroups.length) {
				return;
			}
			let currentGroup = null;
			for (let i = pageGroups.length - 1; i >= 0; i--) {
				const group = pageGroups[i];
				if (scrollTop >= group.top) {
					currentGroup = group;
					break;
				}
			}
			if (!currentGroup) {
				currentGroup = pageGroups[0];
			}
			if (that.currentVisibleGroup !== currentGroup.groupValue) {
				that.header.html(that.templates.fixedGroupTemplate(currentGroup.groupValue));
				that.currentVisibleGroup = currentGroup.groupValue;
				that._currentVisibleGroupIndex = currentGroup.globalGroupIndex;
			}
		},
		_updateFixedHeaderClientGrouped: function(scrollTop, pageGroups, itemHeight) {
			const that = this;
			if (!pageGroups.length) {
				return;
			}
			let currentGroup = null;
			for (let i = pageGroups.length - 1; i >= 0; i--) {
				const group = pageGroups[i];
				if (scrollTop >= group.top) {
					currentGroup = group;
					break;
				}
			}
			if (!currentGroup) {
				currentGroup = pageGroups[0];
			}
			if (that.currentVisibleGroup !== currentGroup.groupValue) {
				that.header.html(that.templates.fixedGroupTemplate(currentGroup.groupValue));
				that.currentVisibleGroup = currentGroup.groupValue;
				that._currentVisibleGroupIndex = currentGroup.globalGroupIndex;
			}
		},
		_removeOrphanUls: function(content) {
			const that = this;
			const trackedUlSet = new Set(that._groupUls);
			const domUls = content.querySelectorAll("ul[data-group-index]");
			for (const domUl of domUls) {
				const groupIndex = parseInt(domUl.getAttribute("data-group-index"), 10);
				if (groupIndex === -1 || trackedUlSet.has(domUl)) {
					continue;
				}
				if (domUl.parentNode) {
					domUl.parentNode.removeChild(domUl);
				}
			}
		},
		_buildExistingUlMap: function() {
			const that = this;
			const existingMap = new Map();
			const cleanedGroupUls = [];
			for (const ul of that._groupUls) {
				const groupIndex = parseInt(ul.getAttribute("data-group-index"), 10);
				const sectionIndex = parseInt(ul.getAttribute("data-section-index") || "0", 10);
				const groupKey = groupIndex + "_" + sectionIndex;
				if (!isNaN(groupIndex)) {
					if (existingMap.has(groupKey)) {
						if (ul.parentNode) {
							ul.parentNode.removeChild(ul);
						}
					} else {
						existingMap.set(groupKey, ul);
						cleanedGroupUls.push(ul);
					}
				}
			}
			that._groupUls = cleanedGroupUls;
			return existingMap;
		},
		_processExistingUl: function(existingUl, groupInfo, listItems, force, keptUls, newUls, existingMap) {
			const that = this;
			const groupIndex = groupInfo.globalGroupIndex;
			const sectionIndex = groupInfo.sectionIndex !== undefined ? groupInfo.sectionIndex : 0;
			const groupKey = groupIndex + "_" + sectionIndex;
			const headerSelector = that._isTableVariant() ? ".k-table-group-row" : ".k-list-group-item";
			const existingHasHeader = !!existingUl.querySelector(headerSelector);
			const headerStatusChanged = existingHasHeader !== groupInfo.hasHeader;
			if (headerStatusChanged) {
				if (existingUl.parentNode) {
					existingUl.parentNode.removeChild(existingUl);
				}
				existingMap.delete(groupKey);
				const newUl = that._createGroupUlElement(groupInfo, listItems);
				if (sectionIndex > 0) {
					newUl.setAttribute("data-section-index", sectionIndex);
				}
				newUls.push({
					ul: newUl,
					groupIndex,
					sectionIndex,
					groupKey
				});
				return;
			}
			const cachedStartIndex = parseInt(existingUl.getAttribute("data-start-index"), 10);
			const cachedItemCount = existingUl.querySelectorAll("li[data-offset-index]").length;
			const startIndexChanged = cachedStartIndex !== groupInfo.startIndex;
			const itemCountChanged = cachedItemCount !== groupInfo.itemCount;
			if (startIndexChanged || itemCountChanged || force) {
				that._updateGroupUl(existingUl, groupInfo, listItems);
				if (startIndexChanged) {
					existingUl.setAttribute("data-start-index", groupInfo.startIndex);
				}
			}
			const currentTop = existingUl._cachedTop;
			if (currentTop !== groupInfo.top) {
				position(existingUl, groupInfo.top);
				existingUl._cachedTop = groupInfo.top;
			}
			keptUls.push(existingUl);
			existingMap.delete(groupKey);
			const items = existingUl.querySelectorAll("li[data-offset-index]");
			that._items.push(...Array.from(items));
		},
		_insertNewUlsInOrder: function(content, newUls) {
			for (const { ul: newUl, sectionIndex: newSectionIndex } of newUls) {
				let insertBefore = null;
				const existingUls = content.querySelectorAll("ul[data-group-index]");
				for (const existingUl of existingUls) {
					const existingSectionIndex = parseInt(existingUl.getAttribute("data-section-index") || "0", 10);
					if (existingSectionIndex > newSectionIndex) {
						insertBefore = existingUl;
						break;
					}
				}
				if (insertBefore) {
					content.insertBefore(newUl, insertBefore);
				} else {
					content.appendChild(newUl);
				}
			}
		},
		_renderGroupedPage: function(list, force, targetScrollTop) {
			const that = this;
			const content = that.content.get(0);
			const pageGroups = list.pageGroups || [];
			const pageStart = list.pageStart;
			const scrollTop = typeof targetScrollTop === "number" ? targetScrollTop : content.scrollTop;
			if (list.isPlaceholder) {
				that._renderPlaceholderPage(list, scrollTop);
				return;
			}
			if (that._placeholderUl && that._placeholderUl.parentNode) {
				that._placeholderUl.parentNode.removeChild(that._placeholderUl);
				that._placeholderUl = null;
			}
			that._lastRenderScrollTop = scrollTop;
			that._removeOrphanUls(content);
			const existingMap = that._buildExistingUlMap();
			const keptUls = [];
			const newUls = [];
			that._items = [];
			for (const groupInfo of pageGroups) {
				const groupIndex = groupInfo.globalGroupIndex;
				const sectionIndex = groupInfo.sectionIndex !== undefined ? groupInfo.sectionIndex : 0;
				const groupKey = groupIndex + "_" + sectionIndex;
				const existingUl = existingMap.get(groupKey);
				if (existingUl) {
					that._processExistingUl(existingUl, groupInfo, list.items, force, keptUls, newUls, existingMap);
				} else {
					const newUl = that._createGroupUlElement(groupInfo, list.items);
					if (sectionIndex > 0) {
						newUl.setAttribute("data-section-index", sectionIndex);
					}
					newUls.push({
						ul: newUl,
						groupIndex,
						sectionIndex,
						groupKey
					});
				}
			}
			for (const ul of existingMap.values()) {
				if (ul.parentNode) {
					ul.parentNode.removeChild(ul);
				}
			}
			that._insertNewUlsInOrder(content, newUls);
			that._groupUls = keptUls.concat(newUls.map((n) => n.ul));
			that._renderedPageStart = pageStart;
			content.scrollTop = scrollTop;
			that._dataView = list.items;
		},
		_calculateCoveredRange: function(visibleTop, visibleBottom, screenHeight) {
			const that = this;
			let coveredTop = Infinity;
			let coveredBottom = 0;
			for (const ul of that._groupUls) {
				if (!ul || !ul.parentNode) {
					continue;
				}
				const transform = ul.style.transform;
				const yMatch = transform.match(/translateY\((\d+)px\)/);
				const ulTop = yMatch ? parseFloat(yMatch[1]) : 0;
				const ulBottom = ulTop + ul.offsetHeight;
				if (ulBottom >= visibleTop - screenHeight && ulTop <= visibleBottom + screenHeight) {
					coveredTop = Math.min(coveredTop, ulTop);
					coveredBottom = Math.max(coveredBottom, ulBottom);
				}
			}
			return {
				coveredTop,
				coveredBottom
			};
		},
		_calculatePlaceholderPosition: function(visibleTop, visibleBottom, coveredTop, coveredBottom, screenHeight, totalContentHeight) {
			let placeholderTop = 0;
			let placeholderHeight = 0;
			if (coveredTop === Infinity) {
				placeholderTop = Math.max(0, visibleTop);
				placeholderHeight = screenHeight;
			} else if (visibleTop < coveredTop) {
				placeholderTop = Math.max(0, visibleTop);
				placeholderHeight = coveredTop - placeholderTop;
			} else if (visibleBottom > coveredBottom && coveredBottom < totalContentHeight) {
				placeholderTop = coveredBottom;
				placeholderHeight = Math.min(visibleBottom, totalContentHeight) - coveredBottom;
			}
			return {
				placeholderTop,
				placeholderHeight
			};
		},
		_ensurePlaceholderUl: function(content) {
			const that = this;
			if (!that._placeholderUl) {
				const ul = document.createElement("ul");
				const ulClass = that._isTableVariant() ? `${LIST_UL} ${TABLE}` : LIST_UL;
				ul.className = ulClass + " k-loading-placeholder";
				ul.style.position = "absolute";
				ul.style.width = "100%";
				ul.style.top = "0";
				ul.style.left = "0";
				ul.setAttribute("role", "group");
				ul.setAttribute("data-group-index", "-1");
				if (that.options.ariaLive) {
					ul.setAttribute("aria-live", that.options.ariaLive);
				}
				that._placeholderUl = ul;
				content.appendChild(ul);
			}
			return that._placeholderUl;
		},
		_updatePlaceholderItems: function(ul, placeholderItemCount) {
			const that = this;
			const templates = that.templates;
			let currentCount = ul.querySelectorAll("li.k-list-item").length;
			while (currentCount < placeholderItemCount) {
				const li = that._createItemLi();
				li.classList.add("k-loading-item");
				const textSpan = li.querySelector(".k-list-item-text");
				if (textSpan) {
					textSpan.innerHTML = templates.placeholderTemplate({});
				}
				ul.appendChild(li);
				currentCount++;
			}
			while (ul.children.length > placeholderItemCount) {
				ul.removeChild(ul.lastChild);
			}
		},
		_renderPlaceholderPage: function(list, scrollTop) {
			const that = this;
			const content = that.content.get(0);
			const itemHeight = that.options.itemHeight;
			const screenHeight = that._screenHeight || 400;
			const total = that.dataSource.total();
			const groupCount = that._totalGroupCount || 1;
			const totalContentHeight = getGroupedTotalHeight(total, itemHeight, groupCount, that._cssGap);
			const visibleTop = scrollTop;
			const visibleBottom = Math.min(scrollTop + screenHeight, totalContentHeight);
			const { coveredTop, coveredBottom } = that._calculateCoveredRange(visibleTop, visibleBottom, screenHeight);
			const { placeholderTop, placeholderHeight } = that._calculatePlaceholderPosition(visibleTop, visibleBottom, coveredTop, coveredBottom, screenHeight, totalContentHeight);
			if (placeholderHeight <= 0) {
				if (that._placeholderUl && that._placeholderUl.parentNode) {
					that._placeholderUl.style.display = "none";
				}
				return;
			}
			const placeholderItemCount = Math.ceil(placeholderHeight / itemHeight) + 1;
			const ul = that._ensurePlaceholderUl(content);
			ul.style.display = "";
			position(ul, placeholderTop);
			that._updatePlaceholderItems(ul, placeholderItemCount);
			that.header.html(that.templates.fixedGroupTemplate("Loading..."));
		},
		_createGroupUlElement: function(groupInfo, allItems) {
			const that = this;
			const templates = that.templates;
			let groupItems;
			if (groupInfo.sectionIndex !== undefined) {
				groupItems = allItems.filter((item) => item.viewIndex === groupInfo.sectionIndex);
			} else {
				groupItems = allItems.filter((item) => item.globalGroupIndex === groupInfo.globalGroupIndex);
			}
			const firstDataItem = groupItems.length > 0 ? groupItems[0].item : null;
			const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.globalGroupIndex, firstDataItem);
			if (groupContainer.header) {
				const headerText = groupContainer.header.querySelector(that._getHeaderTextSelector());
				if (headerText) {
					headerText.innerHTML = templates.groupTemplate(groupInfo.groupValue);
				}
			}
			that._addGroupItems(groupContainer, groupItems.length);
			for (let i = 0; i < groupItems.length; i++) {
				that._renderGroupItem(groupContainer.items[i], groupItems[i], templates);
			}
			position(groupContainer.ul, groupInfo.top);
			groupContainer.ul.setAttribute("data-group-index", groupInfo.globalGroupIndex);
			groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
			groupContainer.ul._cachedTop = groupInfo.top;
			that._items.push(...groupContainer.items);
			return groupContainer.ul;
		},
		_updateGroupUl: function(ul, groupInfo, allItems) {
			const that = this;
			const templates = that.templates;
			const globalGroupIndex = groupInfo.globalGroupIndex;
			let groupItems;
			if (groupInfo.sectionIndex !== undefined) {
				groupItems = allItems.filter((item) => item.viewIndex === groupInfo.sectionIndex);
			} else {
				groupItems = allItems.filter((item) => item.globalGroupIndex === globalGroupIndex);
			}
			const existingItems = Array.from(ul.querySelectorAll("li[data-offset-index]"));
			while (existingItems.length > groupItems.length) {
				const li = existingItems.pop();
				if (li && li.parentNode) {
					li.parentNode.removeChild(li);
				}
			}
			for (let i = 0; i < groupItems.length; i++) {
				const itemData = groupItems[i];
				let li = existingItems[i];
				if (!li) {
					li = that._createItemLi();
					ul.appendChild(li);
				}
				that._renderGroupItem(li, itemData, templates);
			}
		},
		_createItemLi: function() {
			const that = this;
			const itemHeight = that._getItemHeightStyle();
			const hasColumns = that._isTableVariant();
			const itemClass = that._getItemClass();
			const li = document.createElement("li");
			li.setAttribute("tabindex", "-1");
			li.setAttribute("role", "option");
			li.className = itemClass;
			li.style.height = itemHeight;
			li.style.minHeight = itemHeight;
			li.style.position = "relative";
			li.style.transform = "none";
			if (!hasColumns) {
				const textSpan = document.createElement("span");
				textSpan.className = "k-list-item-text";
				li.appendChild(textSpan);
			}
			return li;
		},
		_clearAllGroupUls: function() {
			const that = this;
			for (const ul of that._groupUls) {
				if (ul && ul.parentNode) {
					ul.parentNode.removeChild(ul);
				}
			}
			that._groupUls = [];
			that._items = [];
		},
		_createAndRenderGroupUl: function(groupInfo, allItems) {
			const that = this;
			const content = that.content.get(0);
			const templates = that.templates;
			const groupItems = allItems.filter((item) => item.globalGroupIndex === groupInfo.globalGroupIndex);
			const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.globalGroupIndex);
			if (groupContainer.header) {
				const headerText = groupContainer.header.querySelector(that._getHeaderTextSelector());
				if (headerText) {
					headerText.innerHTML = templates.groupTemplate(groupInfo.groupValue);
				}
			}
			that._addGroupItems(groupContainer, groupItems.length);
			for (let i = 0; i < groupItems.length; i++) {
				that._renderGroupItem(groupContainer.items[i], groupItems[i], templates);
			}
			position(groupContainer.ul, groupInfo.top);
			groupContainer.ul.setAttribute("data-group-index", groupInfo.globalGroupIndex);
			groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
			groupContainer.ul._cachedTop = groupInfo.top;
			content.appendChild(groupContainer.ul);
			that._groupUls.push(groupContainer.ul);
			that._items.push(...groupContainer.items);
		},
		_renderGroupUlPaged: function(groupInfo, allItems) {
			const that = this;
			const content = that.content.get(0);
			const templates = that.templates;
			const viewIndex = groupInfo.viewIndex;
			const groupItems = allItems.filter((item) => item.viewIndex === viewIndex);
			const cached = that._groupUlCache[viewIndex];
			if (!cached) {
				const groupContainer = that._createGroupUl(groupInfo.hasHeader, groupInfo.groupValue, groupInfo.viewIndex);
				if (groupContainer.header) {
					const headerText = groupContainer.header.querySelector(that._getHeaderTextSelector());
					if (headerText) {
						headerText.innerHTML = templates.groupTemplate(groupInfo.groupValue);
					}
				}
				that._addGroupItems(groupContainer, groupItems.length);
				for (let i = 0; i < groupItems.length; i++) {
					that._renderGroupItem(groupContainer.items[i], groupItems[i], templates);
				}
				position(groupContainer.ul, groupInfo.top);
				groupContainer.ul.setAttribute("data-view-index", viewIndex);
				groupContainer.ul.setAttribute("data-start-index", groupInfo.startIndex);
				groupContainer.startIndex = groupInfo.startIndex;
				groupContainer.itemCount = groupItems.length;
				groupContainer.top = groupInfo.top;
				groupContainer.viewIndex = viewIndex;
				content.appendChild(groupContainer.ul);
				that._groupUlCache[viewIndex] = groupContainer;
				that._items.push(...groupContainer.items);
			} else {
				const newTop = groupInfo.top;
				position(cached.ul, newTop);
				cached.top = newTop;
				for (let k = 0; k < groupItems.length; k++) {
					const li = cached.items[k];
					if (li) {
						const itemData = groupItems[k];
						that._renderGroupItem(li, itemData, templates);
						li.style.display = "";
					}
				}
			}
		},
		_reorderGroupUls: function(content, pageGroups) {
			const currentUls = Array.from(content.querySelectorAll("." + LIST_UL));
			let needsReorder = false;
			let lastIndex = -1;
			for (const ul of currentUls) {
				const idx = parseInt(ul.getAttribute("data-view-index"), 10);
				if (idx < lastIndex) {
					needsReorder = true;
					break;
				}
				lastIndex = idx;
			}
			if (needsReorder) {
				const sortedUls = currentUls.sort((a, b) => parseInt(a.getAttribute("data-view-index"), 10) - parseInt(b.getAttribute("data-view-index"), 10));
				for (const ul of sortedUls) {
					content.appendChild(ul);
				}
			}
		},
		_findFirstVisibleItem: function(items, scrollTop, pageGroups) {
			const itemHeight = this.options.itemHeight;
			if (pageGroups) {
				for (let i = 0; i < pageGroups.length; i++) {
					const group = pageGroups[i];
					const groupBottom = group.top + (group.hasHeader ? itemHeight : 0) + group.itemCount * itemHeight;
					if (scrollTop < groupBottom) {
						const groupItems = items.filter((item) => item.viewIndex === group.viewIndex);
						return groupItems[0] || items[0];
					}
				}
			}
			return items[0];
		},
		_renderGroupItem: function(element, data, templates) {
			const that = this;
			const options = that.options;
			const $element = $(element);
			const itemTemplate = data.item ? templates.template : templates.placeholderTemplate;
			const hasColumns = options.columns?.length;
			const dataItem = data.item;
			$element.attr("data-uid", dataItem ? dataItem.uid : "").attr("data-offset-index", data.index);
			if (hasColumns && dataItem) {
				const renderedColumns = $(renderColumns(options, dataItem, templates));
				kendo.applyStylesFromKendoAttributes(renderedColumns, ["width", "max-width"]);
				$element.empty().append(renderedColumns);
				const altRow = data.index % 2 === 1;
				$element.toggleClass("k-table-alt-row", altRow);
			} else if (hasColumns && !dataItem) {
				let placeholderHtml = "";
				for (let i = 0; i < options.columns.length; i++) {
					const currentWidth = options.columns[i].width;
					const currentWidthInt = parseInt(currentWidth, 10);
					let widthStyle = "";
					if (currentWidth) {
						const widthValue = currentWidthInt + "px";
						widthStyle = `${kendo.attr("style-width")}="${widthValue}" ${kendo.attr("style-max-width")}="${widthValue}"`;
					}
					placeholderHtml += `<span class='k-table-td' ${widthStyle}></span>`;
				}
				const renderedPlaceholder = $(placeholderHtml);
				kendo.applyStylesFromKendoAttributes(renderedPlaceholder, ["width", "max-width"]);
				$element.empty().append(renderedPlaceholder);
			} else {
				const textSpan = $element.find(".k-list-item-text");
				textSpan.html(itemTemplate(dataItem || {}));
			}
			$element.toggleClass(FOCUSED, data.current);
			$element.toggleClass(SELECTED, data.selected);
			$element.toggleClass("k-loading-item", !dataItem);
			element.style.display = "";
		},
		_groupedGetter: function(callback) {
			const that = this;
			const dataSource = that.dataSource;
			const pageSize = that.itemCount;
			let lastRequestedRange = null;
			let lastRangeStart = dataSource.skip();
			if (dataSource.pageSize() < pageSize) {
				that.mute(() => {
					dataSource.pageSize(pageSize);
				});
			}
			return function(index, rangeStart) {
				if (!dataSource.inRange(rangeStart, pageSize)) {
					if (lastRequestedRange !== rangeStart) {
						lastRequestedRange = rangeStart;
						lastRangeStart = rangeStart;
						if (that._getterDeferred) {
							that._getterDeferred.reject();
						}
						that._getterDeferred = that.deferredRange(rangeStart);
						that._getterDeferred.then(() => {
							that._getterDeferred = null;
							const firstGroupIdx = that._estimateFirstGroupIndex(rangeStart);
							that._groupRanges = buildGroupRanges(dataSource, rangeStart, firstGroupIdx, that._groupValueMap);
							that._updateGroupCountAndHeight();
							callback();
						});
					}
					return null;
				} else {
					if (lastRangeStart !== rangeStart) {
						that.mute(() => {
							dataSource.range(rangeStart, pageSize);
							lastRangeStart = rangeStart;
						});
						const firstGroupIdx = that._estimateFirstGroupIndex(rangeStart);
						that._groupRanges = buildGroupRanges(dataSource, rangeStart, firstGroupIdx, that._groupValueMap);
						that._updateGroupCountAndHeight();
					}
					const view = dataSource.view();
					const flatIndex = index - rangeStart;
					let currentIndex = 0;
					for (const group of view) {
						const groupItems = group.items || [];
						if (flatIndex < currentIndex + groupItems.length) {
							return {
								item: groupItems[flatIndex - currentIndex],
								group: group.value
							};
						}
						currentIndex += groupItems.length;
					}
					return null;
				}
			};
		},
		_setHeight: function(height) {
			let heightContainer = this.heightContainer;
			let currentHeight;
			if (!heightContainer) {
				heightContainer = this.heightContainer = appendChild(this.content[0], HEIGHTCONTAINER);
			} else {
				currentHeight = heightContainer.offsetHeight;
			}
			if (height !== currentHeight) {
				heightContainer.innerHTML = "";
				while (height > 0) {
					const padHeight = Math.min(height, 25e4);
					appendChild(heightContainer).style.height = padHeight + "px";
					height -= padHeight;
				}
			}
		},
		_getter: function() {
			const dataSource = this.dataSource;
			const type = this.options.type;
			const pageSize = this.itemCount;
			const flatGroups = {};
			let lastRequestedRange = null;
			let lastRangeStart = dataSource.skip();
			if (dataSource.pageSize() < pageSize) {
				this.mute(function() {
					dataSource.pageSize(pageSize);
				});
			}
			return function(index, rangeStart) {
				const that = this;
				if (!dataSource.inRange(rangeStart, pageSize)) {
					if (lastRequestedRange !== rangeStart) {
						lastRequestedRange = rangeStart;
						lastRangeStart = rangeStart;
						if (that._getterDeferred) {
							that._getterDeferred.reject();
						}
						that._getterDeferred = that.deferredRange(rangeStart);
						that._getterDeferred.then(() => {
							const firstItemIndex = that._indexConstraint(that.content[0].scrollTop);
							that._getterDeferred = null;
							if (rangeStart <= firstItemIndex && firstItemIndex <= rangeStart + pageSize) {
								that._fetching = true;
								dataSource.range(rangeStart, pageSize);
							}
						});
					}
					return null;
				} else {
					if (lastRangeStart !== rangeStart) {
						this.mute(() => {
							dataSource.range(rangeStart, pageSize);
							lastRangeStart = rangeStart;
						});
					}
					let result;
					if (type === "group") {
						if (!flatGroups[rangeStart]) {
							const flatGroup = flatGroups[rangeStart] = [];
							const groups = dataSource.view();
							for (const group of groups) {
								for (const item of group.items) {
									flatGroup.push({
										item,
										group: group.value
									});
								}
							}
						}
						result = flatGroups[rangeStart][index - rangeStart];
					} else {
						result = dataSource.view()[index - rangeStart];
					}
					return result;
				}
			};
		},
		_fixedHeader: function(scrollTop, list) {
			const group = this.currentVisibleGroup;
			const itemHeight = this.options.itemHeight;
			const listItems = list.items || [];
			const firstVisibleDataItemIndex = Math.floor((scrollTop - list.top) / itemHeight);
			const firstVisibleDataItem = listItems[firstVisibleDataItemIndex];
			if (firstVisibleDataItem?.item) {
				const firstVisibleGroup = firstVisibleDataItem.group;
				if (firstVisibleGroup !== group) {
					const fixedGroupText = firstVisibleGroup || "";
					this.header.html(this.templates.fixedGroupTemplate(fixedGroupText));
					this.currentVisibleGroup = firstVisibleGroup;
				}
			}
			return list;
		},
		_itemMapper: function(item, index, value) {
			const listType = this.options.type;
			const itemHeight = this.options.itemHeight;
			const cssGap = this._cssGap || 0;
			const currentIndex = this._focusedIndex;
			const valueGetter = this._valueGetter;
			let selected = false;
			let current = false;
			let newGroup = false;
			let group = null;
			if (listType === "group") {
				if (item) {
					newGroup = index === 0 || this._currentGroup !== false && this._currentGroup !== item.group;
					this._currentGroup = item.group;
				}
				group = item ? item.group : null;
				item = item ? item.item : null;
			}
			if (this.options.mapValueTo === "dataItem" && this._selectedDataItems.length && item) {
				for (let i = 0; i < this._selectedDataItems.length; i++) {
					if (valueGetter(this._selectedDataItems[i]) === valueGetter(item)) {
						selected = true;
						break;
					}
				}
			} else if (!this.isFiltered() && value.length && item) {
				for (let j = 0; j < value.length; j++) {
					const match = isPrimitive(item) ? value[j] === item : value[j] === valueGetter(item);
					if (match) {
						value.splice(j, 1);
						selected = true;
						break;
					}
				}
			}
			if (currentIndex === index) {
				current = true;
			}
			return {
				item: item ? item : null,
				group,
				newGroup,
				selected,
				current,
				index,
				top: index * (itemHeight + cssGap)
			};
		},
		_range: function(index) {
			const itemCount = this.itemCount;
			const value = this._values.slice();
			const items = [];
			this._view = {};
			this._currentGroup = false;
			for (let i = index, length = index + itemCount; i < length; i++) {
				const item = this._itemMapper(this.getter(i, index), i, value);
				if (items[items.length - 1]) {
					items[items.length - 1].isLastGroupedItem = item.newGroup;
				}
				items.push(item);
				this._view[item.index] = item;
			}
			this._dataView = items;
			return items;
		},
		_getDataItemsCollection: function(scrollTop, lastScrollTop) {
			const items = this._range(this._listIndex(scrollTop, lastScrollTop));
			return {
				index: items.length ? items[0].index : 0,
				top: items.length ? items[0].top : 0,
				items
			};
		},
		_listItems: function() {
			const screenHeight = this._screenHeight;
			const options = this.options;
			const theValidator = listValidator(options, screenHeight);
			return function(value, force) {
				let result = this.result;
				const lastScrollTop = this._lastScrollTop;
				if (force || !result || !theValidator(result, value, lastScrollTop)) {
					result = this._getDataItemsCollection(value, lastScrollTop);
				}
				this._lastScrollTop = value;
				this.result = result;
				return result;
			}.bind(this);
		},
		_whenChanged: function(getter, callback) {
			let current;
			return (force) => {
				const theNew = getter(force);
				if (theNew !== current) {
					current = theNew;
					callback(theNew, force);
				}
			};
		},
		_whenChangedGrouped: function(getter, callback) {
			let currentPageStart = null;
			let currentGroupKeys = null;
			return (force) => {
				const theNew = getter(force);
				if (!theNew) {
					return;
				}
				const newPageStart = theNew.pageStart;
				const newGroups = theNew.pageGroups || [];
				const newGroupKeys = newGroups.map((g) => `${g.globalGroupIndex}:${g.startIndex}:${g.itemCount}`).join(",");
				const pageChanged = currentPageStart !== newPageStart;
				const groupsChanged = currentGroupKeys !== newGroupKeys;
				if (force || pageChanged || groupsChanged) {
					currentPageStart = newPageStart;
					currentGroupKeys = newGroupKeys;
					callback(theNew, force);
				}
			};
		},
		_reorderList: function(list, reorder) {
			const that = this;
			const length = list.length;
			let currentOffset = -Infinity;
			reorder = map2(reorder, this.templates).bind(this);
			return function(list2, offset, force) {
				const diff = offset - currentOffset;
				let range, range2;
				if (force || Math.abs(diff) >= length) {
					range = list;
					range2 = list2;
				} else {
					range = reshift(list, diff);
					range2 = diff > 0 ? list2.slice(-diff) : list2.slice(0, -diff);
				}
				reorder(range, range2, that.bound());
				currentOffset = offset;
			};
		},
		_bufferSizes: function() {
			const options = this.options;
			return bufferSizes(this._screenHeight, options.listScreens, options.oppositeBuffer);
		},
		_indexConstraint: function(position) {
			const itemCount = this.itemCount;
			const itemHeight = this.options.itemHeight;
			const cssGap = this._cssGap || 0;
			const effectiveItemHeight = itemHeight + cssGap;
			const total = this.dataSource.total();
			return Math.min(Math.max(total - itemCount, 0), Math.max(0, Math.floor(position / effectiveItemHeight)));
		},
		_listIndex: function(scrollTop, lastScrollTop) {
			const buffers = this._bufferSizes();
			const position = scrollTop - (scrollTop > lastScrollTop ? buffers.down : buffers.up);
			return this._indexConstraint(position);
		},
		_selectable: function() {
			if (this.options.selectable) {
				this._selectProxy = this._clickHandler.bind(this);
				this.element.on(CLICK + VIRTUAL_LIST_NS, "." + this._getItemClass(), this._selectProxy);
			}
		},
		getElementIndex: function(element) {
			if (!(element instanceof jQuery)) {
				return undefined;
			}
			return parseInt(element.attr("data-offset-index"), 10);
		},
		_getIndecies: function(candidate) {
			let result = [];
			if (typeof candidate === "function") {
				const data = this.dataSource.flatView();
				for (let idx = 0; idx < data.length; idx++) {
					if (candidate(data[idx])) {
						result.push(idx);
						break;
					}
				}
			}
			if (typeof candidate === "number") {
				result.push(candidate);
			}
			const elementIndex = this.getElementIndex(candidate);
			if (!isNaN(elementIndex)) {
				result.push(elementIndex);
			}
			if (candidate instanceof Array) {
				result = candidate;
			}
			return result;
		},
		_deselect: function(indices) {
			const selectedIndexes = this._selectedIndexes;
			const selectedDataItems = this._selectedDataItems;
			const selectable = this.options.selectable;
			const valueGetter = this._valueGetter;
			const removed = [];
			let position = 0;
			let removedindexesCounter = 0;
			indices = indices.slice();
			if (selectable === true || !indices.length) {
				for (let idx = 0; idx < selectedIndexes.length; idx++) {
					if (selectedIndexes[idx] !== undefined) {
						this._getElementByIndex(selectedIndexes[idx]).removeClass(SELECTED);
					} else if (selectedDataItems[idx]) {
						this._getElementByDataItem(selectedDataItems[idx]).removeClass(SELECTED);
					}
					removed.push({
						index: selectedIndexes[idx],
						position: idx,
						dataItem: selectedDataItems[idx]
					});
				}
				this._values = [];
				this._selectedDataItems = [];
				this._selectedIndexes = [];
			} else if (selectable === "multiple") {
				let i = 0;
				while (i < indices.length) {
					let result = null;
					position = $.inArray(indices[i], selectedIndexes);
					const dataItem = this.dataItemByIndex(indices[i]);
					if (position === -1 && dataItem) {
						for (let j = 0; j < selectedDataItems.length; j++) {
							const match = isPrimitive(dataItem) ? selectedDataItems[j] === dataItem : valueGetter(selectedDataItems[j]) === valueGetter(dataItem);
							if (match) {
								const item = this._getElementByIndex(indices[i]);
								result = this._deselectSingleItem(item, j, indices[i], removedindexesCounter);
							}
						}
					} else {
						const selectedIndex = selectedIndexes[position];
						if (selectedIndex !== undefined) {
							const item = this._getElementByIndex(selectedIndex);
							result = this._deselectSingleItem(item, position, selectedIndex, removedindexesCounter);
						}
					}
					if (result) {
						indices.splice(i, 1);
						removed.push(result);
						removedindexesCounter++;
						i--;
					}
					i++;
				}
			}
			return {
				indices,
				removed
			};
		},
		_deselectSingleItem: function(item, position, selectedIndex, removedindexesCounter) {
			if (!item.hasClass(SELECTED)) {
				return;
			}
			item.removeClass(SELECTED);
			this._values.splice(position, 1);
			this._selectedIndexes.splice(position, 1);
			const dataItem = this._selectedDataItems.splice(position, 1)[0];
			return {
				index: selectedIndex,
				position: position + removedindexesCounter,
				dataItem
			};
		},
		_deselectCurrentValues: function(indices) {
			const items = this._items || [];
			const values = this._values;
			const removed = [];
			if (this.options.selectable !== "multiple" || !this.isFiltered()) {
				return [];
			}
			if (indices[0] === -1) {
				$(items).removeClass(SELECTED);
				const removedItems = $.map(this._selectedDataItems.slice(0), (dataItem, idx) => ({
					dataItem,
					position: idx
				}));
				this._selectedIndexes = [];
				this._selectedDataItems = [];
				this._values = [];
				return removedItems;
			}
			for (let idx = 0; idx < indices.length; idx++) {
				let position = -1;
				const index = indices[idx];
				let value;
				if (this.dataItemByIndex(index)) {
					value = this._valueGetter(this.dataItemByIndex(index));
				}
				for (let j = 0; j < values.length; j++) {
					if (value == values[j]) {
						position = j;
						break;
					}
				}
				if (position > -1) {
					removed.push(this.removeAt(position));
					$(items[index]).removeClass(SELECTED);
				}
			}
			return removed;
		},
		_getSkip: function(index, take) {
			const page = index < take ? 1 : Math.floor(index / take) + 1;
			return (page - 1) * take;
		},
		_select: function(indexes) {
			const that = this;
			const singleSelection = this.options.selectable !== "multiple";
			const dataSource = this.dataSource;
			const take = this.itemCount;
			const valueGetter = this._valueGetter;
			const added = [];
			if (singleSelection) {
				that._selectedIndexes = [];
				that._selectedDataItems = [];
				that._values = [];
			}
			const oldSkip = dataSource.skip();
			$.each(indexes, (_, index) => {
				const skip = that._getSkip(index, take);
				that.mute(() => {
					dataSource.range(skip, take);
					const dataItem = that._findDataItem(dataSource.view(), [index - skip]);
					that._selectedIndexes.push(index);
					that._selectedDataItems.push(dataItem);
					that._values.push(isPrimitive(dataItem) ? dataItem : valueGetter(dataItem));
					added.push({
						index,
						dataItem
					});
					that._getElementByIndex(index).addClass(SELECTED);
					dataSource.range(oldSkip, take);
				});
			});
			that._values = that._checkValuesOrder(that._values);
			return added;
		},
		_clickHandler: function(e) {
			const that = this;
			const item = $(e.currentTarget);
			const options = that.options;
			const actionField = options.actionField;
			if (!e.isDefaultPrevented() && item.attr("data-uid")) {
				if (actionField) {
					const dataItem = that.dataItemByIndex(that.getElementIndex(item));
					if (dataItem && dataItem[actionField]) {
						if (!that.trigger("action", {
							item,
							dataItem,
							action: dataItem[actionField]
						})) {
							return;
						}
					}
				}
				this.trigger(CLICK, { item });
			}
		},
		_buildValueGetter: function() {
			this._valueGetter = kendo.getter(this.options.dataValueField);
		},
		_calculateGroupPadding: function(height) {
			const firstItem = this.items().first();
			const groupHeader = this.header;
			let padding = 0;
			if (groupHeader[0] && groupHeader[0].style.display !== "none") {
				if (height !== "auto") {
					padding = kendo.support.scrollbar();
				}
				padding += parseFloat(firstItem.css("border-right-width"), 10) + parseFloat(firstItem.children(".k-group").css("right"), 10);
				groupHeader.css("padding-right", padding);
			}
		},
		_calculateColumnsHeaderPadding: function() {
			if (this._isTableVariant()) {
				const isRtl = kendo.support.isRtl(this.wrapper);
				const scrollbar = kendo.support.scrollbar();
				const columnsHeader = this.content.parent().parent().find(".k-table-header");
				const total = this.dataSource.total();
				columnsHeader.css(isRtl ? "padding-left" : "padding-right", total ? scrollbar : 0);
			}
		}
	});
	kendo.ui.VirtualList = VirtualList;
	kendo.ui.plugin(VirtualList);
})(window.kendo.jQuery);
var kendo_virtuallist_default = kendo;

//#endregion
export { kendo_virtuallist_default as n, __meta__ as t };