|
| 1 | +import React, { useReducer } from 'react'; |
| 2 | +import PropTypes from 'prop-types'; |
| 3 | +import isEqual from 'lodash/isEqual'; |
| 4 | + |
| 5 | +import { useFieldApi } from '@data-driven-forms/react-form-renderer'; |
| 6 | + |
| 7 | +import reducer, { initialState } from './reducer'; |
| 8 | + |
| 9 | +const getOptionsGroup = (value, lastClicked, options) => { |
| 10 | + const lastIndex = options.map(({ value }) => value.toString()).indexOf(lastClicked.toString()); |
| 11 | + const currentIndex = options.map(({ value }) => value.toString()).indexOf(value); |
| 12 | + const startIndex = Math.min(lastIndex, currentIndex); |
| 13 | + const endIndex = Math.max(lastIndex, currentIndex) + 1; |
| 14 | + return [...options.slice(startIndex, endIndex).map(({ value }) => value.toString())]; |
| 15 | +}; |
| 16 | + |
| 17 | +const handleOptionClick = (event, value, options, isRight, dispatch, state) => { |
| 18 | + const selectedKey = isRight ? 'selectedLeftValues' : 'selectedRightValues'; |
| 19 | + const lastKey = isRight ? 'lastLeftClicked' : 'lastRightClicked'; |
| 20 | + if (event.shiftKey && state[lastKey]) { |
| 21 | + dispatch({ type: 'setSelectedValue', value, values: getOptionsGroup(value, state[lastKey], options), isRight }); |
| 22 | + } else if (event.ctrlKey && state[lastKey]) { |
| 23 | + const selectedValues = state[selectedKey].includes(value) ? state[selectedKey].filter((item) => item !== value) : [...state[selectedKey], value]; |
| 24 | + |
| 25 | + dispatch({ type: 'setSelectedValue', value, values: selectedValues, isRight }); |
| 26 | + } else { |
| 27 | + dispatch({ type: 'setSelectedValue', value, values: [value], isRight }); |
| 28 | + } |
| 29 | +}; |
| 30 | + |
| 31 | +const DualListSelectCommon = (props) => { |
| 32 | + const [state, dispatch] = useReducer(reducer, initialState); |
| 33 | + |
| 34 | + const { DualListSelect, ...rest } = useFieldApi({ |
| 35 | + ...props, |
| 36 | + isEqual: (current, initial) => isEqual([...(current || [])].sort(), [...(initial || [])].sort()) |
| 37 | + }); |
| 38 | + |
| 39 | + const leftValues = rest.options |
| 40 | + .filter((option) => !rest.input.value.includes(option.value) && option.label.includes(state.filterOptions)) |
| 41 | + .sort((a, b) => (state.sortLeftDesc ? a.label.localeCompare(b.label) : b.label.localeCompare(a.label))); |
| 42 | + const rightValues = rest.options |
| 43 | + .filter((option) => rest.input.value.includes(option.value) && option.label.includes(state.filterValue)) |
| 44 | + .sort((a, b) => (state.sortRightDesc ? a.label.localeCompare(b.label) : b.label.localeCompare(a.label))); |
| 45 | + |
| 46 | + const handleOptionsClick = (event, value) => handleOptionClick(event, value, leftValues, true, dispatch, state); |
| 47 | + |
| 48 | + const handleValuesClick = (event, value) => handleOptionClick(event, value, rightValues, false, dispatch, state); |
| 49 | + |
| 50 | + const handleMoveRight = () => { |
| 51 | + rest.input.onChange([...rest.input.value, ...state.selectedLeftValues]); |
| 52 | + dispatch({ type: 'clearLeftOptions' }); |
| 53 | + }; |
| 54 | + |
| 55 | + const handleMoveLeft = () => { |
| 56 | + rest.input.onChange(rest.input.value.filter((value) => !state.selectedRightValues.includes(value))); |
| 57 | + dispatch({ type: 'clearRightValues' }); |
| 58 | + }; |
| 59 | + |
| 60 | + const sortOptions = () => dispatch({ type: 'sortOptions' }); |
| 61 | + |
| 62 | + const sortValues = () => dispatch({ type: 'sortValue' }); |
| 63 | + |
| 64 | + const filterOptions = (value) => dispatch({ type: 'setFilterOptions', value }); |
| 65 | + |
| 66 | + const filterValues = (value) => dispatch({ type: 'setFilterValue', value }); |
| 67 | + |
| 68 | + const handleClearLeftValues = () => { |
| 69 | + dispatch({ type: 'clearLeftValues' }); |
| 70 | + rest.input.onChange([...rest.input.value, ...leftValues.map(({ value }) => value)]); |
| 71 | + }; |
| 72 | + |
| 73 | + const handleClearRightValues = () => { |
| 74 | + dispatch({ type: 'clearRightValue' }); |
| 75 | + rest.input.onChange([...rest.input.value.filter((val) => !rightValues.find(({ value }) => val === value))]); |
| 76 | + }; |
| 77 | + |
| 78 | + return ( |
| 79 | + <DualListSelect |
| 80 | + {...rest} |
| 81 | + leftValues={leftValues} |
| 82 | + rightValues={rightValues} |
| 83 | + handleOptionsClick={handleOptionsClick} |
| 84 | + handleValuesClick={handleValuesClick} |
| 85 | + handleMoveRight={handleMoveRight} |
| 86 | + handleMoveLeft={handleMoveLeft} |
| 87 | + sortOptions={sortOptions} |
| 88 | + sortValues={sortValues} |
| 89 | + filterOptions={filterOptions} |
| 90 | + filterValues={filterValues} |
| 91 | + handleClearLeftValues={handleClearLeftValues} |
| 92 | + handleClearRightValues={handleClearRightValues} |
| 93 | + state={state} |
| 94 | + /> |
| 95 | + ); |
| 96 | +}; |
| 97 | + |
| 98 | +DualListSelectCommon.propTypes = { |
| 99 | + DualListSelect: PropTypes.oneOfType([PropTypes.node, PropTypes.func]) |
| 100 | +}; |
| 101 | + |
| 102 | +export default DualListSelectCommon; |
0 commit comments