@lumx/react 4.3.2-alpha.10 → 4.3.2-alpha.11

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.
@@ -1,9 +1,10 @@
1
- import React__default, { useContext, useEffect, useMemo, useRef, createContext } from 'react';
1
+ import React__default, { useContext, useMemo, createContext, useEffect, useRef } from 'react';
2
2
  import { jsx, Fragment } from 'react/jsx-runtime';
3
3
  import isEmpty from 'lodash/isEmpty';
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';
7
+ import uniqueId from 'lodash/uniqueId';
7
8
  import findLast from 'lodash/findLast';
8
9
  import find from 'lodash/find';
9
10
  import findLastIndex from 'lodash/findLastIndex';
@@ -34,6 +35,40 @@ function useDisabledStateContext() {
34
35
  return useContext(DisabledStateContext);
35
36
  }
36
37
 
38
+ /** Context to store the refs of the combobox elements */
39
+ const ComboboxRefsContext = /*#__PURE__*/createContext({
40
+ triggerRef: {
41
+ current: null
42
+ },
43
+ anchorRef: {
44
+ current: null
45
+ }
46
+ });
47
+ /** Provider to store the required refs for the Combobox */
48
+ const ComboboxRefsProvider = ({
49
+ triggerRef,
50
+ anchorRef,
51
+ children
52
+ }) => {
53
+ const value = useMemo(() => ({
54
+ triggerRef,
55
+ anchorRef
56
+ }), [triggerRef, anchorRef]);
57
+ return /*#__PURE__*/jsx(ComboboxRefsContext.Provider, {
58
+ value: value,
59
+ children: children
60
+ });
61
+ };
62
+
63
+ /** Retrieves the combobox elements references from context */
64
+ const useComboboxRefs = () => {
65
+ const refs = useContext(ComboboxRefsContext);
66
+ if (!refs) {
67
+ throw new Error('The useComboboxRefs hook must be called within a ComboboxRefsProvider');
68
+ }
69
+ return refs;
70
+ };
71
+
37
72
  const EVENT_TYPES = ['mousedown', 'touchstart'];
