@rhc-shared-components/form-multi-select-component 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,11 @@
1
- import * as React from 'react';
2
- import './styles.scss';
3
- export interface IMultiSelectItem {
4
- value: string;
5
- description?: string;
6
- isDisabled?: boolean;
7
- extraProps?: any;
8
- }
1
+ import React from 'react';
2
+ import { SelectOptionProps } from '@patternfly/react-core';
9
3
  export interface FormMultiSelectInputProps {
10
4
  name: string;
11
5
  label: string;
12
6
  isRequired?: boolean;
13
7
  placeholder?: string;
14
- selectOptions: IMultiSelectItem[];
8
+ selectMenuOptions: SelectOptionProps[];
15
9
  ariaLabel?: string;
16
10
  helperText?: string;
17
11
  maxHeight?: string | number;
@@ -20,5 +14,5 @@ export interface FormMultiSelectInputProps {
20
14
  menuAppendTo?: HTMLElement | (() => HTMLElement) | 'inline' | 'parent';
21
15
  extraProps?: any;
22
16
  }
23
- declare const FormMultiSelectInput: React.FC<FormMultiSelectInputProps>;
24
- export default FormMultiSelectInput;
17
+ declare const FormMultiSelectInput: React.FunctionComponent<FormMultiSelectInputProps>;
18
+ export { FormMultiSelectInput, SelectOptionProps as IMultiSelectInputOptionProps };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import FormMultiSelectInput, { IMultiSelectItem } from './FormMultiSelectInput';
2
- export { FormMultiSelectInput, IMultiSelectItem };
1
+ import { FormMultiSelectInput, IMultiSelectInputOptionProps } from './FormMultiSelectInput';
2
+ export { FormMultiSelectInput, IMultiSelectInputOptionProps };
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  var React = require('react');
2
- var formik = require('formik');
3
2
  var reactCore = require('@patternfly/react-core');
4
3
  var formGroupContainer = require('@rhc-shared-components/form-group-container');
5
- var lodash = require('lodash');
4
+ var formik = require('formik');
5
+
6
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
6
7
 
7
8
  function _interopNamespace(e) {
8
9
  if (e && e.__esModule) return e;
@@ -23,24 +24,7 @@ function _interopNamespace(e) {
23
24
  }
24
25
 
25
26
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
26
-
27
- function _extends() {
28
- _extends = Object.assign || function (target) {
29
- for (var i = 1; i < arguments.length; i++) {
30
- var source = arguments[i];
31
-
32
- for (var key in source) {
33
- if (Object.prototype.hasOwnProperty.call(source, key)) {
34
- target[key] = source[key];
35
- }
36
- }
37
- }
38
-
39
- return target;
40
- };
41
-
42
- return _extends.apply(this, arguments);
43
- }
27
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
44
28
 
45
29
  function _objectWithoutPropertiesLoose(source, excluded) {
46
30
  if (source == null) return {};
@@ -57,21 +41,86 @@ function _objectWithoutPropertiesLoose(source, excluded) {
57
41
  return target;
58
42
  }
59
43
 
60
- var _excluded = ["label", "isRequired", "children", "selectOptions", "ariaLabel", "placeholder", "helperText", "maxHeight", "isDisabled", "classNames", "menuAppendTo", "extraProps"];
44
+ /******************************************************************************
45
+ Copyright (c) Microsoft Corporation.
46
+
47
+ Permission to use, copy, modify, and/or distribute this software for any
48
+ purpose with or without fee is hereby granted.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
51
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
52
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
53
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
54
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
55
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
56
+ PERFORMANCE OF THIS SOFTWARE.
57
+ ***************************************************************************** */
58
+
59
+ function __rest(s, e) {
60
+ var t = {};
61
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
62
+ t[p] = s[p];
63
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
64
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
65
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
66
+ t[p[i]] = s[p[i]];
67
+ }
68
+ return t;
69
+ }
70
+
71
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
72
+ var e = new Error(message);
73
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
74
+ };
75
+
76
+ let currentId = 0;
77
+ /**
78
+ * Factory to create Icon class components for consumers
79
+ */
80
+ function createIcon({ name, xOffset = 0, yOffset = 0, width, height, svgPath }) {
81
+ var _a;
82
+ return _a = class SVGIcon extends React__namespace.Component {
83
+ constructor() {
84
+ super(...arguments);
85
+ this.id = `icon-title-${currentId++}`;
86
+ }
87
+ render() {
88
+ const _a = this.props, { title, className } = _a, props = __rest(_a, ["title", "className"]);
89
+ const classes = className ? `pf-v5-svg ${className}` : 'pf-v5-svg';
90
+ const hasTitle = Boolean(title);
91
+ const viewBox = [xOffset, yOffset, width, height].join(' ');
92
+ return (React__namespace.createElement("svg", Object.assign({ className: classes, viewBox: viewBox, fill: "currentColor", "aria-labelledby": hasTitle ? this.id : null, "aria-hidden": hasTitle ? null : true, role: "img", width: "1em", height: "1em" }, props),
93
+ hasTitle && React__namespace.createElement("title", { id: this.id }, title),
94
+ React__namespace.createElement("path", { d: svgPath })));
95
+ }
96
+ },
97
+ _a.displayName = name,
98
+ _a;
99
+ }
100
+
101
+ const TimesIconConfig = {
102
+ name: 'TimesIcon',
103
+ height: 512,
104
+ width: 352,
105
+ svgPath: 'M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z',
106
+ yOffset: 0,
107
+ xOffset: 0,
108
+ };
109
+
110
+ const TimesIcon = createIcon(TimesIconConfig);
111
+
112
+ var TimesIcon$1 = TimesIcon;
113
+
114
+ var _excluded = ["label", "isRequired", "children", "selectMenuOptions", "ariaLabel", "placeholder", "helperText", "maxHeight", "isDisabled", "classNames", "menuAppendTo", "extraProps"];
61
115
 
62
116
  var FormMultiSelectInput = function FormMultiSelectInput(_ref) {
63
117
  var label = _ref.label,
64
118
  isRequired = _ref.isRequired,
65
- selectOptions = _ref.selectOptions,
66
- _ref$ariaLabel = _ref.ariaLabel,
67
- ariaLabel = _ref$ariaLabel === void 0 ? 'Select Input' : _ref$ariaLabel,
119
+ selectMenuOptions = _ref.selectMenuOptions,
68
120
  placeholder = _ref.placeholder,
69
121
  helperText = _ref.helperText,
70
- maxHeight = _ref.maxHeight,
71
122
  isDisabled = _ref.isDisabled,
72
- classNames = _ref.classNames,
73
123
  menuAppendTo = _ref.menuAppendTo,
74
- extraProps = _ref.extraProps,
75
124
  rest = _objectWithoutPropertiesLoose(_ref, _excluded);
76
125
 
77
126
  var _useField = formik.useField(rest),
@@ -84,94 +133,241 @@ var FormMultiSelectInput = function FormMultiSelectInput(_ref) {
84
133
  setFieldValue = _useFormikContext.setFieldValue,
85
134
  setFieldTouched = _useFormikContext.setFieldTouched;
86
135
 
87
- var _React$useState = React__namespace.useState(false),
136
+ var _React$useState = React__default["default"].useState(false),
88
137
  isOpen = _React$useState[0],
89
138
  setIsOpen = _React$useState[1];
90
139
 
91
- var onSelect = function onSelect(_event, selection) {
92
- var newValues = value.includes(selection) ? value.filter(function (item) {
93
- return item !== selection;
94
- }) : [].concat(value, [selection]);
95
- setFieldTouched(rest.name, true, false);
96
- setFieldValue(rest.name, newValues, true);
97
- };
140
+ var _React$useState2 = React__default["default"].useState(''),
141
+ inputValue = _React$useState2[0],
142
+ setInputValue = _React$useState2[1];
143
+
144
+ var _React$useState3 = React__default["default"].useState([]),
145
+ selected = _React$useState3[0],
146
+ setSelected = _React$useState3[1];
147
+
148
+ var _React$useState4 = React__default["default"].useState(selectMenuOptions),
149
+ selectOptions = _React$useState4[0],
150
+ setSelectOptions = _React$useState4[1];
151
+
152
+ var _React$useState5 = React__default["default"].useState(null),
153
+ focusedItemIndex = _React$useState5[0],
154
+ setFocusedItemIndex = _React$useState5[1];
155
+
156
+ var _React$useState6 = React__default["default"].useState(null),
157
+ activeItem = _React$useState6[0],
158
+ setActiveItem = _React$useState6[1];
159
+
160
+ var textInputRef = React__default["default"].useRef();
161
+ React__default["default"].useEffect(function () {
162
+ var newSelectOptions = selectMenuOptions; // Filter menu items based on the text input value when one exists
163
+
164
+ if (inputValue) {
165
+ newSelectOptions = selectMenuOptions.filter(function (menuItem) {
166
+ return String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase());
167
+ }); // When no options are found after filtering, display 'No results found'
168
+
169
+ if (!newSelectOptions.length) {
170
+ newSelectOptions = [{
171
+ isDisabled: false,
172
+ children: "No results found for \"" + inputValue + "\"",
173
+ value: 'no results'
174
+ }];
175
+ } // Open the menu when the input value changes and the new value is not empty
176
+
98
177
 
99
- var disabledItems = selectOptions.filter(function (selectItem) {
100
- var _selectItem$extraProp;
101
-
102
- return (selectItem == null ? void 0 : selectItem.isDisabled) || (selectItem == null ? void 0 : (_selectItem$extraProp = selectItem.extraProps) == null ? void 0 : _selectItem$extraProp.isDisabled);
103
- });
104
-
105
- var CustomChipGroup = function CustomChipGroup() {
106
- return React__namespace.createElement(reactCore.ChipGroup, null, (value || []).map(function (currentValue) {
107
- return React__namespace.createElement(reactCore.Chip, {
108
- isReadOnly: !!disabledItems.find(function (item) {
109
- return item.value === currentValue;
110
- }),
111
- key: currentValue,
112
- onClick: function onClick(event) {
113
- return onSelect(event, currentValue);
178
+ if (!isOpen) {
179
+ setIsOpen(true);
180
+ }
181
+ }
182
+
183
+ setSelectOptions(newSelectOptions);
184
+ setFocusedItemIndex(null);
185
+ setActiveItem(null);
186
+ }, [inputValue]);
187
+ React__default["default"].useEffect(function () {
188
+ if ((value == null ? void 0 : value.length) > 0) {
189
+ setFieldValue(rest.name, value, true);
190
+ setSelected(value);
191
+ }
192
+ }, []);
193
+
194
+ var handleMenuArrowKeys = function handleMenuArrowKeys(key) {
195
+ var indexToFocus = 0;
196
+
197
+ if (isOpen) {
198
+ if (key === 'ArrowUp') {
199
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
200
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
201
+ indexToFocus = selectOptions.length - 1;
202
+ } else {
203
+ indexToFocus = focusedItemIndex - 1;
114
204
  }
115
- }, currentValue);
116
- }));
117
- };
205
+ }
118
206
 
119
- var onClear = function onClear() {
120
- var filtered = value == null ? void 0 : value.filter(function (item) {
121
- var _selectItem$extraProp2;
207
+ if (key === 'ArrowDown') {
208
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
209
+ if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
210
+ indexToFocus = 0;
211
+ } else {
212
+ indexToFocus = focusedItemIndex + 1;
213
+ }
214
+ }
215
+
216
+ setFocusedItemIndex(indexToFocus);
217
+ var focusedItem = selectOptions.filter(function (option) {
218
+ return !option.isDisabled;
219
+ })[indexToFocus];
220
+ setActiveItem("select-multi-typeahead-" + focusedItem.value.replace(' ', '-'));
221
+ }
222
+ };
122
223
 
123
- var selectItem = disabledItems.find(function (obj) {
124
- return obj.value === item;
125
- });
126
- return (selectItem == null ? void 0 : selectItem.isDisabled) || (selectItem == null ? void 0 : (_selectItem$extraProp2 = selectItem.extraProps) == null ? void 0 : _selectItem$extraProp2.isDisabled);
224
+ var onInputKeyDown = function onInputKeyDown(event) {
225
+ var enabledMenuItems = selectOptions.filter(function (menuItem) {
226
+ return !menuItem.isDisabled;
127
227
  });
128
- setFieldValue(rest.name, [].concat(filtered), true);
129
- setIsOpen(false);
228
+ var firstMenuItem = enabledMenuItems[0];
229
+ var focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
230
+
231
+ switch (event.key) {
232
+ // Select the first available option
233
+ case 'Enter':
234
+ if (!isOpen) {
235
+ setIsOpen(function (prevIsOpen) {
236
+ return !prevIsOpen;
237
+ });
238
+ } else if (isOpen && focusedItem.value !== 'no results') {
239
+ _onSelect(focusedItem.value);
240
+ }
241
+
242
+ break;
243
+
244
+ case 'Tab':
245
+ case 'Escape':
246
+ setIsOpen(false);
247
+ setActiveItem(null);
248
+ break;
249
+
250
+ case 'ArrowUp':
251
+ case 'ArrowDown':
252
+ event.preventDefault();
253
+ handleMenuArrowKeys(event.key);
254
+ break;
255
+ }
130
256
  };
131
257
 
132
- var onToggle = function onToggle() {
258
+ var onToggleClick = function onToggleClick() {
133
259
  setIsOpen(!isOpen);
134
260
  };
135
261
 
136
- return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(formGroupContainer.FormGroupContainer, {
262
+ var onTextInputChange = function onTextInputChange(_event, value) {
263
+ setInputValue(value);
264
+ };
265
+
266
+ var _onSelect = function onSelect(value) {
267
+ var _textInputRef$current;
268
+
269
+ // eslint-disable-next-line no-console
270
+ console.log('selected', value);
271
+
272
+ if (value && value !== 'no results') {
273
+ var selectedValues = selected.includes(value) ? selected.filter(function (selection) {
274
+ return selection !== value;
275
+ }) : [].concat(selected, [value]);
276
+ setSelected(selectedValues);
277
+ setFieldTouched(rest.name, true, false);
278
+ setFieldValue(rest.name, selectedValues, true);
279
+ } // eslint-disable-next-line no-unused-expressions
280
+
281
+
282
+ textInputRef == null ? void 0 : (_textInputRef$current = textInputRef.current) == null ? void 0 : _textInputRef$current.focus();
283
+ };
284
+
285
+ var toggle = function toggle(toggleRef) {
286
+ return React__default["default"].createElement(reactCore.MenuToggle, {
287
+ variant: 'typeahead',
288
+ onClick: onToggleClick,
289
+ innerRef: toggleRef,
290
+ isExpanded: isOpen,
291
+ isDisabled: isSubmitting || isDisabled,
292
+ isFullWidth: true
293
+ }, React__default["default"].createElement(reactCore.TextInputGroup, {
294
+ isPlain: true
295
+ }, React__default["default"].createElement(reactCore.TextInputGroupMain, Object.assign({
296
+ value: inputValue,
297
+ onClick: onToggleClick,
298
+ onChange: onTextInputChange,
299
+ onKeyDown: onInputKeyDown,
300
+ id: 'multi-typeahead-select-input',
301
+ autoComplete: 'off',
302
+ innerRef: textInputRef,
303
+ placeholder: placeholder
304
+ }, activeItem && {
305
+ 'aria-activedescendant': activeItem
306
+ }, {
307
+ role: 'combobox',
308
+ isExpanded: isOpen,
309
+ "aria-controls": 'select-multi-typeahead-listbox'
310
+ }), React__default["default"].createElement(reactCore.ChipGroup, {
311
+ "aria-label": 'Current selections'
312
+ }, selected.map(function (selection, index) {
313
+ return React__default["default"].createElement(reactCore.Chip, {
314
+ key: index,
315
+ onClick: function onClick(ev) {
316
+ ev.stopPropagation();
317
+
318
+ _onSelect(selection);
319
+ }
320
+ }, selection);
321
+ }))), React__default["default"].createElement(reactCore.TextInputGroupUtilities, null, selected.length > 0 && React__default["default"].createElement(reactCore.Button, {
322
+ variant: 'plain',
323
+ onClick: function onClick() {
324
+ var _textInputRef$current2;
325
+
326
+ setInputValue('');
327
+ setSelected([]); // eslint-disable-next-line no-unused-expressions
328
+
329
+ textInputRef == null ? void 0 : (_textInputRef$current2 = textInputRef.current) == null ? void 0 : _textInputRef$current2.focus();
330
+ },
331
+ "aria-label": 'Clear input value'
332
+ }, React__default["default"].createElement(TimesIcon$1, {
333
+ "aria-hidden": true
334
+ })))));
335
+ };
336
+
337
+ return React__default["default"].createElement(React__default["default"].Fragment, null, React__default["default"].createElement(formGroupContainer.FormGroupContainer, {
137
338
  validated: meta.touched && meta.error ? reactCore.ValidatedOptions.error : reactCore.ValidatedOptions["default"],
138
339
  helperTextInvalid: meta.error,
139
340
  isRequired: isRequired,
140
341
  fieldId: rest.name,
141
342
  label: label,
142
343
  helperText: helperText
143
- }, React__namespace.createElement(reactCore.Select, Object.assign({
144
- variant: reactCore.SelectVariant.typeaheadMulti,
145
- "aria-labelledby": rest.name,
146
- typeAheadAriaLabel: placeholder || 'Select the options',
147
- placeholderText: placeholder || 'Select the options',
148
- onToggle: onToggle,
149
- onSelect: onSelect,
150
- onClear: onClear,
151
- selections: value,
152
- isOpen: isOpen
153
- }, maxHeight && {
154
- maxHeight: maxHeight
155
- }, {
156
- isDisabled: isSubmitting || isDisabled,
157
- "aria-label": ariaLabel
158
- }, disabledItems && {
159
- chipGroupComponent: React__namespace.createElement(CustomChipGroup, null)
160
- }, classNames && {
161
- className: classNames
344
+ }, React__default["default"].createElement(reactCore.Select, Object.assign({
345
+ id: 'multi-typeahead-select',
346
+ isOpen: isOpen,
347
+ selected: selected,
348
+ onSelect: function onSelect(_ev, selection) {
349
+ return _onSelect(selection);
350
+ },
351
+ onOpenChange: function onOpenChange() {
352
+ return setIsOpen(false);
353
+ },
354
+ toggle: toggle
162
355
  }, menuAppendTo && {
163
356
  menuAppendTo: menuAppendTo
164
- }, extraProps && _extends({}, extraProps), rest), lodash.map(selectOptions, function (option, index) {
165
- return React__namespace.createElement(reactCore.SelectOption, Object.assign({
166
- key: index,
167
- value: option.value,
168
- label: option.value
169
- }, option.description && {
170
- description: option.description
171
- }, option.isDisabled && {
172
- isDisabled: option.isDisabled
173
- }, option.extraProps && _extends({}, option.extraProps)));
174
- }))));
357
+ }), React__default["default"].createElement(reactCore.SelectList, {
358
+ isAriaMultiselectable: true,
359
+ id: 'select-multi-typeahead-listbox'
360
+ }, selectOptions.map(function (option, index) {
361
+ return React__default["default"].createElement(reactCore.SelectOption, Object.assign({
362
+ key: option.value || option.children,
363
+ isFocused: focusedItemIndex === index,
364
+ className: option.className,
365
+ id: "select-multi-typeahead-" + option.value.replace(' ', '-'),
366
+ isDisabled: option.isDisabled || isSubmitting
367
+ }, option, {
368
+ ref: null
369
+ }));
370
+ })))));
175
371
  };
176
372
 
177
373
  exports.FormMultiSelectInput = FormMultiSelectInput;
@@ -1,26 +1,8 @@
1
1
  import * as React from 'react';
2
- import { useField, useFormikContext } from 'formik';
3
- import { ValidatedOptions, Select, SelectVariant, SelectOption, ChipGroup, Chip } from '@patternfly/react-core';
2
+ import React__default from 'react';
3
+ import { ValidatedOptions, Select, SelectList, SelectOption, MenuToggle, TextInputGroup, TextInputGroupMain, ChipGroup, Chip, TextInputGroupUtilities, Button } from '@patternfly/react-core';
4
4
  import { FormGroupContainer } from '@rhc-shared-components/form-group-container';
5
- import { map } from 'lodash';
6
-
7
- function _extends() {
8
- _extends = Object.assign || function (target) {
9
- for (var i = 1; i < arguments.length; i++) {
10
- var source = arguments[i];
11
-
12
- for (var key in source) {
13
- if (Object.prototype.hasOwnProperty.call(source, key)) {
14
- target[key] = source[key];
15
- }
16
- }
17
- }
18
-
19
- return target;
20
- };
21
-
22
- return _extends.apply(this, arguments);
23
- }
5
+ import { useField, useFormikContext } from 'formik';
24
6
 
25
7
  function _objectWithoutPropertiesLoose(source, excluded) {
26
8
  if (source == null) return {};
@@ -37,21 +19,88 @@ function _objectWithoutPropertiesLoose(source, excluded) {
37
19
  return target;
38
20
  }
39
21
 
40
- const _excluded = ["label", "isRequired", "children", "selectOptions", "ariaLabel", "placeholder", "helperText", "maxHeight", "isDisabled", "classNames", "menuAppendTo", "extraProps"];
22
+ /******************************************************************************
23
+ Copyright (c) Microsoft Corporation.
24
+
25
+ Permission to use, copy, modify, and/or distribute this software for any
26
+ purpose with or without fee is hereby granted.
27
+
28
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
29
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
30
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
31
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
32
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
33
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
34
+ PERFORMANCE OF THIS SOFTWARE.
35
+ ***************************************************************************** */
36
+
37
+ function __rest(s, e) {
38
+ var t = {};
39
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
40
+ t[p] = s[p];
41
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
42
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
43
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
44
+ t[p[i]] = s[p[i]];
45
+ }
46
+ return t;
47
+ }
48
+
49
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ };
53
+
54
+ let currentId = 0;
55
+ /**
56
+ * Factory to create Icon class components for consumers
57
+ */
58
+ function createIcon({ name, xOffset = 0, yOffset = 0, width, height, svgPath }) {
59
+ var _a;
60
+ return _a = class SVGIcon extends React.Component {
61
+ constructor() {
62
+ super(...arguments);
63
+ this.id = `icon-title-${currentId++}`;
64
+ }
65
+ render() {
66
+ const _a = this.props, { title, className } = _a, props = __rest(_a, ["title", "className"]);
67
+ const classes = className ? `pf-v5-svg ${className}` : 'pf-v5-svg';
68
+ const hasTitle = Boolean(title);
69
+ const viewBox = [xOffset, yOffset, width, height].join(' ');
70
+ return (React.createElement("svg", Object.assign({ className: classes, viewBox: viewBox, fill: "currentColor", "aria-labelledby": hasTitle ? this.id : null, "aria-hidden": hasTitle ? null : true, role: "img", width: "1em", height: "1em" }, props),
71
+ hasTitle && React.createElement("title", { id: this.id }, title),
72
+ React.createElement("path", { d: svgPath })));
73
+ }
74
+ },
75
+ _a.displayName = name,
76
+ _a;
77
+ }
78
+
79
+ const TimesIconConfig = {
80
+ name: 'TimesIcon',
81
+ height: 512,
82
+ width: 352,
83
+ svgPath: 'M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z',
84
+ yOffset: 0,
85
+ xOffset: 0,
86
+ };
87
+
88
+ const TimesIcon = createIcon(TimesIconConfig);
89
+
90
+ var TimesIcon$1 = TimesIcon;
91
+
92
+ const _excluded = ["label", "isRequired", "children", "selectMenuOptions", "ariaLabel", "placeholder", "helperText", "maxHeight", "isDisabled", "classNames", "menuAppendTo", "extraProps"];
41
93
 
42
94
  const FormMultiSelectInput = _ref => {
43
95
  let {
44
96
  label,
45
97
  isRequired,
46
- selectOptions,
98
+ selectMenuOptions,
47
99
  ariaLabel = 'Select Input',
48
100
  placeholder,
49
101
  helperText,
50
- maxHeight,
51
102
  isDisabled,
52
- classNames,
53
- menuAppendTo,
54
- extraProps
103
+ menuAppendTo
55
104
  } = _ref,
56
105
  rest = _objectWithoutPropertiesLoose(_ref, _excluded);
57
106
 
@@ -64,78 +113,202 @@ const FormMultiSelectInput = _ref => {
64
113
  setFieldValue,
65
114
  setFieldTouched
66
115
  } = useFormikContext();
67
- const [isOpen, setIsOpen] = React.useState(false);
116
+ const [isOpen, setIsOpen] = React__default.useState(false);
117
+ const [inputValue, setInputValue] = React__default.useState('');
118
+ const [selected, setSelected] = React__default.useState([]);
119
+ const [selectOptions, setSelectOptions] = React__default.useState(selectMenuOptions);
120
+ const [focusedItemIndex, setFocusedItemIndex] = React__default.useState(null);
121
+ const [activeItem, setActiveItem] = React__default.useState(null);
122
+ const textInputRef = React__default.useRef();
123
+ React__default.useEffect(() => {
124
+ let newSelectOptions = selectMenuOptions; // Filter menu items based on the text input value when one exists
125
+
126
+ if (inputValue) {
127
+ newSelectOptions = selectMenuOptions.filter(menuItem => String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase())); // When no options are found after filtering, display 'No results found'
128
+
129
+ if (!newSelectOptions.length) {
130
+ newSelectOptions = [{
131
+ isDisabled: false,
132
+ children: `No results found for "${inputValue}"`,
133
+ value: 'no results'
134
+ }];
135
+ } // Open the menu when the input value changes and the new value is not empty
136
+
137
+
138
+ if (!isOpen) {
139
+ setIsOpen(true);
140
+ }
141
+ }
142
+
143
+ setSelectOptions(newSelectOptions);
144
+ setFocusedItemIndex(null);
145
+ setActiveItem(null);
146
+ }, [inputValue]);
147
+ React__default.useEffect(() => {
148
+ if ((value == null ? void 0 : value.length) > 0) {
149
+ setFieldValue(rest.name, value, true);
150
+ setSelected(value);
151
+ }
152
+ }, []);
153
+
154
+ const handleMenuArrowKeys = key => {
155
+ let indexToFocus = 0;
156
+
157
+ if (isOpen) {
158
+ if (key === 'ArrowUp') {
159
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
160
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
161
+ indexToFocus = selectOptions.length - 1;
162
+ } else {
163
+ indexToFocus = focusedItemIndex - 1;
164
+ }
165
+ }
166
+
167
+ if (key === 'ArrowDown') {
168
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
169
+ if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
170
+ indexToFocus = 0;
171
+ } else {
172
+ indexToFocus = focusedItemIndex + 1;
173
+ }
174
+ }
68
175
 
69
- const onSelect = (_event, selection) => {
70
- const newValues = value.includes(selection) ? value.filter(item => item !== selection) : [...value, selection];
71
- setFieldTouched(rest.name, true, false);
72
- setFieldValue(rest.name, newValues, true);
176
+ setFocusedItemIndex(indexToFocus);
177
+ const focusedItem = selectOptions.filter(option => !option.isDisabled)[indexToFocus];
178
+ setActiveItem(`select-multi-typeahead-${focusedItem.value.replace(' ', '-')}`);
179
+ }
73
180
  };
74
181
 
75
- const disabledItems = selectOptions.filter(selectItem => {
76
- var _selectItem$extraProp;
182
+ const onInputKeyDown = event => {
183
+ const enabledMenuItems = selectOptions.filter(menuItem => !menuItem.isDisabled);
184
+ const [firstMenuItem] = enabledMenuItems;
185
+ const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
77
186
 
78
- return (selectItem == null ? void 0 : selectItem.isDisabled) || (selectItem == null ? void 0 : (_selectItem$extraProp = selectItem.extraProps) == null ? void 0 : _selectItem$extraProp.isDisabled);
79
- });
187
+ switch (event.key) {
188
+ // Select the first available option
189
+ case 'Enter':
190
+ if (!isOpen) {
191
+ setIsOpen(prevIsOpen => !prevIsOpen);
192
+ } else if (isOpen && focusedItem.value !== 'no results') {
193
+ onSelect(focusedItem.value);
194
+ }
80
195
 
81
- const CustomChipGroup = () => React.createElement(ChipGroup, null, (value || []).map(currentValue => React.createElement(Chip, {
82
- isReadOnly: !!disabledItems.find(item => item.value === currentValue),
83
- key: currentValue,
84
- onClick: event => onSelect(event, currentValue)
85
- }, currentValue)));
196
+ break;
86
197
 
87
- const onClear = () => {
88
- const filtered = value == null ? void 0 : value.filter(item => {
89
- var _selectItem$extraProp2;
198
+ case 'Tab':
199
+ case 'Escape':
200
+ setIsOpen(false);
201
+ setActiveItem(null);
202
+ break;
90
203
 
91
- const selectItem = disabledItems.find(obj => obj.value === item);
92
- return (selectItem == null ? void 0 : selectItem.isDisabled) || (selectItem == null ? void 0 : (_selectItem$extraProp2 = selectItem.extraProps) == null ? void 0 : _selectItem$extraProp2.isDisabled);
93
- });
94
- setFieldValue(rest.name, [...filtered], true);
95
- setIsOpen(false);
204
+ case 'ArrowUp':
205
+ case 'ArrowDown':
206
+ event.preventDefault();
207
+ handleMenuArrowKeys(event.key);
208
+ break;
209
+ }
96
210
  };
97
211
 
98
- const onToggle = () => {
212
+ const onToggleClick = () => {
99
213
  setIsOpen(!isOpen);
100
214
  };
101
215
 
102
- return React.createElement(React.Fragment, null, React.createElement(FormGroupContainer, {
216
+ const onTextInputChange = (_event, value) => {
217
+ setInputValue(value);
218
+ };
219
+
220
+ const onSelect = value => {
221
+ var _textInputRef$current;
222
+
223
+ // eslint-disable-next-line no-console
224
+ console.log('selected', value);
225
+
226
+ if (value && value !== 'no results') {
227
+ const selectedValues = selected.includes(value) ? selected.filter(selection => selection !== value) : [...selected, value];
228
+ setSelected(selectedValues);
229
+ setFieldTouched(rest.name, true, false);
230
+ setFieldValue(rest.name, selectedValues, true);
231
+ } // eslint-disable-next-line no-unused-expressions
232
+
233
+
234
+ textInputRef == null ? void 0 : (_textInputRef$current = textInputRef.current) == null ? void 0 : _textInputRef$current.focus();
235
+ };
236
+
237
+ const toggle = toggleRef => React__default.createElement(MenuToggle, {
238
+ variant: 'typeahead',
239
+ onClick: onToggleClick,
240
+ innerRef: toggleRef,
241
+ isExpanded: isOpen,
242
+ isDisabled: isSubmitting || isDisabled,
243
+ isFullWidth: true
244
+ }, React__default.createElement(TextInputGroup, {
245
+ isPlain: true
246
+ }, React__default.createElement(TextInputGroupMain, Object.assign({
247
+ value: inputValue,
248
+ onClick: onToggleClick,
249
+ onChange: onTextInputChange,
250
+ onKeyDown: onInputKeyDown,
251
+ id: 'multi-typeahead-select-input',
252
+ autoComplete: 'off',
253
+ innerRef: textInputRef,
254
+ placeholder: placeholder
255
+ }, activeItem && {
256
+ 'aria-activedescendant': activeItem
257
+ }, {
258
+ role: 'combobox',
259
+ isExpanded: isOpen,
260
+ "aria-controls": 'select-multi-typeahead-listbox'
261
+ }), React__default.createElement(ChipGroup, {
262
+ "aria-label": 'Current selections'
263
+ }, selected.map((selection, index) => React__default.createElement(Chip, {
264
+ key: index,
265
+ onClick: ev => {
266
+ ev.stopPropagation();
267
+ onSelect(selection);
268
+ }
269
+ }, selection)))), React__default.createElement(TextInputGroupUtilities, null, selected.length > 0 && React__default.createElement(Button, {
270
+ variant: 'plain',
271
+ onClick: () => {
272
+ var _textInputRef$current2;
273
+
274
+ setInputValue('');
275
+ setSelected([]); // eslint-disable-next-line no-unused-expressions
276
+
277
+ textInputRef == null ? void 0 : (_textInputRef$current2 = textInputRef.current) == null ? void 0 : _textInputRef$current2.focus();
278
+ },
279
+ "aria-label": 'Clear input value'
280
+ }, React__default.createElement(TimesIcon$1, {
281
+ "aria-hidden": true
282
+ })))));
283
+
284
+ return React__default.createElement(React__default.Fragment, null, React__default.createElement(FormGroupContainer, {
103
285
  validated: meta.touched && meta.error ? ValidatedOptions.error : ValidatedOptions.default,
104
286
  helperTextInvalid: meta.error,
105
287
  isRequired: isRequired,
106
288
  fieldId: rest.name,
107
289
  label: label,
108
290
  helperText: helperText
109
- }, React.createElement(Select, Object.assign({
110
- variant: SelectVariant.typeaheadMulti,
111
- "aria-labelledby": rest.name,
112
- typeAheadAriaLabel: placeholder || 'Select the options',
113
- placeholderText: placeholder || 'Select the options',
114
- onToggle: onToggle,
115
- onSelect: onSelect,
116
- onClear: onClear,
117
- selections: value,
118
- isOpen: isOpen
119
- }, maxHeight && {
120
- maxHeight
121
- }, {
122
- isDisabled: isSubmitting || isDisabled,
123
- "aria-label": ariaLabel
124
- }, disabledItems && {
125
- chipGroupComponent: React.createElement(CustomChipGroup, null)
126
- }, classNames && {
127
- className: classNames
291
+ }, React__default.createElement(Select, Object.assign({
292
+ id: 'multi-typeahead-select',
293
+ isOpen: isOpen,
294
+ selected: selected,
295
+ onSelect: (_ev, selection) => onSelect(selection),
296
+ onOpenChange: () => setIsOpen(false),
297
+ toggle: toggle
128
298
  }, menuAppendTo && {
129
299
  menuAppendTo
130
- }, extraProps && _extends({}, extraProps), rest), map(selectOptions, (option, index) => React.createElement(SelectOption, Object.assign({
131
- key: index,
132
- value: option.value,
133
- label: option.value
134
- }, option.description && {
135
- description: option.description
136
- }, option.isDisabled && {
137
- isDisabled: option.isDisabled
138
- }, option.extraProps && _extends({}, option.extraProps)))))));
300
+ }), React__default.createElement(SelectList, {
301
+ isAriaMultiselectable: true,
302
+ id: 'select-multi-typeahead-listbox'
303
+ }, selectOptions.map((option, index) => React__default.createElement(SelectOption, Object.assign({
304
+ key: option.value || option.children,
305
+ isFocused: focusedItemIndex === index,
306
+ className: option.className,
307
+ id: `select-multi-typeahead-${option.value.replace(' ', '-')}`,
308
+ isDisabled: option.isDisabled || isSubmitting
309
+ }, option, {
310
+ ref: null
311
+ })))))));
139
312
  };
140
313
 
141
314
  export { FormMultiSelectInput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhc-shared-components/form-multi-select-component",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "project description",
5
5
  "author": "shkale",
6
6
  "license": "MIT",
@@ -62,8 +62,8 @@
62
62
  "typescript": "^3.7.5"
63
63
  },
64
64
  "dependencies": {
65
- "@patternfly/react-core": "^4.276.6",
66
- "@rhc-shared-components/form-group-container": "^0.3.2",
65
+ "@patternfly/react-core": "5.3.0",
66
+ "@rhc-shared-components/form-group-container": "^0.4.0",
67
67
  "@types/lodash": "^4.14.177",
68
68
  "formik": "^2.1.4",
69
69
  "lodash": "^4.17.21",