components.widgets.Tree.until.jsx Maven / Gradle / Ivy
The newest version!
import React from 'react'
import forEach from 'lodash/forEach'
import keys from 'lodash/keys'
import filter from 'lodash/filter'
import eq from 'lodash/eq'
import omit from 'lodash/omit'
import get from 'lodash/get'
import has from 'lodash/has'
import uniqueId from 'lodash/uniqueId'
import find from 'lodash/find'
import some from 'lodash/some'
import { findDOMNode } from 'react-dom'
import cssAnimation from 'css-animation'
import { Icon } from '../../snippets/Icon/Icon'
import { KEY_CODES } from './component/constants'
/**
* Создаем коллекцию из дерева tree -> [{ id: ..., parentId: ... }, ...]
* @param tree
* @param parentFieldId
* @param valueFieldId
* @param childrenFieldId
*/
export const treeToCollection = (
tree,
{ parentFieldId, valueFieldId, childrenFieldId },
) => {
const buf = [...tree]
buf.forEach((el) => {
if (el[childrenFieldId]) {
const elems = el[childrenFieldId].map(v => ({
...v,
[parentFieldId]: el[valueFieldId],
}))
buf.push(...elems)
} else {
buf.push(...el)
}
})
return buf.map(v => omit(v, [childrenFieldId]))
}
export const FILTER_MODE = ['includes', 'startsWith', 'endsWith']
export const createRegExp = (searchText, filter) => {
let regExp
if (eq(filter, 'includes')) {
regExp = new RegExp(searchText, 'i')
}
if (eq(filter, 'startsWith')) {
regExp = new RegExp(`^${searchText}`, 'i')
}
if (eq(filter, 'endsWith')) {
regExp = new RegExp(`${searchText}$`, 'i')
}
return regExp
}
/**
* Превращаем коллекцию в обьект с ключами id и value Element
* [{ id: 1, ...}, { id: 2, ... }] => { 1: {...}, 2: {...} }
*/
export const collectionToComponentObject = (Component, props) => {
const buf = {}
const valueFieldId = get(props, 'valueFieldId')
const datasource = get(props, 'datasource')
const iconFieldId = get(props, 'iconFieldId')
if (valueFieldId && datasource) {
datasource.forEach((data) => {
buf[data[valueFieldId]] = {
...data,
icon: has(data, iconFieldId) && (
),
key: data[valueFieldId],
title: React.createElement(Component, { data, ...props }),
children: [],
}
})
}
return buf
}
export const createTreeFn = Component => (props) => {
const itemsByID = collectionToComponentObject(Component, props)
const parentFieldId = get(props, 'parentFieldId')
keys(itemsByID).forEach((key) => {
const elem = itemsByID[key]
if (elem[parentFieldId] && itemsByID[elem[parentFieldId]]) {
itemsByID[elem[parentFieldId]].children.push({ ...elem })
}
})
const buf = []
keys(itemsByID).forEach((key) => {
if (!itemsByID[key][parentFieldId]) {
buf.push(itemsByID[key])
}
})
return buf
}
export const takeKeysWhenSearching = (props) => {
const filter = get(props, 'filter')
const value = get(props, 'value')
const datasource = get(props, 'datasource', [])
const valueFieldId = get(props, 'valueFieldId')
const labelFieldId = get(props, 'labelFieldId')
if (filter && FILTER_MODE.includes(filter) && value) {
const regExp = createRegExp(value, filter)
const filterFunc = searchStr => searchStr.search(regExp) + 1
return datasource
.filter(item => filterFunc(item[labelFieldId]))
.map(v => v[valueFieldId])
}
return []
}
/**
* Вспомогогательная функция для клавиатуры
* Определяет путь по которому будет двигаться клавиатура
* возвращает массив из id
* @param data
* @param expandedKeys - открытые ключи
* @param parentFieldId
* @param valueFieldId
* @returns {Array}
*/
export const getTreeLinerRoute = (
data,
expandedKeys,
{ parentFieldId, valueFieldId },
) => {
// берем всех родителей
const parenIds = filter(data, dt => !dt[parentFieldId] && !dt.disabled).map(
dt => dt[valueFieldId],
)
const buff = []
// рекурсивно спускаемся вниз ко всем потомкам
// и если потомки есть в expandedKeys то добавляем в буфер
const recursionFn = ids => forEach(ids, (id) => {
buff.push(id)
if (expandedKeys.includes(id)) {
const childs = filter(data, dt => dt[parentFieldId] === id && !dt.disabled).map(dt => dt[valueFieldId])
if (childs) {
recursionFn(childs)
}
}
})
recursionFn(parenIds)
return buff
}
// / Key base fns
const down = (focusedElement, route, node) => {
if (eq(focusedElement.className, 'hotkey') || focusedElement) {
const child = node.querySelector(`.cls-${route[0]}`)
child.focus()
}
if (focusedElement.dataset.id) {
const { id } = focusedElement.dataset
const inx = route.indexOf(id)
if (route.length > inx + 1) {
const child = node.querySelector(`.cls-${route[inx + 1]}`)
child.focus()
}
}
}
const up = (focusedElement, route, node) => {
if (focusedElement.dataset.id) {
const { id } = focusedElement.dataset
const inx = route.indexOf(id)
if (inx - 1 >= 0) {
const child = node.querySelector(`.cls-${route[inx - 1]}`)
child.focus()
}
}
}
const select = (focusedElement, node) => {
if (focusedElement.dataset.id) {
const { id } = focusedElement.dataset
const child = node.querySelector(`.cls-${id}`)
child.click()
}
}
const toggle = (focusedElement, node, { prefixCls }) => {
if (focusedElement.dataset.id) {
const { id } = focusedElement.dataset
const child = node
.querySelector(`.cls-${id}`)
.closest('li')
.querySelector(`.${prefixCls}-switcher`)
child.click()
}
}
const checked = (focusedElement, node, { prefixCls }) => {
if (focusedElement.dataset.id) {
const { id } = focusedElement.dataset
const child = node
.querySelector(`.cls-${id}`)
.closest('li')
.querySelector(`.${prefixCls}-checkbox`)
if (child) {
child.click()
}
}
}
// / end base fns
export const customTreeActions = ({
key,
treeRef,
datasource,
expandedKeys,
prefixCls,
valueFieldId,
parentFieldId,
}) => {
// eslint-disable-next-line react/no-find-dom-node
const node = findDOMNode(treeRef.current)
const route = getTreeLinerRoute(datasource, expandedKeys, {
valueFieldId,
parentFieldId,
})
const focusedElement = document.activeElement
const isParent = id => some(datasource, [parentFieldId, id])
const isRootParent = (id) => {
const elem = find(datasource, [valueFieldId, id])
return elem && !has(elem, parentFieldId) && !find(datasource, [parentFieldId, id])
}
if (eq(key, KEY_CODES.KEY_DOWN)) {
down(focusedElement, route, node)
}
if (eq(key, KEY_CODES.KEY_UP)) {
up(focusedElement, route, node)
}
if (eq(key, KEY_CODES.KEY_SPACE)) {
select(focusedElement, node)
}
if (eq(key, KEY_CODES.CTRL_ENTER)) {
checked(focusedElement, node, { prefixCls })
}
if (eq(key, KEY_CODES.RIGHT)) {
const { id } = focusedElement.dataset
if (
!expandedKeys.includes(id) &&
isParent(id) &&
!isRootParent(id) &&
route.includes(id)
) {
toggle(focusedElement, node, { prefixCls })
} else {
down(focusedElement, route, node)
}
}
if (eq(key, KEY_CODES.LEFT)) {
const { id } = focusedElement.dataset
if (
expandedKeys.includes(id) &&
isParent(id) &&
!isRootParent(id) &&
route.includes(id)
) {
toggle(focusedElement, node, { prefixCls })
} else {
up(focusedElement, route, node)
}
}
if (eq(key, 'DB_CLICK')) {
toggle(focusedElement, node, { prefixCls })
}
return false
}
export const splitSearchText = (text, searchText, filter) => {
if (FILTER_MODE.includes(filter)) {
const regExp = createRegExp(searchText, filter)
const html = text.replace(
regExp,
str => `${str}`,
)
// eslint-disable-next-line react/no-danger
return
}
return text
}
const animate = (node, show, done) => {
let height = node.offsetHeight
return cssAnimation(node, 'collapse', {
start() {
if (!show) {
node.style.height = `${node.offsetHeight}px`
} else {
height = node.offsetHeight
node.style.height = 0
}
},
active() {
node.style.height = `${show ? height : 0}px`
},
end() {
node.style.height = ''
done()
},
})
}
export const animationTree = {
enter(node, done) {
return animate(node, true, done)
},
leave(node, done) {
return animate(node, false, done)
},
}
export const singleDoubleClickFilter = (
singleCallback,
doubleCallback,
timeout,
) => {
let timer = 0
return (...args) => {
timer += 1
if (timer === 1) {
setTimeout(() => {
if (timer === 1 && singleCallback) {
singleCallback(...args)
} else if (doubleCallback) {
doubleCallback(...args)
}
timer = 0
}, timeout || 100)
}
}
}