UNPKG

@adaptabletools/adaptable

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

205 lines (204 loc) 12.3 kB
import * as React from 'react'; import { useState, useMemo, useCallback } from 'react'; import { Box, Flex, Text } from 'rebass'; import { DragDropContext, Draggable, Droppable } from '../../../components/dnd'; import { CheckBox } from '../../../components/CheckBox'; import { Icon } from '../../../components/icons'; import { EllipsisContainer } from '../../../components/EllipsisContainer'; import SimpleButton from '../../../components/SimpleButton'; import Radio from '../../../components/Radio'; import { Tag } from '../../../components/Tag'; import { AdaptableFormControlTextClear } from '../Forms/AdaptableFormControlTextClear'; import ArrayExtensions from '../../../Utilities/Extensions/ArrayExtensions'; function useValuesMap({ options, value, toIdentifier, selectedMap, }) { const optionsMap = useMemo(() => { if (selectedMap) { return; } const map = new Map([]); options.forEach((option) => { map.set(toIdentifier(option), option); }); return map; }, [options, selectedMap]); const result = useMemo(() => { if (selectedMap) { return selectedMap; } const map = new Map([]); value.forEach((id) => { map.set(id, optionsMap.get(id) ?? null); }); return map; }, [optionsMap, selectedMap, value]); return { selectedMap: result, optionsMap }; } const baseClassName = 'ab-ValueSelector'; export function ValueSelector(props) { const { options, value, filter, onChange, allowReorder = true, singleSelect, toLabel, toListLabel, toIdentifier, showSelectedOnlyLabel, showFilterInput, selectionBoxPosition = 'bottom', onShowSelectedOnlyChange, isOptionDisabled, disabled, } = props; const [searchInputValue, setSearchInputValue] = React.useState(''); const preparedToLabel = toListLabel ?? toLabel; const [selectedOnly, doSetSelectedOnly] = useState(false); const setSelectedOnly = useCallback((selectedOnly) => { doSetSelectedOnly(selectedOnly); onShowSelectedOnlyChange?.(selectedOnly); }, [onShowSelectedOnlyChange]); const { selectedMap } = useValuesMap({ options, toIdentifier, value, }); const notifyChange = useCallback(() => { const newSelection = [...selectedMap.keys()]; if (!newSelection.length && selectedOnly) { setSelectedOnly(false); } onChange(newSelection, new Map(selectedMap)); }, [onChange, selectedOnly, selectedMap]); const renderOption = (option, index) => { const identifier = toIdentifier(option); const label = !allowReorder ? preparedToLabel(option) : null; const reorderable = typeof allowReorder === 'function' ? allowReorder(option) : allowReorder; const renderNode = (props, dragHandleProps) => { return (React.createElement(Flex, { className: `${baseClassName}__option`, alignItems: "center", mt: index ? 1 : 0, key: identifier ?? index, backgroundColor: 'primary', padding: 2, "data-index": index, "data-id": identifier, "data-name": "option", ...props }, React.createElement(Flex, { flex: 1, flexDirection: "row", alignItems: "center" }, reorderable ? (React.createElement(Box, { mr: 3, ...dragHandleProps }, React.createElement(Icon, { name: "drag", style: { cursor: 'grab' } }))) : null, singleSelect ? (React.createElement(Radio, { checked: selectedMap.has(identifier), "data-name": identifier, onChange: (checked) => { if (checked) { selectedMap.clear(); selectedMap.set(identifier, option); } else { selectedMap.delete(identifier); } notifyChange(); } }, label)) : (React.createElement(CheckBox, { "data-name": identifier, disabled: disabled || (isOptionDisabled ? isOptionDisabled(option) : false), onChange: (checked) => { if (checked) { selectedMap.set(identifier, option); } else { selectedMap.delete(identifier); } notifyChange(); }, checked: selectedMap.has(identifier) }, label)), React.createElement(Text, { flex: 1, ml: 2 }, allowReorder ? preparedToLabel(option) : null)))); }; return (React.createElement(Draggable, { key: identifier, index: index, draggableId: `${identifier}`, isDragDisabled: !reorderable }, (draggableProvided) => { return renderNode({ ref: draggableProvided.innerRef, ...draggableProvided.draggableProps, style: draggableProvided.draggableProps.style, }, draggableProvided.dragHandleProps); })); }; const showOnlySelectedCheckbox = (React.createElement(CheckBox, { disabled: !value.length, checked: selectedOnly, onChange: setSelectedOnly }, showSelectedOnlyLabel ?? 'Show Selected Only')); const showSelectedOnlyPosition = props.showSelectedOnlyPosition ?? 'floating'; const selectionSectionProps = { ...props, onSelectAll: () => { options.forEach((option) => { selectedMap.set(toIdentifier(option), option); }); notifyChange(); }, onClearOption: (id) => { selectedMap.delete(id); notifyChange(); }, onClear: () => { selectedMap.clear(); notifyChange(); }, }; return (React.createElement(Flex, { style: props.style, className: baseClassName, flexDirection: "column", flex: 1 }, React.createElement(Flex, { mb: 1 }, showFilterInput && filter ? (React.createElement(AdaptableFormControlTextClear, { value: searchInputValue, OnTextChange: setSearchInputValue, placeholder: "Type to search", style: { flex: 1, border: 0, margin: 3 } })) : (React.createElement(Box, { flex: 1 })), showSelectedOnlyPosition === 'top' && React.createElement(Box, { ml: 20 }, showOnlySelectedCheckbox)), selectionBoxPosition === 'top' && renderSelectionSection(selectionSectionProps), React.createElement(DragDropContext, { onDragEnd: (result) => { const { source, destination } = result; const selection = []; const extraKeys = []; for (let [key, value] of selectedMap) { if (value != null) { selection.push(key); } else { // null/non-existent keys have to be stored separately extraKeys.push(key); } } const clone = new Map(selectedMap); const newSelection = ArrayExtensions.reorderArray(selection, source.index, destination.index); // and then pushed back in the new order, at the end newSelection.push(...extraKeys); selectedMap.clear(); newSelection.forEach((key) => { selectedMap.set(key, clone.get(key)); }); notifyChange(); } }, React.createElement(Flex, { className: `${baseClassName}__body`, flexDirection: "column", flex: 1, style: { overflow: 'auto' } }, showSelectedOnlyPosition === 'floating' && (React.createElement(Box, { className: `${baseClassName}__show-selected-only-checkbox` }, React.createElement(Box, { className: `${baseClassName}__show-selected-only-checkbox__text`, "data-name": "show-selected-only", backgroundColor: "defaultbackground" }, showOnlySelectedCheckbox))), React.createElement(Droppable, { droppableId: "droppable" }, (droppableProvided) => { return (React.createElement("div", { ref: droppableProvided.innerRef, ...droppableProvided.droppableProps }, options .filter((option) => { let result = true; if (filter) { result = filter(option, searchInputValue); } result = result && (selectedOnly ? selectedMap.has(toIdentifier(option)) : true); return result; }) .map(renderOption), droppableProvided.placeholder)); }))), selectionBoxPosition === 'bottom' && renderSelectionSection(selectionSectionProps))); } export const renderSelectionSection = (props) => { const { noSelectionLabel, clearSelectionLabel, value, options, disabled, singleSelect, toLabel, toIdentifier, selectionBoxPosition = 'bottom', } = props; const selectionBox = (React.createElement(Box, { fontSize: 2 }, !value.length ? (React.createElement(React.Fragment, null, noSelectionLabel ?? 'No selected options', !singleSelect ? (React.createElement(SimpleButton, { px: 1, disabled: disabled, variant: "text", style: { textDecoration: 'underline', display: 'inline-block' }, onClick: props.onSelectAll }, "Select all")) : null)) : (React.createElement(React.Fragment, null, React.createElement(SimpleButton, { disabled: disabled, tabIndex: 0, px: 1, mr: 1, variant: "text", style: { textDecoration: 'underline', display: 'inline-block' }, onClick: props.onClear }, clearSelectionLabel ?? 'Clear selection.'), props.xSelectedLabel ? props.xSelectedLabel(value.length) : `Your selected ${value.length} ${value.length > 1 ? 'options' : 'option'}.`)))); return (React.createElement(Box, { className: `${baseClassName}__header`, mt: selectionBoxPosition === 'bottom' ? 3 : 0, mb: selectionBoxPosition === 'top' ? 3 : 0 }, selectionBox, React.createElement(ValueOptionsTags, { options: options, value: value, toLabel: toLabel, toIdentifier: toIdentifier, onClearOption: props.onClearOption, readOnly: disabled, isOptionDisabled: props.isOptionDisabled }))); }; export function ValueOptionsTags(props) { const { allowWrap, value, toLabel, isOptionDisabled, onChange, onClearOption, readOnly: isReadOnly, renderLabel, } = props; const { selectedMap } = useValuesMap(props); const renderOptionTag = (index) => { const optionId = value[index]; const clear = () => { selectedMap.delete(optionId); onClearOption?.(optionId); if (onChange) { onChange([...selectedMap.keys()], new Map(selectedMap)); } }; const option = selectedMap.get(optionId); const readOnly = isReadOnly || (isOptionDisabled ? isOptionDisabled(option) : false); const label = option != null ? toLabel(option) : `${optionId}`; return (React.createElement(Tag, { flexDirection: "row", alignItems: "center", "data-name": "selected-option", "data-id": optionId, mt: allowWrap ? 1 : 0, mr: 1, className: "ab-ValueSelector__tag", onClick: (e) => { e.target?.lastChild?.focus?.(); }, style: { flex: 'none' } }, renderLabel ? renderLabel(label) : label, React.createElement(SimpleButton, { icon: "close", ml: 2, iconSize: 14, variant: "text", style: { border: 'none', visibility: readOnly ? 'hidden' : 'visible' }, onClick: clear }))); }; const renderEllipsis = useCallback(({ remaining }) => { return (React.createElement(Text, { fontSize: 2, style: { whiteSpace: 'nowrap' } }, "+", remaining, " ", remaining > 1 ? 'others' : 'other')); }, []); return (React.createElement(EllipsisContainer, { style: props.style, marginRight: 4, my: 1, allowWrap: allowWrap, count: value.length, direction: "horizontal", renderItem: renderOptionTag, renderEllipsis: renderEllipsis })); }