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

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>;
17
+ declare const FormMultiSelectInput: React.FunctionComponent<FormMultiSelectInputProps>;
24
18
  export default FormMultiSelectInput;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import FormMultiSelectInput, { IMultiSelectItem } from './FormMultiSelectInput';
2
- export { FormMultiSelectInput, IMultiSelectItem };
1
+ import FormMultiSelectInput from './FormMultiSelectInput';
2
+ export { FormMultiSelectInput };
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,121 +41,325 @@ 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),
78
127
  meta = _useField[1];
79
128
 
80
- var value = meta.value;
81
-
82
129
  var _useFormikContext = formik.useFormikContext(),
83
130
  isSubmitting = _useFormikContext.isSubmitting,
84
131
  setFieldValue = _useFormikContext.setFieldValue,
85
132
  setFieldTouched = _useFormikContext.setFieldTouched;
86
133
 
87
- var _React$useState = React__namespace.useState(false),
134
+ var _React$useState = React__default["default"].useState(false),
88
135
  isOpen = _React$useState[0],
89
136
  setIsOpen = _React$useState[1];
90
137
 
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
- };
138
+ var _React$useState2 = React__default["default"].useState(''),
139
+ inputValue = _React$useState2[0],
140
+ setInputValue = _React$useState2[1];
141
+
142
+ var _React$useState3 = React__default["default"].useState([]),
143
+ selected = _React$useState3[0],
144
+ setSelected = _React$useState3[1];
145
+
146
+ var _React$useState4 = React__default["default"].useState(selectMenuOptions),
147
+ selectOptions = _React$useState4[0],
148
+ setSelectOptions = _React$useState4[1];
149
+
150
+ var _React$useState5 = React__default["default"].useState(null),
151
+ focusedItemIndex = _React$useState5[0],
152
+ setFocusedItemIndex = _React$useState5[1];
153
+
154
+ var _React$useState6 = React__default["default"].useState(null),
155
+ activeItem = _React$useState6[0],
156
+ setActiveItem = _React$useState6[1];
157
+
158
+ var textInputRef = React__default["default"].useRef();
159
+ React__default["default"].useEffect(function () {
160
+ var newSelectOptions = selectMenuOptions; // Filter menu items based on the text input value when one exists
161
+
162
+ if (inputValue) {
163
+ newSelectOptions = selectMenuOptions.filter(function (menuItem) {
164
+ return String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase());
165
+ }); // When no options are found after filtering, display 'No results found'
166
+
167
+ if (!newSelectOptions.length) {
168
+ newSelectOptions = [{
169
+ isDisabled: false,
170
+ children: "No results found for \"" + inputValue + "\"",
171
+ value: 'no results'
172
+ }];
173
+ } // Open the menu when the input value changes and the new value is not empty
98
174
 
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);
175
+
176
+ if (!isOpen) {
177
+ setIsOpen(true);
178
+ }
179
+ }
180
+
181
+ setSelectOptions(newSelectOptions);
182
+ setFocusedItemIndex(null);
183
+ setActiveItem(null);
184
+ }, [inputValue]);
185
+
186
+ var handleMenuArrowKeys = function handleMenuArrowKeys(key) {
187
+ var indexToFocus = 0;
188
+
189
+ if (isOpen) {
190
+ if (key === 'ArrowUp') {
191
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
192
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
193
+ indexToFocus = selectOptions.length - 1;
194
+ } else {
195
+ indexToFocus = focusedItemIndex - 1;
114
196
  }
115
- }, currentValue);
116
- }));
117
- };
197
+ }
118
198
 
