@mongodb-js/compass-components 1.29.3 → 1.30.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.
Files changed (240) hide show
  1. package/README.md +10 -14
  2. package/lib/components/breadcrumb.d.ts +2 -1
  3. package/lib/components/breadcrumb.d.ts.map +1 -1
  4. package/lib/components/breadcrumb.js +1 -1
  5. package/lib/components/breadcrumb.js.map +1 -1
  6. package/lib/components/bson-value.js +4 -0
  7. package/lib/components/bson-value.js.map +1 -1
  8. package/lib/components/collapsible-field-set.js +4 -1
  9. package/lib/components/collapsible-field-set.js.map +1 -1
  10. package/lib/components/combobox/Chip.js +5 -2
  11. package/lib/components/combobox/Chip.js.map +1 -1
  12. package/lib/components/combobox/Combobox.d.ts +11 -1
  13. package/lib/components/combobox/Combobox.d.ts.map +1 -1
  14. package/lib/components/combobox/Combobox.js +225 -5
  15. package/lib/components/combobox/Combobox.js.map +1 -1
  16. package/lib/components/combobox/Combobox.styles.d.ts +15 -0
  17. package/lib/components/combobox/Combobox.styles.d.ts.map +1 -1
  18. package/lib/components/combobox/Combobox.styles.js +19 -2
  19. package/lib/components/combobox/Combobox.styles.js.map +1 -1
  20. package/lib/components/combobox/Combobox.types.d.ts +174 -0
  21. package/lib/components/combobox/Combobox.types.d.ts.map +1 -1
  22. package/lib/components/combobox/Combobox.types.js +20 -0
  23. package/lib/components/combobox/Combobox.types.js.map +1 -1
  24. package/lib/components/combobox/ComboboxGroup.d.ts +1 -0
  25. package/lib/components/combobox/ComboboxGroup.d.ts.map +1 -1
  26. package/lib/components/combobox/ComboboxMenu/ComboboxMenu.js +5 -0
  27. package/lib/components/combobox/ComboboxMenu/ComboboxMenu.js.map +1 -1
  28. package/lib/components/combobox/ComboboxMenu/Menu.styles.d.ts +4 -0
  29. package/lib/components/combobox/ComboboxMenu/Menu.styles.d.ts.map +1 -1
  30. package/lib/components/combobox/ComboboxMenu/Menu.styles.js +4 -0
  31. package/lib/components/combobox/ComboboxMenu/Menu.styles.js.map +1 -1
  32. package/lib/components/combobox/ComboboxOption.d.ts +3 -0
  33. package/lib/components/combobox/ComboboxOption.d.ts.map +1 -1
  34. package/lib/components/combobox/ComboboxOption.js +14 -0
  35. package/lib/components/combobox/ComboboxOption.js.map +1 -1
  36. package/lib/components/combobox/Menu.styles.d.ts +4 -0
  37. package/lib/components/combobox/Menu.styles.d.ts.map +1 -1
  38. package/lib/components/combobox/Menu.styles.js +7 -3
  39. package/lib/components/combobox/Menu.styles.js.map +1 -1
  40. package/lib/components/combobox/utils/OptionObjectUtils.d.ts +18 -0
  41. package/lib/components/combobox/utils/OptionObjectUtils.d.ts.map +1 -1
  42. package/lib/components/combobox/utils/OptionObjectUtils.js +18 -0
  43. package/lib/components/combobox/utils/OptionObjectUtils.js.map +1 -1
  44. package/lib/components/combobox/utils/flattenChildren.d.ts +8 -0
  45. package/lib/components/combobox/utils/flattenChildren.d.ts.map +1 -1
  46. package/lib/components/combobox/utils/flattenChildren.js +13 -1
  47. package/lib/components/combobox/utils/flattenChildren.js.map +1 -1
  48. package/lib/components/combobox/utils/getNameAndValue.d.ts +9 -0
  49. package/lib/components/combobox/utils/getNameAndValue.d.ts.map +1 -1
  50. package/lib/components/combobox/utils/getNameAndValue.js +10 -1
  51. package/lib/components/combobox/utils/getNameAndValue.js.map +1 -1
  52. package/lib/components/combobox/utils/wrapJSX.d.ts +13 -0
  53. package/lib/components/combobox/utils/wrapJSX.d.ts.map +1 -1
  54. package/lib/components/combobox/utils/wrapJSX.js +21 -0
  55. package/lib/components/combobox/utils/wrapJSX.js.map +1 -1
  56. package/lib/components/combobox-with-custom-option.d.ts +2 -1
  57. package/lib/components/combobox-with-custom-option.d.ts.map +1 -1
  58. package/lib/components/combobox-with-custom-option.js +2 -0
  59. package/lib/components/combobox-with-custom-option.js.map +1 -1
  60. package/lib/components/compass-components-provider.d.ts +24 -1
  61. package/lib/components/compass-components-provider.d.ts.map +1 -1
  62. package/lib/components/compass-components-provider.js +22 -1
  63. package/lib/components/compass-components-provider.js.map +1 -1
  64. package/lib/components/content-with-fallback.js +3 -0
  65. package/lib/components/content-with-fallback.js.map +1 -1
  66. package/lib/components/document-list/document-actions-group.js +10 -1
  67. package/lib/components/document-list/document-actions-group.js.map +1 -1
  68. package/lib/components/document-list/document-edit-actions-footer.js +3 -0
  69. package/lib/components/document-list/document-edit-actions-footer.js.map +1 -1
  70. package/lib/components/document-list/document.js +23 -1
  71. package/lib/components/document-list/document.js.map +1 -1
  72. package/lib/components/document-list/element-actions.js +2 -0
  73. package/lib/components/document-list/element-actions.js.map +1 -1
  74. package/lib/components/document-list/element-editors.js +51 -9
  75. package/lib/components/document-list/element-editors.js.map +1 -1
  76. package/lib/components/document-list/element.js +45 -5
  77. package/lib/components/document-list/element.js.map +1 -1
  78. package/lib/components/empty-content.js +1 -1
  79. package/lib/components/empty-content.js.map +1 -1
  80. package/lib/components/error-boundary.js +3 -0
  81. package/lib/components/error-boundary.js.map +1 -1
  82. package/lib/components/feedback-popover.d.ts +1 -1
  83. package/lib/components/feedback-popover.d.ts.map +1 -1
  84. package/lib/components/feedback-popover.js +3 -0
  85. package/lib/components/feedback-popover.js.map +1 -1
  86. package/lib/components/file-input.js +22 -3
  87. package/lib/components/file-input.js.map +1 -1
  88. package/lib/components/guide-cue/guide-cue-groups.js +3 -0
  89. package/lib/components/guide-cue/guide-cue-groups.js.map +1 -1
  90. package/lib/components/guide-cue/guide-cue-service.js +5 -0
  91. package/lib/components/guide-cue/guide-cue-service.js.map +1 -1
  92. package/lib/components/guide-cue/guide-cue.d.ts +1 -1
  93. package/lib/components/guide-cue/guide-cue.d.ts.map +1 -1
  94. package/lib/components/guide-cue/guide-cue.js +14 -1
  95. package/lib/components/guide-cue/guide-cue.js.map +1 -1
  96. package/lib/components/horizontal-rule.d.ts +1 -1
  97. package/lib/components/horizontal-rule.d.ts.map +1 -1
  98. package/lib/components/icons/favorite-icon.js +3 -1
  99. package/lib/components/icons/favorite-icon.js.map +1 -1
  100. package/lib/components/icons/logo-icon.d.ts +1 -1
  101. package/lib/components/icons/logo-icon.d.ts.map +1 -1
  102. package/lib/components/icons/server-icon.d.ts +2 -1
  103. package/lib/components/icons/server-icon.d.ts.map +1 -1
  104. package/lib/components/index-icon.d.ts +2 -1
  105. package/lib/components/index-icon.d.ts.map +1 -1
  106. package/lib/components/index-keys-badge.js +1 -0
  107. package/lib/components/index-keys-badge.js.map +1 -1
  108. package/lib/components/inline-definition.js +1 -0
  109. package/lib/components/inline-definition.js.map +1 -1
  110. package/lib/components/inline-info-link.d.ts +1 -0
  111. package/lib/components/inline-info-link.d.ts.map +1 -1
  112. package/lib/components/interactive-popover.d.ts +3 -0
  113. package/lib/components/interactive-popover.d.ts.map +1 -1
  114. package/lib/components/interactive-popover.js +6 -0
  115. package/lib/components/interactive-popover.js.map +1 -1
  116. package/lib/components/item-action-controls.d.ts +6 -5
  117. package/lib/components/item-action-controls.d.ts.map +1 -1
  118. package/lib/components/item-action-controls.js +31 -4
  119. package/lib/components/item-action-controls.js.map +1 -1
  120. package/lib/components/keyline-card.d.ts +9 -2
  121. package/lib/components/keyline-card.d.ts.map +1 -1
  122. package/lib/components/leafygreen.d.ts +1 -1
  123. package/lib/components/leafygreen.js +8 -0
  124. package/lib/components/leafygreen.js.map +1 -1
  125. package/lib/components/links/link.js +2 -0
  126. package/lib/components/links/link.js.map +1 -1
  127. package/lib/components/loader.d.ts +1 -1
  128. package/lib/components/loader.d.ts.map +1 -1
  129. package/lib/components/modals/form-modal.d.ts +1 -1
  130. package/lib/components/modals/form-modal.d.ts.map +1 -1
  131. package/lib/components/modals/info-modal.d.ts +1 -1
  132. package/lib/components/modals/info-modal.d.ts.map +1 -1
  133. package/lib/components/modals/modal-body.js +2 -0
  134. package/lib/components/modals/modal-body.js.map +1 -1
  135. package/lib/components/modals/modal-footer-button.d.ts +2 -1
  136. package/lib/components/modals/modal-footer-button.d.ts.map +1 -1
  137. package/lib/components/modals/modal-header.d.ts +1 -1
  138. package/lib/components/modals/modal-header.d.ts.map +1 -1
  139. package/lib/components/modals/modal.js +8 -0
  140. package/lib/components/modals/modal.js.map +1 -1
  141. package/lib/components/options-toggle.js +5 -1
  142. package/lib/components/options-toggle.js.map +1 -1
  143. package/lib/components/placeholder.js +1 -0
  144. package/lib/components/placeholder.js.map +1 -1
  145. package/lib/components/resize-handle.js +4 -0
  146. package/lib/components/resize-handle.js.map +1 -1
  147. package/lib/components/resizeable-sidebar.js +2 -0
  148. package/lib/components/resizeable-sidebar.js.map +1 -1
  149. package/lib/components/signal-popover.d.ts +19 -0
  150. package/lib/components/signal-popover.d.ts.map +1 -1
  151. package/lib/components/signal-popover.js +43 -3
  152. package/lib/components/signal-popover.js.map +1 -1
  153. package/lib/components/signals.d.ts +4 -3
  154. package/lib/components/signals.d.ts.map +1 -1
  155. package/lib/components/tab-nav-bar.d.ts +11 -4
  156. package/lib/components/tab-nav-bar.d.ts.map +1 -1
  157. package/lib/components/tab-nav-bar.js +14 -11
  158. package/lib/components/tab-nav-bar.js.map +1 -1
  159. package/lib/components/toast-body.d.ts +2 -1
  160. package/lib/components/toast-body.d.ts.map +1 -1
  161. package/lib/components/toast-body.js +1 -0
  162. package/lib/components/toast-body.js.map +1 -1
  163. package/lib/components/virtual-grid.d.ts +43 -1
  164. package/lib/components/virtual-grid.d.ts.map +1 -1
  165. package/lib/components/virtual-grid.js +3 -0
  166. package/lib/components/virtual-grid.js.map +1 -1
  167. package/lib/components/virtual-list.d.ts +70 -1
  168. package/lib/components/virtual-list.d.ts.map +1 -1
  169. package/lib/components/virtual-list.js +9 -1
  170. package/lib/components/virtual-list.js.map +1 -1
  171. package/lib/components/workspace-container.d.ts +1 -1
  172. package/lib/components/workspace-container.d.ts.map +1 -1
  173. package/lib/components/workspace-container.js +10 -0
  174. package/lib/components/workspace-container.js.map +1 -1
  175. package/lib/components/workspace-tabs/tab.d.ts +1 -1
  176. package/lib/components/workspace-tabs/tab.d.ts.map +1 -1
  177. package/lib/components/workspace-tabs/tab.js +15 -1
  178. package/lib/components/workspace-tabs/tab.js.map +1 -1
  179. package/lib/components/workspace-tabs/workspace-tabs.d.ts +1 -1
  180. package/lib/components/workspace-tabs/workspace-tabs.d.ts.map +1 -1
  181. package/lib/components/workspace-tabs/workspace-tabs.js +5 -1
  182. package/lib/components/workspace-tabs/workspace-tabs.js.map +1 -1
  183. package/lib/hooks/use-confirmation.js +12 -1
  184. package/lib/hooks/use-confirmation.js.map +1 -1
  185. package/lib/hooks/use-default-action.d.ts +8 -0
  186. package/lib/hooks/use-default-action.d.ts.map +1 -1
  187. package/lib/hooks/use-default-action.js +11 -1
  188. package/lib/hooks/use-default-action.js.map +1 -1
  189. package/lib/hooks/use-effect-on-change.d.ts +6 -0
  190. package/lib/hooks/use-effect-on-change.d.ts.map +1 -1
  191. package/lib/hooks/use-effect-on-change.js +18 -1
  192. package/lib/hooks/use-effect-on-change.js.map +1 -1
  193. package/lib/hooks/use-focus-ring.d.ts +19 -0
  194. package/lib/hooks/use-focus-ring.d.ts.map +1 -1
  195. package/lib/hooks/use-focus-ring.js +21 -0
  196. package/lib/hooks/use-focus-ring.js.map +1 -1
  197. package/lib/hooks/use-hotkeys.d.ts +15 -1
  198. package/lib/hooks/use-hotkeys.d.ts.map +1 -1
  199. package/lib/hooks/use-hotkeys.js +18 -3
  200. package/lib/hooks/use-hotkeys.js.map +1 -1
  201. package/lib/hooks/use-persisted-state.d.ts +9 -0
  202. package/lib/hooks/use-persisted-state.d.ts.map +1 -1
  203. package/lib/hooks/use-persisted-state.js +9 -0
  204. package/lib/hooks/use-persisted-state.js.map +1 -1
  205. package/lib/hooks/use-scrollbars.d.ts +8 -1
  206. package/lib/hooks/use-scrollbars.d.ts.map +1 -1
  207. package/lib/hooks/use-scrollbars.js +19 -0
  208. package/lib/hooks/use-scrollbars.js.map +1 -1
  209. package/lib/hooks/use-sort.js +6 -0
  210. package/lib/hooks/use-sort.js.map +1 -1
  211. package/lib/hooks/use-stacked-component.d.ts +1 -1
  212. package/lib/hooks/use-stacked-component.d.ts.map +1 -1
  213. package/lib/hooks/use-stacked-component.js +1 -0
  214. package/lib/hooks/use-stacked-component.js.map +1 -1
  215. package/lib/hooks/use-theme.d.ts.map +1 -1
  216. package/lib/hooks/use-theme.js +10 -1
  217. package/lib/hooks/use-theme.js.map +1 -1
  218. package/lib/hooks/use-toast.d.ts +15 -0
  219. package/lib/hooks/use-toast.d.ts.map +1 -1
  220. package/lib/hooks/use-toast.js +30 -0
  221. package/lib/hooks/use-toast.js.map +1 -1
  222. package/lib/hooks/use-virtual-grid.d.ts +8 -0
  223. package/lib/hooks/use-virtual-grid.d.ts.map +1 -1
  224. package/lib/hooks/use-virtual-grid.js +29 -0
  225. package/lib/hooks/use-virtual-grid.js.map +1 -1
  226. package/lib/hooks/use-virtual-list-item-observer.js +23 -0
  227. package/lib/hooks/use-virtual-list-item-observer.js.map +1 -1
  228. package/lib/utils/format-date.js +2 -0
  229. package/lib/utils/format-date.js.map +1 -1
  230. package/lib/utils/merge-props.js +1 -0
  231. package/lib/utils/merge-props.js.map +1 -1
  232. package/lib/utils/rafraf.d.ts +10 -0
  233. package/lib/utils/rafraf.d.ts.map +1 -1
  234. package/lib/utils/rafraf.js +10 -0
  235. package/lib/utils/rafraf.js.map +1 -1
  236. package/package.json +10 -13
  237. package/lib/hooks/use-virtual-grid.test.d.ts +0 -2
  238. package/lib/hooks/use-virtual-grid.test.d.ts.map +0 -1
  239. package/lib/hooks/use-virtual-grid.test.js +0 -210
  240. package/lib/hooks/use-virtual-grid.test.js.map +0 -1
