@acusti/dropdown 0.12.0 → 0.14.0

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/dist/Dropdown.js CHANGED
@@ -3,12 +3,41 @@ import { Style } from '@acusti/styling';
3
3
  import useIsOutOfBounds from '@acusti/use-is-out-of-bounds';
4
4
  import classnames from 'classnames';
5
5
  import * as React from 'react';
6
- import { BODY_CLASS_NAME, BODY_SELECTOR, LABEL_CLASS_NAME, LABEL_TEXT_CLASS_NAME, ROOT_CLASS_NAME, STYLES, TRIGGER_CLASS_NAME, } from './styles.js';
7
- import { getActiveItemElement, getItemElements, ITEM_SELECTOR, KEY_EVENT_ELEMENTS, setActiveItem, } from './helpers.js';
6
+ import {
7
+ BODY_CLASS_NAME,
8
+ BODY_SELECTOR,
9
+ LABEL_CLASS_NAME,
10
+ LABEL_TEXT_CLASS_NAME,
11
+ ROOT_CLASS_NAME,
12
+ STYLES,
13
+ TRIGGER_CLASS_NAME,
14
+ } from './styles.js';
15
+ import {
16
+ getActiveItemElement,
17
+ getItemElements,
18
+ ITEM_SELECTOR,
19
+ KEY_EVENT_ELEMENTS,
20
+ setActiveItem,
21
+ } from './helpers.js';
8
22
  const { Children, Fragment, useCallback, useLayoutEffect, useRef, useState } = React;