119
- var onClear = function onClear() {
120
- var filtered = value == null ? void 0 : value.filter(function (item) {
121
- var _selectItem$extraProp2;
199
+ if (key === 'ArrowDown') {
200
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
201
+ if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
202
+ indexToFocus = 0;
203
+ } else {
204
+ indexToFocus = focusedItemIndex + 1;
205
+ }
206
+ }
207
+
208
+ setFocusedItemIndex(indexToFocus);
209
+ var focusedItem = selectOptions.filter(function (option) {
210
+ return !option.isDisabled;
211
+ })[indexToFocus];
212
+ setActiveItem("select-multi-typeahead-" + focusedItem.value.replace(' ', '-'));
213
+ }
214
+ };
122
215
 
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);
216
+ var onInputKeyDown = function onInputKeyDown(event) {
217
+ var enabledMenuItems = selectOptions.filter(function (menuItem) {
218
+ return !menuItem.isDisabled;
127
219
  });
128
- setFieldValue(rest.name, [].concat(filtered), true);
129
- setIsOpen(false);
220
+ var firstMenuItem = enabledMenuItems[0];
221
+ var focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
222
+
223
+ switch (event.key) {
224
+ // Select the first available option
225
+ case 'Enter':
226
+ if (!isOpen) {
227
+ setIsOpen(function (prevIsOpen) {
228
+ return !prevIsOpen;
229
+ });
230
+ } else if (isOpen && focusedItem.value !== 'no results') {
231
+ _onSelect(focusedItem.value);
232
+ }
233
+
234
+ break;
235
+
236
+ case 'Tab':
237
+ case 'Escape':
238
+ setIsOpen(false);
239
+ setActiveItem(null);
240
+ break;
241
+
242
+ case 'ArrowUp':
243
+ case 'ArrowDown':
244
+ event.preventDefault();
245
+ handleMenuArrowKeys(event.key);
246
+ break;
247
+ }
130
248
  };
131
249
 