@@ -27,6 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.Combobox = void 0;
30
+ /* eslint-disable filename-rules/match */
30
31
  const react_1 = __importStar(require("react"));
31
32
  const clone_1 = __importDefault(require("lodash/clone"));
32
33
  const isArray_1 = __importDefault(require("lodash/isArray"));
@@ -53,6 +54,8 @@ const ComboboxOption_1 = require("./ComboboxOption");
53
54
  const utils_1 = require("./utils");
54
55
  const tokens_1 = require("@leafygreen-ui/tokens");
55
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.
56
59
  const popoverMenuStyles = (width, numDescChars) => {
57
60
  if (numDescChars === 0) {
58
61
  return (0, emotion_1.css) `
@@ -70,6 +73,11 @@ const popoverMenuStyles = (width, numDescChars) => {
70
73
  );
71
74
  `;
72
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
+ */
73
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 }) {
74
82
  const { darkMode, theme } = (0, leafygreen_provider_1.useDarkMode)(darkModeProp);
75
83
  const getOptionRef = (0, hooks_1.useDynamicRefs)({ prefix: 'option' });
@@ -97,7 +105,13 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
97
105
  : placeholder;
98
106
  const closeMenu = () => setOpen(false);
99
107
  const openMenu = () => setOpen(true);
108
+ /**
109
+ * Array of all of the options objects
110
+ */
100
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
+ */
101
115
  const isMultiselect = (0, react_1.useCallback)((val) => {
102
116
  if (multiselect && (typeof val === 'string' || typeof val === 'number')) {
103
117
  lib_1.consoleOnce.error(`Error in Combobox: multiselect is set to \`true\`, but received a ${typeof val} value: "${val}"`);
@@ -107,6 +121,10 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
107
121
  }
108
122
  return multiselect && (0, isArray_1.default)(val);
109
123
  }, [multiselect]);
124
+ /**
125
+ * Forces focus of input box
126
+ * @param cursorPos index the cursor should be set to
127
+ */
110
128
  const setInputFocus = (0, react_1.useCallback)((cursorPos) => {
111
129
  if (!disabled && inputRef && inputRef.current) {
112
130
  inputRef.current.focus();
@@ -115,18 +133,27 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
115
133
  }
116
134
  }
117
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
+ */
118
141
  const updateSelection = (0, react_1.useCallback)((value) => {
119
142
  if (isMultiselect(selection)) {
143
+ // We know M is true here
120
144
  const newSelection = (0, clone_1.default)(selection);
121
145
  if ((0, isNull_1.default)(value)) {
122
146
  newSelection.length = 0;
123
147
  }
124
148
  else {
125
149
  if (selection.includes(value)) {
150
+ // remove from array
126
151
  newSelection.splice(newSelection.indexOf(value), 1);
127
152
  }
128
153
  else {
154
+ // add to array
129
155
  newSelection.push(value);
156
+ // clear text
130
157
  setInputValue('');
131
158
  }
132
159
  }
@@ -139,15 +166,28 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
139
166
  onChange?.(newSelection);
140
167
  }
141
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
+ */
142
173
  const isValueCurrentSelection = (0, react_1.useCallback)((value) => {
143
174
  return isMultiselect(selection)
144
175
  ? selection.includes(value)
145
176
  : value === selection;
146
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
+ */
147
183
  const isTextCurrentSelection = (0, react_1.useCallback)((text) => {
148
184
  const value = (0, utils_1.getValueForDisplayName)(text, allOptions);
149
185
  return isValueCurrentSelection(value);
150
186
  }, [allOptions, isValueCurrentSelection]);
187
+ /**
188
+ * Returns whether the provided option is disabled
189
+ * @param option the option value or OptionObject to check
190
+ */
151
191
  const isOptionDisabled = (option) => {
152
192
  if (typeof option === 'string') {
153
193
  const optionObj = (0, utils_1.getOptionObjectFromValue)(option, allOptions);
@@ -157,14 +197,23 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
157
197
  return !!option.isDisabled;
158
198
  }
159
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
+ */
160
204
  const shouldOptionBeVisible = (0, react_1.useCallback)((option) => {
161
205
  const value = typeof option === 'string' ? option : option.value;
206
+ // If filtered options are provided
162
207
  if (filteredOptions && filteredOptions.length > 0) {
163
208
  return filteredOptions.includes(value);
164
209
  }
210
+ // If the text input value is the current selection
211
+ // (or included in the selection)
212
+ // then all options should be visible
165
213
  if (isTextCurrentSelection(inputValue)) {
166
214
  return true;
167
215
  }
216
+ // otherwise, we do our own filtering
168
217
  const displayName = typeof option === 'string'
169
218
  ? (0, utils_1.getDisplayNameForValue)(value, allOptions)
170
219
  : option.displayName;
@@ -173,30 +222,58 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
173
222
  .includes(inputValue.toLowerCase());
174
223
  return isValueInDisplayName;
175
224
  }, [filteredOptions, isTextCurrentSelection, inputValue, allOptions]);
225
+ /**
226
+ * The array of visible options objects
227
+ */
176
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
+ */
177
233
  const isValueValid = (0, react_1.useCallback)((value) => {
178
234
  return value ? !!allOptions.find((opt) => opt.value === value) : false;
179
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
+ */
180
240
  const getIndexOfValue = (0, react_1.useCallback)((value) => {
181
241
  return visibleOptions
182
242
  ? visibleOptions.findIndex((option) => option.value === value)
183
243
  : -1;
184
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
+ */
185
249
  const getValueAtIndex = (0, react_1.useCallback)((index) => {
186
250
  if (visibleOptions && visibleOptions.length >= index) {
187
251
  const option = visibleOptions[index];
188
252
  return option ? option.value : undefined;
189
253
  }
190
254
  }, [visibleOptions]);
255
+ /**
256
+ * Returns the index of the active chip in the selection array
257
+ */
191
258
  const getActiveChipIndex = (0, react_1.useCallback)(() => isMultiselect(selection)
192
259
  ? selection.findIndex((value) => getChipRef(value)?.current?.contains(document.activeElement))
193
260
  : -1, [getChipRef, isMultiselect, selection]);
261
+ /**
262
+ *
263
+ * Focus Management
264
+ *
265
+ */
194
266
  const [focusedElementName, trackFocusedElement] = (0, react_1.useState)();
195
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
+ */
196
272
  const updateHighlightedOption = (0, react_1.useCallback)((direction) => {
197
273
  const optionsCount = visibleOptions?.length ?? 0;
198
274
  const lastIndex = optionsCount - 1 > 0 ? optionsCount - 1 : 0;
199
275
  const indexOfHighlight = getIndexOfValue(highlightedOption);
276
+ // Remove focus from chip
200
277
  if (direction && isOpen) {
201
278
  setFocusedChip(null);
202
279
  setInputFocus();
@@ -235,6 +312,11 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
235
312
  setInputFocus,
236
313
  visibleOptions?.length,
237
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
+ */
238
320
  const updateFocusedChip = (0, react_1.useCallback)((direction, relativeToIndex) => {
239
321
  if (isMultiselect(selection)) {
240
322
  switch (direction) {
@@ -274,13 +356,18 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
274
356
  }
275
357
  }
276
358
  }, [getActiveChipIndex, isMultiselect, selection]);
359
+ /**
360
+ * Handles an arrow key press
361
+ */
277
362
  const handleArrowKey = (0, react_1.useCallback)((direction, event) => {
363
+ // Remove focus from menu
278
364
  if (direction)
279
365
  setHighlightedOption(null);
280
366
  switch (direction) {
281
367
  case 'right':
282
368
  switch (focusedElementName) {
283
369
  case Combobox_types_1.ComboboxElement.Input: {
370
+ // If cursor is at the end of the input
284
371
  if (inputRef.current?.selectionEnd ===
285
372
  inputRef.current?.value.length) {
286
373
  clearButtonRef.current?.focus();
@@ -291,12 +378,15 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
291
378
  case Combobox_types_1.ComboboxElement.MiddleChip:
292
379
  case Combobox_types_1.ComboboxElement.LastChip: {
293
380
  if (focusedElementName === Combobox_types_1.ComboboxElement.LastChip ||
381
+ // the first chip is also the last chip (i.e. only one)
294
382
  selection?.length === 1) {
383
+ // if focus is on last chip, go to input
295
384
  setInputFocus(0);
296
385
  updateFocusedChip(null);
297
386
  event.preventDefault();
298
387
  break;
299
388
  }
389
+ // First/middle chips
300
390
  updateFocusedChip('next');
301
391
  break;
302
392
  }
@@ -316,6 +406,7 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
316
406
  case Combobox_types_1.ComboboxElement.MiddleChip:
317
407
  case Combobox_types_1.ComboboxElement.LastChip: {
318
408
  if (isMultiselect(selection)) {
409
+ // Break if cursor is not at the start of the input
319
410
  if (focusedElementName === Combobox_types_1.ComboboxElement.Input &&
320
411
  inputRef.current?.selectionStart !== 0) {
321
412
  break;
@@ -340,11 +431,14 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
340
431
  setInputFocus,
341
432
  updateFocusedChip,
342
433
  ]);
434
+ // When the input value changes (or when the menu opens)
435
+ // Update the focused option
343
436
  (0, react_1.useEffect)(() => {
344
437
  if (inputValue !== prevValue) {
345
438
  updateHighlightedOption('first');
346
439
  }
347
440
  }, [inputValue, isOpen, prevValue, updateHighlightedOption]);
441
+ // When the focused option changes, update the menu scroll if necessary
348
442
  (0, react_1.useEffect)(() => {
349
443
  if (highlightedOption) {
350
444
  const focusedElementRef = getOptionRef(highlightedOption);
@@ -357,6 +451,12 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
357
451
  }
358
452
  }
359
453
  }, [highlightedOption, getOptionRef]);
454
+ /**
455
+ * Rendering
456
+ */
457
+ /**
458
+ * Callback to render a child as an <InternalComboboxOption> element
459
+ */
360
460
  const renderOption = (0, react_1.useCallback)((child) => {
361
461
  if ((0, lib_1.isComponentType)(child, 'ComboboxOption')) {
362
462
  const { value, displayName } = (0, utils_1.getNameAndValue)(child.props);
@@ -395,7 +495,13 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
395
495
  shouldOptionBeVisible,
396
496
  updateSelection,
397
497
  ]);
498
+ /**
499
+ * The rendered JSX elements for the options
500
+ */
398
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
+ */
399
505
  const renderedChips = (0, react_1.useMemo)(() => {
400
506
  if (isMultiselect(selection)) {
401
507
  return selection.filter(isValueValid).map((value, index) => {
@@ -405,6 +511,7 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
405
511
  const isLastChip = index >= selection.length - 1;
406
512
  const onRemove = () => {
407
513
  if (isLastChip) {
514
+ // Focus the input if this is the last chip in the set
408
515
  setInputFocus();
409
516
  updateFocusedChip(null);
410
517
  }
@@ -433,6 +540,9 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
433
540
  const handleClearButtonFocus = () => {
434
541
  setHighlightedOption(null);
435
542
  };
543
+ /**
544
+ * The rendered JSX for the input icons (clear, warn & caret)
545
+ */
436
546
  const renderedInputIcons = (0, react_1.useMemo)(() => {
437
547
  const handleClearButtonClick = (e) => {
438
548
  if (!disabled) {
@@ -460,14 +570,27 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
460
570
  onFilter,
461
571
  isOpen,
462
572
  ]);
573
+ /**
574
+ * Flag to determine whether the rendered options have icons
575
+ */
463
576
  const withIcons = (0, react_1.useMemo)(() => allOptions.some((opt) => opt.hasGlyph), [allOptions]);
577
+ /**
578
+ *
579
+ * Selection Management
580
+ *
581
+ */
464
582
  const onCloseMenu = (0, react_1.useCallback)(() => {
583
+ // Single select, and no change to selection
465
584
  if (!isMultiselect(selection) && selection === prevSelection) {
466
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
467
588
  if (exactMatchedOption && !value) {
468
589
  setSelection(exactMatchedOption.value);
469
590
  }
470
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
471
594
  setInputValue(selection);
472
595
  }
473
596
  }
@@ -483,9 +606,12 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
483
606
  const onSelect = (0, react_1.useCallback)(() => {
484
607
  if (doesSelectionExist) {
485
608
  if (isMultiselect(selection)) {
609
+ // Scroll the wrapper to the end. No effect if not `overflow="scroll-x"`
486
610
  scrollInputToEnd();
487
611
  }
488
612
  else if (!isMultiselect(selection)) {
613
+ // Update the text input.
614
+ // Set the value instead of displayName to align with handleInputChange COMPASS-6511
489
615
  setInputValue(selection);
490
616
  closeMenu();
491
617
  }
@@ -494,9 +620,11 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
494
620
  setInputValue('');
495
621
  }
496
622
  }, [doesSelectionExist, allOptions, isMultiselect, selection]);
623
+ // Set the initialValue
497
624
  (0, react_1.useEffect)(() => {
498
625
  if (initialValue) {
499
626
  if ((0, isArray_1.default)(initialValue)) {
627
+ // Ensure the values we set are real options
500
628
  const filteredValue = initialValue.filter((value) => isValueValid(value)) ?? [];
501
629
  setSelection(filteredValue);
502
630
  }
@@ -509,13 +637,16 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
509
637
  else {
510
638
  setSelection((0, Combobox_types_1.getNullSelection)(multiselect));
511
639
  }
640
+ // eslint-disable-next-line react-hooks/exhaustive-deps
512
641
  }, []);
642
+ // When controlled value changes, update the selection
513
643
  (0, react_1.useEffect)(() => {
514
644
  if (!(0, isUndefined_1.default)(value) && value !== prevValue) {
515
645
  if ((0, isNull_1.default)(value)) {
516
646
  setSelection(null);
517
647
  }
518
648
  else if (isMultiselect(value)) {
649
+ // Ensure the value(s) passed in are valid options
519
650
  const newSelection = value.filter(isValueValid);
520
651
  setSelection(newSelection);
521
652
  }
@@ -524,29 +655,49 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
524
655
  }
525
656
  }
526
657
  }, [isMultiselect, isValueValid, prevValue, value]);
658
+ // onSelect
659
+ // Side effects to run when the selection changes
527
660
  (0, react_1.useEffect)(() => {
528
661
  if (!(0, isEqual_1.default)(selection, prevSelection)) {
529
662
  onSelect();
530
663
  }
531
664
  }, [onSelect, prevSelection, selection]);
665
+ // when the menu closes, update the value if needed
532
666
  (0, react_1.useEffect)(() => {
533
667
  if (!isOpen && wasOpen) {
534
668
  onCloseMenu();
535
669
  }
536
670
  }, [isOpen, wasOpen, onCloseMenu]);
671
+ /**
672
+ *
673
+ * Menu management
674
+ *
675
+ */
537
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
538
679
  (0, react_1.useEffect)(() => {
539
680
  setPopoverMenuWidth(comboboxRef.current?.clientWidth ?? 0);
540
681
  }, [comboboxRef, isOpen, highlightedOption, selection]);
682
+ // Handler fired when the menu has finished transitioning in/out
541
683
  const handleTransitionEnd = () => {
542
684
  setPopoverMenuWidth(comboboxRef.current?.clientWidth ?? 0);
543
685
  };
686
+ /**
687
+ *
688
+ * Event Handlers
689
+ *
690
+ */
691
+ // Prevent combobox from gaining focus by default
544
692
  const handleInputWrapperMousedown = (e) => {
545
693
  if (disabled) {
546
694
  e.preventDefault();
547
695
  }
548
696
  };
697
+ // Set focus to the input element on click
549
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)
550
701
  if (e.target !== inputRef.current) {
551
702
  let cursorPos = 0;
552
703
  if (inputRef.current) {
@@ -556,21 +707,29 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
556
707
  }
557
708
  setInputFocus(cursorPos);
558
709
  }
710
+ // Only open the menu in response to a click
559
711
  openMenu();
560
712
  };
713
+ // Fired whenever the wrapper gains focus,
714
+ // and any time the focus within changes
561
715
  const handleComboboxFocus = (e) => {
562
716
  scrollInputToEnd();
563
717
  trackFocusedElement(getNameFromElement(e.target));
564
718
  };
719
+ // Fired onChange
565
720
  const handleInputChange = ({ target: { value }, }) => {
566
721
  setInputValue(value);
722
+ // fire any filter function passed in
567
723
  onFilter?.(value);
568
724
  };
569
725
  const handleKeyDown = (event) => {
570
726
  const isFocusInMenu = menuRef.current?.contains(document.activeElement);
571
727
  const isFocusOnCombobox = comboboxRef.current?.contains(document.activeElement);
572
728
  const isFocusInComponent = isFocusOnCombobox || isFocusInMenu;
729
+ // Only run if the focus is in the component
573
730
  if (isFocusInComponent) {
731
+ // No support for modifiers yet
732
+ // TODO - Handle support for multiple chip selection
574
733
  if (event.ctrlKey || event.shiftKey || event.altKey) {
575
734
  return;
576
735
  }
@@ -583,14 +742,17 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
583
742
  updateHighlightedOption('first');
584
743
  updateFocusedChip(null);
585
744
  }
745
+ // else use default behavior
586
746
  break;
587
747
  }
588
748
  case 'LastChip': {
749
+ // use default behavior
589
750
  updateFocusedChip(null);
590
751
  break;
591
752
  }
592
753
  case 'FirstChip':
593
754
  case 'MiddleChip': {
755
+ // use default behavior
594
756
  break;
595
757
  }
596
758
  case 'ClearButton':
@@ -606,32 +768,45 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
606
768
  }
607
769
  case lib_1.keyMap.Enter: {
608
770
  if (!isOpen) {
771
+ // If the menu is not open, enter should open the menu
609
772
  openMenu();
610
773
  }
611
- else if (focusedElementName === Combobox_types_1.ComboboxElement.Input &&
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 &&
612
780
  !(0, isNull_1.default)(highlightedOption) &&
613
781
  !isOptionDisabled(highlightedOption)) {
614
782
  updateSelection(highlightedOption);
615
783
  }
616
- else if (focusedElementName === Combobox_types_1.ComboboxElement.ClearButton) {
784
+ else if (
785
+ // Focused on clear button
786
+ focusedElementName === Combobox_types_1.ComboboxElement.ClearButton) {
617
787
  updateSelection(null);
618
788
  setInputFocus();
619
789
  }
620
790
  break;
621
791
  }
622
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`
623
795
  if (isMultiselect(selection)) {
624
796
  if (focusedElementName === 'Input' &&
625
797
  inputRef.current?.selectionStart === 0) {
626
798
  updateFocusedChip('last');
627
799
  }
628
800
  }
801
+ // Open the menu regardless
629
802
  openMenu();
630
803
  break;
631
804
  }
632
805
  case lib_1.keyMap.ArrowDown: {
633
806
  if (isOpen) {
807
+ // Prevent the page from scrolling
634
808
  event.preventDefault();
809
+ // only change option if the menu is already open
635
810
  updateHighlightedOption('next');
636
811
  }
637
812
  else {
@@ -641,7 +816,9 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
641
816
  }
642
817
  case lib_1.keyMap.ArrowUp: {
643
818
  if (isOpen) {
819
+ // Prevent the page from scrolling
644
820
  event.preventDefault();
821
+ // only change option if the menu is already open
645
822
  updateHighlightedOption('prev');
646
823
  }
647
824
  else {
@@ -665,17 +842,34 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
665
842
  }
666
843
  }
667
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
+ */
668
862
  (0, hooks_1.useEventListener)('mousedown', (mousedown) => {
669
863
  if (!doesComponentContainEventTarget(mousedown)) {
670
- mousedown.preventDefault();
671
- mousedown.stopPropagation();
864
+ mousedown.preventDefault(); // Prevent focus from being applied to body
865
+ mousedown.stopPropagation(); // Stop any other mousedown events from firing
672
866
  }
673
867
  }, {
674
868
  enabled: isOpen,
675
869
  });
676
870
  (0, hooks_1.useEventListener)('click', (click) => {
677
871
  if (!doesComponentContainEventTarget(click)) {
678
- click.stopPropagation();
872
+ click.stopPropagation(); // Stop any other click events from firing
679
873
  closeMenu();
680
874
  }
681
875
  }, {
@@ -710,6 +904,12 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
710
904
  chipTruncationLocation,
711
905
  chipCharacterLimit,
712
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.
713
913
  searchInputSize: popoverMenuWidth,
714
914
  } },
715
915
  react_1.default.createElement("div", { className: (0, emotion_1.cx)((0, Combobox_styles_1.comboboxParentStyle)(size), className), ...rest },
@@ -733,16 +933,29 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
733
933
  renderedInputIcons),
734
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)),
735
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
+ */
736
940
  function doesComponentContainEventTarget({ target }) {
737
941
  return (menuRef.current?.contains(target) ||
738
942
  comboboxRef.current?.contains(target) ||
739
943
  false);
740
944
  }
945
+ /**
946
+ * Scrolls the combobox to the far right.
947
+ * Used when `overflow === 'scroll-x'`.
948
+ * Has no effect otherwise
949
+ */
741
950
  function scrollInputToEnd() {
742
951
  if (inputWrapperRef && inputWrapperRef.current) {
952
+ // TODO - consider converting to .scrollTo(). This is not yet supported in IE or jsdom
743
953
  inputWrapperRef.current.scrollLeft = inputWrapperRef.current.scrollWidth;
744
954
  }
745
955
  }
956
+ /**
957
+ * Returns the provided element as a ComboboxElement string
958
+ */
746
959
  function getNameFromElement(element) {
747
960
  if (!element)
748
961
  return;
@@ -769,6 +982,7 @@ function Combobox({ children, label, description, placeholder = 'Select', 'aria-
769
982
  }
770
983
  exports.Combobox = Combobox;
771
984
  Combobox.propTypes = {
985
+ // Multiselect props
772
986
  multiselect: prop_types_1.default.bool,
773
987
  value: prop_types_1.default.oneOfType([
774
988
  prop_types_1.default.string,
@@ -779,6 +993,7 @@ Combobox.propTypes = {
779
993
  prop_types_1.default.arrayOf(prop_types_1.default.string),
780
994
  ]),
781
995
  overflow: prop_types_1.default.oneOf(Object.values(Combobox_types_1.Overflow)),
996
+ // Standard Props
782
997
  darkMode: prop_types_1.default.bool,
783
998
  label: prop_types_1.default.string,
784
999
  'aria-label': prop_types_1.default.string,
@@ -800,10 +1015,15 @@ Combobox.propTypes = {
800
1015
  description: prop_types_1.default.string,
801
1016
  placeholder: prop_types_1.default.string,
802
1017
  filteredOptions: prop_types_1.default.arrayOf(prop_types_1.default.string),
1018
+ // Popover Props
803
1019
  popoverZIndex: prop_types_1.default.number,
804
1020
  usePortal: prop_types_1.default.bool,
805
1021
  scrollContainer: prop_types_1.default.elementType,
806
1022
  portalContainer: prop_types_1.default.elementType,
807
1023
  portalClassName: prop_types_1.default.string,
808
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
+ */
809
1029
  //# sourceMappingURL=Combobox.js.map