@mongodb-js/compass-components 1.32.1 → 1.32.2
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.
- package/lib/components/actions/action-glyph.d.ts +11 -0
- package/lib/components/actions/action-glyph.d.ts.map +1 -0
- package/lib/components/actions/action-glyph.js +19 -0
- package/lib/components/actions/action-glyph.js.map +1 -0
- package/lib/components/actions/constants.d.ts +7 -0
- package/lib/components/actions/constants.d.ts.map +1 -0
- package/lib/components/actions/constants.js +9 -0
- package/lib/components/actions/constants.js.map +1 -0
- package/lib/components/actions/dropdown-menu-button.d.ts +19 -0
- package/lib/components/actions/dropdown-menu-button.d.ts.map +1 -0
- package/lib/components/actions/dropdown-menu-button.js +75 -0
- package/lib/components/actions/dropdown-menu-button.js.map +1 -0
- package/lib/components/actions/item-action-button.d.ts +4 -0
- package/lib/components/actions/item-action-button.d.ts.map +1 -0
- package/lib/components/actions/item-action-button.js +22 -0
- package/lib/components/actions/item-action-button.js.map +1 -0
- package/lib/components/actions/item-action-controls.d.ts +20 -0
- package/lib/components/actions/item-action-controls.d.ts.map +1 -0
- package/lib/components/actions/item-action-controls.js +85 -0
- package/lib/components/actions/item-action-controls.js.map +1 -0
- package/lib/components/actions/item-action-group.d.ts +19 -0
- package/lib/components/actions/item-action-group.d.ts.map +1 -0
- package/lib/components/actions/item-action-group.js +69 -0
- package/lib/components/actions/item-action-group.js.map +1 -0
- package/lib/components/actions/item-action-menu.d.ts +20 -0
- package/lib/components/actions/item-action-menu.d.ts.map +1 -0
- package/lib/components/actions/item-action-menu.js +89 -0
- package/lib/components/actions/item-action-menu.js.map +1 -0
- package/lib/components/actions/small-icon-button.d.ts +11 -0
- package/lib/components/actions/small-icon-button.d.ts.map +1 -0
- package/lib/components/actions/small-icon-button.js +66 -0
- package/lib/components/actions/small-icon-button.js.map +1 -0
- package/lib/components/actions/types.d.ts +32 -0
- package/lib/components/actions/types.d.ts.map +1 -0
- package/lib/components/actions/types.js +3 -0
- package/lib/components/actions/types.js.map +1 -0
- package/lib/components/actions/utils.d.ts +2 -0
- package/lib/components/actions/utils.d.ts.map +1 -0
- package/lib/components/actions/utils.js +8 -0
- package/lib/components/actions/utils.js.map +1 -0
- package/lib/components/combobox-with-custom-option.d.ts +1 -1
- package/lib/components/combobox-with-custom-option.d.ts.map +1 -1
- package/lib/components/combobox-with-custom-option.js +3 -3
- package/lib/components/combobox-with-custom-option.js.map +1 -1
- package/lib/components/document-list/element-actions.d.ts.map +1 -1
- package/lib/components/document-list/element-actions.js +1 -1
- package/lib/components/document-list/element-actions.js.map +1 -1
- package/lib/components/document-list/element-editors.d.ts.map +1 -1
- package/lib/components/document-list/element-editors.js +12 -5
- package/lib/components/document-list/element-editors.js.map +1 -1
- package/lib/components/document-list/element.d.ts.map +1 -1
- package/lib/components/document-list/element.js +10 -3
- package/lib/components/document-list/element.js.map +1 -1
- package/lib/components/feedback-popover.d.ts +2 -5
- package/lib/components/feedback-popover.d.ts.map +1 -1
- package/lib/components/feedback-popover.js +1 -5
- package/lib/components/feedback-popover.js.map +1 -1
- package/lib/components/file-input.js +3 -3
- package/lib/components/file-input.js.map +1 -1
- package/lib/components/guide-cue/guide-cue.d.ts +1 -2
- package/lib/components/guide-cue/guide-cue.d.ts.map +1 -1
- package/lib/components/guide-cue/guide-cue.js +1 -3
- package/lib/components/guide-cue/guide-cue.js.map +1 -1
- package/lib/components/interactive-popover.d.ts +4 -4
- package/lib/components/interactive-popover.d.ts.map +1 -1
- package/lib/components/interactive-popover.js +1 -1
- package/lib/components/interactive-popover.js.map +1 -1
- package/lib/components/leafygreen.d.ts +4 -54
- package/lib/components/leafygreen.d.ts.map +1 -1
- package/lib/components/leafygreen.js +7 -6
- package/lib/components/leafygreen.js.map +1 -1
- package/lib/components/links/link.d.ts +7 -7
- package/lib/components/tab-nav-bar.d.ts.map +1 -1
- package/lib/components/tab-nav-bar.js +8 -1
- package/lib/components/tab-nav-bar.js.map +1 -1
- package/lib/components/virtual-list.d.ts +3 -5
- package/lib/components/virtual-list.d.ts.map +1 -1
- package/lib/components/virtual-list.js +2 -2
- package/lib/components/virtual-list.js.map +1 -1
- package/lib/components/workspace-tabs/tab.d.ts +2 -3
- package/lib/components/workspace-tabs/tab.d.ts.map +1 -1
- package/lib/components/workspace-tabs/tab.js.map +1 -1
- package/lib/components/workspace-tabs/workspace-tabs.d.ts +2 -2
- package/lib/components/workspace-tabs/workspace-tabs.d.ts.map +1 -1
- package/lib/hooks/use-confirmation.d.ts +0 -1
- package/lib/hooks/use-confirmation.d.ts.map +1 -1
- package/lib/hooks/use-confirmation.js +0 -1
- package/lib/hooks/use-confirmation.js.map +1 -1
- package/lib/hooks/use-stacked-component.d.ts +1 -6
- package/lib/hooks/use-stacked-component.d.ts.map +1 -1
- package/lib/hooks/use-stacked-component.js +9 -28
- package/lib/hooks/use-stacked-component.js.map +1 -1
- package/lib/hooks/use-virtual-list-item-observer.d.ts +2 -4
- package/lib/hooks/use-virtual-list-item-observer.d.ts.map +1 -1
- package/lib/hooks/use-virtual-list-item-observer.js +1 -1
- package/lib/hooks/use-virtual-list-item-observer.js.map +1 -1
- package/lib/index.d.ts +9 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +8 -9
- package/lib/index.js.map +1 -1
- package/package.json +47 -49
- package/lib/components/combobox/Chip.d.ts +0 -4
- package/lib/components/combobox/Chip.d.ts.map +0 -1
- package/lib/components/combobox/Chip.js +0 -230
- package/lib/components/combobox/Chip.js.map +0 -1
- package/lib/components/combobox/Combobox.d.ts +0 -48
- package/lib/components/combobox/Combobox.d.ts.map +0 -1
- package/lib/components/combobox/Combobox.js +0 -1029
- package/lib/components/combobox/Combobox.js.map +0 -1
- package/lib/components/combobox/Combobox.styles.d.ts +0 -53
- package/lib/components/combobox/Combobox.styles.d.ts.map +0 -1
- package/lib/components/combobox/Combobox.styles.js +0 -294
- package/lib/components/combobox/Combobox.styles.js.map +0 -1
- package/lib/components/combobox/Combobox.types.d.ts +0 -295
- package/lib/components/combobox/Combobox.types.d.ts.map +0 -1
- package/lib/components/combobox/Combobox.types.js +0 -66
- package/lib/components/combobox/Combobox.types.js.map +0 -1
- package/lib/components/combobox/ComboboxContext.d.ts +0 -21
- package/lib/components/combobox/ComboboxContext.d.ts.map +0 -1
- package/lib/components/combobox/ComboboxContext.js +0 -19
- package/lib/components/combobox/ComboboxContext.js.map +0 -1
- package/lib/components/combobox/ComboboxGroup.d.ts +0 -15
- package/lib/components/combobox/ComboboxGroup.d.ts.map +0 -1
- package/lib/components/combobox/ComboboxGroup.js +0 -84
- package/lib/components/combobox/ComboboxGroup.js.map +0 -1
- package/lib/components/combobox/ComboboxMenu/ComboboxMenu.d.ts +0 -9
- package/lib/components/combobox/ComboboxMenu/ComboboxMenu.d.ts.map +0 -1
- package/lib/components/combobox/ComboboxMenu/ComboboxMenu.js +0 -91
- package/lib/components/combobox/ComboboxMenu/ComboboxMenu.js.map +0 -1
- package/lib/components/combobox/ComboboxMenu/Menu.styles.d.ts +0 -26
- package/lib/components/combobox/ComboboxMenu/Menu.styles.d.ts.map +0 -1
- package/lib/components/combobox/ComboboxMenu/Menu.styles.js +0 -119
- package/lib/components/combobox/ComboboxMenu/Menu.styles.js.map +0 -1
- package/lib/components/combobox/ComboboxOption.d.ts +0 -20
- package/lib/components/combobox/ComboboxOption.d.ts.map +0 -1
- package/lib/components/combobox/ComboboxOption.js +0 -272
- package/lib/components/combobox/ComboboxOption.js.map +0 -1
- package/lib/components/combobox/Menu.styles.d.ts +0 -22
- package/lib/components/combobox/Menu.styles.d.ts.map +0 -1
- package/lib/components/combobox/Menu.styles.js +0 -102
- package/lib/components/combobox/Menu.styles.js.map +0 -1
- package/lib/components/combobox/index.d.ts +0 -6
- package/lib/components/combobox/index.d.ts.map +0 -1
- package/lib/components/combobox/index.js +0 -15
- package/lib/components/combobox/index.js.map +0 -1
- package/lib/components/combobox/utils/OptionObjectUtils.d.ts +0 -23
- package/lib/components/combobox/utils/OptionObjectUtils.d.ts.map +0 -1
- package/lib/components/combobox/utils/OptionObjectUtils.js +0 -40
- package/lib/components/combobox/utils/OptionObjectUtils.js.map +0 -1
- package/lib/components/combobox/utils/flattenChildren.d.ts +0 -12
- package/lib/components/combobox/utils/flattenChildren.d.ts.map +0 -1
- package/lib/components/combobox/utils/flattenChildren.js +0 -47
- package/lib/components/combobox/utils/flattenChildren.js.map +0 -1
- package/lib/components/combobox/utils/getNameAndValue.d.ts +0 -15
- package/lib/components/combobox/utils/getNameAndValue.d.ts.map +0 -1
- package/lib/components/combobox/utils/getNameAndValue.js +0 -24
- package/lib/components/combobox/utils/getNameAndValue.js.map +0 -1
- package/lib/components/combobox/utils/index.d.ts +0 -5
- package/lib/components/combobox/utils/index.d.ts.map +0 -1
- package/lib/components/combobox/utils/index.js +0 -14
- package/lib/components/combobox/utils/index.js.map +0 -1
- package/lib/components/combobox/utils/wrapJSX.d.ts +0 -15
- package/lib/components/combobox/utils/wrapJSX.d.ts.map +0 -1
- package/lib/components/combobox/utils/wrapJSX.js +0 -53
- package/lib/components/combobox/utils/wrapJSX.js.map +0 -1
- package/lib/components/item-action-controls.d.ts +0 -87
- package/lib/components/item-action-controls.d.ts.map +0 -1
- package/lib/components/item-action-controls.js +0 -239
- package/lib/components/item-action-controls.js.map +0 -1
|
@@ -1,1029 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.Combobox = void 0;
|
|
30
|
-
/* eslint-disable filename-rules/match */
|
|
31
|
-
const react_1 = __importStar(require("react"));
|
|
32
|
-
const clone_1 = __importDefault(require("lodash/clone"));
|
|
33
|
-
const isArray_1 = __importDefault(require("lodash/isArray"));
|
|
34
|
-
const isEqual_1 = __importDefault(require("lodash/isEqual"));
|
|
35
|
-
const isNull_1 = __importDefault(require("lodash/isNull"));
|
|
36
|
-
const isString_1 = __importDefault(require("lodash/isString"));
|
|
37
|
-
const isUndefined_1 = __importDefault(require("lodash/isUndefined"));
|
|
38
|
-
const prop_types_1 = __importDefault(require("prop-types"));
|
|
39
|
-
const emotion_1 = require("@leafygreen-ui/emotion");
|
|
40
|
-
const hooks_1 = require("@leafygreen-ui/hooks");
|
|
41
|
-
const icon_1 = __importDefault(require("@leafygreen-ui/icon"));
|
|
42
|
-
const icon_button_1 = __importDefault(require("@leafygreen-ui/icon-button"));
|
|
43
|
-
const leafygreen_provider_1 = require("@leafygreen-ui/leafygreen-provider");
|
|
44
|
-
const lib_1 = require("@leafygreen-ui/lib");
|
|
45
|
-
const palette_1 = require("@leafygreen-ui/palette");
|
|
46
|
-
const typography_1 = require("@leafygreen-ui/typography");
|
|
47
|
-
const ComboboxMenu_1 = require("./ComboboxMenu/ComboboxMenu");
|
|
48
|
-
const Chip_1 = require("./Chip");
|
|
49
|
-
const Combobox_styles_1 = require("./Combobox.styles");
|
|
50
|
-
const Combobox_types_1 = require("./Combobox.types");
|
|
51
|
-
const ComboboxContext_1 = require("./ComboboxContext");
|
|
52
|
-
const ComboboxGroup_1 = require("./ComboboxGroup");
|
|
53
|
-
const ComboboxOption_1 = require("./ComboboxOption");
|
|
54
|
-
const utils_1 = require("./utils");
|
|
55
|
-
const tokens_1 = require("@leafygreen-ui/tokens");
|
|
56
|
-
const descriptionWidth = tokens_1.spacing[5] * 14;
|
|
57
|
-
// By default we want the menu option to be the same width as the input
|
|
58
|
-
// If the user has specified a description, we add extra space to fit the description.
|
|
59
|
-
const popoverMenuStyles = (width, numDescChars) => {
|
|
60
|
-
if (numDescChars === 0) {
|
|
61
|
-
return (0, emotion_1.css) `
|
|
62
|
-
width: ${width}px;
|
|
63
|
-
`;
|
|
64
|
-
}
|
|
65
|
-
const descWithExtraSpace = numDescChars + 5;
|
|
66
|
-
return (0, emotion_1.css) `
|
|
67
|
-
width: calc(
|
|
68
|
-
${width}px + min(${descriptionWidth}px, ${descWithExtraSpace}ch)
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
margin-left: calc(
|
|
72
|
-
min(${descriptionWidth / 2}px, ${descWithExtraSpace / 2}ch)
|
|
73
|
-
);
|
|
74
|
-
`;
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* Combobox is a combination of a Select and TextInput,
|
|
78
|
-
* allowing the user to either type a value directly or select a value from the list.
|
|
79
|
-
* Can be configured to select a single or multiple options.
|
|
80
|
-
*/
|
|
81
|
-
function Combobox({ children, label, description, placeholder = 'Select', 'aria-label': ariaLabel, disabled = false, size = Combobox_types_1.ComboboxSize.Default, darkMode: darkModeProp, state = 'none', errorMessage, searchState = 'unset', searchEmptyMessage = 'No results found', searchErrorMessage = 'Could not get results!', searchLoadingMessage = 'Loading results...', filteredOptions, onFilter, clearable = true, onClear, overflow = 'expand-y', multiselect = false, initialValue, onChange, value, chipTruncationLocation, chipCharacterLimit = 12, className, usePortal = true, portalClassName, portalContainer, scrollContainer, popoverZIndex, popoverClassName, ...rest }) {
|
|
82
|
-
const { darkMode, theme } = (0, leafygreen_provider_1.useDarkMode)(darkModeProp);
|
|
83
|
-
const getOptionRef = (0, hooks_1.useDynamicRefs)({ prefix: 'option' });
|
|
84
|
-
const getChipRef = (0, hooks_1.useDynamicRefs)({ prefix: 'chip' });
|
|
85
|
-
const inputId = (0, hooks_1.useIdAllocator)({ prefix: 'combobox-input' });
|
|
86
|
-
const labelId = (0, hooks_1.useIdAllocator)({ prefix: 'combobox-label' });
|
|
87
|
-
const menuId = (0, hooks_1.useIdAllocator)({ prefix: 'combobox-menu' });
|
|
88
|
-
const comboboxRef = (0, react_1.useRef)(null);
|
|
89
|
-
const clearButtonRef = (0, react_1.useRef)(null);
|
|
90
|
-
const inputWrapperRef = (0, react_1.useRef)(null);
|
|
91
|
-
const inputRef = (0, react_1.useRef)(null);
|
|
92
|
-
const menuRef = (0, react_1.useRef)(null);
|
|
93
|
-
const [isOpen, setOpen] = (0, react_1.useState)(false);
|
|
94
|
-
const wasOpen = (0, hooks_1.usePrevious)(isOpen);
|
|
95
|
-
const [highlightedOption, setHighlightedOption] = (0, react_1.useState)(null);
|
|
96
|
-
const [selection, setSelection] = (0, react_1.useState)(null);
|
|
97
|
-
const prevSelection = (0, hooks_1.usePrevious)(selection);
|
|
98
|
-
const [inputValue, setInputValue] = (0, react_1.useState)('');
|
|
99
|
-
const prevValue = (0, hooks_1.usePrevious)(inputValue);
|
|
100
|
-
const [focusedChip, setFocusedChip] = (0, react_1.useState)(null);
|
|
101
|
-
const doesSelectionExist = !(0, isNull_1.default)(selection) &&
|
|
102
|
-
(((0, isArray_1.default)(selection) && selection.length > 0) || (0, isString_1.default)(selection));
|
|
103
|
-
const placeholderValue = multiselect && (0, isArray_1.default)(selection) && selection.length > 0
|
|
104
|
-
? undefined
|
|
105
|
-
: placeholder;
|
|
106
|
-
const closeMenu = () => setOpen(false);
|
|
107
|
-
const openMenu = () => setOpen(true);
|
|
108
|
-
/**
|
|
109
|
-
* Array of all of the options objects
|
|
110
|
-
*/
|
|
111
|
-
const allOptions = (0, react_1.useMemo)(() => (0, utils_1.flattenChildren)(children), [children]);
|
|
112
|
-
/**
|
|
113
|
-
* Utility function that tells Typescript whether selection is multiselect
|
|
114
|
-
*/
|
|
115
|
-
const isMultiselect = (0, react_1.useCallback)((val) => {
|
|
116
|
-
if (multiselect && (typeof val === 'string' || typeof val === 'number')) {
|
|
117
|
-
lib_1.consoleOnce.error(`Error in Combobox: multiselect is set to \`true\`, but received a ${typeof val} value: "${val}"`);
|
|
118
|
-
}
|
|
119
|
-
else if (!multiselect && (0, isArray_1.default)(val)) {
|
|
120
|
-
lib_1.consoleOnce.error('Error in Combobox: multiselect is set to `false`, but received an Array value');
|
|
121
|
-
}
|
|
122
|
-
return multiselect && (0, isArray_1.default)(val);
|
|
123
|
-
}, [multiselect]);
|
|
124
|
-
/**
|
|
125
|
-
* Forces focus of input box
|
|
126
|
-
* @param cursorPos index the cursor should be set to
|
|
127
|
-
*/
|
|
128
|
-
const setInputFocus = (0, react_1.useCallback)((cursorPos) => {
|
|
129
|
-
if (!disabled && inputRef && inputRef.current) {
|
|
130
|
-
inputRef.current.focus();
|
|
131
|
-
if (!(0, isUndefined_1.default)(cursorPos)) {
|
|
132
|
-
inputRef.current.setSelectionRange(cursorPos, cursorPos);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}, [disabled]);
|
|
136
|
-
/**
|
|
137
|
-
* Update selection.
|
|
138
|
-
* This behaves differently in multi. vs single select.
|
|
139
|
-
* @param value option value the selection should be set to
|
|
140
|
-
*/
|
|
141
|
-
const updateSelection = (0, react_1.useCallback)((value) => {
|
|
142
|
-
if (isMultiselect(selection)) {
|
|
143
|
-
// We know M is true here
|
|
144
|
-
const newSelection = (0, clone_1.default)(selection);
|
|
145
|
-
if ((0, isNull_1.default)(value)) {
|
|
146
|
-
newSelection.length = 0;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
if (selection.includes(value)) {
|
|
150
|
-
// remove from array
|
|
151
|
-
newSelection.splice(newSelection.indexOf(value), 1);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
// add to array
|
|
155
|
-
newSelection.push(value);
|
|
156
|
-
// clear text
|
|
157
|
-
setInputValue('');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
setSelection(newSelection);
|
|
161
|
-
onChange?.(newSelection);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
const newSelection = value;
|
|
165
|
-
setSelection(newSelection);
|
|
166
|
-
onChange?.(newSelection);
|
|
167
|
-
}
|
|
168
|
-
}, [isMultiselect, onChange, selection]);
|
|
169
|
-
/**
|
|
170
|
-
* Returns whether a given value is included in, or equal to, the current selection
|
|
171
|
-
* @param value the option value to check
|
|
172
|
-
*/
|
|
173
|
-
const isValueCurrentSelection = (0, react_1.useCallback)((value) => {
|
|
174
|
-
return isMultiselect(selection)
|
|
175
|
-
? selection.includes(value)
|
|
176
|
-
: value === selection;
|
|
177
|
-
}, [isMultiselect, selection]);
|
|
178
|
-
/**
|
|
179
|
-
* Returns whether given text is included in, or equal to, the current selection.
|
|
180
|
-
* Similar to `isValueCurrentSelection`, but assumes the text argument is the `displayName` for the selection
|
|
181
|
-
* @param text the text to check
|
|
182
|
-
*/
|
|
183
|
-
const isTextCurrentSelection = (0, react_1.useCallback)((text) => {
|
|
184
|
-
const value = (0, utils_1.getValueForDisplayName)(text, allOptions);
|
|
185
|
-
return isValueCurrentSelection(value);
|
|
186
|
-
}, [allOptions, isValueCurrentSelection]);
|
|
187
|
-
/**
|
|
188
|
-
* Returns whether the provided option is disabled
|
|
189
|
-
* @param option the option value or OptionObject to check
|
|
190
|
-
*/
|
|
191
|
-
const isOptionDisabled = (option) => {
|
|
192
|
-
if (typeof option === 'string') {
|
|
193
|
-
const optionObj = (0, utils_1.getOptionObjectFromValue)(option, allOptions);
|
|
194
|
-
return !!optionObj?.isDisabled;
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
return !!option.isDisabled;
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
/**
|
|
201
|
-
* Computes whether the option is visible based on the current input
|
|
202
|
-
* @param option the option value or OptionObject to compute
|
|
203
|
-
*/
|
|
204
|
-
const shouldOptionBeVisible = (0, react_1.useCallback)((option) => {
|
|
205
|
-
const value = typeof option === 'string' ? option : option.value;
|
|
206
|
-
// If filtered options are provided
|
|
207
|
-
if (filteredOptions && filteredOptions.length > 0) {
|
|
208
|
-
return filteredOptions.includes(value);
|
|
209
|
-
}
|
|
210
|
-
// If the text input value is the current selection
|
|
211
|
-
// (or included in the selection)
|
|
212
|
-
// then all options should be visible
|
|
213
|
-
if (isTextCurrentSelection(inputValue)) {
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
// otherwise, we do our own filtering
|
|
217
|
-
const displayName = typeof option === 'string'
|
|
218
|
-
? (0, utils_1.getDisplayNameForValue)(value, allOptions)
|
|
219
|
-
: option.displayName;
|
|
220
|
-
const isValueInDisplayName = displayName
|
|
221
|
-
.toLowerCase()
|
|
222
|
-
.includes(inputValue.toLowerCase());
|
|
223
|
-
return isValueInDisplayName;
|
|
224
|
-
}, [filteredOptions, isTextCurrentSelection, inputValue, allOptions]);
|
|
225
|
-
/**
|
|
226
|
-
* The array of visible options objects
|
|
227
|
-
*/
|
|
228
|
-
const visibleOptions = (0, react_1.useMemo)(() => allOptions.filter(shouldOptionBeVisible), [allOptions, shouldOptionBeVisible]);
|
|
229
|
-
/**
|
|
230
|
-
* Returns whether the given value is in the options array
|
|
231
|
-
* @param value the value to check
|
|
232
|
-
*/
|
|
233
|
-
const isValueValid = (0, react_1.useCallback)((value) => {
|
|
234
|
-
return value ? !!allOptions.find((opt) => opt.value === value) : false;
|
|
235
|
-
}, [allOptions]);
|
|
236
|
-
/**
|
|
237
|
-
* Returns the index of a given value in the array of visible (filtered) options
|
|
238
|
-
* @param value the option value to get the index of
|
|
239
|
-
*/
|
|
240
|
-
const getIndexOfValue = (0, react_1.useCallback)((value) => {
|
|
241
|
-
return visibleOptions
|
|
242
|
-
? visibleOptions.findIndex((option) => option.value === value)
|
|
243
|
-
: -1;
|
|
244
|
-
}, [visibleOptions]);
|
|
245
|
-
/**
|
|
246
|
-
* Returns the option value of a given index in the array of visible (filtered) options
|
|
247
|
-
* @param index the option index to get the value of
|
|
248
|
-
*/
|
|
249
|
-
const getValueAtIndex = (0, react_1.useCallback)((index) => {
|
|
250
|
-
if (visibleOptions && visibleOptions.length >= index) {
|
|
251
|
-
const option = visibleOptions[index];
|
|
252
|
-
return option ? option.value : undefined;
|
|
253
|
-
}
|
|
254
|
-
}, [visibleOptions]);
|
|
255
|
-
/**
|
|
256
|
-
* Returns the index of the active chip in the selection array
|
|
257
|
-
*/
|
|
258
|
-
const getActiveChipIndex = (0, react_1.useCallback)(() => isMultiselect(selection)
|
|
259
|
-
? selection.findIndex((value) => getChipRef(value)?.current?.contains(document.activeElement))
|
|
260
|
-
: -1, [getChipRef, isMultiselect, selection]);
|
|
261
|
-
/**
|
|
262
|
-
*
|
|
263
|
-
* Focus Management
|
|
264
|
-
*
|
|
265
|
-
*/
|
|
266
|
-
const [focusedElementName, trackFocusedElement] = (0, react_1.useState)();
|
|
267
|
-
const isElementFocused = (elementName) => elementName === focusedElementName;
|
|
268
|
-
/**
|
|
269
|
-
* Updates the highlighted menu option based on the provided direction
|
|
270
|
-
* @param direction the direction to move the focus. `'next' | 'prev' | 'first' | 'last'`
|
|
271
|
-
*/
|
|
272
|
-
const updateHighlightedOption = (0, react_1.useCallback)((direction) => {
|
|
273
|
-
const optionsCount = visibleOptions?.length ?? 0;
|
|
274
|
-
const lastIndex = optionsCount - 1 > 0 ? optionsCount - 1 : 0;
|
|
275
|
-
const indexOfHighlight = getIndexOfValue(highlightedOption);
|
|
276
|
-
// Remove focus from chip
|
|
277
|
-
if (direction && isOpen) {
|
|
278
|
-
setFocusedChip(null);
|
|
279
|
-
setInputFocus();
|
|
280
|
-
}
|
|
281
|
-
switch (direction) {
|
|
282
|
-
case 'next': {
|
|
283
|
-
const newValue = indexOfHighlight + 1 < optionsCount
|
|
284
|
-
? getValueAtIndex(indexOfHighlight + 1)
|
|
285
|
-
: getValueAtIndex(0);
|
|
286
|
-
setHighlightedOption(newValue ?? null);
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
case 'prev': {
|
|
290
|
-
const newValue = indexOfHighlight - 1 >= 0
|
|
291
|
-
? getValueAtIndex(indexOfHighlight - 1)
|
|
292
|
-
: getValueAtIndex(lastIndex);
|
|
293
|
-
setHighlightedOption(newValue ?? null);
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
case 'last': {
|
|
297
|
-
const newValue = getValueAtIndex(lastIndex);
|
|
298
|
-
setHighlightedOption(newValue ?? null);
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
case 'first':
|
|
302
|
-
default: {
|
|
303
|
-
const newValue = getValueAtIndex(0);
|
|
304
|
-
setHighlightedOption(newValue ?? null);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}, [
|
|
308
|
-
highlightedOption,
|
|
309
|
-
getIndexOfValue,
|
|
310
|
-
getValueAtIndex,
|
|
311
|
-
isOpen,
|
|
312
|
-
setInputFocus,
|
|
313
|
-
visibleOptions?.length,
|
|
314
|
-
]);
|
|
315
|
-
/**
|
|
316
|
-
* Updates the focused chip based on the provided direction
|
|
317
|
-
* @param direction the direction to move the focus. `'next' | 'prev' | 'first' | 'last'`
|
|
318
|
-
* @param relativeToIndex the chip index to move focus relative to
|
|
319
|
-
*/
|
|
320
|
-
const updateFocusedChip = (0, react_1.useCallback)((direction, relativeToIndex) => {
|
|
321
|
-
if (isMultiselect(selection)) {
|
|
322
|
-
switch (direction) {
|
|
323
|
-
case 'next': {
|
|
324
|
-
const referenceChipIndex = relativeToIndex ?? getActiveChipIndex();
|
|
325
|
-
const nextChipIndex = referenceChipIndex + 1 < selection.length
|
|
326
|
-
? referenceChipIndex + 1
|
|
327
|
-
: selection.length - 1;
|
|
328
|
-
const nextChipValue = selection[nextChipIndex];
|
|
329
|
-
setFocusedChip(nextChipValue);
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
case 'prev': {
|
|
333
|
-
const referenceChipIndex = relativeToIndex ?? getActiveChipIndex();
|
|
334
|
-
const prevChipIndex = referenceChipIndex > 0
|
|
335
|
-
? referenceChipIndex - 1
|
|
336
|
-
: referenceChipIndex < 0
|
|
337
|
-
? selection.length - 1
|
|
338
|
-
: 0;
|
|
339
|
-
const prevChipValue = selection[prevChipIndex];
|
|
340
|
-
setFocusedChip(prevChipValue);
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
case 'first': {
|
|
344
|
-
const firstChipValue = selection[0];
|
|
345
|
-
setFocusedChip(firstChipValue);
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
case 'last': {
|
|
349
|
-
const lastChipValue = selection[selection.length - 1];
|
|
350
|
-
setFocusedChip(lastChipValue);
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
default:
|
|
354
|
-
setFocusedChip(null);
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}, [getActiveChipIndex, isMultiselect, selection]);
|
|
359
|
-
/**
|
|
360
|
-
* Handles an arrow key press
|
|
361
|
-
*/
|
|
362
|
-
const handleArrowKey = (0, react_1.useCallback)((direction, event) => {
|
|
363
|
-
// Remove focus from menu
|
|
364
|
-
if (direction)
|
|
365
|
-
setHighlightedOption(null);
|
|
366
|
-
switch (direction) {
|
|
367
|
-
case 'right':
|
|
368
|
-
switch (focusedElementName) {
|
|
369
|
-
case Combobox_types_1.ComboboxElement.Input: {
|
|
370
|
-
// If cursor is at the end of the input
|
|
371
|
-
if (inputRef.current?.selectionEnd ===
|
|
372
|
-
inputRef.current?.value.length) {
|
|
373
|
-
clearButtonRef.current?.focus();
|
|
374
|
-
}
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
case Combobox_types_1.ComboboxElement.FirstChip:
|
|
378
|
-
case Combobox_types_1.ComboboxElement.MiddleChip:
|
|
379
|
-
case Combobox_types_1.ComboboxElement.LastChip: {
|
|
380
|
-
if (focusedElementName === Combobox_types_1.ComboboxElement.LastChip ||
|
|
381
|
-
// the first chip is also the last chip (i.e. only one)
|
|
382
|
-
selection?.length === 1) {
|
|
383
|
-
// if focus is on last chip, go to input
|
|
384
|
-
setInputFocus(0);
|
|
385
|
-
updateFocusedChip(null);
|
|
386
|
-
event.preventDefault();
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
// First/middle chips
|
|
390
|
-
updateFocusedChip('next');
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
case Combobox_types_1.ComboboxElement.ClearButton:
|
|
394
|
-
default:
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
break;
|
|
398
|
-
case 'left':
|
|
399
|
-
switch (focusedElementName) {
|
|
400
|
-
case Combobox_types_1.ComboboxElement.ClearButton: {
|
|
401
|
-
event.preventDefault();
|
|
402
|
-
setInputFocus(inputRef?.current?.value.length);
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
case Combobox_types_1.ComboboxElement.Input:
|
|
406
|
-
case Combobox_types_1.ComboboxElement.MiddleChip:
|
|
407
|
-
case Combobox_types_1.ComboboxElement.LastChip: {
|
|
408
|
-
if (isMultiselect(selection)) {
|
|
409
|
-
// Break if cursor is not at the start of the input
|
|
410
|
-
if (focusedElementName === Combobox_types_1.ComboboxElement.Input &&
|
|
411
|
-
inputRef.current?.selectionStart !== 0) {
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
updateFocusedChip('prev');
|
|
415
|
-
}
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
case Combobox_types_1.ComboboxElement.FirstChip:
|
|
419
|
-
default:
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
break;
|
|
423
|
-
default:
|
|
424
|
-
updateFocusedChip(null);
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
}, [
|
|
428
|
-
focusedElementName,
|
|
429
|
-
isMultiselect,
|
|
430
|
-
selection,
|
|
431
|
-
setInputFocus,
|
|
432
|
-
updateFocusedChip,
|
|
433
|
-
]);
|
|
434
|
-
// When the input value changes (or when the menu opens)
|
|
435
|
-
// Update the focused option
|
|
436
|
-
(0, react_1.useEffect)(() => {
|
|
437
|
-
if (inputValue !== prevValue) {
|
|
438
|
-
updateHighlightedOption('first');
|
|
439
|
-
}
|
|
440
|
-
}, [inputValue, isOpen, prevValue, updateHighlightedOption]);
|
|
441
|
-
// When the focused option changes, update the menu scroll if necessary
|
|
442
|
-
(0, react_1.useEffect)(() => {
|
|
443
|
-
if (highlightedOption) {
|
|
444
|
-
const focusedElementRef = getOptionRef(highlightedOption);
|
|
445
|
-
if (focusedElementRef && focusedElementRef.current && menuRef.current) {
|
|
446
|
-
const { offsetTop: optionTop } = focusedElementRef.current;
|
|
447
|
-
const { scrollTop: menuScroll, offsetHeight: menuHeight } = menuRef.current;
|
|
448
|
-
if (optionTop > menuHeight || optionTop < menuScroll) {
|
|
449
|
-
menuRef.current.scrollTop = optionTop;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}, [highlightedOption, getOptionRef]);
|
|
454
|
-
/**
|
|
455
|
-
* Rendering
|
|
456
|
-
*/
|
|
457
|
-
/**
|
|
458
|
-
* Callback to render a child as an <InternalComboboxOption> element
|
|
459
|
-
*/
|
|
460
|
-
const renderOption = (0, react_1.useCallback)((child) => {
|
|
461
|
-
if ((0, lib_1.isComponentType)(child, 'ComboboxOption')) {
|
|
462
|
-
const { value, displayName } = (0, utils_1.getNameAndValue)(child.props);
|
|
463
|
-
if (shouldOptionBeVisible(value)) {
|
|
464
|
-
const { className, glyph, disabled } = child.props;
|
|
465
|
-
const index = allOptions.findIndex((opt) => opt.value === value);
|
|
466
|
-
const isFocused = highlightedOption === value;
|
|
467
|
-
const isSelected = isMultiselect(selection)
|
|
468
|
-
? selection.includes(value)
|
|
469
|
-
: selection === value;
|
|
470
|
-
const setSelected = () => {
|
|
471
|
-
setHighlightedOption(value);
|
|
472
|
-
updateSelection(value);
|
|
473
|
-
setInputFocus();
|
|
474
|
-
if (value === selection) {
|
|
475
|
-
closeMenu();
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
const optionRef = getOptionRef(value);
|
|
479
|
-
return (react_1.default.createElement(ComboboxOption_1.InternalComboboxOption, { value: value, displayName: displayName, isFocused: isFocused, isSelected: isSelected, disabled: disabled, setSelected: setSelected, glyph: glyph, className: className, index: index, ref: optionRef, description: child.props.description }));
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
else if ((0, lib_1.isComponentType)(child, 'ComboboxGroup')) {
|
|
483
|
-
const nestedChildren = react_1.default.Children.map(child.props.children, renderOption);
|
|
484
|
-
if (nestedChildren && nestedChildren?.length > 0) {
|
|
485
|
-
return (react_1.default.createElement(ComboboxGroup_1.InternalComboboxGroup, { label: child.props.label, className: child.props.className }, react_1.default.Children.map(nestedChildren, renderOption)));
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}, [
|
|
489
|
-
allOptions,
|
|
490
|
-
getOptionRef,
|
|
491
|
-
highlightedOption,
|
|
492
|
-
isMultiselect,
|
|
493
|
-
selection,
|
|
494
|
-
setInputFocus,
|
|
495
|
-
shouldOptionBeVisible,
|
|
496
|
-
updateSelection,
|
|
497
|
-
]);
|
|
498
|
-
/**
|
|
499
|
-
* The rendered JSX elements for the options
|
|
500
|
-
*/
|
|
501
|
-
const renderedOptionsJSX = (0, react_1.useMemo)(() => react_1.default.Children.map(children, renderOption), [children, renderOption]);
|
|
502
|
-
/**
|
|
503
|
-
* The rendered JSX for the selection Chips
|
|
504
|
-
*/
|
|
505
|
-
const renderedChips = (0, react_1.useMemo)(() => {
|
|
506
|
-
if (isMultiselect(selection)) {
|
|
507
|
-
return selection.filter(isValueValid).map((value, index) => {
|
|
508
|
-
const displayName = (0, utils_1.getDisplayNameForValue)(value, allOptions);
|
|
509
|
-
const isFocused = focusedChip === value;
|
|
510
|
-
const chipRef = getChipRef(value);
|
|
511
|
-
const isLastChip = index >= selection.length - 1;
|
|
512
|
-
const onRemove = () => {
|
|
513
|
-
if (isLastChip) {
|
|
514
|
-
// Focus the input if this is the last chip in the set
|
|
515
|
-
setInputFocus();
|
|
516
|
-
updateFocusedChip(null);
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
updateFocusedChip('next', index);
|
|
520
|
-
}
|
|
521
|
-
updateSelection(value);
|
|
522
|
-
};
|
|
523
|
-
const onFocus = () => {
|
|
524
|
-
setFocusedChip(value);
|
|
525
|
-
};
|
|
526
|
-
return (react_1.default.createElement(Chip_1.Chip, { key: value, displayName: displayName, isFocused: isFocused, onRemove: onRemove, onFocus: onFocus, ref: chipRef }));
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
}, [
|
|
530
|
-
isMultiselect,
|
|
531
|
-
selection,
|
|
532
|
-
isValueValid,
|
|
533
|
-
allOptions,
|
|
534
|
-
focusedChip,
|
|
535
|
-
getChipRef,
|
|
536
|
-
updateSelection,
|
|
537
|
-
setInputFocus,
|
|
538
|
-
updateFocusedChip,
|
|
539
|
-
]);
|
|
540
|
-
const handleClearButtonFocus = () => {
|
|
541
|
-
setHighlightedOption(null);
|
|
542
|
-
};
|
|
543
|
-
/**
|
|
544
|
-
* The rendered JSX for the input icons (clear, warn & caret)
|
|
545
|
-
*/
|
|
546
|
-
const renderedInputIcons = (0, react_1.useMemo)(() => {
|
|
547
|
-
const handleClearButtonClick = (e) => {
|
|
548
|
-
if (!disabled) {
|
|
549
|
-
updateSelection(null);
|
|
550
|
-
onClear?.(e);
|
|
551
|
-
onFilter?.('');
|
|
552
|
-
if (!isOpen) {
|
|
553
|
-
openMenu();
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
};
|
|
557
|
-
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
558
|
-
clearable && doesSelectionExist && (react_1.default.createElement(icon_button_1.default, { "aria-label": "Clear selection", "aria-disabled": disabled, disabled: disabled, ref: clearButtonRef, onClick: handleClearButtonClick, onFocus: handleClearButtonFocus, className: (0, emotion_1.cx)(Combobox_styles_1.clearButtonStyle), darkMode: darkMode },
|
|
559
|
-
react_1.default.createElement(icon_1.default, { glyph: "XWithCircle" }))),
|
|
560
|
-
state === 'error' ? (react_1.default.createElement(icon_1.default, { glyph: "Warning", color: darkMode ? palette_1.palette.red.light1 : palette_1.palette.red.base, className: (0, Combobox_styles_1.endIconStyle)(size) })) : (react_1.default.createElement(icon_1.default, { glyph: "CaretDown", className: (0, Combobox_styles_1.endIconStyle)(size) }))));
|
|
561
|
-
}, [
|
|
562
|
-
clearable,
|
|
563
|
-
doesSelectionExist,
|
|
564
|
-
disabled,
|
|
565
|
-
state,
|
|
566
|
-
darkMode,
|
|
567
|
-
size,
|
|
568
|
-
updateSelection,
|
|
569
|
-
onClear,
|
|
570
|
-
onFilter,
|
|
571
|
-
isOpen,
|
|
572
|
-
]);
|
|
573
|
-
/**
|
|
574
|
-
* Flag to determine whether the rendered options have icons
|
|
575
|
-
*/
|
|
576
|
-
const withIcons = (0, react_1.useMemo)(() => allOptions.some((opt) => opt.hasGlyph), [allOptions]);
|
|
577
|
-
/**
|
|
578
|
-
*
|
|
579
|
-
* Selection Management
|
|
580
|
-
*
|
|
581
|
-
*/
|
|
582
|
-
const onCloseMenu = (0, react_1.useCallback)(() => {
|
|
583
|
-
// Single select, and no change to selection
|
|
584
|
-
if (!isMultiselect(selection) && selection === prevSelection) {
|
|
585
|
-
const exactMatchedOption = visibleOptions.find((option) => option.displayName === inputValue || option.value === inputValue);
|
|
586
|
-
// check if inputValue is matches a valid option
|
|
587
|
-
// Set the selection to that value if the component is not controlled
|
|
588
|
-
if (exactMatchedOption && !value) {
|
|
589
|
-
setSelection(exactMatchedOption.value);
|
|
590
|
-
}
|
|
591
|
-
else if (selection) {
|
|
592
|
-
// Revert the value to the previous selection.
|
|
593
|
-
// Set the value instead of displayName to align with handleInputChange COMPASS-6511
|
|
594
|
-
setInputValue(selection);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}, [
|
|
598
|
-
allOptions,
|
|
599
|
-
inputValue,
|
|
600
|
-
isMultiselect,
|
|
601
|
-
prevSelection,
|
|
602
|
-
selection,
|
|
603
|
-
value,
|
|
604
|
-
visibleOptions,
|
|
605
|
-
]);
|
|
606
|
-
const onSelect = (0, react_1.useCallback)(() => {
|
|
607
|
-
if (doesSelectionExist) {
|
|
608
|
-
if (isMultiselect(selection)) {
|
|
609
|
-
// Scroll the wrapper to the end. No effect if not `overflow="scroll-x"`
|
|
610
|
-
scrollInputToEnd();
|
|
611
|
-
}
|
|
612
|
-
else if (!isMultiselect(selection)) {
|
|
613
|
-
// Update the text input.
|
|
614
|
-
// Set the value instead of displayName to align with handleInputChange COMPASS-6511
|
|
615
|
-
setInputValue(selection);
|
|
616
|
-
closeMenu();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
else {
|
|
620
|
-
setInputValue('');
|
|
621
|
-
}
|
|
622
|
-
}, [doesSelectionExist, allOptions, isMultiselect, selection]);
|
|
623
|
-
// Set the initialValue
|
|
624
|
-
(0, react_1.useEffect)(() => {
|
|
625
|
-
if (initialValue) {
|
|
626
|
-
if ((0, isArray_1.default)(initialValue)) {
|
|
627
|
-
// Ensure the values we set are real options
|
|
628
|
-
const filteredValue = initialValue.filter((value) => isValueValid(value)) ?? [];
|
|
629
|
-
setSelection(filteredValue);
|
|
630
|
-
}
|
|
631
|
-
else {
|
|
632
|
-
if (isValueValid(initialValue)) {
|
|
633
|
-
setSelection(initialValue);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
setSelection((0, Combobox_types_1.getNullSelection)(multiselect));
|
|
639
|
-
}
|
|
640
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
641
|
-
}, []);
|
|
642
|
-
// When controlled value changes, update the selection
|
|
643
|
-
(0, react_1.useEffect)(() => {
|
|
644
|
-
if (!(0, isUndefined_1.default)(value) && value !== prevValue) {
|
|
645
|
-
if ((0, isNull_1.default)(value)) {
|
|
646
|
-
setSelection(null);
|
|
647
|
-
}
|
|
648
|
-
else if (isMultiselect(value)) {
|
|
649
|
-
// Ensure the value(s) passed in are valid options
|
|
650
|
-
const newSelection = value.filter(isValueValid);
|
|
651
|
-
setSelection(newSelection);
|
|
652
|
-
}
|
|
653
|
-
else {
|
|
654
|
-
setSelection(isValueValid(value) ? value : null);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}, [isMultiselect, isValueValid, prevValue, value]);
|
|
658
|
-
// onSelect
|
|
659
|
-
// Side effects to run when the selection changes
|
|
660
|
-
(0, react_1.useEffect)(() => {
|
|
661
|
-
if (!(0, isEqual_1.default)(selection, prevSelection)) {
|
|
662
|
-
onSelect();
|
|
663
|
-
}
|
|
664
|
-
}, [onSelect, prevSelection, selection]);
|
|
665
|
-
// when the menu closes, update the value if needed
|
|
666
|
-
(0, react_1.useEffect)(() => {
|
|
667
|
-
if (!isOpen && wasOpen) {
|
|
668
|
-
onCloseMenu();
|
|
669
|
-
}
|
|
670
|
-
}, [isOpen, wasOpen, onCloseMenu]);
|
|
671
|
-
/**
|
|
672
|
-
*
|
|
673
|
-
* Menu management
|
|
674
|
-
*
|
|
675
|
-
*/
|
|
676
|
-
const [popoverMenuWidth, setPopoverMenuWidth] = (0, react_1.useState)(0);
|
|
677
|
-
// When the menu opens, or the selection changes, or the focused option changes
|
|
678
|
-
// update the menu width
|
|
679
|
-
(0, react_1.useEffect)(() => {
|
|
680
|
-
setPopoverMenuWidth(comboboxRef.current?.clientWidth ?? 0);
|
|
681
|
-
}, [comboboxRef, isOpen, highlightedOption, selection]);
|
|
682
|
-
// Handler fired when the menu has finished transitioning in/out
|
|
683
|
-
const handleTransitionEnd = () => {
|
|
684
|
-
setPopoverMenuWidth(comboboxRef.current?.clientWidth ?? 0);
|
|
685
|
-
};
|
|
686
|
-
/**
|
|
687
|
-
*
|
|
688
|
-
* Event Handlers
|
|
689
|
-
*
|
|
690
|
-
*/
|
|
691
|
-
// Prevent combobox from gaining focus by default
|
|
692
|
-
const handleInputWrapperMousedown = (e) => {
|
|
693
|
-
if (disabled) {
|
|
694
|
-
e.preventDefault();
|
|
695
|
-
}
|
|
696
|
-
};
|
|
697
|
-
// Set focus to the input element on click
|
|
698
|
-
const handleComboboxClick = (e) => {
|
|
699
|
-
// If we clicked the wrapper, not the input itself.
|
|
700
|
-
// (Focus is set automatically if the click is on the input)
|
|
701
|
-
if (e.target !== inputRef.current) {
|
|
702
|
-
let cursorPos = 0;
|
|
703
|
-
if (inputRef.current) {
|
|
704
|
-
const mouseX = e.nativeEvent.offsetX;
|
|
705
|
-
const inputRight = inputRef.current.offsetLeft + inputRef.current.clientWidth;
|
|
706
|
-
cursorPos = mouseX > inputRight ? inputValue.length : 0;
|
|
707
|
-
}
|
|
708
|
-
setInputFocus(cursorPos);
|
|
709
|
-
}
|
|
710
|
-
// Only open the menu in response to a click
|
|
711
|
-
openMenu();
|
|
712
|
-
};
|
|
713
|
-
// Fired whenever the wrapper gains focus,
|
|
714
|
-
// and any time the focus within changes
|
|
715
|
-
const handleComboboxFocus = (e) => {
|
|
716
|
-
scrollInputToEnd();
|
|
717
|
-
trackFocusedElement(getNameFromElement(e.target));
|
|
718
|
-
};
|
|
719
|
-
// Fired onChange
|
|
720
|
-
const handleInputChange = ({ target: { value }, }) => {
|
|
721
|
-
setInputValue(value);
|
|
722
|
-
// fire any filter function passed in
|
|
723
|
-
onFilter?.(value);
|
|
724
|
-
};
|
|
725
|
-
const handleKeyDown = (event) => {
|
|
726
|
-
const isFocusInMenu = menuRef.current?.contains(document.activeElement);
|
|
727
|
-
const isFocusOnCombobox = comboboxRef.current?.contains(document.activeElement);
|
|
728
|
-
const isFocusInComponent = isFocusOnCombobox || isFocusInMenu;
|
|
729
|
-
// Only run if the focus is in the component
|
|
730
|
-
if (isFocusInComponent) {
|
|
731
|
-
// No support for modifiers yet
|
|
732
|
-
// TODO - Handle support for multiple chip selection
|
|
733
|
-
if (event.ctrlKey || event.shiftKey || event.altKey) {
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
736
|
-
switch (event.key) {
|
|
737
|
-
case lib_1.keyMap.Tab: {
|
|
738
|
-
switch (focusedElementName) {
|
|
739
|
-
case 'Input': {
|
|
740
|
-
if (!doesSelectionExist) {
|
|
741
|
-
closeMenu();
|
|
742
|
-
updateHighlightedOption('first');
|
|
743
|
-
updateFocusedChip(null);
|
|
744
|
-
}
|
|
745
|
-
// else use default behavior
|
|
746
|
-
break;
|
|
747
|
-
}
|
|
748
|
-
case 'LastChip': {
|
|
749
|
-
// use default behavior
|
|
750
|
-
updateFocusedChip(null);
|
|
751
|
-
break;
|
|
752
|
-
}
|
|
753
|
-
case 'FirstChip':
|
|
754
|
-
case 'MiddleChip': {
|
|
755
|
-
// use default behavior
|
|
756
|
-
break;
|
|
757
|
-
}
|
|
758
|
-
case 'ClearButton':
|
|
759
|
-
default:
|
|
760
|
-
break;
|
|
761
|
-
}
|
|
762
|
-
break;
|
|
763
|
-
}
|
|
764
|
-
case lib_1.keyMap.Escape: {
|
|
765
|
-
closeMenu();
|
|
766
|
-
updateHighlightedOption('first');
|
|
767
|
-
break;
|
|
768
|
-
}
|
|
769
|
-
case lib_1.keyMap.Enter: {
|
|
770
|
-
if (!isOpen) {
|
|
771
|
-
// If the menu is not open, enter should open the menu
|
|
772
|
-
openMenu();
|
|
773
|
-
}
|
|
774
|
-
else if (
|
|
775
|
-
// Select the highlighted option iff
|
|
776
|
-
// the menu is open,
|
|
777
|
-
// we're focused on input element,
|
|
778
|
-
// and the highlighted option is not disabled
|
|
779
|
-
focusedElementName === Combobox_types_1.ComboboxElement.Input &&
|
|
780
|
-
!(0, isNull_1.default)(highlightedOption) &&
|
|
781
|
-
!isOptionDisabled(highlightedOption)) {
|
|
782
|
-
updateSelection(highlightedOption);
|
|
783
|
-
}
|
|
784
|
-
else if (
|
|
785
|
-
// Focused on clear button
|
|
786
|
-
focusedElementName === Combobox_types_1.ComboboxElement.ClearButton) {
|
|
787
|
-
updateSelection(null);
|
|
788
|
-
setInputFocus();
|
|
789
|
-
}
|
|
790
|
-
break;
|
|
791
|
-
}
|
|
792
|
-
case lib_1.keyMap.Backspace: {
|
|
793
|
-
// Backspace key focuses last chip if the input is focused
|
|
794
|
-
// Note: Chip removal behavior is handled in `onRemove` defined in `renderChips`
|
|
795
|
-
if (isMultiselect(selection)) {
|
|
796
|
-
if (focusedElementName === 'Input' &&
|
|
797
|
-
inputRef.current?.selectionStart === 0) {
|
|
798
|
-
updateFocusedChip('last');
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
// Open the menu regardless
|
|
802
|
-
openMenu();
|
|
803
|
-
break;
|
|
804
|
-
}
|
|
805
|
-
case lib_1.keyMap.ArrowDown: {
|
|
806
|
-
if (isOpen) {
|
|
807
|
-
// Prevent the page from scrolling
|
|
808
|
-
event.preventDefault();
|
|
809
|
-
// only change option if the menu is already open
|
|
810
|
-
updateHighlightedOption('next');
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
openMenu();
|
|
814
|
-
}
|
|
815
|
-
break;
|
|
816
|
-
}
|
|
817
|
-
case lib_1.keyMap.ArrowUp: {
|
|
818
|
-
if (isOpen) {
|
|
819
|
-
// Prevent the page from scrolling
|
|
820
|
-
event.preventDefault();
|
|
821
|
-
// only change option if the menu is already open
|
|
822
|
-
updateHighlightedOption('prev');
|
|
823
|
-
}
|
|
824
|
-
else {
|
|
825
|
-
openMenu();
|
|
826
|
-
}
|
|
827
|
-
break;
|
|
828
|
-
}
|
|
829
|
-
case lib_1.keyMap.ArrowRight: {
|
|
830
|
-
handleArrowKey('right', event);
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
833
|
-
case lib_1.keyMap.ArrowLeft: {
|
|
834
|
-
handleArrowKey('left', event);
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
default: {
|
|
838
|
-
if (!isOpen) {
|
|
839
|
-
openMenu();
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
};
|
|
845
|
-
/**
|
|
846
|
-
*
|
|
847
|
-
* Global Event Handler
|
|
848
|
-
*
|
|
849
|
-
*/
|
|
850
|
-
/**
|
|
851
|
-
* We add two event handlers to the document to handle the backdrop click behavior.
|
|
852
|
-
* Intended behavior is to close the menu, and keep focus on the Combobox.
|
|
853
|
-
* No other click event handlers should fire on backdrop click
|
|
854
|
-
*
|
|
855
|
-
* 1. Mousedown event fires
|
|
856
|
-
* 2. We prevent `mousedown`'s default behavior, to prevent focus from being applied to the body (or other target)
|
|
857
|
-
* 3. Click event fires
|
|
858
|
-
* 4. We handle this event on _capture_, and stop propagation before the `click` event propagates all the way to any other element.
|
|
859
|
-
* This ensures that even if we click on a button, that handler is not fired
|
|
860
|
-
* 5. Then we call `closeMenu`, setting `isOpen = false`, and rerender the component
|
|
861
|
-
*/
|
|
862
|
-
(0, hooks_1.useEventListener)('mousedown', (mousedown) => {
|
|
863
|
-
if (!doesComponentContainEventTarget(mousedown)) {
|
|
864
|
-
mousedown.preventDefault(); // Prevent focus from being applied to body
|
|
865
|
-
mousedown.stopPropagation(); // Stop any other mousedown events from firing
|
|
866
|
-
}
|
|
867
|
-
}, {
|
|
868
|
-
enabled: isOpen,
|
|
869
|
-
});
|
|
870
|
-
(0, hooks_1.useEventListener)('click', (click) => {
|
|
871
|
-
if (!doesComponentContainEventTarget(click)) {
|
|
872
|
-
click.stopPropagation(); // Stop any other click events from firing
|
|
873
|
-
closeMenu();
|
|
874
|
-
}
|
|
875
|
-
}, {
|
|
876
|
-
options: { capture: true },
|
|
877
|
-
enabled: isOpen,
|
|
878
|
-
});
|
|
879
|
-
const popoverProps = {
|
|
880
|
-
popoverZIndex,
|
|
881
|
-
...(usePortal
|
|
882
|
-
? {
|
|
883
|
-
usePortal,
|
|
884
|
-
portalClassName,
|
|
885
|
-
portalContainer,
|
|
886
|
-
scrollContainer,
|
|
887
|
-
}
|
|
888
|
-
: { usePortal }),
|
|
889
|
-
};
|
|
890
|
-
const descriptionCharacters = (0, react_1.useMemo)(() => {
|
|
891
|
-
const characters = react_1.default.Children.map(children, (child) => child?.props?.description?.length ?? 0) ?? [];
|
|
892
|
-
return characters.length === 0 ? 0 : Math.max(...characters);
|
|
893
|
-
}, [children]);
|
|
894
|
-
return (react_1.default.createElement(ComboboxContext_1.ComboboxContext.Provider, { value: {
|
|
895
|
-
multiselect,
|
|
896
|
-
darkMode,
|
|
897
|
-
theme,
|
|
898
|
-
size,
|
|
899
|
-
withIcons,
|
|
900
|
-
disabled,
|
|
901
|
-
isOpen,
|
|
902
|
-
state,
|
|
903
|
-
searchState,
|
|
904
|
-
chipTruncationLocation,
|
|
905
|
-
chipCharacterLimit,
|
|
906
|
-
inputValue,
|
|
907
|
-
// When we open the menu (popover), we want the width of
|
|
908
|
-
// menu options to be same as the size of the input by default.
|
|
909
|
-
// If user has specifed a description with the option, we want
|
|
910
|
-
// the width of the option display value to be the same as the
|
|
911
|
-
// width of the input and the accomodate the description
|
|
912
|
-
// in rest of the popover width.
|
|
913
|
-
searchInputSize: popoverMenuWidth,
|
|
914
|
-
} },
|
|
915
|
-
react_1.default.createElement("div", { className: (0, emotion_1.cx)((0, Combobox_styles_1.comboboxParentStyle)(size), className), ...rest },
|
|
916
|
-
(label || description) && (react_1.default.createElement("div", { className: Combobox_styles_1.labelDescriptionContainerStyle },
|
|
917
|
-
label && (react_1.default.createElement(typography_1.Label, { id: labelId, htmlFor: inputId, darkMode: darkMode }, label)),
|
|
918
|
-
description && (react_1.default.createElement(typography_1.Description, { darkMode: darkMode }, description)))),
|
|
919
|
-
react_1.default.createElement("div", { ref: comboboxRef, role: "combobox", "aria-expanded": isOpen, "aria-controls": menuId, "aria-owns": menuId, tabIndex: -1, onMouseDown: handleInputWrapperMousedown, onClick: handleComboboxClick, onFocus: handleComboboxFocus, onKeyDown: handleKeyDown, onTransitionEnd: handleTransitionEnd, className: (0, emotion_1.cx)(Combobox_styles_1.baseComboboxStyles, Combobox_styles_1.comboboxThemeStyles[theme], (0, Combobox_styles_1.comboboxSizeStyles)(size), {
|
|
920
|
-
[Combobox_styles_1.comboboxSelectionStyles]: clearable && doesSelectionExist,
|
|
921
|
-
[Combobox_styles_1.comboboxDisabledStyles[theme]]: disabled,
|
|
922
|
-
[Combobox_styles_1.comboboxErrorStyles[theme]]: state === Combobox_types_1.State.error,
|
|
923
|
-
[Combobox_styles_1.comboboxFocusStyle[theme]]: isElementFocused(Combobox_types_1.ComboboxElement.Input),
|
|
924
|
-
}) },
|
|
925
|
-
react_1.default.createElement("div", { ref: inputWrapperRef, className: (0, Combobox_styles_1.inputWrapperStyle)({
|
|
926
|
-
size,
|
|
927
|
-
overflow,
|
|
928
|
-
}) },
|
|
929
|
-
renderedChips,
|
|
930
|
-
react_1.default.createElement("input", { "aria-label": ariaLabel ?? label, "aria-autocomplete": "list", "aria-controls": menuId, "aria-labelledby": labelId, ref: inputRef, id: inputId, className: (0, emotion_1.cx)(Combobox_styles_1.baseInputElementStyle, Combobox_styles_1.inputElementSizeStyle[size], Combobox_styles_1.inputElementThemeStyle[theme], (0, Combobox_styles_1.inputElementTransitionStyles)(isOpen), {
|
|
931
|
-
[(0, Combobox_styles_1.multiselectInputElementStyle)(size, inputValue)]: isMultiselect(selection),
|
|
932
|
-
}), placeholder: placeholderValue, disabled: disabled ?? undefined, onChange: handleInputChange, value: inputValue, autoComplete: "off" })),
|
|
933
|
-
renderedInputIcons),
|
|
934
|
-
state === 'error' && errorMessage && (react_1.default.createElement("div", { className: (0, emotion_1.cx)(Combobox_styles_1.errorMessageThemeStyle[theme], Combobox_styles_1.errorMessageSizeStyle[size]) }, errorMessage)),
|
|
935
|
-
react_1.default.createElement(ComboboxMenu_1.ComboboxMenu, { id: menuId, labelId: labelId, refEl: comboboxRef, ref: menuRef, className: (0, emotion_1.cx)(popoverMenuStyles(popoverMenuWidth, descriptionCharacters), popoverClassName), searchLoadingMessage: searchLoadingMessage, searchErrorMessage: searchErrorMessage, searchEmptyMessage: searchEmptyMessage, ...popoverProps }, renderedOptionsJSX))));
|
|
936
|
-
// Closure-dependant utils
|
|
937
|
-
/**
|
|
938
|
-
* Returns whether the event target is a Combobox element
|
|
939
|
-
*/
|
|
940
|
-
function doesComponentContainEventTarget({ target }) {
|
|
941
|
-
return (menuRef.current?.contains(target) ||
|
|
942
|
-
comboboxRef.current?.contains(target) ||
|
|
943
|
-
false);
|
|
944
|
-
}
|
|
945
|
-
/**
|
|
946
|
-
* Scrolls the combobox to the far right.
|
|
947
|
-
* Used when `overflow === 'scroll-x'`.
|
|
948
|
-
* Has no effect otherwise
|
|
949
|
-
*/
|
|
950
|
-
function scrollInputToEnd() {
|
|
951
|
-
if (inputWrapperRef && inputWrapperRef.current) {
|
|
952
|
-
// TODO - consider converting to .scrollTo(). This is not yet supported in IE or jsdom
|
|
953
|
-
inputWrapperRef.current.scrollLeft = inputWrapperRef.current.scrollWidth;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* Returns the provided element as a ComboboxElement string
|
|
958
|
-
*/
|
|
959
|
-
function getNameFromElement(element) {
|
|
960
|
-
if (!element)
|
|
961
|
-
return;
|
|
962
|
-
if (inputRef.current?.contains(element))
|
|
963
|
-
return Combobox_types_1.ComboboxElement.Input;
|
|
964
|
-
if (clearButtonRef.current?.contains(element))
|
|
965
|
-
return Combobox_types_1.ComboboxElement.ClearButton;
|
|
966
|
-
const activeChipIndex = isMultiselect(selection)
|
|
967
|
-
? selection.findIndex((value) => getChipRef(value)?.current?.contains(element))
|
|
968
|
-
: -1;
|
|
969
|
-
if (isMultiselect(selection)) {
|
|
970
|
-
if (activeChipIndex === 0)
|
|
971
|
-
return Combobox_types_1.ComboboxElement.FirstChip;
|
|
972
|
-
if (activeChipIndex === selection.length - 1)
|
|
973
|
-
return Combobox_types_1.ComboboxElement.LastChip;
|
|
974
|
-
if (activeChipIndex > 0)
|
|
975
|
-
return Combobox_types_1.ComboboxElement.MiddleChip;
|
|
976
|
-
}
|
|
977
|
-
if (menuRef.current?.contains(element))
|
|
978
|
-
return Combobox_types_1.ComboboxElement.Menu;
|
|
979
|
-
if (comboboxRef.current?.contains(element))
|
|
980
|
-
return Combobox_types_1.ComboboxElement.Combobox;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
exports.Combobox = Combobox;
|
|
984
|
-
Combobox.propTypes = {
|
|
985
|
-
// Multiselect props
|
|
986
|
-
multiselect: prop_types_1.default.bool,
|
|
987
|
-
value: prop_types_1.default.oneOfType([
|
|
988
|
-
prop_types_1.default.string,
|
|
989
|
-
prop_types_1.default.arrayOf(prop_types_1.default.string),
|
|
990
|
-
]),
|
|
991
|
-
initialValue: prop_types_1.default.oneOfType([
|
|
992
|
-
prop_types_1.default.string,
|
|
993
|
-
prop_types_1.default.arrayOf(prop_types_1.default.string),
|
|
994
|
-
]),
|
|
995
|
-
overflow: prop_types_1.default.oneOf(Object.values(Combobox_types_1.Overflow)),
|
|
996
|
-
// Standard Props
|
|
997
|
-
darkMode: prop_types_1.default.bool,
|
|
998
|
-
label: prop_types_1.default.string,
|
|
999
|
-
'aria-label': prop_types_1.default.string,
|
|
1000
|
-
children: prop_types_1.default.node,
|
|
1001
|
-
onChange: prop_types_1.default.func,
|
|
1002
|
-
chipCharacterLimit: prop_types_1.default.number,
|
|
1003
|
-
chipTruncationLocation: prop_types_1.default.oneOf(Object.values(Combobox_types_1.TruncationLocation)),
|
|
1004
|
-
onClear: prop_types_1.default.func,
|
|
1005
|
-
onFilter: prop_types_1.default.func,
|
|
1006
|
-
clearable: prop_types_1.default.bool,
|
|
1007
|
-
searchLoadingMessage: prop_types_1.default.string,
|
|
1008
|
-
searchErrorMessage: prop_types_1.default.string,
|
|
1009
|
-
searchEmptyMessage: prop_types_1.default.string,
|
|
1010
|
-
searchState: prop_types_1.default.oneOf(Object.values(Combobox_types_1.SearchState)),
|
|
1011
|
-
errorMessage: prop_types_1.default.string,
|
|
1012
|
-
state: prop_types_1.default.oneOf(Object.values(Combobox_types_1.State)),
|
|
1013
|
-
size: prop_types_1.default.oneOf(Object.values(Combobox_types_1.ComboboxSize)),
|
|
1014
|
-
disabled: prop_types_1.default.bool,
|
|
1015
|
-
description: prop_types_1.default.string,
|
|
1016
|
-
placeholder: prop_types_1.default.string,
|
|
1017
|
-
filteredOptions: prop_types_1.default.arrayOf(prop_types_1.default.string),
|
|
1018
|
-
// Popover Props
|
|
1019
|
-
popoverZIndex: prop_types_1.default.number,
|
|
1020
|
-
usePortal: prop_types_1.default.bool,
|
|
1021
|
-
scrollContainer: prop_types_1.default.elementType,
|
|
1022
|
-
portalContainer: prop_types_1.default.elementType,
|
|
1023
|
-
portalClassName: prop_types_1.default.string,
|
|
1024
|
-
};
|
|
1025
|
-
/**
|
|
1026
|
-
* Why'd you have to go and make things so complicated?
|
|
1027
|
-
* - Avril; and also me to myself about this component
|
|
1028
|
-
*/
|
|
1029
|
-
//# sourceMappingURL=Combobox.js.map
|