@lumx/react 4.3.2-alpha.22 → 4.3.2-alpha.24
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/Tooltip-2RZbmkfM.d.ts +915 -0
- package/_internal/{CzTdCnO5.js → BQFZG9jO.js} +398 -5
- package/_internal/BQFZG9jO.js.map +1 -0
- package/index.d.ts +80 -955
- package/index.js +2 -272
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/utils/index.d.ts +189 -2
- package/utils/index.js +364 -4
- package/utils/index.js.map +1 -1
- package/_internal/CzTdCnO5.js.map +0 -1
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"url": "https://github.com/lumapps/design-system/issues"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@lumx/core": "^4.3.2-alpha.
|
|
10
|
-
"@lumx/icons": "^4.3.2-alpha.
|
|
9
|
+
"@lumx/core": "^4.3.2-alpha.24",
|
|
10
|
+
"@lumx/icons": "^4.3.2-alpha.24",
|
|
11
11
|
"@popperjs/core": "^2.5.4",
|
|
12
12
|
"body-scroll-lock": "^3.1.5",
|
|
13
13
|
"react-popper": "^2.2.4"
|
|
@@ -102,6 +102,6 @@
|
|
|
102
102
|
"build:storybook": "storybook build"
|
|
103
103
|
},
|
|
104
104
|
"sideEffects": false,
|
|
105
|
-
"version": "4.3.2-alpha.
|
|
105
|
+
"version": "4.3.2-alpha.24",
|
|
106
106
|
"stableVersion": "4.3.1"
|
|
107
107
|
}
|
package/utils/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import * as React$1 from 'react';
|
|
1
2
|
import React__default, { RefObject, ReactNode, AriaAttributes } from 'react';
|
|
3
|
+
import { q as ComboboxAction, X as ComboboxState, Y as ComboboxReducer, y as OnComboboxSelect, T as TextFieldProps, r as ComboboxProps, Z as ComboboxSelectionType, _ as ComboboxTranslations, $ as BaseLoadingStatus, a0 as RegisteredComboboxOption, a1 as ComboboxOptionSelectEventSource } from '../Tooltip-2RZbmkfM.js';
|
|
2
4
|
import { Falsy, ObjectValues } from '@lumx/core/js/types';
|
|
3
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
6
|
import { DisabledStateContextValue } from '@lumx/core/js/utils/disabledState';
|
|
7
|
+
import '@lumx/core/js/constants';
|
|
5
8
|
|
|
6
9
|
interface ClickAwayParameters {
|
|
7
10
|
/**
|
|
@@ -395,5 +398,189 @@ type Output = [
|
|
|
395
398
|
*/
|
|
396
399
|
declare const useRovingTabIndex: (...args: BaseHookOptions) => Output;
|
|
397
400
|
|
|
398
|
-
|
|
399
|
-
|
|
401
|
+
declare const initialState: ComboboxState;
|
|
402
|
+
/** Main reducer */
|
|
403
|
+
declare const reducer: ComboboxReducer<ComboboxAction>;
|
|
404
|
+
/** Dispatch for the combobox component */
|
|
405
|
+
type ComboboxDispatch = React.Dispatch<ComboboxAction>;
|
|
406
|
+
|
|
407
|
+
interface ComboboxContextActions {
|
|
408
|
+
onSelect?: OnComboboxSelect;
|
|
409
|
+
onInputChange?: TextFieldProps['onChange'];
|
|
410
|
+
onOpen?: (params: {
|
|
411
|
+
manual: boolean;
|
|
412
|
+
currentValue: string;
|
|
413
|
+
}) => void;
|
|
414
|
+
}
|
|
415
|
+
interface ComboboxContextProps extends ComboboxState, ComboboxContextActions {
|
|
416
|
+
openOnFocus?: ComboboxProps['openOnFocus'];
|
|
417
|
+
openOnClick?: ComboboxProps['openOnClick'];
|
|
418
|
+
optionsLength: number;
|
|
419
|
+
/** The dispatch function to manage the inner state */
|
|
420
|
+
dispatch: ComboboxDispatch;
|
|
421
|
+
/** The ids of the currently selected options */
|
|
422
|
+
selectedIds?: Array<string>;
|
|
423
|
+
/** the type of selection currently configured for the combobox */
|
|
424
|
+
selectionType?: ComboboxSelectionType;
|
|
425
|
+
/**
|
|
426
|
+
* Whether the error state should be displayed when the status is in error.
|
|
427
|
+
*/
|
|
428
|
+
showErrorState?: boolean;
|
|
429
|
+
/**
|
|
430
|
+
* Whether the empty state should be displayed when there is no results.
|
|
431
|
+
*/
|
|
432
|
+
showEmptyState?: boolean;
|
|
433
|
+
/** translations to be used across the combobox */
|
|
434
|
+
translations: ComboboxTranslations;
|
|
435
|
+
}
|
|
436
|
+
/** Context for the Combobox component */
|
|
437
|
+
declare const ComboboxContext: React__default.Context<ComboboxContextProps>;
|
|
438
|
+
/** Context for a combobox section to store its unique id */
|
|
439
|
+
declare const SectionContext: React__default.Context<{
|
|
440
|
+
sectionId: string;
|
|
441
|
+
isLoading?: boolean;
|
|
442
|
+
}>;
|
|
443
|
+
|
|
444
|
+
interface ComboboxOptionContextValue {
|
|
445
|
+
optionId?: string;
|
|
446
|
+
isKeyboardHighlighted?: boolean;
|
|
447
|
+
}
|
|
448
|
+
declare const ComboboxOptionContext: React__default.Context<ComboboxOptionContextValue>;
|
|
449
|
+
interface ComboboxOptionIdProviderProps extends Required<ComboboxOptionContextValue> {
|
|
450
|
+
/** Option to display */
|
|
451
|
+
children: ReactNode;
|
|
452
|
+
}
|
|
453
|
+
/** Context Provider to store the current combobox option id. */
|
|
454
|
+
declare const ComboboxOptionContextProvider: ({ optionId, isKeyboardHighlighted, children, }: ComboboxOptionIdProviderProps) => react_jsx_runtime.JSX.Element;
|
|
455
|
+
/**
|
|
456
|
+
* Retrieve the current combobox option id.
|
|
457
|
+
* Must be used within a ComboboxOptionIdProvider
|
|
458
|
+
*/
|
|
459
|
+
declare const useComboboxOptionContext: () => Required<ComboboxOptionContextValue>;
|
|
460
|
+
|
|
461
|
+
interface ComboboxProviderProps {
|
|
462
|
+
children: ReactNode;
|
|
463
|
+
/** The data to pass the provider. Can be generated by the useCombobox hook */
|
|
464
|
+
value: ComboboxContextProps;
|
|
465
|
+
}
|
|
466
|
+
/** Context provider for the combobox. */
|
|
467
|
+
declare const ComboboxProvider: ({ children, value }: ComboboxProviderProps) => react_jsx_runtime.JSX.Element;
|
|
468
|
+
|
|
469
|
+
interface ComboboxRefsContext {
|
|
470
|
+
triggerRef: RefObject<HTMLElement>;
|
|
471
|
+
anchorRef: RefObject<HTMLElement>;
|
|
472
|
+
}
|
|
473
|
+
/** Context to store the refs of the combobox elements */
|
|
474
|
+
declare const ComboboxRefsContext: React$1.Context<ComboboxRefsContext>;
|
|
475
|
+
interface ComboboxRefsProviderProps extends ComboboxRefsContext {
|
|
476
|
+
children: ReactNode;
|
|
477
|
+
}
|
|
478
|
+
/** Provider to store the required refs for the Combobox */
|
|
479
|
+
declare const ComboboxRefsProvider: ({ triggerRef, anchorRef, children }: ComboboxRefsProviderProps) => react_jsx_runtime.JSX.Element;
|
|
480
|
+
/** Retrieves the combobox elements references from context */
|
|
481
|
+
declare const useComboboxRefs: () => ComboboxRefsContext;
|
|
482
|
+
|
|
483
|
+
/** Retrieve the current combobox state and actions */
|
|
484
|
+
declare const useCombobox: () => {
|
|
485
|
+
openOnFocus?: ComboboxProps["openOnFocus"];
|
|
486
|
+
openOnClick?: ComboboxProps["openOnClick"];
|
|
487
|
+
optionsLength: number;
|
|
488
|
+
selectedIds?: Array<string>;
|
|
489
|
+
selectionType?: ComboboxSelectionType;
|
|
490
|
+
showErrorState?: boolean;
|
|
491
|
+
showEmptyState?: boolean;
|
|
492
|
+
translations: ComboboxTranslations;
|
|
493
|
+
comboboxId: string;
|
|
494
|
+
listboxId: string;
|
|
495
|
+
status: BaseLoadingStatus;
|
|
496
|
+
options: Record<string, RegisteredComboboxOption>;
|
|
497
|
+
isOpen: boolean;
|
|
498
|
+
showAll: boolean;
|
|
499
|
+
type: "listbox" | "grid";
|
|
500
|
+
defaultInputValue?: string;
|
|
501
|
+
handleClose: () => void;
|
|
502
|
+
handleOpen: (params?: {
|
|
503
|
+
manual?: boolean;
|
|
504
|
+
}) => Promise<RegisteredComboboxOption[] | undefined>;
|
|
505
|
+
handleInputChange: (value: string, name?: string, event?: React__default.SyntheticEvent) => void;
|
|
506
|
+
handleSelected: (option: RegisteredComboboxOption, source?: ComboboxOptionSelectEventSource) => void;
|
|
507
|
+
dispatch: ComboboxDispatch;
|
|
508
|
+
inputValue: string;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
interface UseComboboxTriggerArgs {
|
|
512
|
+
context: ReturnType<typeof useCombobox>;
|
|
513
|
+
refs: ReturnType<typeof useComboboxRefs>;
|
|
514
|
+
onKeyDown?: React__default.KeyboardEventHandler;
|
|
515
|
+
onFocus?: React__default.FocusEventHandler;
|
|
516
|
+
onBlur?: React__default.FocusEventHandler;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Provide props for the semantic and behaviors the combobox trigger.
|
|
520
|
+
*
|
|
521
|
+
*
|
|
522
|
+
*/
|
|
523
|
+
declare function useComboboxTrigger({ context, refs, onBlur, onFocus, onKeyDown }: UseComboboxTriggerArgs): {
|
|
524
|
+
id: string;
|
|
525
|
+
role: string;
|
|
526
|
+
'aria-activedescendant': string;
|
|
527
|
+
'aria-controls': string;
|
|
528
|
+
'aria-owns': string;
|
|
529
|
+
'aria-expanded': boolean;
|
|
530
|
+
onFocus: (event: React__default.FocusEvent) => void;
|
|
531
|
+
onBlur: (event: React__default.FocusEvent<Element>) => void;
|
|
532
|
+
onClick: () => void;
|
|
533
|
+
onKeyDown: React__default.KeyboardEventHandler<Element>;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Provide props for the semantic and behaviors the combobox button trigger
|
|
538
|
+
*
|
|
539
|
+
* Overrides the useComboboxTrigger() props & behavior to add a jump to option
|
|
540
|
+
* on printable character key down.
|
|
541
|
+
*/
|
|
542
|
+
declare function useComboboxButton(args: UseComboboxTriggerArgs): {
|
|
543
|
+
id: string;
|
|
544
|
+
role: string;
|
|
545
|
+
'aria-activedescendant': string;
|
|
546
|
+
'aria-controls': string;
|
|
547
|
+
'aria-owns': string;
|
|
548
|
+
'aria-expanded': boolean;
|
|
549
|
+
onFocus: (event: React__default.FocusEvent) => void;
|
|
550
|
+
onBlur: (event: React__default.FocusEvent<Element>) => void;
|
|
551
|
+
onClick: () => void;
|
|
552
|
+
onKeyDown: React__default.KeyboardEventHandler<Element>;
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Provide props for the semantic and behaviors the combobox input trigger
|
|
557
|
+
*
|
|
558
|
+
* Overrides the useComboboxTrigger() props & behavior to prevent open on click
|
|
559
|
+
* if `openOnFocus` is not enabled.
|
|
560
|
+
*/
|
|
561
|
+
declare function useComboboxInput({ onKeyDown: propsOnKeyDown, ...args }: UseComboboxTriggerArgs): {
|
|
562
|
+
onClick: () => void;
|
|
563
|
+
onKeyDown: React__default.KeyboardEventHandler<Element>;
|
|
564
|
+
id: string;
|
|
565
|
+
role: string;
|
|
566
|
+
'aria-activedescendant': string;
|
|
567
|
+
'aria-controls': string;
|
|
568
|
+
'aria-owns': string;
|
|
569
|
+
'aria-expanded': boolean;
|
|
570
|
+
onFocus: (event: React__default.FocusEvent) => void;
|
|
571
|
+
onBlur: (event: React__default.FocusEvent<Element>) => void;
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
/** Retrieve the current combobox section id */
|
|
575
|
+
declare const useComboboxSectionId: () => {
|
|
576
|
+
sectionId: string;
|
|
577
|
+
isLoading?: boolean;
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Register the given option to the context
|
|
582
|
+
*/
|
|
583
|
+
declare const useRegisterOption: (registerId: string, option: RegisteredComboboxOption, shouldRegister?: boolean) => void;
|
|
584
|
+
|
|
585
|
+
export { A11YLiveMessage, ClickAwayProvider, ComboboxContext, initialState as ComboboxInitialState, ComboboxOptionContext, ComboboxOptionContextProvider, ComboboxProvider, reducer as ComboboxReducer, ComboboxRefsProvider, DisabledStateProvider, InfiniteScroll, MovingFocusContext, MovingFocusProvider, Portal, PortalProvider, SectionContext, useCombobox, useComboboxButton, useComboboxInput, useComboboxOptionContext, useComboboxRefs, useComboboxSectionId, useComboboxTrigger, useDisabledStateContext, useRegisterOption, useRovingTabIndex, useVirtualFocus, useVirtualFocusParent };
|
|
586
|
+
export type { A11YLiveMessageProps, ComboboxContextActions, ComboboxContextProps, ComboboxDispatch, ComboboxOptionContextValue, ComboboxProviderProps, InfiniteScrollProps, PortalInit, PortalProps, PortalProviderProps, UseComboboxTriggerArgs };
|
package/utils/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { r as reducer, I as INITIAL_STATE,
|
|
2
|
-
export { A as A11YLiveMessage,
|
|
3
|
-
import React__default, { useEffect } from 'react';
|
|
1
|
+
import { r as reducer, I as INITIAL_STATE, i as buildLoopAroundObject, M as MovingFocusContext, N as NAV_KEYS, j as getPointerTypeFromEvent, k as ComboboxContext, l as isComboboxValue } from '../_internal/BQFZG9jO.js';
|
|
2
|
+
export { A as A11YLiveMessage, h as ClickAwayProvider, m as ComboboxInitialState, n as ComboboxOptionContext, C as ComboboxOptionContextProvider, o as ComboboxReducer, p as ComboboxRefsProvider, D as DisabledStateProvider, P as Portal, q as PortalProvider, S as SectionContext, b as useCombobox, e as useComboboxOptionContext, f as useComboboxRefs, a as useComboboxSectionId, u as useDisabledStateContext, c as useRegisterOption, d as useVirtualFocus } from '../_internal/BQFZG9jO.js';
|
|
4
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import React__default, { useEffect } from 'react';
|
|
5
|
+
import debounce from 'lodash/debounce.js';
|
|
5
6
|
import isNil from 'lodash/isNil.js';
|
|
6
7
|
import uniqueId from 'lodash/uniqueId.js';
|
|
7
8
|
|
|
@@ -275,5 +276,364 @@ const useRovingTabIndex = (ref, disabled = false, rowKey = null, autofocus = fal
|
|
|
275
276
|
return [tabIndex, focused, handleKeyDown, handleClick];
|
|
276
277
|
};
|
|
277
278
|
|
|
278
|
-
|
|
279
|
+
/** Context provider for the combobox. */
|
|
280
|
+
const ComboboxProvider = ({
|
|
281
|
+
children,
|
|
282
|
+
value
|
|
283
|
+
}) => {
|
|
284
|
+
return /*#__PURE__*/jsx(ComboboxContext.Provider, {
|
|
285
|
+
value: value,
|
|
286
|
+
children: /*#__PURE__*/jsx(MovingFocusProvider, {
|
|
287
|
+
options: {
|
|
288
|
+
direction: value.type === 'grid' ? 'both' : 'vertical',
|
|
289
|
+
firstFocusDirection: value.type === 'grid' ? 'vertical' : undefined,
|
|
290
|
+
loopAround: {
|
|
291
|
+
row: 'next-loop',
|
|
292
|
+
col: 'inside'
|
|
293
|
+
},
|
|
294
|
+
gridJumpToShortcutDirection: 'vertical',
|
|
295
|
+
defaultSelectedId: value.selectedIds?.[0],
|
|
296
|
+
listKey: value.inputValue
|
|
297
|
+
},
|
|
298
|
+
children: children
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Provide props for the semantic and behaviors the combobox trigger.
|
|
305
|
+
*
|
|
306
|
+
*
|
|
307
|
+
*/
|
|
308
|
+
function useComboboxTrigger({
|
|
309
|
+
context,
|
|
310
|
+
refs,
|
|
311
|
+
onBlur,
|
|
312
|
+
onFocus,
|
|
313
|
+
onKeyDown
|
|
314
|
+
}) {
|
|
315
|
+
const {
|
|
316
|
+
comboboxId,
|
|
317
|
+
listboxId,
|
|
318
|
+
isOpen,
|
|
319
|
+
options,
|
|
320
|
+
optionsLength,
|
|
321
|
+
selectedIds,
|
|
322
|
+
openOnFocus,
|
|
323
|
+
handleClose,
|
|
324
|
+
handleOpen,
|
|
325
|
+
handleSelected,
|
|
326
|
+
showEmptyState = false,
|
|
327
|
+
showErrorState = false,
|
|
328
|
+
status
|
|
329
|
+
} = context;
|
|
330
|
+
const highlightedId = useVirtualFocusParent(refs.triggerRef);
|
|
331
|
+
const {
|
|
332
|
+
dispatch: movingFocusDispatch
|
|
333
|
+
} = React__default.useContext(MovingFocusContext);
|
|
334
|
+
const isErrorStateDisplayed = showErrorState && isOpen && status === 'error';
|
|
335
|
+
const isEmptyStateDisplayed = showEmptyState && isOpen && status === 'idle' && optionsLength === 0;
|
|
336
|
+
const showPopover = isOpen && optionsLength > 0 || isEmptyStateDisplayed || isErrorStateDisplayed;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* A debounce close to use to leave time for the
|
|
340
|
+
* listbox to process things before closing.
|
|
341
|
+
* This can be useful when clicking an option to leave time for the
|
|
342
|
+
* event to be processed before closing the popover.
|
|
343
|
+
*/
|
|
344
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
345
|
+
const debouncedClose = React__default.useCallback(debounce(handleClose, 200), []);
|
|
346
|
+
|
|
347
|
+
/** Cleanup any ongoing cleanup */
|
|
348
|
+
React__default.useEffect(() => {
|
|
349
|
+
return () => {
|
|
350
|
+
if (debouncedClose?.cancel) {
|
|
351
|
+
debouncedClose.cancel();
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}, [debouncedClose]);
|
|
355
|
+
const handleBlur = React__default.useCallback(event => {
|
|
356
|
+
debouncedClose();
|
|
357
|
+
if (onBlur) {
|
|
358
|
+
onBlur(event);
|
|
359
|
+
}
|
|
360
|
+
}, [debouncedClose, onBlur]);
|
|
361
|
+
|
|
362
|
+
/** Actions triggered when the input field is focused */
|
|
363
|
+
const handleFocus = event => {
|
|
364
|
+
// If the field is refocused in the process of closing, cancel close
|
|
365
|
+
if (debouncedClose?.cancel) {
|
|
366
|
+
debouncedClose.cancel();
|
|
367
|
+
}
|
|
368
|
+
if (onFocus) {
|
|
369
|
+
onFocus(event);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Only set the open on focus if the combobox isn't already opened.
|
|
373
|
+
* This avoids the popover re-opening when an option is selected and the
|
|
374
|
+
* field is refocused
|
|
375
|
+
*/
|
|
376
|
+
if (openOnFocus && !showPopover) {
|
|
377
|
+
handleOpen({
|
|
378
|
+
manual: true
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
const handleClick = () => {
|
|
383
|
+
handleOpen({
|
|
384
|
+
manual: true
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Keyboard shortcut management following the WAI APG pattern
|
|
390
|
+
* https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/#kbd_label
|
|
391
|
+
*/
|
|
392
|
+
const handleKeyDown = React__default.useCallback(async event => {
|
|
393
|
+
const {
|
|
394
|
+
key,
|
|
395
|
+
altKey
|
|
396
|
+
} = event;
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* On Enter key
|
|
400
|
+
* * Select option that is currently highlighted, if any
|
|
401
|
+
* * Close suggestions, event if none is highlighted
|
|
402
|
+
*/
|
|
403
|
+
if (event.key === 'Enter') {
|
|
404
|
+
if (!showPopover) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (highlightedId) {
|
|
408
|
+
// prevent any potential form submission
|
|
409
|
+
event.preventDefault();
|
|
410
|
+
const option = options[highlightedId];
|
|
411
|
+
handleSelected(option, 'keyboard');
|
|
412
|
+
} else {
|
|
413
|
+
handleClose();
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* On arrow up/down
|
|
420
|
+
* * If popover is already opened, do nothing
|
|
421
|
+
* * If alt key pressed, show the listbox without focusing an option
|
|
422
|
+
* * If arrow Up is pressed, select last option
|
|
423
|
+
* * If arrow Down is pressed, select first option
|
|
424
|
+
* */
|
|
425
|
+
if (key === 'ArrowDown' || key === 'ArrowUp') {
|
|
426
|
+
if (showPopover) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Open the listbox */
|
|
431
|
+
const listBoxOptions = await handleOpen({
|
|
432
|
+
manual: true
|
|
433
|
+
});
|
|
434
|
+
if (!listBoxOptions) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* If alt key is pressed, only open without changing visual focus
|
|
440
|
+
* as per WAI Guidelines
|
|
441
|
+
*/
|
|
442
|
+
if (!altKey) {
|
|
443
|
+
/** If a selected id is set, set it as current tabstop */
|
|
444
|
+
if (selectedIds?.length) {
|
|
445
|
+
movingFocusDispatch({
|
|
446
|
+
type: 'SELECT_TAB_STOP',
|
|
447
|
+
payload: {
|
|
448
|
+
id: selectedIds?.[0],
|
|
449
|
+
type: 'keyboard'
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
} else {
|
|
453
|
+
/** Focus either the first or last item depending on the keyboard arrow pressed */
|
|
454
|
+
movingFocusDispatch({
|
|
455
|
+
type: 'KEY_NAV',
|
|
456
|
+
payload: {
|
|
457
|
+
ctrlKey: false,
|
|
458
|
+
key: key === 'ArrowUp' ? 'End' : 'Home'
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Forward event
|
|
467
|
+
onKeyDown?.(event);
|
|
468
|
+
}, [onKeyDown, showPopover, highlightedId, options, handleSelected, handleClose, handleOpen, selectedIds, movingFocusDispatch]);
|
|
469
|
+
return {
|
|
470
|
+
id: comboboxId,
|
|
471
|
+
role: 'combobox',
|
|
472
|
+
'aria-activedescendant': showPopover && highlightedId ? highlightedId : '',
|
|
473
|
+
'aria-controls': listboxId,
|
|
474
|
+
'aria-owns': listboxId,
|
|
475
|
+
'aria-expanded': showPopover,
|
|
476
|
+
onFocus: handleFocus,
|
|
477
|
+
onBlur: handleBlur,
|
|
478
|
+
onClick: handleClick,
|
|
479
|
+
onKeyDown: handleKeyDown
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Is printable character key press */
|
|
484
|
+
const isTypeEvent = ({
|
|
485
|
+
key,
|
|
486
|
+
altKey,
|
|
487
|
+
ctrlKey,
|
|
488
|
+
metaKey
|
|
489
|
+
}) => key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Provide props for the semantic and behaviors the combobox button trigger
|
|
493
|
+
*
|
|
494
|
+
* Overrides the useComboboxTrigger() props & behavior to add a jump to option
|
|
495
|
+
* on printable character key down.
|
|
496
|
+
*/
|
|
497
|
+
function useComboboxButton(args) {
|
|
498
|
+
const {
|
|
499
|
+
context: {
|
|
500
|
+
handleOpen
|
|
501
|
+
},
|
|
502
|
+
onKeyDown: parentOnKeyDown
|
|
503
|
+
} = args;
|
|
504
|
+
const {
|
|
505
|
+
dispatch: movingFocusDispatch,
|
|
506
|
+
state: {
|
|
507
|
+
selectedId: highlightedId
|
|
508
|
+
}
|
|
509
|
+
} = React__default.useContext(MovingFocusContext);
|
|
510
|
+
const searchValueRef = React__default.useRef('');
|
|
511
|
+
const searchTimeoutRef = React__default.useRef();
|
|
512
|
+
const onKeyDown = React__default.useMemo(() => {
|
|
513
|
+
function clearSearchTimeout() {
|
|
514
|
+
const searchTimeout = searchTimeoutRef.current;
|
|
515
|
+
if (!searchTimeout) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
clearTimeout(searchTimeout);
|
|
519
|
+
searchTimeoutRef.current = undefined;
|
|
520
|
+
}
|
|
521
|
+
function clearSearch() {
|
|
522
|
+
searchValueRef.current = '';
|
|
523
|
+
clearSearchTimeout();
|
|
524
|
+
}
|
|
525
|
+
return async event => {
|
|
526
|
+
if (!isTypeEvent(event)) {
|
|
527
|
+
// Forward key down event
|
|
528
|
+
parentOnKeyDown?.(event);
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
event.stopPropagation();
|
|
532
|
+
|
|
533
|
+
// Clear current search timeout
|
|
534
|
+
clearSearchTimeout();
|
|
535
|
+
|
|
536
|
+
// Open combobox and wait for options to mount
|
|
537
|
+
const options = await handleOpen({
|
|
538
|
+
manual: false
|
|
539
|
+
});
|
|
540
|
+
const optionArray = options && Object.values(options);
|
|
541
|
+
if (!optionArray?.length) {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Append key to current search
|
|
546
|
+
searchValueRef.current += event.key.toLowerCase();
|
|
547
|
+
const searchValue = searchValueRef.current;
|
|
548
|
+
|
|
549
|
+
// Clear search after 500ms
|
|
550
|
+
searchTimeoutRef.current = setTimeout(clearSearch, 500);
|
|
551
|
+
|
|
552
|
+
// Search is containing all the same letters (ex: aaaa)
|
|
553
|
+
const allTheSameLetters = searchValue.split('').every(letter => letter === searchValue[0]);
|
|
554
|
+
|
|
555
|
+
// start from currently selected option
|
|
556
|
+
let startIndex = optionArray.findIndex(option => option.generatedId === highlightedId);
|
|
557
|
+
if (startIndex === -1) {
|
|
558
|
+
startIndex = 0;
|
|
559
|
+
}
|
|
560
|
+
let index = startIndex;
|
|
561
|
+
do {
|
|
562
|
+
// Increment index and loop around to 0 if we get over the array length
|
|
563
|
+
index = (index + 1) % optionArray.length;
|
|
564
|
+
const option = optionArray[index];
|
|
565
|
+
// Search by text value or fallback on id.
|
|
566
|
+
const optionText = isComboboxValue(option) ? option?.textValue || option?.id : null;
|
|
567
|
+
if (isComboboxValue(option) && optionText) {
|
|
568
|
+
const optionTextValue = optionText.toLowerCase();
|
|
569
|
+
|
|
570
|
+
// Search on first letter if search is all the same letters
|
|
571
|
+
const searchText = allTheSameLetters ? searchValue[0] : searchValue;
|
|
572
|
+
|
|
573
|
+
// Option text value starts with searched text
|
|
574
|
+
if (optionTextValue.startsWith(searchText)) {
|
|
575
|
+
movingFocusDispatch({
|
|
576
|
+
type: 'SELECT_TAB_STOP',
|
|
577
|
+
payload: {
|
|
578
|
+
id: option.generatedId,
|
|
579
|
+
type: 'keyboard'
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
} while (index !== startIndex);
|
|
586
|
+
return clearSearchTimeout;
|
|
587
|
+
};
|
|
588
|
+
}, [handleOpen, parentOnKeyDown, highlightedId, movingFocusDispatch]);
|
|
589
|
+
return useComboboxTrigger({
|
|
590
|
+
...args,
|
|
591
|
+
onKeyDown
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Provide props for the semantic and behaviors the combobox input trigger
|
|
597
|
+
*
|
|
598
|
+
* Overrides the useComboboxTrigger() props & behavior to prevent open on click
|
|
599
|
+
* if `openOnFocus` is not enabled.
|
|
600
|
+
*/
|
|
601
|
+
function useComboboxInput({
|
|
602
|
+
onKeyDown: propsOnKeyDown,
|
|
603
|
+
...args
|
|
604
|
+
}) {
|
|
605
|
+
const triggerProps = useComboboxTrigger(args);
|
|
606
|
+
const {
|
|
607
|
+
onClick,
|
|
608
|
+
onKeyDown,
|
|
609
|
+
'aria-expanded': showPopover
|
|
610
|
+
} = triggerProps;
|
|
611
|
+
const handleClick = () => {
|
|
612
|
+
if (!args.context.openOnFocus && !args.context.openOnClick) {
|
|
613
|
+
// Skip if input should not opening on focus nor click
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
onClick();
|
|
617
|
+
};
|
|
618
|
+
const handleKeyDown = evt => {
|
|
619
|
+
if (evt.key === 'Escape') {
|
|
620
|
+
// Reset field if closed
|
|
621
|
+
if (!showPopover) {
|
|
622
|
+
args.context.handleInputChange('');
|
|
623
|
+
}
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
onKeyDown(evt);
|
|
627
|
+
if (propsOnKeyDown) {
|
|
628
|
+
propsOnKeyDown(evt);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
return {
|
|
632
|
+
...triggerProps,
|
|
633
|
+
onClick: handleClick,
|
|
634
|
+
onKeyDown: handleKeyDown
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export { ComboboxContext, ComboboxProvider, InfiniteScroll, MovingFocusContext, MovingFocusProvider, useComboboxButton, useComboboxInput, useComboboxTrigger, useRovingTabIndex, useVirtualFocusParent };
|
|
279
639
|
//# sourceMappingURL=index.js.map
|