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