UNPKG

5.98 kBJavaScriptView Raw
1const _excluded = ["multiple", "data", "value", "onChange", "accessors", "className", "messages", "disabled", "renderItem", "renderGroup", "searchTerm", "groupBy", "elementRef", "optionComponent", "renderList"];
2
3function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
4
5/* eslint-disable @typescript-eslint/no-empty-function */
6import cn from 'classnames';
7import PropTypes from 'prop-types';
8import React, { useCallback, useImperativeHandle, useMemo } from 'react';
9import ListOption from './ListOption';
10import ListOptionGroup from './ListOptionGroup';
11import { useMessagesWithDefaults } from './messages'; // import { WidgetHTMLProps } from './shared'
12
13import * as CustomPropTypes from './PropTypes';
14import { groupBySortedKeys, makeArray, toItemArray } from './_';
15import { useInstanceId } from './WidgetHelpers';
16import useMutationObserver from '@restart/hooks/useMutationObserver';
17import useCallbackRef from '@restart/hooks/useCallbackRef';
18import useMergedRefs from '@restart/hooks/useMergedRefs';
19const whitelist = ['style', 'className', 'role', 'id', 'autocomplete', 'size', 'tabIndex', 'maxLength', 'name'];
20const whitelistRegex = [/^aria-/, /^data-/, /^on[A-Z]\w+/];
21
22function pickElementProps(props) {
23 const result = {};
24 Object.keys(props).forEach(key => {
25 if (whitelist.indexOf(key) !== -1 || whitelistRegex.some(r => !!key.match(r))) result[key] = props[key];
26 });
27 return result;
28}
29
30const propTypes = {
31 data: PropTypes.array,
32 dataKey: CustomPropTypes.accessor,
33 textField: CustomPropTypes.accessor,
34 onSelect: PropTypes.func,
35 onMove: PropTypes.func,
36 onHoverOption: PropTypes.func,
37 optionComponent: PropTypes.elementType,
38 renderItem: PropTypes.func,
39 renderGroup: PropTypes.func,
40 focusedItem: PropTypes.any,
41 selectedItem: PropTypes.any,
42 searchTerm: PropTypes.string,
43 disabled: CustomPropTypes.disabled.acceptsArray,
44 messages: PropTypes.shape({
45 emptyList: PropTypes.func.isRequired
46 })
47};
48export const useScrollFocusedIntoView = (element, observeChanges = false) => {
49 const scrollIntoView = useCallback(() => {
50 if (!element) return;
51 let selectedItem = element.querySelector('[data-rw-focused]');
52
53 if (selectedItem && selectedItem.scrollIntoView) {
54 selectedItem.scrollIntoView({
55 block: 'nearest',
56 inline: 'nearest'
57 });
58 }
59 }, [element]);
60 useMutationObserver(observeChanges ? element : null, {
61 subtree: true,
62 attributes: true,
63 attributeFilter: ['data-rw-focused']
64 }, scrollIntoView);
65 return scrollIntoView;
66};
67export function useHandleSelect(multiple, dataItems, onChange) {
68 return (dataItem, event) => {
69 if (multiple === false) {
70 onChange(dataItem, {
71 dataItem,
72 lastValue: dataItems[0],
73 originalEvent: event
74 });
75 return;
76 }
77
78 const checked = dataItems.includes(dataItem);
79 onChange(checked ? dataItems.filter(d => d !== dataItem) : [...dataItems, dataItem], {
80 dataItem,
81 lastValue: dataItems,
82 action: checked ? 'remove' : 'insert',
83 originalEvent: event
84 });
85 };
86}
87const List = /*#__PURE__*/React.forwardRef(function List(_ref, outerRef) {
88 var _elementProps$role;
89
90 let {
91 multiple = false,
92 data = [],
93 value,
94 onChange,
95 accessors,
96 className,
97 messages,
98 disabled,
99 renderItem,
100 renderGroup,
101 searchTerm,
102 groupBy,
103 elementRef,
104 optionComponent: Option = ListOption,
105 renderList
106 } = _ref,
107 props = _objectWithoutPropertiesLoose(_ref, _excluded);
108
109 const id = useInstanceId();
110 const dataItems = makeArray(value, multiple);
111 const groupedData = useMemo(() => groupBy ? groupBySortedKeys(groupBy, data) : undefined, [data, groupBy]);
112 const [element, ref] = useCallbackRef();
113 const disabledItems = toItemArray(disabled);
114 const {
115 emptyList
116 } = useMessagesWithDefaults(messages);
117 const divRef = useMergedRefs(ref, elementRef);
118 const handleSelect = useHandleSelect(multiple, dataItems, onChange);
119 const scrollIntoView = useScrollFocusedIntoView(element, true);
120 let elementProps = pickElementProps(props);
121 useImperativeHandle(outerRef, () => ({
122 scrollIntoView
123 }), [scrollIntoView]);
124
125 function renderOption(item, idx) {
126 const textValue = accessors.text(item);
127 const itemIsDisabled = disabledItems.includes(item);
128 const itemIsSelected = dataItems.includes(item);
129 return /*#__PURE__*/React.createElement(Option, {
130 dataItem: item,
131 key: `item_${idx}`,
132 searchTerm: searchTerm,
133 onSelect: handleSelect,
134 disabled: itemIsDisabled,
135 selected: itemIsSelected
136 }, renderItem ? renderItem({
137 item,
138 searchTerm,
139 index: idx,
140 text: textValue,
141 // TODO: probably remove
142 value: accessors.value(item),
143 disabled: itemIsDisabled
144 }) : textValue);
145 }
146
147 const items = groupedData ? groupedData.map(([group, items], idx) => /*#__PURE__*/React.createElement("div", {
148 role: "group",
149 key: `group_${idx}`
150 }, /*#__PURE__*/React.createElement(ListOptionGroup, null, renderGroup ? renderGroup({
151 group
152 }) : group), items.map(renderOption))) : data.map(renderOption);
153 const rootProps = Object.assign({
154 id,
155 tabIndex: 0,
156 ref: divRef
157 }, elementProps, {
158 'aria-multiselectable': !!multiple,
159 className: cn(className, 'rw-list'),
160 role: (_elementProps$role = elementProps.role) != null ? _elementProps$role : 'listbox',
161 children: React.Children.count(items) ? items : /*#__PURE__*/React.createElement("div", {
162 className: "rw-list-empty"
163 }, emptyList())
164 });
165 return renderList ? renderList(rootProps) : /*#__PURE__*/React.createElement("div", rootProps);
166});
167List.displayName = 'List';
168List.propTypes = propTypes;
169export default List;
\No newline at end of file