UNPKG

26.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.DualListSelector = void 0;
4const tslib_1 = require("tslib");
5const React = tslib_1.__importStar(require("react"));
6const dual_list_selector_1 = tslib_1.__importDefault(require("@patternfly/react-styles/css/components/DualListSelector/dual-list-selector"));
7const react_styles_1 = require("@patternfly/react-styles");
8const angle_double_left_icon_1 = tslib_1.__importDefault(require('@patternfly/react-icons/dist/js/icons/angle-double-left-icon'));
9const angle_left_icon_1 = tslib_1.__importDefault(require('@patternfly/react-icons/dist/js/icons/angle-left-icon'));
10const angle_double_right_icon_1 = tslib_1.__importDefault(require('@patternfly/react-icons/dist/js/icons/angle-double-right-icon'));
11const angle_right_icon_1 = tslib_1.__importDefault(require('@patternfly/react-icons/dist/js/icons/angle-right-icon'));
12const DualListSelectorPane_1 = require("./DualListSelectorPane");
13const helpers_1 = require("../../helpers");
14const treeUtils_1 = require("./treeUtils");
15const DualListSelectorControlsWrapper_1 = require("./DualListSelectorControlsWrapper");
16const DualListSelectorControl_1 = require("./DualListSelectorControl");
17const DualListSelectorContext_1 = require("./DualListSelectorContext");
18class DualListSelector extends React.Component {
19 constructor(props) {
20 super(props);
21 this.addAllButtonRef = React.createRef();
22 this.addSelectedButtonRef = React.createRef();
23 this.removeSelectedButtonRef = React.createRef();
24 this.removeAllButtonRef = React.createRef();
25 this.onFilterUpdate = (newFilteredOptions, paneType, isSearchReset) => {
26 const { isTree } = this.props;
27 if (paneType === 'available') {
28 if (isSearchReset) {
29 this.setState({
30 availableFilteredOptions: null,
31 availableTreeFilteredOptions: null
32 });
33 return;
34 }
35 if (isTree) {
36 this.setState({
37 availableTreeFilteredOptions: treeUtils_1.flattenTreeWithFolders(newFilteredOptions)
38 });
39 }
40 else {
41 this.setState({
42 availableFilteredOptions: newFilteredOptions
43 });
44 }
45 }
46 else if (paneType === 'chosen') {
47 if (isSearchReset) {
48 this.setState({
49 chosenFilteredOptions: null,
50 chosenTreeFilteredOptions: null
51 });
52 return;
53 }
54 if (isTree) {
55 this.setState({
56 chosenTreeFilteredOptions: treeUtils_1.flattenTreeWithFolders(newFilteredOptions)
57 });
58 }
59 else {
60 this.setState({
61 chosenFilteredOptions: newFilteredOptions
62 });
63 }
64 }
65 };
66 this.addAllVisible = () => {
67 this.setState(prevState => {
68 const itemsToRemove = [];
69 const newAvailable = [];
70 const movedOptions = prevState.availableFilteredOptions || prevState.availableOptions;
71 prevState.availableOptions.forEach(value => {
72 if (movedOptions.indexOf(value) !== -1) {
73 itemsToRemove.push(value);
74 }
75 else {
76 newAvailable.push(value);
77 }
78 });
79 const newChosen = [...prevState.chosenOptions, ...itemsToRemove];
80 this.props.addAll && this.props.addAll(newAvailable, newChosen);
81 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
82 return {
83 chosenOptions: newChosen,
84 availableOptions: newAvailable,
85 chosenOptionsSelected: [],
86 availableOptionsSelected: []
87 };
88 });
89 };
90 this.addAllTreeVisible = () => {
91 this.setState(prevState => {
92 const movedOptions = prevState.availableTreeFilteredOptions ||
93 treeUtils_1.flattenTreeWithFolders(prevState.availableOptions);
94 const newAvailable = prevState.availableOptions
95 .map(opt => Object.assign({}, opt))
96 .filter(item => treeUtils_1.filterRestTreeItems(item, movedOptions));
97 const currChosen = treeUtils_1.flattenTree(prevState.chosenOptions);
98 const nextChosenOptions = currChosen.concat(movedOptions);
99 const newChosen = this.createMergedCopy()
100 .map(opt => Object.assign({}, opt))
101 .filter(item => treeUtils_1.filterTreeItemsWithoutFolders(item, nextChosenOptions));
102 this.props.addAll && this.props.addAll(newAvailable, newChosen);
103 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
104 return {
105 chosenOptions: newChosen,
106 chosenFilteredOptions: newChosen,
107 availableOptions: newAvailable,
108 availableFilteredOptions: newAvailable,
109 availableTreeOptionsChecked: [],
110 chosenTreeOptionsChecked: []
111 };
112 });
113 };
114 this.addSelected = () => {
115 this.setState(prevState => {
116 const itemsToRemove = [];
117 const newAvailable = [];
118 prevState.availableOptions.forEach((value, index) => {
119 if (prevState.availableOptionsSelected.indexOf(index) !== -1) {
120 itemsToRemove.push(value);
121 }
122 else {
123 newAvailable.push(value);
124 }
125 });
126 const newChosen = [...prevState.chosenOptions, ...itemsToRemove];
127 this.props.addSelected && this.props.addSelected(newAvailable, newChosen);
128 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
129 return {
130 chosenOptionsSelected: [],
131 availableOptionsSelected: [],
132 chosenOptions: newChosen,
133 availableOptions: newAvailable
134 };
135 });
136 };
137 this.addTreeSelected = () => {
138 this.setState(prevState => {
139 // Remove selected available nodes from current available nodes
140 const newAvailable = prevState.availableOptions
141 .map(opt => Object.assign({}, opt))
142 .filter(item => treeUtils_1.filterRestTreeItems(item, prevState.availableTreeOptionsChecked));
143 // Get next chosen options from current + new nodes and remap from base
144 const currChosen = treeUtils_1.flattenTree(prevState.chosenOptions);
145 const nextChosenOptions = currChosen.concat(prevState.availableTreeOptionsChecked);
146 const newChosen = this.createMergedCopy()
147 .map(opt => Object.assign({}, opt))
148 .filter(item => treeUtils_1.filterTreeItemsWithoutFolders(item, nextChosenOptions));
149 this.props.addSelected && this.props.addSelected(newAvailable, newChosen);
150 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
151 return {
152 availableTreeOptionsChecked: [],
153 chosenTreeOptionsChecked: [],
154 availableOptions: newAvailable,
155 chosenOptions: newChosen
156 };
157 });
158 };
159 this.removeAllVisible = () => {
160 this.setState(prevState => {
161 const itemsToRemove = [];
162 const newChosen = [];
163 const movedOptions = prevState.chosenFilteredOptions || prevState.chosenOptions;
164 prevState.chosenOptions.forEach(value => {
165 if (movedOptions.indexOf(value) !== -1) {
166 itemsToRemove.push(value);
167 }
168 else {
169 newChosen.push(value);
170 }
171 });
172 const newAvailable = [...prevState.availableOptions, ...itemsToRemove];
173 this.props.removeAll && this.props.removeAll(newAvailable, newChosen);
174 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
175 return {
176 chosenOptions: newChosen,
177 availableOptions: newAvailable,
178 chosenOptionsSelected: [],
179 availableOptionsSelected: []
180 };
181 });
182 };
183 this.removeAllTreeVisible = () => {
184 this.setState(prevState => {
185 const movedOptions = prevState.chosenTreeFilteredOptions ||
186 treeUtils_1.flattenTreeWithFolders(prevState.chosenOptions);
187 const newChosen = prevState.chosenOptions
188 .map(opt => Object.assign({}, opt))
189 .filter(item => treeUtils_1.filterRestTreeItems(item, movedOptions));
190 const currAvailable = treeUtils_1.flattenTree(prevState.availableOptions);
191 const nextAvailableOptions = currAvailable.concat(movedOptions);
192 const newAvailable = this.createMergedCopy()
193 .map(opt => Object.assign({}, opt))
194 .filter(item => treeUtils_1.filterTreeItemsWithoutFolders(item, nextAvailableOptions));
195 this.props.removeAll && this.props.removeAll(newAvailable, newChosen);
196 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
197 return {
198 chosenOptions: newChosen,
199 availableOptions: newAvailable,
200 availableTreeOptionsChecked: [],
201 chosenTreeOptionsChecked: []
202 };
203 });
204 };
205 this.removeSelected = () => {
206 this.setState(prevState => {
207 const itemsToRemove = [];
208 const newChosen = [];
209 prevState.chosenOptions.forEach((value, index) => {
210 if (prevState.chosenOptionsSelected.indexOf(index) !== -1) {
211 itemsToRemove.push(value);
212 }
213 else {
214 newChosen.push(value);
215 }
216 });
217 const newAvailable = [...prevState.availableOptions, ...itemsToRemove];
218 this.props.removeSelected && this.props.removeSelected(newAvailable, newChosen);
219 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
220 return {
221 chosenOptionsSelected: [],
222 availableOptionsSelected: [],
223 chosenOptions: newChosen,
224 availableOptions: newAvailable
225 };
226 });
227 };
228 this.removeTreeSelected = () => {
229 this.setState(prevState => {
230 // Remove selected chosen nodes from current chosen nodes
231 const newChosen = prevState.chosenOptions
232 .map(opt => Object.assign({}, opt))
233 .filter(item => treeUtils_1.filterRestTreeItems(item, prevState.chosenTreeOptionsChecked));
234 // Get next chosen options from current and remap from base
235 const currAvailable = treeUtils_1.flattenTree(prevState.availableOptions);
236 const nextAvailableOptions = currAvailable.concat(prevState.chosenTreeOptionsChecked);
237 const newAvailable = this.createMergedCopy()
238 .map(opt => Object.assign({}, opt))
239 .filter(item => treeUtils_1.filterTreeItemsWithoutFolders(item, nextAvailableOptions));
240 this.props.removeSelected && this.props.removeSelected(newAvailable, newChosen);
241 this.props.onListChange && this.props.onListChange(newAvailable, newChosen);
242 return {
243 availableTreeOptionsChecked: [],
244 chosenTreeOptionsChecked: [],
245 availableOptions: newAvailable,
246 chosenOptions: newChosen
247 };
248 });
249 };
250 this.onOptionSelect = (e, index, isChosen,
251 /* eslint-disable @typescript-eslint/no-unused-vars */
252 id, itemData, parentData
253 /* eslint-enable @typescript-eslint/no-unused-vars */
254 ) => {
255 this.setState(prevState => {
256 const originalArray = isChosen ? prevState.chosenOptionsSelected : prevState.availableOptionsSelected;
257 let updatedArray = null;
258 if (originalArray.indexOf(index) !== -1) {
259 updatedArray = originalArray.filter(value => value !== index);
260 }
261 else {
262 updatedArray = [...originalArray, index];
263 }
264 return {
265 chosenOptionsSelected: isChosen ? updatedArray : prevState.chosenOptionsSelected,
266 availableOptionsSelected: isChosen ? prevState.availableOptionsSelected : updatedArray
267 };
268 });
269 this.props.onOptionSelect && this.props.onOptionSelect(e, index, isChosen, id, itemData, parentData);
270 };
271 this.isChecked = (treeItem, isChosen) => isChosen
272 ? this.state.chosenTreeOptionsChecked.includes(treeItem.id)
273 : this.state.availableTreeOptionsChecked.includes(treeItem.id);
274 this.areAllDescendantsChecked = (treeItem, isChosen) => treeItem.children
275 ? treeItem.children.every(child => this.areAllDescendantsChecked(child, isChosen))
276 : this.isChecked(treeItem, isChosen);
277 this.areSomeDescendantsChecked = (treeItem, isChosen) => treeItem.children
278 ? treeItem.children.some(child => this.areSomeDescendantsChecked(child, isChosen))
279 : this.isChecked(treeItem, isChosen);
280 this.mapChecked = (item, isChosen) => {
281 const hasCheck = this.areAllDescendantsChecked(item, isChosen);
282 item.isChecked = false;
283 if (hasCheck) {
284 item.isChecked = true;
285 }
286 else {
287 const hasPartialCheck = this.areSomeDescendantsChecked(item, isChosen);
288 if (hasPartialCheck) {
289 item.isChecked = null;
290 }
291 }
292 if (item.children) {
293 return Object.assign(Object.assign({}, item), { children: item.children.map(child => this.mapChecked(child, isChosen)) });
294 }
295 return item;
296 };
297 this.onTreeOptionCheck = (evt, isChecked, itemData, isChosen) => {
298 const { availableOptions, availableTreeFilteredOptions, chosenOptions, chosenTreeFilteredOptions } = this.state;
299 let panelOptions;
300 if (isChosen) {
301 if (chosenTreeFilteredOptions) {
302 panelOptions = chosenOptions
303 .map(opt => Object.assign({}, opt))
304 .filter(item => treeUtils_1.filterTreeItemsWithoutFolders(item, chosenTreeFilteredOptions));
305 }
306 else {
307 panelOptions = chosenOptions;
308 }
309 }
310 else {
311 if (availableTreeFilteredOptions) {
312 panelOptions = availableOptions
313 .map(opt => Object.assign({}, opt))
314 .filter(item => treeUtils_1.filterTreeItemsWithoutFolders(item, availableTreeFilteredOptions));
315 }
316 else {
317 panelOptions = availableOptions;
318 }
319 }
320 const checkedOptionTree = panelOptions
321 .map(opt => Object.assign({}, opt))
322 .filter(item => treeUtils_1.filterTreeItems(item, [itemData.id]));
323 const flatTree = treeUtils_1.flattenTreeWithFolders(checkedOptionTree);
324 const prevChecked = isChosen ? this.state.chosenTreeOptionsChecked : this.state.availableTreeOptionsChecked;
325 let updatedChecked = [];
326 if (isChecked) {
327 updatedChecked = prevChecked.concat(flatTree.filter(id => !prevChecked.includes(id)));
328 }
329 else {
330 updatedChecked = prevChecked.filter(id => !flatTree.includes(id));
331 }
332 this.setState(prevState => ({
333 availableTreeOptionsChecked: isChosen ? prevState.availableTreeOptionsChecked : updatedChecked,
334 chosenTreeOptionsChecked: isChosen ? updatedChecked : prevState.chosenTreeOptionsChecked
335 }), () => {
336 this.props.onOptionCheck && this.props.onOptionCheck(evt, isChecked, itemData.id, updatedChecked);
337 });
338 };
339 this.state = {
340 availableOptions: [...this.props.availableOptions],
341 availableOptionsSelected: [],
342 availableFilteredOptions: null,
343 availableTreeFilteredOptions: null,
344 chosenOptions: [...this.props.chosenOptions],
345 chosenOptionsSelected: [],
346 chosenFilteredOptions: null,
347 chosenTreeFilteredOptions: null,
348 availableTreeOptionsChecked: [],
349 chosenTreeOptionsChecked: []
350 };
351 }
352 // If the DualListSelector uses trees, concat the two initial arrays and merge duplicate folder IDs
353 createMergedCopy() {
354 const copyOfAvailable = JSON.parse(JSON.stringify(this.props.availableOptions));
355 const copyOfChosen = JSON.parse(JSON.stringify(this.props.chosenOptions));
356 return this.props.isTree
357 ? Object.values(copyOfAvailable
358 .concat(copyOfChosen)
359 .reduce((mapObj, item) => {
360 const key = item.id;
361 if (mapObj[key]) {
362 // If map already has an item ID, add the dupe ID's children to the existing map
363 mapObj[key].children.push(...item.children);
364 }
365 else {
366 // Else clone the item data
367 mapObj[key] = Object.assign({}, item);
368 }
369 return mapObj;
370 }, {}))
371 : null;
372 }
373 componentDidUpdate() {
374 if (JSON.stringify(this.props.availableOptions) !== JSON.stringify(this.state.availableOptions) ||
375 JSON.stringify(this.props.chosenOptions) !== JSON.stringify(this.state.chosenOptions)) {
376 this.setState({
377 availableOptions: [...this.props.availableOptions],
378 chosenOptions: [...this.props.chosenOptions]
379 });
380 }
381 }
382 render() {
383 const _a = this.props, { availableOptionsTitle, availableOptionsActions, availableOptionsSearchAriaLabel, className, children, chosenOptionsTitle, chosenOptionsActions, chosenOptionsSearchAriaLabel, filterOption, isSearchable, chosenOptionsStatus, availableOptionsStatus, controlsAriaLabel, addAllAriaLabel, addSelectedAriaLabel, removeSelectedAriaLabel, removeAllAriaLabel,
384 /* eslint-disable @typescript-eslint/no-unused-vars */
385 availableOptions: consumerPassedAvailableOptions, chosenOptions: consumerPassedChosenOptions, removeSelected, addAll, removeAll, addSelected, onListChange, onAvailableOptionsSearchInputChanged, onChosenOptionsSearchInputChanged, onOptionSelect, onOptionCheck, id, isTree, isDisabled, addAllTooltip, addAllTooltipProps, addSelectedTooltip, addSelectedTooltipProps, removeAllTooltip, removeAllTooltipProps, removeSelectedTooltip, removeSelectedTooltipProps } = _a, props = tslib_1.__rest(_a, ["availableOptionsTitle", "availableOptionsActions", "availableOptionsSearchAriaLabel", "className", "children", "chosenOptionsTitle", "chosenOptionsActions", "chosenOptionsSearchAriaLabel", "filterOption", "isSearchable", "chosenOptionsStatus", "availableOptionsStatus", "controlsAriaLabel", "addAllAriaLabel", "addSelectedAriaLabel", "removeSelectedAriaLabel", "removeAllAriaLabel", "availableOptions", "chosenOptions", "removeSelected", "addAll", "removeAll", "addSelected", "onListChange", "onAvailableOptionsSearchInputChanged", "onChosenOptionsSearchInputChanged", "onOptionSelect", "onOptionCheck", "id", "isTree", "isDisabled", "addAllTooltip", "addAllTooltipProps", "addSelectedTooltip", "addSelectedTooltipProps", "removeAllTooltip", "removeAllTooltipProps", "removeSelectedTooltip", "removeSelectedTooltipProps"]);
386 const { availableOptions, chosenOptions, chosenOptionsSelected, availableOptionsSelected, chosenTreeOptionsChecked, availableTreeOptionsChecked } = this.state;
387 const availableOptionsStatusToDisplay = availableOptionsStatus ||
388 (isTree
389 ? `${treeUtils_1.filterFolders(availableOptions, availableTreeOptionsChecked).length} of ${treeUtils_1.flattenTree(availableOptions).length} items selected`
390 : `${availableOptionsSelected.length} of ${availableOptions.length} items selected`);
391 const chosenOptionsStatusToDisplay = chosenOptionsStatus ||
392 (isTree
393 ? `${treeUtils_1.filterFolders(chosenOptions, chosenTreeOptionsChecked).length} of ${treeUtils_1.flattenTree(chosenOptions).length} items selected`
394 : `${chosenOptionsSelected.length} of ${chosenOptions.length} items selected`);
395 const available = isTree
396 ? availableOptions.map(item => this.mapChecked(item, false))
397 : availableOptions;
398 const chosen = isTree
399 ? chosenOptions.map(item => this.mapChecked(item, true))
400 : chosenOptions;
401 return (React.createElement(DualListSelectorContext_1.DualListSelectorContext.Provider, { value: { isTree } },
402 React.createElement("div", Object.assign({ className: react_styles_1.css(dual_list_selector_1.default.dualListSelector, className), id: id }, props), children === '' ? (React.createElement(React.Fragment, null,
403 React.createElement(DualListSelectorPane_1.DualListSelectorPane, { isSearchable: isSearchable, onFilterUpdate: this.onFilterUpdate, searchInputAriaLabel: availableOptionsSearchAriaLabel, filterOption: filterOption, onSearchInputChanged: onAvailableOptionsSearchInputChanged, status: availableOptionsStatusToDisplay, title: availableOptionsTitle, options: available, selectedOptions: isTree ? availableTreeOptionsChecked : availableOptionsSelected, onOptionSelect: this.onOptionSelect, onOptionCheck: (e, isChecked, itemData) => this.onTreeOptionCheck(e, isChecked, itemData, false), actions: availableOptionsActions, id: `${id}-available-pane`, isDisabled: isDisabled }),
404 React.createElement(DualListSelectorControlsWrapper_1.DualListSelectorControlsWrapper, { "aria-label": controlsAriaLabel },
405 React.createElement(DualListSelectorControl_1.DualListSelectorControl, { isDisabled: (isTree ? availableTreeOptionsChecked.length === 0 : availableOptionsSelected.length === 0) ||
406 isDisabled, onClick: isTree ? this.addTreeSelected : this.addSelected, ref: this.addSelectedButtonRef, "aria-label": addSelectedAriaLabel, tooltipContent: addSelectedTooltip, tooltipProps: addSelectedTooltipProps },
407 React.createElement(angle_right_icon_1.default, null)),
408 React.createElement(DualListSelectorControl_1.DualListSelectorControl, { isDisabled: availableOptions.length === 0 || isDisabled, onClick: isTree ? this.addAllTreeVisible : this.addAllVisible, ref: this.addAllButtonRef, "aria-label": addAllAriaLabel, tooltipContent: addAllTooltip, tooltipProps: addAllTooltipProps },
409 React.createElement(angle_double_right_icon_1.default, null)),
410 React.createElement(DualListSelectorControl_1.DualListSelectorControl, { isDisabled: chosenOptions.length === 0 || isDisabled, onClick: isTree ? this.removeAllTreeVisible : this.removeAllVisible, "aria-label": removeAllAriaLabel, ref: this.removeAllButtonRef, tooltipContent: removeAllTooltip, tooltipProps: removeAllTooltipProps },
411 React.createElement(angle_double_left_icon_1.default, null)),
412 React.createElement(DualListSelectorControl_1.DualListSelectorControl, { onClick: isTree ? this.removeTreeSelected : this.removeSelected, isDisabled: (isTree ? chosenTreeOptionsChecked.length === 0 : chosenOptionsSelected.length === 0) || isDisabled, ref: this.removeSelectedButtonRef, "aria-label": removeSelectedAriaLabel, tooltipContent: removeSelectedTooltip, tooltipProps: removeSelectedTooltipProps },
413 React.createElement(angle_left_icon_1.default, null))),
414 React.createElement(DualListSelectorPane_1.DualListSelectorPane, { isChosen: true, isSearchable: isSearchable, onFilterUpdate: this.onFilterUpdate, searchInputAriaLabel: chosenOptionsSearchAriaLabel, filterOption: filterOption, onSearchInputChanged: onChosenOptionsSearchInputChanged, title: chosenOptionsTitle, status: chosenOptionsStatusToDisplay, options: chosen, selectedOptions: isTree ? chosenTreeOptionsChecked : chosenOptionsSelected, onOptionSelect: this.onOptionSelect, onOptionCheck: (e, isChecked, itemData) => this.onTreeOptionCheck(e, isChecked, itemData, true), actions: chosenOptionsActions, id: `${id}-chosen-pane`, isDisabled: isDisabled }))) : (children))));
415 }
416}
417exports.DualListSelector = DualListSelector;
418DualListSelector.displayName = 'DualListSelector';
419DualListSelector.defaultProps = {
420 children: '',
421 availableOptions: [],
422 availableOptionsTitle: 'Available options',
423 availableOptionsSearchAriaLabel: 'Available search input',
424 chosenOptions: [],
425 chosenOptionsTitle: 'Chosen options',
426 chosenOptionsSearchAriaLabel: 'Chosen search input',
427 id: helpers_1.getUniqueId('dual-list-selector'),
428 controlsAriaLabel: 'Selector controls',
429 addAllAriaLabel: 'Add all',
430 addSelectedAriaLabel: 'Add selected',
431 removeSelectedAriaLabel: 'Remove selected',
432 removeAllAriaLabel: 'Remove all',
433 isTree: false,
434 isDisabled: false
435};
436//# sourceMappingURL=DualListSelector.js.map
\No newline at end of file