You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

921 lines
24 KiB

/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
const ZBX_STYLE_SORTABLE = 'sortable';
const ZBX_STYLE_SORTABLE_LIST = 'sortable-list';
const ZBX_STYLE_SORTABLE_ITEM = 'sortable-item';
const ZBX_STYLE_SORTABLE_DRAG_HANDLE = 'sortable-drag-handle';
const ZBX_STYLE_SORTABLE_DRAGGING = 'sortable-dragging';
const SORTABLE_EVENT_DRAG_START = 'sortable-drag-start';
const SORTABLE_EVENT_DRAG_END = 'sortable-drag-end';
const SORTABLE_EVENT_SORT = 'sortable-sort';
class CSortable extends CBaseComponent {
/**
* Create CSortable instance.
*
* @param {HTMLElement} target
*
* @returns {CSortable}
*/
constructor(target, {
is_vertical,
is_sorting_enabled = true,
drag_scroll_delay_short = 150,
drag_scroll_delay_long = 400,
wheel_step = 100,
show_grabbing_cursor = true,
do_activate = true
}) {
super(target);
this._is_vertical = is_vertical;
this._is_sorting_enabled = is_sorting_enabled;
this._drag_scroll_delay_short = drag_scroll_delay_short;
this._drag_scroll_delay_long = drag_scroll_delay_long;
this._wheel_step = wheel_step;
this._show_grabbing_cursor = show_grabbing_cursor;
this._init();
this._registerEvents();
if (do_activate) {
this.activate();
}
}
/**
* Activate the interactive functionality.
*
* @returns {CSortable}
*/
activate() {
if (this._is_activated) {
throw Error('Instance already activated.');
}
this._fixListPos();
this._activateEvents();
this._is_activated = true;
return this;
}
/**
* Deactivate the interactive functionality.
*
* @returns {CSortable}
*/
deactivate() {
if (!this._is_activated) {
throw Error('Instance already deactivated.');
}
this._cancelDragging();
this._deactivateEvents();
this._is_activated = false;
return this;
}
/**
* Enable or disable the sorting functionality.
*
* @param {boolean} enable
* @returns {CSortable}
*/
enableSorting(enable = true) {
if (this._is_sorting_enabled && !enable) {
this._cancelDragging();
}
this._is_sorting_enabled = enable;
return this;
}
/**
* Get list of items.
*
* @returns {HTMLCollection}
*/
getList() {
return this._list;
}
/**
* Is the list scrollable (not all items visible)?
*
* @returns {boolean}
*/
isScrollable() {
return !this._isPosEqual(this._getListPosMax(), 0);
}
/**
* Insert item to the list before the reference item or at the end.
*
* @param {HTMLLIElement} item
* @param {HTMLLIElement|null} reference_item
*
* @returns {CSortable}
*/
insertItemBefore(item, reference_item = null) {
item.classList.add(ZBX_STYLE_SORTABLE_ITEM);
item.tabIndex = 0;
this._cancelDragging();
this._list.insertBefore(item, reference_item);
return this;
}
/**
* Remove item from the list.
*
* @param {HTMLLIElement} item
*
* @returns {CSortable}
*/
removeItem(item) {
if (item.parentNode !== this._list) {
throw RangeError('Item does not belong to the list.');
}
this._cancelDragging();
this._list.removeChild(item);
return this;
}
/**
* Scroll item into view.
*
* @param {HTMLLIElement} item
*
* @returns {CSortable}
*/
scrollItemIntoView(item) {
if (item.parentNode !== this._list) {
throw RangeError('Item does not belong to the list.');
}
const list_loc = this._getRectLoc(this._list.getBoundingClientRect());
const item_loc = this._getRectLoc(item.getBoundingClientRect());
this._scrollIntoView(item_loc.pos - list_loc.pos, item_loc.dim);
return this;
}
/**
* Initialize the instance.
*/
_init() {
this._target.classList.add(ZBX_STYLE_SORTABLE);
this._list = this._target.querySelector(`.${ZBX_STYLE_SORTABLE_LIST}`);
if (this._list === null) {
this._list = document.createElement('ul');
this._target.appendChild(this._list);
}
this._list.classList.add(ZBX_STYLE_SORTABLE_LIST);
this._list_pos = -parseFloat(getComputedStyle(this._list).getPropertyValue(
this._is_vertical ? 'top' : 'left'
));
this._drag_item = null;
this._drag_scroll_timeout = null;
this._is_activated = false;
}
/**
* Start dragging the item.
*
* @param {HTMLLIElement} drag_item Dragged item.
* @param {number} pos Starting axis position.
*/
_startDragging(drag_item, pos) {
this._drag_item_index_original = [...this._list.children].indexOf(drag_item);
this._drag_item_index = this._drag_item_index_original;
const target_rect = this._target.getBoundingClientRect();
const target_loc = this._getRectLoc(target_rect);
const list_rect = this._list.getBoundingClientRect();
const list_loc = this._getRectLoc(list_rect);
const drag_item_rect = drag_item.getBoundingClientRect();
const drag_item_loc = this._getRectLoc(drag_item_rect);
this._drag_item_loc = {
pos: drag_item_loc.pos - target_loc.pos,
dim: drag_item_loc.dim
};
this._drag_item_event_delta_pos = this._drag_item_loc.pos - pos;
this._item_loc = [];
for (const item of this._list.children) {
if (item === drag_item) {
continue;
}
const item_rect = item.getBoundingClientRect();
const item_loc = this._getRectLoc(item_rect);
this._item_loc.push({
pos: item_loc.pos - list_loc.pos,
dim: item_loc.dim
});
item.style.left = `${item_rect.x - list_rect.x}px`;
item.style.top = `${item_rect.y - list_rect.y}px`;
item.style.width = `${item_rect.width}px`;
item.style.height = `${item_rect.height}px`;
}
this._target.classList.add(ZBX_STYLE_SORTABLE_DRAGGING);
this._list.style.width = `${list_rect.width}px`;
this._list.style.height = `${list_rect.height}px`;
// Clone the dragging item not to disturb the original order while dragging.
this._drag_item = drag_item;
this._drag_item.style.left = `${drag_item_rect.x - target_rect.x}px`;
this._drag_item.style.top = `${drag_item_rect.y - target_rect.y}px`;
this._drag_item.style.width = `${drag_item_rect.width}px`;
this._drag_item.style.height = `${drag_item_rect.height}px`;
this._target.appendChild(this._drag_item);
// Hide the actual dragging item.
drag_item.classList.add(ZBX_STYLE_SORTABLE_DRAGGING);
// Set mouse cursor to "grabbing".
if (this._show_grabbing_cursor) {
this._dragging_style = document.createElement('style');
document.head.appendChild(this._dragging_style);
this._dragging_style.sheet.insertRule('* { cursor: grabbing !important; }');
}
this.fire(SORTABLE_EVENT_DRAG_START, {item: drag_item});
}
/**
* Drag the currently dragged item to a new position.
*
* @param {number} pos New axis position.
*/
_drag(pos) {
const items = this._getNonDraggingItems();
const target_rect = this._target.getBoundingClientRect();
const target_loc = this._getRectLoc(target_rect);
const drag_item_rect = this._drag_item.getBoundingClientRect();
const drag_item_loc = this._getRectLoc(drag_item_rect);
const drag_item_max_pos = target_loc.dim - drag_item_loc.dim;
this._drag_item_loc.pos = Math.max(0, Math.min(drag_item_max_pos, pos + this._drag_item_event_delta_pos));
this._drag_item.style[this._is_vertical ? 'top' : 'left'] = `${this._drag_item_loc.pos}px`;
const center_pos = this._list_pos + this._drag_item_loc.pos + this._drag_item_loc.dim / 2;
for (let index = this._drag_item_index - 1; index >= 0; index--) {
if (center_pos >= this._item_loc[index].pos + (this._item_loc[index].dim + this._drag_item_loc.dim) / 2) {
break;
}
this._drag_item_index--;
this._item_loc[index].pos += this._drag_item_loc.dim;
items[index].style[this._is_vertical ? 'top' : 'left'] = `${this._item_loc[index].pos}px`;
}
for (let index = this._drag_item_index; index < items.length; index++) {
if (center_pos <= this._item_loc[index].pos + (this._item_loc[index].dim - this._drag_item_loc.dim) / 2) {
break;
}
this._drag_item_index++;
this._item_loc[index].pos -= this._drag_item_loc.dim;
items[index].style[this._is_vertical ? 'top' : 'left'] = `${this._item_loc[index].pos}px`;
}
if (this._drag_item_loc.pos === 0) {
this._startDragScrolling(-1);
}
else if (this._drag_item_loc.pos === drag_item_max_pos) {
this._startDragScrolling(1);
}
else {
this._endDragScrolling();
}
}
/**
* End dragging the item.
*/
_endDragging() {
this._endDragScrolling();
const drag_item_pos = (this._drag_item_index > 0)
? this._item_loc[this._drag_item_index - 1].pos + this._item_loc[this._drag_item_index - 1].dim
: 0;
this._scrollIntoView(drag_item_pos, this._drag_item_loc.dim);
}
/**
* End dragging the item after the positional transitions have ended.
*/
_endDraggingAfterTransitions() {
const items = this._getNonDraggingItems();
const drag_item = this._drag_item;
this._list.insertBefore(drag_item, this._drag_item_index < items.length ? items[this._drag_item_index] : null);
drag_item.classList.remove(ZBX_STYLE_SORTABLE_DRAGGING);
drag_item.style.left = '';
drag_item.style.top = '';
drag_item.style.width = '';
drag_item.style.height = '';
this._target.classList.remove(ZBX_STYLE_SORTABLE_DRAGGING);
this._list.style.width = '';
this._list.style.height = '';
for (const item of items) {
item.style.left = '';
item.style.top = '';
item.style.width = '';
item.style.height = '';
}
// Re-focus the dragged item.
drag_item.focus();
this._drag_item = null;
// Reset mouse cursor.
if (this._show_grabbing_cursor) {
this._dragging_style.remove();
}
this.fire(SORTABLE_EVENT_DRAG_END, {item: drag_item});
if (this._drag_item_index !== this._drag_item_index_original) {
this.fire(SORTABLE_EVENT_SORT);
}
}
/**
* Start list scrolling iteratively when the item is dragged to the beginning or to the end of the list.
*
* @param {number} direction Either 1 or -1 for scrolling forward or backward respectively.
*/
_startDragScrolling(direction) {
if (this._drag_scroll_timeout === null) {
this._drag_scroll_tick = 0;
this._drag_scroll_timeout = setTimeout(() => {
this._dragScroll(direction);
}, this._getDragScrollDelay(0));
}
}
/**
* Scroll the list by one item when the item is dragged to the beginning or to the end of the list.
*
* @param {number} direction Either 1 or -1 for scrolling forward or backward respectively.
*/
_dragScroll(direction) {
const items = this._getNonDraggingItems();
const prev_item_pos = (this._drag_item_index > 0) ? this._item_loc[this._drag_item_index - 1].pos : 0;
const drag_item_pos = (this._drag_item_index > 0)
? prev_item_pos + this._item_loc[this._drag_item_index - 1].dim
: 0;
if (direction === -1) {
if (this._drag_item_index > 0) {
this._drag_item_index--;
this._setListPos(prev_item_pos);
this._item_loc[this._drag_item_index].pos += this._drag_item_loc.dim;
items[this._drag_item_index].style[this._is_vertical ? 'top' : 'left'] =
`${this._item_loc[this._drag_item_index].pos}px`;
}
else {
this._scrollIntoView(drag_item_pos, this._drag_item_loc.dim);
}
}
else {
const next_item_pos = (this._drag_item_index < items.length)
? this._item_loc[this._drag_item_index].pos
: drag_item_pos + this._drag_item_loc.dim;
const next_next_item_pos = (this._drag_item_index < items.length - 1)
? this._item_loc[this._drag_item_index + 1].pos
: next_item_pos + (
(this._drag_item_index < items.length) ? this._item_loc[this._drag_item_index].dim : 0
);
if (this._drag_item_index < items.length) {
const list_loc = this._getRectLoc(this._list.getBoundingClientRect());
this._setListPos(next_next_item_pos - list_loc.dim);
this._item_loc[this._drag_item_index].pos -= this._drag_item_loc.dim;
items[this._drag_item_index].style[this._is_vertical ? 'top' : 'left'] =
`${this._item_loc[this._drag_item_index].pos}px`;
this._drag_item_index++;
}
else {
this._scrollIntoView(drag_item_pos, this._drag_item_loc.dim);
}
}
this._drag_scroll_timeout = setTimeout(() => this._dragScroll(direction),
this._getDragScrollDelay(++this._drag_scroll_tick)
);
}
/**
* End list scrolling iteratively when the item is dragged to the beginning or to the end of the list.
*/
_endDragScrolling() {
if (this._drag_scroll_timeout !== null) {
clearTimeout(this._drag_scroll_timeout);
this._drag_scroll_timeout = null;
}
}
/**
* Get the delay for a sequent list scrolling when the item is dragged to the beginning or to the end of the list.
*
* @param {number} iteration Zero-based list scrolling iteration.
*
* @returns {number}
*/
_getDragScrollDelay(iteration) {
return (iteration === 0 || iteration > 2) ? this._drag_scroll_delay_short : this._drag_scroll_delay_long;
}
/**
* Cancel item dragging and return the item to its original position.
*/
_cancelDragging() {
if (this._drag_item !== null) {
// Simulate dropping the item at its original position.
this._drag_item_index = this._drag_item_index_original;
this.fire('_dragcancel');
}
}
/**
* Scroll the list by mouse wheel in the given direction.
*
* @param {number} direction Either 1 or -1 for scrolling forward or backward respectively.
* @param {number} pos Mouse axis position.
*/
_wheel(direction, pos) {
// Prevent using wheel while scrolling by dragging.
if (this._drag_scroll_timeout !== null) {
return;
}
this._setListPos(Math.max(0, Math.min(this._getListPosMax(), this._list_pos + this._wheel_step * direction)));
if (this._drag_item !== null) {
this._drag(pos);
}
}
/**
* Scroll the list as little as possible to fully contain the object with the given position and dimension.
*
* @param {number} pos Object position in decimal pixels.
* @param {number} dim Object dimension in decimal pixels.
*/
_scrollIntoView(pos, dim) {
if (pos < this._list_pos) {
this._setListPos(pos);
}
else {
const list_loc = this._getRectLoc(this._list.getBoundingClientRect());
if (pos + dim > this._list_pos + list_loc.dim) {
this._setListPos(pos + dim - list_loc.dim);
}
}
}
/**
* Scroll the list to the given position.
*
* @param {number} pos Position in decimal pixels.
*/
_setListPos(pos) {
if (this._isPosEqual(pos, this._list_pos)) {
return;
}
this._list_pos = pos;
this._list.style[this._is_vertical ? 'top' : 'left'] = `-${pos}px`;
}
/**
* Fix the list scroll position (on list resize).
*/
_fixListPos() {
const list_pos_max = this._getListPosMax();
if (this._list_pos > list_pos_max) {
this._setListPos(list_pos_max);
}
}
/**
* Get the maximum scroll position of the list.
*
* @returns {number} Position in decimal pixels.
*/
_getListPosMax() {
const items = this._getNonDraggingItems();
const list_loc = this._getRectLoc(this._list.getBoundingClientRect());
if (this._drag_item === null) {
if (items.length === 0) {
return 0;
}
const last_item_loc = this._getRectLoc(items[items.length - 1].getBoundingClientRect());
return Math.max(0, last_item_loc.pos + last_item_loc.dim - list_loc.pos - list_loc.dim);
}
else {
if (items.length === 0) {
return Math.max(0, this._drag_item_loc.dim - list_loc.dim);
}
const scroll_dim = (this._drag_item_index < items.length)
? this._item_loc[items.length - 1].pos + this._item_loc[items.length - 1].dim
: this._item_loc[items.length - 1].pos + this._item_loc[items.length - 1].dim + this._drag_item_loc.dim;
return Math.max(0, scroll_dim - list_loc.dim);
}
}
/**
* Get all list items except the one being dragged.
*
* @returns {HTMLElement[]}
*/
_getNonDraggingItems() {
return [...this._list.children].filter((item) => !item.classList.contains(ZBX_STYLE_SORTABLE_DRAGGING));
}
/**
* Get the position and dimension of the DOMRect, based on the current instance orientation.
*
* @param {DOMRect} rect
*
* @returns {Object}
*/
_getRectLoc(rect) {
return (this._is_vertical
? {pos: rect.top, dim: rect.height}
: {pos: rect.left, dim: rect.width}
);
}
/**
* Check if decimal positions are equal by dismissing floating-point calculation errors.
*
* @param {number} pos_1 Decimal position.
* @param {number} pos_2 Decimal position.
*
* @returns {boolean}
*/
_isPosEqual(pos_1, pos_2) {
return (Math.abs(pos_1 - pos_2) < 0.001);
}
/**
* Register all DOM events.
*/
_registerEvents() {
let prevent_clicks;
let mouse_down_item;
let mouse_down_pos;
let mouse_move_request;
let mouse_move_pos;
let wheel_request;
let wheel_direction;
let wheel_pos;
let end_dragging_after_transitions;
let transitions_set;
let list_resize_observer;
this._events = {
targetClick: (e) => {
if (prevent_clicks) {
e.preventDefault();
e.stopImmediatePropagation();
}
},
targetScroll: () => {
// Prevent browsers from automatically scrolling focusable elements into view.
this._target[this._is_vertical ? 'scrollTop' : 'scrollLeft'] = 0;
},
wheel: (e) => {
if (mouse_down_item !== null) {
this._startDragging(mouse_down_item, mouse_down_pos);
mouse_down_item = null;
// Prevent clicks after dragging has ended.
prevent_clicks = true;
}
if (this._drag_item !== null) {
e.preventDefault();
}
wheel_direction = (e.deltaY !== 0) ? Math.sign(e.deltaY) : Math.sign(e.deltaX);
wheel_pos = this._is_vertical ? e.clientY : e.clientX;
if (wheel_request === null) {
wheel_request = requestAnimationFrame(() => {
this._wheel(wheel_direction, wheel_pos);
wheel_request = null;
});
}
},
listMouseDown: (e) => {
if (e.button !== 0) {
return;
}
if (!this._is_sorting_enabled) {
return;
}
// Prevent clicks while transitions are running.
if (transitions_set.size > 0) {
return;
}
mouse_down_item = e.target.closest(`.${ZBX_STYLE_SORTABLE_ITEM}`);
// Interested in items and not the list itself.
if (mouse_down_item === null) {
return;
}
// Scroll item into view if it is partially visible.
this.scrollItemIntoView(mouse_down_item);
// Drag handle specified, but clicked elsewhere?
if (mouse_down_item.getElementsByClassName(ZBX_STYLE_SORTABLE_DRAG_HANDLE).length > 0
&& e.target.closest(`.${ZBX_STYLE_SORTABLE_DRAG_HANDLE}`) === null) {
mouse_down_item = null;
return;
}
// Prevent content selection while dragging the item.
e.preventDefault();
// Re-focus the item.
mouse_down_item.focus();
// Save initial mouse position.
mouse_down_pos = this._is_vertical ? e.clientY : e.clientX;
this.off('wheel', this._events.wheel);
window.addEventListener('mousemove', this._events.windowMouseMove);
window.addEventListener('mouseup', this._events.windowMouseUp);
window.addEventListener('wheel', this._events.wheel, {passive: false});
},
windowMouseMove: (e) => {
if (mouse_down_item !== null) {
this._startDragging(mouse_down_item, mouse_down_pos);
mouse_down_item = null;
// Prevent clicks after dragging has ended.
prevent_clicks = true;
}
mouse_move_pos = this._is_vertical ? e.clientY : e.clientX;
if (mouse_move_request === null) {
mouse_move_request = requestAnimationFrame(() => {
this._drag(mouse_move_pos);
mouse_move_request = null;
});
}
},
windowMouseUp: () => {
// Was dragging in progress?
if (mouse_down_item === null) {
const prev_list_pos = this._list_pos;
// Will occasionally update this._list_pos and start the transition later.
this._endDragging();
end_dragging_after_transitions = (transitions_set.size > 0
|| !this._isPosEqual(this._list_pos, prev_list_pos));
if (!end_dragging_after_transitions) {
this._endDraggingAfterTransitions();
}
}
else {
mouse_down_item = null;
}
prevent_clicks = false;
if (mouse_move_request !== null) {
cancelAnimationFrame(mouse_move_request);
mouse_move_request = null;
}
window.removeEventListener('mousemove', this._events.windowMouseMove);
window.removeEventListener('mouseup', this._events.windowMouseUp);
window.removeEventListener('wheel', this._events.wheel);
this.on('wheel', this._events.wheel, {passive: false});
},
listKeyDown: (e) => {
if (!this._is_sorting_enabled) {
return;
}
if (e.target.parentNode !== this._list) {
return;
}
if ((e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') || !e.ctrlKey) {
return;
}
if (e.key === 'ArrowLeft' && e.target.previousElementSibling === null
|| e.key === 'ArrowRight' && e.target.nextElementSibling === null) {
return;
}
this.insertItemBefore(e.target, e.key === 'ArrowLeft'
? e.target.previousElementSibling
: e.target.nextElementSibling.nextElementSibling
);
e.preventDefault();
// Re-focus the moved item.
e.target.focus();
this.fire(SORTABLE_EVENT_SORT);
},
listFocusIn: (e) => {
const item = e.target.closest(`.${ZBX_STYLE_SORTABLE_ITEM}`);
if (item) {
this.scrollItemIntoView(item);
}
},
listRunTransition: (e) => {
if (e.propertyName === (this._is_vertical ? 'top' : 'left')) {
transitions_set.add(e.target);
}
},
listEndTransition: (e) => {
transitions_set.delete(e.target);
// Delete outdated targets.
for (const target of transitions_set) {
if (target === this._list) {
continue;
}
const item = target.closest(`.${ZBX_STYLE_SORTABLE_ITEM}`);
if (item === null || item.parentNode !== this._list) {
transitions_set.delete(target);
}
}
if (end_dragging_after_transitions && transitions_set.size === 0) {
this._endDraggingAfterTransitions();
end_dragging_after_transitions = false;
}
},
listResize: () => {
this._fixListPos();
},
_cancelDragging: () => {
// Actually dragging an item?
if (prevent_clicks || mouse_down_item !== null) {
this._events.windowMouseUp();
}
},
};
this._activateEvents = () => {
prevent_clicks = false;
mouse_down_item = null;
mouse_move_request = null;
wheel_request = null;
end_dragging_after_transitions = false;
transitions_set = new Set();
this.on('click', this._events.targetClick);
this.on('scroll', this._events.targetScroll);
this.on('wheel', this._events.wheel, {passive: false});
this.on('_dragcancel', this._events._cancelDragging);
this._list.addEventListener('mousedown', this._events.listMouseDown);
this._list.addEventListener('keydown', this._events.listKeyDown);
this._list.addEventListener('focusin', this._events.listFocusIn);
this._list.addEventListener('transitionrun', this._events.listRunTransition);
this._list.addEventListener('transitionend', this._events.listEndTransition);
list_resize_observer = new ResizeObserver(this._events.listResize);
list_resize_observer.observe(this._list);
};
this._deactivateEvents = () => {
if (wheel_request !== null) {
cancelAnimationFrame(wheel_request);
}
if (end_dragging_after_transitions) {
this._endDraggingAfterTransitions();
}
this.off('click', this._events.targetClick);
this.off('scroll', this._events.targetScroll);
this.off('wheel', this._events.wheel);
this.off('_dragcancel', this._events._cancelDragging);
this._list.removeEventListener('mousedown', this._events.listMouseDown);
this._list.removeEventListener('keydown', this._events.listKeyDown);
this._list.removeEventListener('focusin', this._events.listFocusIn);
this._list.removeEventListener('transitionrun', this._events.listRunTransition);
this._list.removeEventListener('transitionend', this._events.listEndTransition);
// Added by mousedown event handler.
window.removeEventListener('mousemove', this._events.windowMouseMove);
window.removeEventListener('mouseup', this._events.windowMouseUp);
window.removeEventListener('wheel', this._events.wheel);
list_resize_observer.disconnect();
};
}
}