38
73
  function isClickAway(targets, refs) {
39
74
  // The targets elements are not contained in any of the listed element references.
@@ -1234,7 +1269,7 @@ const OPTIONS_UPDATED = (state, action) => {
1234
1269
  };
1235
1270
 
1236
1271
  /** Reducers for each action type: */
1237
- const REDUCERS = {
1272
+ const REDUCERS$1 = {
1238
1273
  REGISTER_TAB_STOP,
1239
1274
  UNREGISTER_TAB_STOP,
1240
1275
  UPDATE_TAB_STOP,
@@ -1246,8 +1281,8 @@ const REDUCERS = {
1246
1281
  };
1247
1282
 
1248
1283
  /** Main reducer */
1249
- const reducer = (state, action) => {
1250
- return REDUCERS[action.type]?.(state, action) || state;
1284
+ const reducer$1 = (state, action) => {
1285
+ return REDUCERS$1[action.type]?.(state, action) || state;
1251
1286
  };
1252
1287
 
1253
1288
  const MovingFocusContext = /*#__PURE__*/React__default.createContext({
@@ -1262,7 +1297,7 @@ const MovingFocusProvider = ({
1262
1297
  children,
1263
1298
  options
1264
1299
  }) => {
1265
- const [state, dispatch] = React__default.useReducer(reducer, INITIAL_STATE, st => ({
1300
+ const [state, dispatch] = React__default.useReducer(reducer$1, INITIAL_STATE, st => ({
1266
1301
  ...st,
1267
1302
  ...options,
1268
1303
  direction: options?.direction || st.direction,
@@ -1476,5 +1511,295 @@ const useVirtualFocusParent = ref => {
1476
1511
  return focused;
1477
1512
  };
1478
1513
 
1479
- export { A11YLiveMessage as A, ClickAwayProvider as C, DisabledStateProvider as D, MovingFocusContext as M, NAV_KEYS as N, Portal as P, useVirtualFocusParent as a, useVirtualFocus as b, MovingFocusProvider as c, PortalProvider as d, getPointerTypeFromEvent as g, useDisabledStateContext as u };
1480
- //# sourceMappingURL=BcRzrT9Y.js.map
1514
+ /** Generate the combobox option id from the combobox id and the given id */
1515
+ const generateOptionId = (comboboxId, optionId) => `${comboboxId}-option-${optionId}`;
1516
+
1517
+ /** Verifies that the combobox registered option is an action */
1518
+ const isComboboxAction = option => Boolean(option?.isAction);
1519
+
1520
+ /** Verifies that the combobox registered option is the option's value */
1521
+ const isComboboxValue = option => {
1522
+ return !isComboboxAction(option);
1523
+ };
1524
+
1525
+ const comboboxId = `combobox-${uniqueId()}`;
1526
+ const initialState = {
1527
+ comboboxId,
1528
+ listboxId: `${comboboxId}-popover`,
1529
+ status: 'idle',
1530
+ isOpen: false,
1531
+ inputValue: '',
1532
+ showAll: true,
1533
+ options: {},
1534
+ type: 'listbox',
1535
+ optionsLength: 0
1536
+ };
1537
+
1538
+ /** Actions when the combobox opens. */
1539
+ const OPEN_COMBOBOX = (state, action) => {
1540
+ const {
1541
+ manual
1542
+ } = action.payload || {};
1543
+ // If the combobox was manually opened, show all suggestions
1544
+ return {
1545
+ ...state,
1546
+ showAll: Boolean(manual),
1547
+ isOpen: true
1548
+ };
1549
+ };
1550
+
1551
+ /** Actions when the combobox closes */
1552
+ const CLOSE_COMBOBOX = state => {
1553
+ return {
1554
+ ...state,
1555
+ showAll: true,
1556
+ isOpen: false
1557
+ };
1558
+ };
1559
+
1560
+ /** Actions on input update. */
1561
+ const SET_INPUT_VALUE = (state, action) => {
1562
+ return {
1563
+ ...state,
1564
+ inputValue: action.payload,
1565
+ // When the user is changing the value, show only values that are related to the input value.
1566
+ showAll: false,
1567
+ isOpen: true
1568
+ };
1569
+ };
1570
+
1571
+ /** Register an option to the state */
1572
+ const ADD_OPTION = (state, action) => {
1573
+ const {
1574
+ id,
1575
+ option
1576
+ } = action.payload;
1577
+ const {
1578
+ options
1579
+ } = state;
1580
+ if (options[id]) {
1581
+ // Option already exists, return state unchanged
1582
+ return state;
1583
+ }
1584
+ const newOptions = {
1585
+ ...options,
1586
+ [id]: option
1587
+ };
1588
+ let newType = state.type;
1589
+ if (isComboboxAction(option)) {
1590
+ newType = 'grid';
1591
+ }
1592
+ let newOptionsLength = state.optionsLength;
1593
+ if (isComboboxValue(option)) {
1594
+ newOptionsLength += 1;
1595
+ }
1596
+ return {
1597
+ ...state,
1598
+ options: newOptions,
1599
+ type: newType,
1600
+ optionsLength: newOptionsLength
1601
+ };
1602
+ };
1603
+
1604
+ /** Remove an option from the state */
1605
+ const REMOVE_OPTION = (state, action) => {
1606
+ const {
1607
+ id
1608
+ } = action.payload;
1609
+ const {
1610
+ options
1611
+ } = state;
1612
+ const option = options[id];
1613
+ if (!options[id]) {
1614
+ // Option doesn't exist, return state unchanged
1615
+ return state;
1616
+ }
1617
+ const newOptions = {
1618
+ ...options
1619
+ };
1620
+ delete newOptions[id];
1621
+ let newOptionsLength = state.optionsLength;
1622
+ if (isComboboxValue(option)) {
1623
+ newOptionsLength -= 1;
1624
+ }
1625
+ return {
1626
+ ...state,
1627
+ options: newOptions,
1628
+ optionsLength: newOptionsLength
1629
+ };
1630
+ };
1631
+
1632
+ /** Reducers for each action type: */
1633
+ const REDUCERS = {
1634
+ OPEN_COMBOBOX,
1635
+ CLOSE_COMBOBOX,
1636
+ SET_INPUT_VALUE,
1637
+ ADD_OPTION,
1638
+ REMOVE_OPTION
1639
+ };
1640
+
1641
+ /** Main reducer */
1642
+ const reducer = (state, action) => {
1643
+ return REDUCERS[action.type]?.(state, action) || state;
1644
+ };
1645
+
1646
+ /** Dispatch for the combobox component */
1647
+
1648
+ /** Context for the Combobox component */
1649
+ const ComboboxContext = /*#__PURE__*/React__default.createContext({
1650
+ ...initialState,
1651
+ openOnFocus: false,
1652
+ openOnClick: false,
1653
+ selectionType: 'single',
1654
+ optionsLength: 0,
1655
+ onSelect: noop,
1656
+ onInputChange: noop,
1657
+ onOpen: noop,
1658
+ dispatch: noop,
1659
+ translations: {
1660
+ clearLabel: '',
1661
+ tryReloadLabel: '',
1662
+ showSuggestionsLabel: '',
1663
+ noResultsForInputLabel: input => input || '',
1664
+ loadingLabel: '',
1665
+ serviceUnavailableLabel: '',
1666
+ nbOptionsLabel: options => `${options}`
1667
+ }
1668
+ });
1669
+
1670
+ /** Context for a combobox section to store its unique id */
1671
+ const SectionContext = /*#__PURE__*/React__default.createContext({
1672
+ sectionId: ''
1673
+ });
1674
+
1675
+ /** Retrieve the current combobox state and actions */
1676
+ const useCombobox = () => {
1677
+ const comboboxContext = React__default.useContext(ComboboxContext);
1678
+ const {
1679
+ dispatch: movingFocusDispatch
1680
+ } = React__default.useContext(MovingFocusContext);
1681
+ const {
1682
+ onSelect,
1683
+ onInputChange,
1684
+ onOpen,
1685
+ dispatch,
1686
+ inputValue,
1687
+ ...contextValues
1688
+ } = comboboxContext;
1689
+ const {
1690
+ triggerRef
1691
+ } = useComboboxRefs();
1692
+
1693
+ /** Action triggered when the listBox is closed without selecting any option */
1694
+ const handleClose = React__default.useCallback(() => {
1695
+ dispatch({
1696
+ type: 'CLOSE_COMBOBOX'
1697
+ });
1698
+ // Reset visual focus
1699
+ movingFocusDispatch({
1700
+ type: 'RESET_SELECTED_TAB_STOP'
1701
+ });
1702
+ }, [dispatch, movingFocusDispatch]);
1703
+
1704
+ // Handle callbacks on options mounted
1705
+ const [optionsMountedCallbacks, setOptionsMountedCallback] = React__default.useState();
1706
+ React__default.useEffect(() => {
1707
+ if (comboboxContext.optionsLength > 0 && optionsMountedCallbacks?.length) {
1708
+ const optionsArray = Object.values(comboboxContext.options);
1709
+ // Execute callbacks
1710
+ for (const callback of optionsMountedCallbacks) {
1711
+ callback(optionsArray);
1712
+ }
1713
+ setOptionsMountedCallback(undefined);
1714
+ }
1715
+ }, [comboboxContext.options, comboboxContext.optionsLength, optionsMountedCallbacks]);
1716
+
1717
+ /** Callback for when an option is selected */
1718
+ const handleSelected = React__default.useCallback((option, source) => {
1719
+ if (option?.isDisabled) {
1720
+ return;
1721
+ }
1722
+ if (isComboboxValue(option)) {
1723
+ /**
1724
+ * We only close the list if the selection type is single.
1725
+ * If it is multiple, we want to allow the user to continue
1726
+ * selecting multiple options.
1727
+ */
1728
+ if (comboboxContext.selectionType !== 'multiple') {
1729
+ handleClose();
1730
+ }
1731
+ /** Call parent onSelect callback */
1732
+ if (onSelect) {
1733
+ onSelect(option);
1734
+ }
1735
+ }
1736
+
1737
+ /** If the option itself has a custom action, also call it */
1738
+ if (option?.onSelect) {
1739
+ option.onSelect(option, source);
1740
+ }
1741
+
1742
+ /** Reset focus on input */
1743
+ if (triggerRef?.current) {
1744
+ triggerRef.current?.focus();
1745
+ }
1746
+ }, [comboboxContext.selectionType, handleClose, onSelect, triggerRef]);
1747
+
1748
+ /** Callback for when the input must be updated */
1749
+ const handleInputChange = React__default.useCallback((value, ...args) => {
1750
+ // Update the local state
1751
+ dispatch({
1752
+ type: 'SET_INPUT_VALUE',
1753
+ payload: value
1754
+ });
1755
+ // If a callback if given, call it with the value
1756
+ if (onInputChange) {
1757
+ onInputChange(value, ...args);
1758
+ }
1759
+ // Reset visual focus
1760
+ movingFocusDispatch({
1761
+ type: 'RESET_SELECTED_TAB_STOP'
1762
+ });
1763
+ }, [dispatch, movingFocusDispatch, onInputChange]);
1764
+
1765
+ /**
1766
+ * Open the popover
1767
+ *
1768
+ * @returns a promise with the updated context once all options are mounted
1769
+ */
1770
+ const handleOpen = React__default.useCallback(params => {
1771
+ /** update the local state */
1772
+ dispatch({
1773
+ type: 'OPEN_COMBOBOX',
1774
+ payload: params
1775
+ });
1776
+ /** If a parent callback was given, trigger it with state information */
1777
+ if (onOpen) {
1778
+ onOpen({
1779
+ currentValue: inputValue,
1780
+ manual: Boolean(params?.manual)
1781
+ });
1782
+ }
1783
+
1784
+ // Promise resolving options on mount
1785
+ return new Promise(resolve => {
1786
+ // Append to the list of callback on options mounted
1787
+ setOptionsMountedCallback((callbacks = []) => {
1788
+ callbacks.push(resolve);
1789
+ return callbacks;
1790
+ });
1791
+ });
1792
+ }, [dispatch, inputValue, onOpen]);
1793
+ return React__default.useMemo(() => ({
1794
+ handleClose,
1795
+ handleOpen,
1796
+ handleInputChange,
1797
+ handleSelected,
1798
+ dispatch,
1799
+ inputValue,
1800
+ ...contextValues
1801
+ }), [contextValues, dispatch, handleClose, handleInputChange, handleOpen, handleSelected, inputValue]);
1802
+ };
1803
+
1804
+ export { A11YLiveMessage as A, ComboboxContext as C, DisabledStateProvider as D, MovingFocusContext as M, NAV_KEYS as N, Portal as P, SectionContext as S, useVirtualFocusParent as a, useComboboxRefs as b, useCombobox as c, useVirtualFocus as d, MovingFocusProvider as e, initialState as f, generateOptionId as g, ComboboxRefsProvider as h, isComboboxValue as i, ClickAwayProvider as j, getPointerTypeFromEvent as k, PortalProvider as l, reducer as r, useDisabledStateContext as u };
1805
+ //# sourceMappingURL=BCgo9dYV.js.map