132
- var onToggle = function onToggle() {
250
+ var onToggleClick = function onToggleClick() {
133
251
  setIsOpen(!isOpen);
134
252
  };
135
253
 
136
- return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(formGroupContainer.FormGroupContainer, {
254
+ var onTextInputChange = function onTextInputChange(_event, value) {
255
+ setInputValue(value);
256
+ };
257
+
258
+ var _onSelect = function onSelect(value) {
259
+ var _textInputRef$current;
260
+
261
+ // eslint-disable-next-line no-console
262
+ console.log('selected', value);
263
+
264
+ if (value && value !== 'no results') {
265
+ var selectedValues = selected.includes(value) ? selected.filter(function (selection) {
266
+ return selection !== value;
267
+ }) : [].concat(selected, [value]);
268
+ setSelected(selectedValues);
269
+ setFieldTouched(rest.name, true, false);
270
+ setFieldValue(rest.name, selectedValues, true);
271
+ } // eslint-disable-next-line no-unused-expressions
272
+
273
+
274
+ textInputRef == null ? void 0 : (_textInputRef$current = textInputRef.current) == null ? void 0 : _textInputRef$current.focus();
275
+ };
276
+
277
+ var toggle = function toggle(toggleRef) {
278
+ return React__default["default"].createElement(reactCore.MenuToggle, {
279
+ variant: 'typeahead',
280
+ onClick: onToggleClick,
281
+ innerRef: toggleRef,
282
+ isExpanded: isOpen,
283
+ isDisabled: isSubmitting || isDisabled,
284
+ isFullWidth: true
285
+ }, React__default["default"].createElement(reactCore.TextInputGroup, {
286
+ isPlain: true
287
+ }, React__default["default"].createElement(reactCore.TextInputGroupMain, Object.assign({
288
+ value: inputValue,
289
+ onClick: onToggleClick,
290
+ onChange: onTextInputChange,
291
+ onKeyDown: onInputKeyDown,
292
+ id: 'multi-typeahead-select-input',
293
+ autoComplete: 'off',
294
+ innerRef: textInputRef,
295
+ placeholder: placeholder
296
+ }, activeItem && {
297
+ 'aria-activedescendant': activeItem
298
+ }, {
299
+ role: 'combobox',
300
+ isExpanded: isOpen,
301
+ "aria-controls": 'select-multi-typeahead-listbox'
302
+ }), React__default["default"].createElement(reactCore.ChipGroup, {
303
+ "aria-label": 'Current selections'
304
+ }, selected.map(function (selection, index) {
305
+ return React__default["default"].createElement(reactCore.Chip, {
306
+ key: index,
307
+ onClick: function onClick(ev) {
308
+ ev.stopPropagation();
309
+
310
+ _onSelect(selection);
311
+ }
312
+ }, selection);
313
+ }))), React__default["default"].createElement(reactCore.TextInputGroupUtilities, null, selected.length > 0 && React__default["default"].createElement(reactCore.Button, {
314
+ variant: 'plain',
315
+ onClick: function onClick() {
316
+ var _textInputRef$current2;
317
+
318
+ setInputValue('');
319
+ setSelected([]); // eslint-disable-next-line no-unused-expressions
320
+
321
+ textInputRef == null ? void 0 : (_textInputRef$current2 = textInputRef.current) == null ? void 0 : _textInputRef$current2.focus();
322
+ },
323
+ "aria-label": 'Clear input value'
324
+ }, React__default["default"].createElement(TimesIcon$1, {
325
+ "aria-hidden": true
326
+ })))));
327
+ };
328
+
329
+ return React__default["default"].createElement(React__default["default"].Fragment, null, React__default["default"].createElement(formGroupContainer.FormGroupContainer, {
137
330
  validated: meta.touched && meta.error ? reactCore.ValidatedOptions.error : reactCore.ValidatedOptions["default"],
138
331
  helperTextInvalid: meta.error,
139
332
  isRequired: isRequired,
140
333
  fieldId: rest.name,
141
334
  label: label,
142
335
  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
336
+ }, React__default["default"].createElement(reactCore.Select, Object.assign({
337
+ id: 'multi-typeahead-select',
338
+ isOpen: isOpen,
339
+ selected: selected,
340
+ onSelect: function onSelect(_ev, selection) {
341
+ return _onSelect(selection);
342
+ },
343
+ onOpenChange: function onOpenChange() {
344
+ return setIsOpen(false);
345
+ },
346
+ toggle: toggle
162
347
  }, menuAppendTo && {
163
348
  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 && {
349
+ }), React__default["default"].createElement(reactCore.SelectList, {
350
+ isAriaMultiselectable: true,
351
+ id: 'select-multi-typeahead-listbox'
352
+ }, selectOptions.map(function (option, index) {
353
+ return React__default["default"].createElement(reactCore.SelectOption, Object.assign({
354
+ key: option.value || option.children,
355
+ isFocused: focusedItemIndex === index,
356
+ className: option.className,
357
+ id: "select-multi-typeahead-" + option.value.replace(' ', '-'),
172
358
  isDisabled: option.isDisabled
173
- }, option.extraProps && _extends({}, option.extraProps)));
174
- }))));
359
+ }, option, {
360
+ ref: null
361
+ }));
362
+ })))));
175
363
  };
176
364
 
177
365
  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,105 +19,287 @@ 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
 
58
107
  const [, meta] = useField(rest);
59
- const {
60
- value
61
- } = meta;
62
108
  const {
63
109
  isSubmitting,
64
110
  setFieldValue,
65
111
  setFieldTouched
66
112
  } = useFormikContext();