9
- const noop = () => { };
10
- const CHILDREN_ERROR = '@acusti/dropdown requires either 1 child (the dropdown body) or 2 children: the dropdown trigger and the dropdown body.';
11
- const Dropdown = ({ allowEmpty = true, children, className, disabled, hasItems = true, isOpenOnMount, isSearchable, label, name, onSubmitItem, placeholder, tabIndex, value, }) => {
23
+ const noop = () => {};
24
+ const CHILDREN_ERROR =
25
+ '@acusti/dropdown requires either 1 child (the dropdown body) or 2 children: the dropdown trigger and the dropdown body.';
26
+ const Dropdown = ({
27
+ allowEmpty = true,
28
+ children,
29
+ className,
30
+ disabled,
31
+ hasItems = true,
32
+ isOpenOnMount,
33
+ isSearchable,
34
+ label,
35
+ name,
36
+ onSubmitItem,
37
+ placeholder,
38
+ tabIndex,
39
+ value,
40
+ }) => {
12
41
  const childrenCount = Children.count(children);
13
42
  if (childrenCount !== 1 && childrenCount !== 2) {
14
43
  if (childrenCount === 0) {
@@ -70,30 +99,39 @@ const Dropdown = ({ allowEmpty = true, children, className, disabled, hasItems =
70
99
  // doesn’t close before expected. It also enables using <Link />s in the dropdown body.
71
100
  closingTimerRef.current = setTimeout(closeDropdown, 90);
72
101
  }
73
- if (!hasItemsRef.current)
74
- return;
102
+ if (!hasItemsRef.current) return;
75
103
  const nextElement = getActiveItemElement(dropdownElementRef.current);
76
104
  if (!nextElement) {
77
105
  // If not allowEmpty, don’t allow submitting an empty item
78
- if (!allowEmptyRef.current)
79
- return;
106
+ if (!allowEmptyRef.current) return;
80
107
  // If we have an input element as trigger & the user didn’t clear the text, do nothing
81
- if ((_a = inputElementRef.current) === null || _a === void 0 ? void 0 : _a.value)
108
+ if (
109
+ (_a = inputElementRef.current) === null || _a === void 0
110
+ ? void 0
111
+ : _a.value
112
+ )
82
113
  return;
83
114
  }
84
- const label = (nextElement === null || nextElement === void 0 ? void 0 : nextElement.innerText) || '';
85
- const nextValue = (nextElement === null || nextElement === void 0 ? void 0 : nextElement.dataset.uktValue) || label;
115
+ const label =
116
+ (nextElement === null || nextElement === void 0
117
+ ? void 0
118
+ : nextElement.innerText) || '';
119
+ const nextValue =
120
+ (nextElement === null || nextElement === void 0
121
+ ? void 0
122
+ : nextElement.dataset.uktValue) || label;
86
123
  const nextItem = { element: nextElement, value: nextValue };
87
124
  if (inputElementRef.current) {
88
125
  inputElementRef.current.value = label;
89
- if (inputElementRef.current ===
90
- inputElementRef.current.ownerDocument.activeElement) {
126
+ if (
127
+ inputElementRef.current ===
128
+ inputElementRef.current.ownerDocument.activeElement
129
+ ) {
91
130
  inputElementRef.current.blur();
92
131
  }
93
132
  }
94
133
  // If parent is controlling Dropdown via props.value and nextValue is the same, do nothing
95
- if (valueRef.current && valueRef.current === nextValue)
96
- return;
134
+ if (valueRef.current && valueRef.current === nextValue) return;
97
135
  if (onSubmitItemRef.current) {
98
136
  onSubmitItemRef.current(nextItem);
99
137
  }
@@ -101,27 +139,24 @@ const Dropdown = ({ allowEmpty = true, children, className, disabled, hasItems =
101
139
  const handleMouseMove = useCallback(({ clientX, clientY }) => {
102
140
  currentInputMethodRef.current = 'mouse';
103
141
  const initialPosition = mouseDownPositionRef.current;
104
- if (!initialPosition)
105
- return;
106
- if (Math.abs(initialPosition.clientX - clientX) < 12 &&
107
- Math.abs(initialPosition.clientY - clientY) < 12) {
142
+ if (!initialPosition) return;
143
+ if (
144
+ Math.abs(initialPosition.clientX - clientX) < 12 &&
145
+ Math.abs(initialPosition.clientY - clientY) < 12
146
+ ) {
108
147
  return;
109
148
  }
110
149
  setIsOpening(false);
111
150
  }, []);
112
151
  const handleMouseOver = useCallback((event) => {
113
- if (!hasItemsRef.current)
114
- return;
152
+ if (!hasItemsRef.current) return;
115
153
  // If user isn’t currently using the mouse to navigate the dropdown, do nothing
116
- if (currentInputMethodRef.current !== 'mouse')
117
- return;
154
+ if (currentInputMethodRef.current !== 'mouse') return;
118
155
  // Ensure we have the dropdown root HTMLElement
119
156
  const dropdownElement = dropdownElementRef.current;
120
- if (!dropdownElement)
121
- return;
157
+ if (!dropdownElement) return;
122
158
  const itemElements = getItemElements(dropdownElement);
123
- if (!itemElements)
124
- return;
159
+ if (!itemElements) return;
125
160
  const eventTarget = event.target;
126
161
  const item = eventTarget.closest(ITEM_SELECTOR);
127
162
  const element = item || eventTarget;
@@ -136,281 +171,327 @@ const Dropdown = ({ allowEmpty = true, children, className, disabled, hasItems =
136
171
  }
137
172
  }, []);
138
173
  const cleanupEventListenersRef = useRef(noop);
139
- const handleRef = useCallback((ref) => {
140
- dropdownElementRef.current = ref;
141
- if (!ref) {
142
- // If component was unmounted, cleanup handlers
143
- cleanupEventListenersRef.current();
144
- cleanupEventListenersRef.current = noop;
145
- return;
146
- }
147
- const { ownerDocument } = ref;
148
- let inputElement = inputElementRef.current;
149
- // Check if trigger from props is an input or textarea element
150
- if (isTriggerFromProps && !inputElement && ref.firstElementChild) {
151
- inputElement = ref.firstElementChild.querySelector('input:not([type=radio]):not([type=checkbox]):not([type=range]),textarea');
152
- inputElementRef.current = inputElement;
153
- }
154
- const handleMouseDown = ({ clientX, clientY, target }) => {
155
- const eventTarget = target;
156
- if (dropdownElementRef.current &&
157
- !dropdownElementRef.current.contains(eventTarget)) {
158
- // Close dropdown on an outside click
159
- closeDropdown();
174
+ const handleRef = useCallback(
175
+ (ref) => {
176
+ dropdownElementRef.current = ref;
177
+ if (!ref) {
178
+ // If component was unmounted, cleanup handlers
179
+ cleanupEventListenersRef.current();
180
+ cleanupEventListenersRef.current = noop;
160
181
  return;
161
182
  }
162
- if (isOpenRef.current)
163
- return;
164
- setIsOpen(true);
165
- setIsOpening(true);
166
- mouseDownPositionRef.current = { clientX, clientY };
167
- isOpeningTimerRef.current = setTimeout(() => {
168
- setIsOpening(false);
169
- isOpeningTimerRef.current = null;
170
- }, 1000);
171
- };
172
- const handleMouseUp = ({ target }) => {
173
- if (!isOpenRef.current || closingTimerRef.current)
174
- return;
175
- // If still isOpening (gets set false 1s after open triggers), set it to false onMouseUp
176
- if (isOpeningRef.current) {
177
- setIsOpening(false);
178
- if (isOpeningTimerRef.current) {
179
- clearTimeout(isOpeningTimerRef.current);
180
- isOpeningTimerRef.current = null;
181
- }
182
- return;
183
+ const { ownerDocument } = ref;
184
+ let inputElement = inputElementRef.current;
185
+ // Check if trigger from props is an input or textarea element
186
+ if (isTriggerFromProps && !inputElement && ref.firstElementChild) {
187
+ inputElement = ref.firstElementChild.querySelector(
188
+ 'input:not([type=radio]):not([type=checkbox]):not([type=range]),textarea',
189
+ );
190
+ inputElementRef.current = inputElement;
183
191
  }
184
- const isTargetInBody = target.closest(BODY_SELECTOR);
185
- // If mouseup is on dropdown body and there are no items, don’t close the dropdown
186
- if (!hasItemsRef.current && isTargetInBody)
187
- return;
188
- // If mouseup is on an item, trigger submit item, else close the dropdown
189
- if (isTargetInBody) {
190
- handleSubmitItem();
191
- }
192
- else if (!inputElementRef.current ||
193
- (dropdownElementRef.current &&
194
- dropdownElementRef.current.contains(ownerDocument.activeElement))) {
195
- // If dropdown is searchable and ref is still focused, this won’t be invoked
196
- closeDropdown();
197
- }
198
- };
199
- const handleKeyDown = (event) => {
200
- const { altKey, ctrlKey, key, metaKey } = event;
201
- const eventTarget = event.target;
202
- const dropdownElement = dropdownElementRef.current;
203
- if (!dropdownElement)
204
- return;
205
- const onEventHandled = () => {
206
- event.stopPropagation();
207
- event.preventDefault();
208
- currentInputMethodRef.current = 'keyboard';
209
- };
210
- const isEventTargetingDropdown = dropdownElement.contains(eventTarget);
211
- if (!isOpenRef.current) {
212
- // If dropdown is closed, don’t handle key events if event target isn’t within dropdown
213
- if (!isEventTargetingDropdown)
214
- return;
215
- // Open the dropdown on spacebar, enter, or if isSearchable and user hits the up/down arrows
216
- if (key === ' ' ||
217
- key === 'Enter' ||
218
- (hasItemsRef.current &&
219
- (key === 'ArrowUp' || key === 'ArrowDown'))) {
220
- onEventHandled();
221
- setIsOpen(true);
192
+ const handleMouseDown = ({ clientX, clientY, target }) => {
193
+ const eventTarget = target;
194
+ if (
195
+ dropdownElementRef.current &&
196
+ !dropdownElementRef.current.contains(eventTarget)
197
+ ) {
198
+ // Close dropdown on an outside click
199
+ closeDropdown();
222
200
  return;
223
201
  }
224
- return;
225
- }
226
- // If dropdown isOpen, hasItems, and not isSearchable, handle entering characters
227
- if (hasItemsRef.current && !inputElementRef.current) {
228
- let isEditingCharacters = !ctrlKey && !metaKey && /^[A-Za-z0-9]$/.test(key);
229
- // User could also be editing characters if there are already characters entered
230
- // and they are hitting delete or spacebar
231
- if (!isEditingCharacters && enteredCharactersRef.current) {
232
- isEditingCharacters = key === ' ' || key === 'Backspace';
233
- }
234
- if (isEditingCharacters) {
235
- onEventHandled();
236
- if (key === 'Backspace') {
237
- enteredCharactersRef.current = enteredCharactersRef.current.slice(0, -1);
238
- }
239
- else {
240
- enteredCharactersRef.current += key;
241
- }
242
- setActiveItem({
243
- dropdownElement,
244
- // If input element came from props, only override the input’s value
245
- // with an exact text match so user can enter a value not in items
246
- isExactMatch: isTriggerFromPropsRef.current,
247
- text: enteredCharactersRef.current,
248
- });
249
- if (clearEnteredCharactersTimerRef.current) {
250
- clearTimeout(clearEnteredCharactersTimerRef.current);
202
+ if (isOpenRef.current) return;
203
+ setIsOpen(true);
204
+ setIsOpening(true);
205
+ mouseDownPositionRef.current = { clientX, clientY };
206
+ isOpeningTimerRef.current = setTimeout(() => {
207
+ setIsOpening(false);
208
+ isOpeningTimerRef.current = null;
209
+ }, 1000);
210
+ };
211
+ const handleMouseUp = ({ target }) => {
212
+ if (!isOpenRef.current || closingTimerRef.current) return;
213
+ // If still isOpening (gets set false 1s after open triggers), set it to false onMouseUp
214
+ if (isOpeningRef.current) {
215
+ setIsOpening(false);
216
+ if (isOpeningTimerRef.current) {
217
+ clearTimeout(isOpeningTimerRef.current);
218
+ isOpeningTimerRef.current = null;
251
219
  }
252
- clearEnteredCharactersTimerRef.current = setTimeout(() => {
253
- enteredCharactersRef.current = '';
254
- clearEnteredCharactersTimerRef.current = null;
255
- }, 1500);
256
220
  return;
257
221
  }
258
- }
259
- // If dropdown isOpen, handle submitting the value
260
- if (key === 'Enter' || (key === ' ' && !inputElementRef.current)) {
261
- onEventHandled();
262
- handleSubmitItem();
263
- return;
264
- }
265
- // If dropdown isOpen, handle closing it on escape or spacebar if !hasItems
266
- if (key === 'Escape' ||
267
- (isEventTargetingDropdown && key === ' ' && !hasItemsRef.current)) {
268
- // If there are no items & event target element uses key events, don’t close it
269
- if (!hasItemsRef.current &&
270
- (eventTarget.isContentEditable ||
271
- KEY_EVENT_ELEMENTS.has(eventTarget.tagName))) {
222
+ const isTargetInBody = target.closest(BODY_SELECTOR);
223
+ // If mouseup is on dropdown body and there are no items, don’t close the dropdown
224
+ if (!hasItemsRef.current && isTargetInBody) return;
225
+ // If mouseup is on an item, trigger submit item, else close the dropdown
226
+ if (isTargetInBody) {
227
+ handleSubmitItem();
228
+ } else if (
229
+ !inputElementRef.current ||
230
+ (dropdownElementRef.current &&
231
+ dropdownElementRef.current.contains(ownerDocument.activeElement))
232
+ ) {
233
+ // If dropdown is searchable and ref is still focused, this won’t be invoked
234
+ closeDropdown();
235
+ }
236
+ };
237
+ const handleKeyDown = (event) => {
238
+ const { altKey, ctrlKey, key, metaKey } = event;
239
+ const eventTarget = event.target;
240
+ const dropdownElement = dropdownElementRef.current;
241
+ if (!dropdownElement) return;
242
+ const onEventHandled = () => {
243
+ event.stopPropagation();
244
+ event.preventDefault();
245
+ currentInputMethodRef.current = 'keyboard';
246
+ };
247
+ const isEventTargetingDropdown = dropdownElement.contains(eventTarget);
248
+ if (!isOpenRef.current) {
249
+ // If dropdown is closed, don’t handle key events if event target isn’t within dropdown
250
+ if (!isEventTargetingDropdown) return;
251
+ // Open the dropdown on spacebar, enter, or if isSearchable and user hits the up/down arrows
252
+ if (
253
+ key === ' ' ||
254
+ key === 'Enter' ||
255
+ (hasItemsRef.current &&
256
+ (key === 'ArrowUp' || key === 'ArrowDown'))
257
+ ) {
258
+ onEventHandled();
259
+ setIsOpen(true);
260
+ return;
261
+ }
272
262
  return;
273
263
  }
274
- closeDropdown();
275
- return;
276
- }
277
- // Handle ↑/↓ arrows
278
- if (hasItemsRef.current) {
279
- if (key === 'ArrowUp') {
280
- onEventHandled();
281
- if (altKey || metaKey) {
282
- setActiveItem({
283
- dropdownElement,
284
- index: 0,
285
- });
264
+ // If dropdown isOpen, hasItems, and not isSearchable, handle entering characters
265
+ if (hasItemsRef.current && !inputElementRef.current) {
266
+ let isEditingCharacters =
267
+ !ctrlKey && !metaKey && /^[A-Za-z0-9]$/.test(key);
268
+ // User could also be editing characters if there are already characters entered
269
+ // and they are hitting delete or spacebar
270
+ if (!isEditingCharacters && enteredCharactersRef.current) {
271
+ isEditingCharacters = key === ' ' || key === 'Backspace';
286
272
  }
287
- else {
273
+ if (isEditingCharacters) {
274
+ onEventHandled();
275
+ if (key === 'Backspace') {
276
+ enteredCharactersRef.current =
277
+ enteredCharactersRef.current.slice(0, -1);
278
+ } else {
279
+ enteredCharactersRef.current += key;
280
+ }
288
281
  setActiveItem({
289
282
  dropdownElement,
290
- indexAddend: -1,
283
+ // If input element came from props, only override the input’s value
284
+ // with an exact text match so user can enter a value not in items
285
+ isExactMatch: isTriggerFromPropsRef.current,
286
+ text: enteredCharactersRef.current,
291
287
  });
288
+ if (clearEnteredCharactersTimerRef.current) {
289
+ clearTimeout(clearEnteredCharactersTimerRef.current);
290
+ }
291
+ clearEnteredCharactersTimerRef.current = setTimeout(() => {
292
+ enteredCharactersRef.current = '';
293
+ clearEnteredCharactersTimerRef.current = null;
294
+ }, 1500);
295
+ return;
292
296
  }
293
- return;
294
297
  }
295
- if (key === 'ArrowDown') {
298
+ // If dropdown isOpen, handle submitting the value
299
+ if (key === 'Enter' || (key === ' ' && !inputElementRef.current)) {
296
300
  onEventHandled();
297
- if (altKey || metaKey) {
298
- // Using a negative index counts back from the end
299
- setActiveItem({
300
- dropdownElement,
301
- index: -1,
302
- });
301
+ handleSubmitItem();
302
+ return;
303
+ }
304
+ // If dropdown isOpen, handle closing it on escape or spacebar if !hasItems
305
+ if (
306
+ key === 'Escape' ||
307
+ (isEventTargetingDropdown && key === ' ' && !hasItemsRef.current)
308
+ ) {
309
+ // If there are no items & event target element uses key events, don’t close it
310
+ if (
311
+ !hasItemsRef.current &&
312
+ (eventTarget.isContentEditable ||
313
+ KEY_EVENT_ELEMENTS.has(eventTarget.tagName))
314
+ ) {
315
+ return;
303
316
  }
304
- else {
305
- setActiveItem({
306
- dropdownElement,
307
- indexAddend: 1,
308
- });
317
+ closeDropdown();
318
+ return;
319
+ }
320
+ // Handle ↑/↓ arrows
321
+ if (hasItemsRef.current) {
322
+ if (key === 'ArrowUp') {
323
+ onEventHandled();
324
+ if (altKey || metaKey) {
325
+ setActiveItem({
326
+ dropdownElement,
327
+ index: 0,
328
+ });
329
+ } else {
330
+ setActiveItem({
331
+ dropdownElement,
332
+ indexAddend: -1,
333
+ });
334
+ }
335
+ return;
336
+ }
337
+ if (key === 'ArrowDown') {
338
+ onEventHandled();
339
+ if (altKey || metaKey) {
340
+ // Using a negative index counts back from the end
341
+ setActiveItem({
342
+ dropdownElement,
343
+ index: -1,
344
+ });
345
+ } else {
346
+ setActiveItem({
347
+ dropdownElement,
348
+ indexAddend: 1,
349
+ });
350
+ }
351
+ return;
309
352
  }
353
+ }
354
+ };
355
+ // Close dropdown if any element is focused outside of this dropdown
356
+ const handleFocusIn = (event) => {
357
+ if (!isOpenRef.current) return;
358
+ const eventTarget = event.target;
359
+ // If focused element is a descendant or a parent of the dropdown, do nothing
360
+ if (
361
+ !dropdownElementRef.current ||
362
+ dropdownElementRef.current.contains(eventTarget) ||
363
+ eventTarget.contains(dropdownElementRef.current)
364
+ ) {
310
365
  return;
311
366
  }
312
- }
313
- };
314
- // Close dropdown if any element is focused outside of this dropdown
315
- const handleFocusIn = (event) => {
316
- if (!isOpenRef.current)
317
- return;
318
- const eventTarget = event.target;
319
- // If focused element is a descendant or a parent of the dropdown, do nothing
320
- if (!dropdownElementRef.current ||
321
- dropdownElementRef.current.contains(eventTarget) ||
322
- eventTarget.contains(dropdownElementRef.current)) {
323
- return;
324
- }
325
- closeDropdown();
326
- };
327
- document.addEventListener('focusin', handleFocusIn);
328
- document.addEventListener('keydown', handleKeyDown);
329
- document.addEventListener('mousedown', handleMouseDown);
330
- document.addEventListener('mouseup', handleMouseUp);
331
- if (ownerDocument !== document) {
332
- ownerDocument.addEventListener('focusin', handleFocusIn);
333
- ownerDocument.addEventListener('keydown', handleKeyDown);
334
- ownerDocument.addEventListener('mousedown', handleMouseDown);
335
- ownerDocument.addEventListener('mouseup', handleMouseUp);
336
- }
337
- // If dropdown should be open on mount, focus it
338
- if (isOpenOnMount) {
339
- ref.focus();
340
- }
341
- const handleInput = (event) => {
342
- const dropdownElement = dropdownElementRef.current;
343
- if (!dropdownElement)
344
- return;
345
- if (!isOpenRef.current)
346
- setIsOpen(true);
347
- const input = event.target;
348
- const isDeleting = enteredCharactersRef.current.length > input.value.length;
349
- enteredCharactersRef.current = input.value;
350
- // Don’t set a new active item if user is deleting text unless text is now empty
351
- if (isDeleting && input.value.length)
352
- return;
353
- setActiveItem({
354
- dropdownElement,
355
- // If input element came from props, only override the input’s value
356
- // with an exact text match so user can enter a value not in items
357
- isExactMatch: isTriggerFromPropsRef.current,
358
- text: enteredCharactersRef.current,
359
- });
360
- };
361
- if (inputElement) {
362
- inputElement.addEventListener('input', handleInput);
363
- }
364
- cleanupEventListenersRef.current = () => {
365
- document.removeEventListener('focusin', handleFocusIn);
366
- document.removeEventListener('keydown', handleKeyDown);
367
- document.removeEventListener('mousedown', handleMouseDown);
368
- document.removeEventListener('mouseup', handleMouseUp);
367
+ closeDropdown();
368
+ };
369
+ document.addEventListener('focusin', handleFocusIn);
370
+ document.addEventListener('keydown', handleKeyDown);
371
+ document.addEventListener('mousedown', handleMouseDown);
372
+ document.addEventListener('mouseup', handleMouseUp);
369
373
  if (ownerDocument !== document) {
370
- ownerDocument.removeEventListener('focusin', handleFocusIn);
371
- ownerDocument.removeEventListener('keydown', handleKeyDown);
372
- ownerDocument.removeEventListener('mousedown', handleMouseDown);
373
- ownerDocument.removeEventListener('mouseup', handleMouseUp);
374
+ ownerDocument.addEventListener('focusin', handleFocusIn);
375
+ ownerDocument.addEventListener('keydown', handleKeyDown);
376
+ ownerDocument.addEventListener('mousedown', handleMouseDown);
377
+ ownerDocument.addEventListener('mouseup', handleMouseUp);
378
+ }
379
+ // If dropdown should be open on mount, focus it
380
+ if (isOpenOnMount) {
381
+ ref.focus();
374
382
  }
383
+ const handleInput = (event) => {
384
+ const dropdownElement = dropdownElementRef.current;
385
+ if (!dropdownElement) return;
386
+ if (!isOpenRef.current) setIsOpen(true);
387
+ const input = event.target;
388
+ const isDeleting =
389
+ enteredCharactersRef.current.length > input.value.length;
390
+ enteredCharactersRef.current = input.value;
391
+ // Don’t set a new active item if user is deleting text unless text is now empty
392
+ if (isDeleting && input.value.length) return;
393
+ setActiveItem({
394
+ dropdownElement,
395
+ // If input element came from props, only override the input’s value
396
+ // with an exact text match so user can enter a value not in items
397
+ isExactMatch: isTriggerFromPropsRef.current,
398
+ text: enteredCharactersRef.current,
399
+ });
400
+ };
375
401
  if (inputElement) {
376
- inputElement.removeEventListener('input', handleInput);
402
+ inputElement.addEventListener('input', handleInput);
377
403
  }
378
- };
379
- }, [closeDropdown, handleSubmitItem, isOpenOnMount, isTriggerFromProps]);
404
+ cleanupEventListenersRef.current = () => {
405
+ document.removeEventListener('focusin', handleFocusIn);
406
+ document.removeEventListener('keydown', handleKeyDown);
407
+ document.removeEventListener('mousedown', handleMouseDown);
408
+ document.removeEventListener('mouseup', handleMouseUp);
409
+ if (ownerDocument !== document) {
410
+ ownerDocument.removeEventListener('focusin', handleFocusIn);
411
+ ownerDocument.removeEventListener('keydown', handleKeyDown);
412
+ ownerDocument.removeEventListener('mousedown', handleMouseDown);
413
+ ownerDocument.removeEventListener('mouseup', handleMouseUp);
414
+ }
415
+ if (inputElement) {
416
+ inputElement.removeEventListener('input', handleInput);
417
+ }
418
+ };
419
+ },
420
+ [closeDropdown, handleSubmitItem, isOpenOnMount, isTriggerFromProps],
421
+ );
380
422
  const handleTriggerFocus = useCallback(() => {
381
423
  setIsOpen(true);
382
424
  }, []);
383
425
  if (!isTriggerFromProps) {
384
426
  if (isSearchable) {
385
- trigger = (React.createElement(InputText, { className: TRIGGER_CLASS_NAME, disabled: disabled, initialValue: value || '', name: name, onFocus: handleTriggerFocus, placeholder: placeholder, ref: inputElementRef, selectTextOnFocus: true, tabIndex: tabIndex, type: "text" }));
386
- }
387
- else {
388
- trigger = (React.createElement("button", { className: TRIGGER_CLASS_NAME, tabIndex: 0 }, trigger));
427
+ trigger = React.createElement(InputText, {
428
+ className: TRIGGER_CLASS_NAME,
429
+ disabled: disabled,
430
+ initialValue: value || '',
431
+ name: name,
432
+ onFocus: handleTriggerFocus,
433
+ placeholder: placeholder,
434
+ ref: inputElementRef,
435
+ selectTextOnFocus: true,
436
+ tabIndex: tabIndex,
437
+ type: 'text',
438
+ });
439
+ } else {
440
+ trigger = React.createElement(
441
+ 'button',
442
+ { className: TRIGGER_CLASS_NAME, tabIndex: 0 },
443
+ trigger,
444
+ );
389
445
  }
390
446
  }
391
447
  if (label) {
392
- trigger = (React.createElement("label", { className: LABEL_CLASS_NAME },
393
- React.createElement("div", { className: LABEL_TEXT_CLASS_NAME }, label),
394
- trigger));
448
+ trigger = React.createElement(
449
+ 'label',
450
+ { className: LABEL_CLASS_NAME },
451
+ React.createElement('div', { className: LABEL_TEXT_CLASS_NAME }, label),
452
+ trigger,
453
+ );
395
454
  }
396
- return (React.createElement(Fragment, null,
455
+ return React.createElement(
456
+ Fragment,
457
+ null,
397
458
  React.createElement(Style, null, STYLES),
398
- React.createElement("div", { className: classnames(ROOT_CLASS_NAME, className, {
399
- disabled,
400
- 'is-open': isOpen,
401
- 'is-searchable': isSearchable,
402
- }), onMouseMove: handleMouseMove, onMouseOver: handleMouseOver, ref: handleRef, tabIndex: isSearchable || inputElementRef.current || !isTriggerFromProps
403
- ? undefined
404
- : 0 },
459
+ React.createElement(
460
+ 'div',
461
+ {
462
+ className: classnames(ROOT_CLASS_NAME, className, {
463
+ disabled,
464
+ 'is-open': isOpen,
465
+ 'is-searchable': isSearchable,
466
+ }),
467
+ onMouseMove: handleMouseMove,
468
+ onMouseOver: handleMouseOver,
469
+ ref: handleRef,
470
+ tabIndex:
471
+ isSearchable || inputElementRef.current || !isTriggerFromProps
472
+ ? undefined
473
+ : 0,
474
+ },
405
475
  trigger,
406
- isOpen ? (React.createElement("div", { className: classnames(BODY_CLASS_NAME, {
407
- 'calculating-position': !outOfBounds.hasLayout,
408
- 'has-items': hasItems,
409
- 'out-of-bounds-bottom': outOfBounds.bottom,
410
- 'out-of-bounds-left': outOfBounds.left,
411
- 'out-of-bounds-right': outOfBounds.right,
412
- 'out-of-bounds-top': outOfBounds.top,
413
- }), ref: setDropdownBodyElement }, children[1] || children[0] || children)) : null)));
476
+ isOpen
477
+ ? React.createElement(
478
+ 'div',
479
+ {
480
+ className: classnames(BODY_CLASS_NAME, {
481
+ 'calculating-position': !outOfBounds.hasLayout,
482
+ 'has-items': hasItems,
483
+ 'out-of-bounds-bottom': outOfBounds.bottom,
484
+ 'out-of-bounds-left': outOfBounds.left,
485
+ 'out-of-bounds-right': outOfBounds.right,
486
+ 'out-of-bounds-top': outOfBounds.top,
487
+ }),
488
+ ref: setDropdownBodyElement,
489
+ },
490
+ children[1] || children[0] || children,
491
+ )
492
+ : null,
493
+ ),
494
+ );
414
495
  };
415
496
  export default Dropdown;
416
- //# sourceMappingURL=Dropdown.js.map
497
+ //# sourceMappingURL=Dropdown.js.map