import BaseFoundation, { DefaultAdapter } from '../base/foundation';
import isElement from '../utils/isElement';
import { slice, find, findIndex } from 'lodash';
import { append, prepend } from '../utils/dom';
export interface Item {
[x: string]: any;
transform?: (value: any, text: string) => string;
value: any;
text?: string;
disabled?: boolean;
}
export interface ScrollItemAdapter
, S = Record, I = Item> extends DefaultAdapter {
setPrependCount: (prependCount: number) => void;
setAppendCount: (appendCount: number) => void;
setSelectedNode: (el: HTMLElement) => void;
isDisabledIndex: (i: number) => boolean;
notifySelectItem: (data: I) => void;
scrollToCenter: (selectedNode: Element, scrollWrapper?: Element, duration?: number) => void;
}
export default class ItemFoundation
, S = Record, I = Item> extends BaseFoundation, P, S> {
_cachedSelectedNode: HTMLElement = null;
selectIndex(index: number, listWrapper: HTMLElement) {
const { type, list } = this.getProps();
if (index > -1 && Array.isArray(list) && list.length && isElement(listWrapper)) {
const indexInData = index % list.length;
const item = list[indexInData];
const node = listWrapper.children[index] as HTMLElement;
this._adapter.setSelectedNode(node);
this._adapter.notifySelectItem({
...item,
value: item.value,
type,
index: indexInData,
});
}
}
selectNode(node: HTMLElement, listWrapper: HTMLElement) {
const { type, list: data } = this.getProps();
if (isElement(node) && isElement(listWrapper)) {
const indexInList = findIndex(listWrapper.children, ele => ele === node);
const indexInData = indexInList % data.length;
const cachedIndexInList = findIndex(listWrapper.children, ele => ele === this._cachedSelectedNode);
const cachedIndexData = cachedIndexInList % data.length;
const item = data[indexInData];
this._adapter.setSelectedNode(node);
this._adapter.scrollToCenter(node);
// Avoid triggerring notifySelectItem twice,
// because that scroll event will be trigger
// when you click to select an item.
if (this._cachedSelectedNode !== node) {
this._cachedSelectedNode = node;
if (cachedIndexData !== indexInData) {
this._adapter.notifySelectItem({
...item,
value: item.value,
type,
index: indexInData,
});
}
}
}
}
/**
*
* @param {HTMLElement} listWrapper
* @param {HTMLElement} scrollWrapper
* @param {number} ratio
* @returns {boolean}
*/
shouldAppend(listWrapper: HTMLElement, scrollWrapper: HTMLElement, ratio = 2) {
const tag = 'li';
if (isElement(listWrapper) && isElement(scrollWrapper)) {
const itemNodes = listWrapper.querySelectorAll(tag);
const lastNode = itemNodes[itemNodes.length - 1];
const { list } = this.getProps();
if (lastNode) {
const scrollRect = scrollWrapper.getBoundingClientRect();
const lastRect = lastNode.getBoundingClientRect();
const listHeight = lastRect.height * list.length;
let baseTop = lastRect.top;
let count = 0;
while (baseTop <= scrollRect.top + scrollRect.height * ratio) {
count += 1;
baseTop += listHeight;
}
return count;
}
}
return false;
}
/**
*
* @param {HTMLElement} listWrapper
* @param {HTMLElement} scrollWrapper
* @param {number} ratio
*
* @returns {boolean}
*/
shouldPrepend(listWrapper: HTMLElement, scrollWrapper: HTMLElement, ratio = 2) {
const tag = 'li';
if (isElement(listWrapper) && isElement(scrollWrapper)) {
const itemNodes = listWrapper.querySelectorAll(tag);
const firstNode = itemNodes[0];
const { list } = this.getProps();
if (firstNode) {
const scrollRect = scrollWrapper.getBoundingClientRect();
const firstRect = firstNode.getBoundingClientRect();
const listHeight = firstRect.height * list.length;
let baseTop = firstRect.top;
let count = 0;
while (baseTop + firstRect.height >= scrollRect.top - scrollRect.height * ratio) {
count += 1;
baseTop -= listHeight;
}
return count;
}
}
return 0;
}
/**
*
* @param {HTMLElement} listWrapper
* @param {HTMLElement} wrapper
* @param {Function} [callback]
*/
initWheelList(listWrapper: HTMLElement, wrapper: HTMLElement, callback: () => void) {
const { list } = this.getProps();
if (isElement(wrapper) && isElement(listWrapper) && list && list.length) {
const allNodes = listWrapper.children;
const baseNodes = slice(allNodes, 0, list.length);
const prependCount = this.shouldPrepend(listWrapper, wrapper);
const appendCount = this.shouldAppend(listWrapper, wrapper);
// this._adapter.setPrependCount(prependCount);
// this._adapter.setAppendCount(appendCount);
this._adapter.setState(
{
prependCount,
appendCount,
} as any,
callback
);
}
}
/**
*
* @param {HTMLElement} listWrapper
* @param {HTMLElement} wrapper
* @param {HTMLElement} [nearestNode]
*/
adjustInfiniteList(listWrapper: HTMLElement, wrapper: HTMLElement, nearestNode: HTMLElement) {
const { list } = this.getProps();
const nodeTag = 'li';
if (isElement(wrapper) && isElement(listWrapper) && list && list.length) {
const allNodes = listWrapper.querySelectorAll(nodeTag);
const total = allNodes.length;
const ratio = 1;
const prependCount = this.shouldPrepend(listWrapper, wrapper, ratio);
const appendCount = this.shouldAppend(listWrapper, wrapper, ratio);
// while (this.shouldPrepend(listWrapper, wrapper, nearestNode)) {
if (prependCount) {
// move last nodes to first position
for (let i = 0; i < prependCount; i++) {
const nodes = slice(allNodes, total - list.length * (i + 1), total - list.length * i);
prepend(listWrapper, ...nodes);
}
}
// while (this.shouldAppend(listWrapper, wrapper, nearestNode)) {
if (appendCount) {
for (let i = 0; i < appendCount; i++) {
const nodes = slice(allNodes, i * list.length, (i + 1) * list.length);
append(listWrapper, ...nodes);
}
}
}
}
/**
*
* @param {HTMLElement} listWrapper
* @param {HTMLElement} selector
*
*/
getNearestNodeInfo(listWrapper: HTMLElement, selector: HTMLElement) {
if (isElement(listWrapper) && isElement(selector)) {
const selectorRect = selector.getBoundingClientRect();
const selectorTop = selectorRect.top;
const itemNodes = listWrapper.querySelectorAll('li');
let nearestNode: HTMLElement = null;
let nearestIndex = -1;
let nearestDistance = Infinity;
Array.from(itemNodes).map((node, index) => {
const rect = node.getBoundingClientRect();
const rectTop = rect.top;
const absDistance = Math.abs(rectTop - selectorTop);
if (absDistance < nearestDistance && !this._adapter.isDisabledIndex(index)) {
nearestDistance = absDistance;
nearestNode = node;
nearestIndex = index;
}
});
return { nearestNode, nearestIndex };
}
return undefined;
}
/**
*
* @param {HTMLElement} listWrapper
*
* @param {HTMLElement|null}
*/
getTargetNode(e: any, listWrapper: HTMLElement) {
if (e && isElement(listWrapper)) {
const targetTagName = 'li';
const currentTarget = e.target;
const itemNodes = listWrapper.querySelectorAll(targetTagName);
const list = this.getProp('list');
const length = Array.isArray(list) ? list.length : 0;
let targetIndex = -1;
let indexInList = -1;
let infoInList = null;
const targetNode = find(itemNodes, (node, index) => {
if (node === currentTarget || node.contains(currentTarget)) {
targetIndex = index;
if (length > 0) {
indexInList = index % length;
}
return true;
}
return undefined;
});
if (indexInList > -1) {
infoInList = list[indexInList];
}
return {
targetNode,
targetIndex,
indexInList,
infoInList,
};
}
return null;
}
}