67
- const [isOpen, setIsOpen] = React.useState(false);
113
+ const [isOpen, setIsOpen] = React__default.useState(false);
114
+ const [inputValue, setInputValue] = React__default.useState('');
115
+ const [selected, setSelected] = React__default.useState([]);
116
+ const [selectOptions, setSelectOptions] = React__default.useState(selectMenuOptions);
117
+ const [focusedItemIndex, setFocusedItemIndex] = React__default.useState(null);
118
+ const [activeItem, setActiveItem] = React__default.useState(null);
119
+ const textInputRef = React__default.useRef();
120
+ React__default.useEffect(() => {
121
+ let newSelectOptions = selectMenuOptions; // Filter menu items based on the text input value when one exists
122
+
123
+ if (inputValue) {
124
+ newSelectOptions = selectMenuOptions.filter(menuItem => String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase())); // When no options are found after filtering, display 'No results found'
125
+
126
+ if (!newSelectOptions.length) {
127
+ newSelectOptions = [{
128
+ isDisabled: false,
129
+ children: `No results found for "${inputValue}"`,
130
+ value: 'no results'
131
+ }];
132
+ } // Open the menu when the input value changes and the new value is not empty
133
+
134
+
135
+ if (!isOpen) {
136
+ setIsOpen(true);
137
+ }
138
+ }
139
+
140
+ setSelectOptions(newSelectOptions);
141
+ setFocusedItemIndex(null);
142
+ setActiveItem(null);
143
+ }, [inputValue]);
144
+
145
+ const handleMenuArrowKeys = key => {
146
+ let indexToFocus = 0;
147
+
148
+ if (isOpen) {
149
+ if (key === 'ArrowUp') {
150
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
151
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
152
+ indexToFocus = selectOptions.length - 1;
153
+ } else {
154
+ indexToFocus = focusedItemIndex - 1;
155
+ }
156
+ }
157
+
158
+ if (key === 'ArrowDown') {
159
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
160
+ if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
161
+ indexToFocus = 0;
162
+ } else {
163
+ indexToFocus = focusedItemIndex + 1;
164
+ }
165
+ }
68
166
 
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);
167
+ setFocusedItemIndex(indexToFocus);
168
+ const focusedItem = selectOptions.filter(option => !option.isDisabled)[indexToFocus];
169
+ setActiveItem(`select-multi-typeahead-${focusedItem.value.replace(' ', '-')}`);
170
+ }
73
171
  };
74
172
 
75
- const disabledItems = selectOptions.filter(selectItem => {
76
- var _selectItem$extraProp;
173
+ const onInputKeyDown = event => {
174
+ const enabledMenuItems = selectOptions.filter(menuItem => !menuItem.isDisabled);
175
+ const [firstMenuItem] = enabledMenuItems;
176
+ const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
77
177
 
78
- return (selectItem == null ? void 0 : selectItem.isDisabled) || (selectItem == null ? void 0 : (_selectItem$extraProp = selectItem.extraProps) == null ? void 0 : _selectItem$extraProp.isDisabled);
79
- });
178
+ switch (event.key) {
179
+ // Select the first available option
180
+ case 'Enter':
181
+ if (!isOpen) {
182
+ setIsOpen(prevIsOpen => !prevIsOpen);
183
+ } else if (isOpen && focusedItem.value !== 'no results') {
184
+ onSelect(focusedItem.value);
185
+ }
80
186
 
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)));
187
+ break;
86
188
 
87
- const onClear = () => {
88
- const filtered = value == null ? void 0 : value.filter(item => {
89
- var _selectItem$extraProp2;
189
+ case 'Tab':
190
+ case 'Escape':
191
+ setIsOpen(false);
192
+ setActiveItem(null);
193
+ break;
90
194
 
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);
195
+ case 'ArrowUp':
196
+ case 'ArrowDown':
197
+ event.preventDefault();
198
+ handleMenuArrowKeys(event.key);
199
+ break;
200
+ }
96
201
  };
97
202
 
