@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.
@@ -4,6 +4,7 @@ import isEmpty from 'lodash/isEmpty.js';
4
4
  import { join, visuallyHidden } from '@lumx/core/js/utils/classNames';
5
5
  import { createPortal } from 'react-dom';
6
6
  import noop from 'lodash/noop.js';
7
+ import uniqueId from 'lodash/uniqueId.js';
7
8
  import findLast from 'lodash/findLast.js';
8
9
  import find from 'lodash/find.js';
9
10
  import findLastIndex from 'lodash/findLastIndex.js';
@@ -1234,7 +1235,7 @@ const OPTIONS_UPDATED = (state, action) => {
1234
1235
  };
1235
1236
 
1236
1237
  /** Reducers for each action type: */
1237
- const REDUCERS = {
1238
+ const REDUCERS$1 = {
1238
1239
  REGISTER_TAB_STOP,
1239
1240
  UNREGISTER_TAB_STOP,
1240
1241
  UPDATE_TAB_STOP,
@@ -1246,8 +1247,8 @@ const REDUCERS = {
1246
1247
  };
1247
1248
 
1248
1249
  /** Main reducer */
1249
- const reducer = (state, action) => {
1250
- return REDUCERS[action.type]?.(state, action) || state;
1250
+ const reducer$1 = (state, action) => {
1251
+ return REDUCERS$1[action.type]?.(state, action) || state;
1251
1252
  };
1252
1253
 
1253
1254
  const MovingFocusContext = /*#__PURE__*/React__default.createContext({
@@ -1371,5 +1372,397 @@ const useVirtualFocus = (id, domElementRef, disabled = false, rowKey = null, aut
1371
1372
  return focused;
1372
1373
  };
1373
1374
 
1374
- export { A11YLiveMessage as A, ClickAwayProvider as C, DisabledStateProvider as D, INITIAL_STATE as I, MovingFocusContext as M, NAV_KEYS as N, Portal as P, useVirtualFocus as a, buildLoopAroundObject as b, PortalProvider as c, getPointerTypeFromEvent as g, reducer as r, useDisabledStateContext as u };
1375
- //# sourceMappingURL=CzTdCnO5.js.map
1375
+ /** Generate the combobox option id from the combobox id and the given id */
1376
+ const generateOptionId = (comboboxId, optionId) => `${comboboxId}-option-${optionId}`;
1377
+
1378
+ /** Verifies that the combobox registered option is an action */
1379
+ const isComboboxAction = option => Boolean(option?.isAction);
1380
+
1381
+ /** Verifies that the combobox registered option is the option's value */
1382
+ const isComboboxValue = option => {
1383
+ return !isComboboxAction(option);
1384
+ };
1385
+
1386
+ const comboboxId = `combobox-${uniqueId()}`;
1387
+ const initialState = {
1388
+ comboboxId,
1389
+ listboxId: `${comboboxId}-popover`,
1390
+ status: 'idle',
1391
+ isOpen: false,
1392
+ inputValue: '',
1393
+ showAll: true,
1394
+ options: {},
1395
+ type: 'listbox',
1396
+ optionsLength: 0
1397
+ };
1398
+
1399
+ /** Actions when the combobox opens. */
1400
+ const OPEN_COMBOBOX = (state, action) => {
1401
+ const {
1402
+ manual
1403
+ } = action.payload || {};
1404
+ // If the combobox was manually opened, show all suggestions
1405
+ return {
1406
+ ...state,
1407
+ showAll: Boolean(manual),
1408
+ isOpen: true
1409
+ };
1410
+ };
1411
+
1412
+ /** Actions when the combobox closes */
1413
+ const CLOSE_COMBOBOX = state => {
1414
+ return {
1415
+ ...state,
1416
+ showAll: true,
1417
+ isOpen: false
1418
+ };
1419
+ };
1420
+
1421
+ /** Actions on input update. */
1422
+ const SET_INPUT_VALUE = (state, action) => {
1423
+ return {
1424
+ ...state,
1425
+ inputValue: action.payload,
1426
+ // When the user is changing the value, show only values that are related to the input value.
1427
+ showAll: false,
1428
+ isOpen: true
1429
+ };
1430
+ };
1431
+
1432
+ /** Register an option to the state */
1433
+ const ADD_OPTION = (state, action) => {
1434
+ const {
1435
+ id,
1436
+ option
1437
+ } = action.payload;
1438
+ const {
1439
+ options
1440
+ } = state;
1441
+ if (options[id]) {
1442
+ // Option already exists, return state unchanged
1443
+ return state;
1444
+ }
1445
+ const newOptions = {
1446
+ ...options,
1447
+ [id]: option
1448
+ };
1449
+ let newType = state.type;
1450
+ if (isComboboxAction(option)) {
1451
+ newType = 'grid';
1452
+ }
1453
+ let newOptionsLength = state.optionsLength;
1454
+ if (isComboboxValue(option)) {
1455
+ newOptionsLength += 1;
1456
+ }
1457
+ return {
1458
+ ...state,
1459
+ options: newOptions,
1460
+ type: newType,
1461
+ optionsLength: newOptionsLength
1462
+ };
1463
+ };
1464
+
1465
+ /** Remove an option from the state */
1466
+ const REMOVE_OPTION = (state, action) => {
1467
+ const {
1468
+ id
1469
+ } = action.payload;
1470
+ const {
1471
+ options
1472
+ } = state;
1473
+ const option = options[id];
1474
+ if (!options[id]) {
1475
+ // Option doesn't exist, return state unchanged
1476
+ return state;
1477
+ }
1478
+ const newOptions = {
1479
+ ...options
1480
+ };
1481
+ delete newOptions[id];
1482
+ let newOptionsLength = state.optionsLength;
1483
+ if (isComboboxValue(option)) {
1484
+ newOptionsLength -= 1;
1485
+ }
1486
+ return {
1487
+ ...state,
1488
+ options: newOptions,
1489
+ optionsLength: newOptionsLength
1490
+ };
1491
+ };
1492
+
1493
+ /** Reducers for each action type: */
1494
+ const REDUCERS = {
1495
+ OPEN_COMBOBOX,
1496
+ CLOSE_COMBOBOX,
1497
+ SET_INPUT_VALUE,
1498
+ ADD_OPTION,
1499
+ REMOVE_OPTION
1500
+ };
1501
+
1502
+ /** Main reducer */
1503
+ const reducer = (state, action) => {
1504
+ return REDUCERS[action.type]?.(state, action) || state;
1505
+ };
1506
+
1507
+ /** Dispatch for the combobox component */
1508
+
1509
+ /** Context for the Combobox component */
1510
+ const ComboboxContext = /*#__PURE__*/React__default.createContext({
1511
+ ...initialState,
1512
+ openOnFocus: false,
1513
+ openOnClick: false,
1514
+ selectionType: 'single',
1515
+ optionsLength: 0,
1516
+ onSelect: noop,
1517
+ onInputChange: noop,
1518
+ onOpen: noop,
1519
+ dispatch: noop,
1520
+ translations: {
1521
+ clearLabel: '',
1522
+ tryReloadLabel: '',
1523
+ showSuggestionsLabel: '',
1524
+ noResultsForInputLabel: input => input || '',
1525
+ loadingLabel: '',
1526
+ serviceUnavailableLabel: '',
1527
+ nbOptionsLabel: options => `${options}`
1528
+ }
1529
+ });
1530
+
1531
+ /** Context for a combobox section to store its unique id */
1532
+ const SectionContext = /*#__PURE__*/React__default.createContext({
1533
+ sectionId: ''
1534
+ });
1535
+
1536
+ const ComboboxOptionContext = /*#__PURE__*/createContext({});
1537
+ /** Context Provider to store the current combobox option id. */
1538
+ const ComboboxOptionContextProvider = ({
1539
+ optionId,
1540
+ isKeyboardHighlighted,
1541
+ children
1542
+ }) => {
1543
+ const value = React__default.useMemo(() => ({
1544
+ optionId,
1545
+ isKeyboardHighlighted
1546
+ }), [optionId, isKeyboardHighlighted]);
1547
+ return /*#__PURE__*/jsx(ComboboxOptionContext.Provider, {
1548
+ value: value,
1549
+ children: children
1550
+ });
1551
+ };
1552
+
1553
+ /**
1554
+ * Retrieve the current combobox option id.
1555
+ * Must be used within a ComboboxOptionIdProvider
1556
+ */
1557
+ const useComboboxOptionContext = () => {
1558
+ const comboboxOption = useContext(ComboboxOptionContext);
1559
+ if (!comboboxOption?.optionId) {
1560
+ throw new Error('This hook must be used within a ComboboxOptionIdProvider');
1561
+ }
1562
+ return comboboxOption;
1563
+ };
1564
+
1565
+ /** Context to store the refs of the combobox elements */
1566
+ const ComboboxRefsContext = /*#__PURE__*/createContext({
1567
+ triggerRef: {
1568
+ current: null
1569
+ },
1570
+ anchorRef: {
1571
+ current: null
1572
+ }
1573
+ });
1574
+ /** Provider to store the required refs for the Combobox */
1575
+ const ComboboxRefsProvider = ({
1576
+ triggerRef,
1577
+ anchorRef,
1578
+ children
1579
+ }) => {
1580
+ const value = useMemo(() => ({
1581
+ triggerRef,
1582
+ anchorRef
1583
+ }), [triggerRef, anchorRef]);
1584
+ return /*#__PURE__*/jsx(ComboboxRefsContext.Provider, {
1585
+ value: value,
1586
+ children: children
1587
+ });
1588
+ };
1589
+
1590
+ /** Retrieves the combobox elements references from context */
1591
+ const useComboboxRefs = () => {
1592
+ const refs = useContext(ComboboxRefsContext);
1593
+ if (!refs) {
1594
+ throw new Error('The useComboboxRefs hook must be called within a ComboboxRefsProvider');
1595
+ }
1596
+ return refs;
1597
+ };
1598
+
1599
+ /** Retrieve the current combobox state and actions */
1600
+ const useCombobox = () => {
1601
+ const comboboxContext = React__default.useContext(ComboboxContext);
1602
+ const {
1603
+ dispatch: movingFocusDispatch
1604
+ } = React__default.useContext(MovingFocusContext);
1605
+ const {
1606
+ onSelect,
1607
+ onInputChange,
1608
+ onOpen,
1609
+ dispatch,
1610
+ inputValue,
1611
+ ...contextValues
1612
+ } = comboboxContext;
1613
+ const {
1614
+ triggerRef
1615
+ } = useComboboxRefs();
1616
+
1617
+ /** Action triggered when the listBox is closed without selecting any option */
1618
+ const handleClose = React__default.useCallback(() => {
1619
+ dispatch({
1620
+ type: 'CLOSE_COMBOBOX'
1621
+ });
1622
+ // Reset visual focus
1623
+ movingFocusDispatch({
1624
+ type: 'RESET_SELECTED_TAB_STOP'
1625
+ });
1626
+ }, [dispatch, movingFocusDispatch]);
1627
+
1628
+ // Handle callbacks on options mounted
1629
+ const [optionsMountedCallbacks, setOptionsMountedCallback] = React__default.useState();
1630
+ React__default.useEffect(() => {
1631
+ if (comboboxContext.optionsLength > 0 && optionsMountedCallbacks?.length) {
1632
+ const optionsArray = Object.values(comboboxContext.options);
1633
+ // Execute callbacks
1634
+ for (const callback of optionsMountedCallbacks) {
1635
+ callback(optionsArray);
1636
+ }
1637
+ setOptionsMountedCallback(undefined);
1638
+ }
1639
+ }, [comboboxContext.options, comboboxContext.optionsLength, optionsMountedCallbacks]);
1640
+
1641
+ /** Callback for when an option is selected */
1642
+ const handleSelected = React__default.useCallback((option, source) => {
1643
+ if (option?.isDisabled) {
1644
+ return;
1645
+ }
1646
+ if (isComboboxValue(option)) {
1647
+ /**
1648
+ * We only close the list if the selection type is single.
1649
+ * If it is multiple, we want to allow the user to continue
1650
+ * selecting multiple options.
1651
+ */
1652
+ if (comboboxContext.selectionType !== 'multiple') {
1653
+ handleClose();
1654
+ }
1655
+ /** Call parent onSelect callback */
1656
+ if (onSelect) {
1657
+ onSelect(option);
1658
+ }
1659
+ }
1660
+
1661
+ /** If the option itself has a custom action, also call it */
1662
+ if (option?.onSelect) {
1663
+ option.onSelect(option, source);
1664
+ }
1665
+
1666
+ /** Reset focus on input */
1667
+ if (triggerRef?.current) {
1668
+ triggerRef.current?.focus();
1669
+ }
1670
+ }, [comboboxContext.selectionType, handleClose, onSelect, triggerRef]);
1671
+
1672
+ /** Callback for when the input must be updated */
1673
+ const handleInputChange = React__default.useCallback((value, ...args) => {
1674
+ // Update the local state
1675
+ dispatch({
1676
+ type: 'SET_INPUT_VALUE',
1677
+ payload: value
1678
+ });
1679
+ // If a callback if given, call it with the value
1680
+ if (onInputChange) {
1681
+ onInputChange(value, ...args);
1682
+ }
1683
+ // Reset visual focus
1684
+ movingFocusDispatch({
1685
+ type: 'RESET_SELECTED_TAB_STOP'
1686
+ });
1687
+ }, [dispatch, movingFocusDispatch, onInputChange]);
1688
+
1689
+ /**
1690
+ * Open the popover
1691
+ *
1692
+ * @returns a promise with the updated context once all options are mounted
1693
+ */
1694
+ const handleOpen = React__default.useCallback(params => {
1695
+ /** update the local state */
1696
+ dispatch({
1697
+ type: 'OPEN_COMBOBOX',
1698
+ payload: params
1699
+ });
1700
+ /** If a parent callback was given, trigger it with state information */
1701
+ if (onOpen) {
1702
+ onOpen({
1703
+ currentValue: inputValue,
1704
+ manual: Boolean(params?.manual)
1705
+ });
1706
+ }
1707
+
1708
+ // Promise resolving options on mount
1709
+ return new Promise(resolve => {
1710
+ // Append to the list of callback on options mounted
1711
+ setOptionsMountedCallback((callbacks = []) => {
1712
+ callbacks.push(resolve);
1713
+ return callbacks;
1714
+ });
1715
+ });
1716
+ }, [dispatch, inputValue, onOpen]);
1717
+ return React__default.useMemo(() => ({
1718
+ handleClose,
1719
+ handleOpen,
1720
+ handleInputChange,
1721
+ handleSelected,
1722
+ dispatch,
1723
+ inputValue,
1724
+ ...contextValues
1725
+ }), [contextValues, dispatch, handleClose, handleInputChange, handleOpen, handleSelected, inputValue]);
1726
+ };
1727
+
1728
+ /** Retrieve the current combobox section id */
1729
+ const useComboboxSectionId = () => {
1730
+ return useContext(SectionContext);
1731
+ };
1732
+
1733
+ /**
1734
+ * Register the given option to the context
1735
+ */
1736
+ const useRegisterOption = (registerId, option, shouldRegister) => {
1737
+ const {
1738
+ dispatch
1739
+ } = useCombobox();
1740
+
1741
+ /** Register the given options */
1742
+ React__default.useEffect(() => {
1743
+ if (option && shouldRegister) {
1744
+ dispatch({
1745
+ type: 'ADD_OPTION',
1746
+ payload: {
1747
+ id: registerId,
1748
+ option
1749
+ }
1750
+ });
1751
+ }
1752
+
1753
+ // Unregister ids if/when the component unmounts or if option changes
1754
+ return () => {
1755
+ if (option) {
1756
+ dispatch({
1757
+ type: 'REMOVE_OPTION',
1758
+ payload: {
1759
+ id: registerId
1760
+ }
1761
+ });
1762
+ }
1763
+ };
1764
+ }, [dispatch, option, registerId, shouldRegister]);
1765
+ };
1766
+
1767
+ export { A11YLiveMessage as A, ComboboxOptionContextProvider as C, DisabledStateProvider as D, INITIAL_STATE as I, MovingFocusContext as M, NAV_KEYS as N, Portal as P, SectionContext as S, useComboboxSectionId as a, useCombobox as b, useRegisterOption as c, useVirtualFocus as d, useComboboxOptionContext as e, useComboboxRefs as f, generateOptionId as g, ClickAwayProvider as h, buildLoopAroundObject as i, getPointerTypeFromEvent as j, ComboboxContext as k, isComboboxValue as l, initialState as m, ComboboxOptionContext as n, reducer as o, ComboboxRefsProvider as p, PortalProvider as q, reducer$1 as r, useDisabledStateContext as u };
1768
+ //# sourceMappingURL=BQFZG9jO.js.map