98
- const onToggle = () => {
203
+ const onToggleClick = () => {
99
204
  setIsOpen(!isOpen);
100
205
  };
101
206
 
102
- return React.createElement(React.Fragment, null, React.createElement(FormGroupContainer, {
207
+ const onTextInputChange = (_event, value) => {
208
+ setInputValue(value);
209
+ };
210
+
211
+ const onSelect = value => {
212
+ var _textInputRef$current;
213
+
214
+ // eslint-disable-next-line no-console
215
+ console.log('selected', value);
216
+
217
+ if (value && value !== 'no results') {
218
+ const selectedValues = selected.includes(value) ? selected.filter(selection => selection !== value) : [...selected, value];
219
+ setSelected(selectedValues);
220
+ setFieldTouched(rest.name, true, false);
221
+ setFieldValue(rest.name, selectedValues, true);
222
+ } // eslint-disable-next-line no-unused-expressions
223
+
224
+
225
+ textInputRef == null ? void 0 : (_textInputRef$current = textInputRef.current) == null ? void 0 : _textInputRef$current.focus();
226
+ };
227
+
228
+ const toggle = toggleRef => React__default.createElement(MenuToggle, {
229
+ variant: 'typeahead',
230
+ onClick: onToggleClick,
231
+ innerRef: toggleRef,
232
+ isExpanded: isOpen,
233
+ isDisabled: isSubmitting || isDisabled,
234
+ isFullWidth: true
235
+ }, React__default.createElement(TextInputGroup, {
236
+ isPlain: true
237
+ }, React__default.createElement(TextInputGroupMain, Object.assign({
238
+ value: inputValue,
239
+ onClick: onToggleClick,
240
+ onChange: onTextInputChange,
241
+ onKeyDown: onInputKeyDown,
242
+ id: 'multi-typeahead-select-input',
243
+ autoComplete: 'off',
244
+ innerRef: textInputRef,
245
+ placeholder: placeholder
246
+ }, activeItem && {
247
+ 'aria-activedescendant': activeItem
248
+ }, {
249
+ role: 'combobox',
250
+ isExpanded: isOpen,
251
+ "aria-controls": 'select-multi-typeahead-listbox'
252
+ }), React__default.createElement(ChipGroup, {
253
+ "aria-label": 'Current selections'
254
+ }, selected.map((selection, index) => React__default.createElement(Chip, {
255
+ key: index,
256
+ onClick: ev => {
257
+ ev.stopPropagation();
258
+ onSelect(selection);
259
+ }
260
+ }, selection)))), React__default.createElement(TextInputGroupUtilities, null, selected.length > 0 && React__default.createElement(Button, {
261
+ variant: 'plain',
262
+ onClick: () => {
263
+ var _textInputRef$current2;
264
+
265
+ setInputValue('');
266
+ setSelected([]); // eslint-disable-next-line no-unused-expressions
267
+
268
+ textInputRef == null ? void 0 : (_textInputRef$current2 = textInputRef.current) == null ? void 0 : _textInputRef$current2.focus();
269
+ },
270
+ "aria-label": 'Clear input value'
271
+ }, React__default.createElement(TimesIcon$1, {
272
+ "aria-hidden": true
273
+ })))));
274
+
275
+ return React__default.createElement(React__default.Fragment, null, React__default.createElement(FormGroupContainer, {
103
276
  validated: meta.touched && meta.error ? ValidatedOptions.error : ValidatedOptions.default,
104
277
  helperTextInvalid: meta.error,
105
278
  isRequired: isRequired,
106
279
  fieldId: rest.name,
107
280
  label: label,
108
281
  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
282
+ }, React__default.createElement(Select, Object.assign({
283
+ id: 'multi-typeahead-select',
284
+ isOpen: isOpen,
285
+ selected: selected,
286
+ onSelect: (_ev, selection) => onSelect(selection),
287
+ onOpenChange: () => setIsOpen(false),
288
+ toggle: toggle
128
289
  }, menuAppendTo && {
129
290
  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 && {
291
+ }), React__default.createElement(SelectList, {
292
+ isAriaMultiselectable: true,
293
+ id: 'select-multi-typeahead-listbox'
294
+ }, selectOptions.map((option, index) => React__default.createElement(SelectOption, Object.assign({
295
+ key: option.value || option.children,
296
+ isFocused: focusedItemIndex === index,
297
+ className: option.className,
298
+ id: `select-multi-typeahead-${option.value.replace(' ', '-')}`,
137
299
  isDisabled: option.isDisabled
138
- }, option.extraProps && _extends({}, option.extraProps)))))));
300
+ }, option, {
301
+ ref: null
302
+ })))))));
139
303
  };
140
304
 
141
305
  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.2.1",
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",