@snack-uikit/list 0.11.1-preview-5d3667ec.0 → 0.11.1

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +22 -61
  3. package/dist/components/Items/AccordionItem/AccordionItem.d.ts +4 -2
  4. package/dist/components/Items/AccordionItem/AccordionItem.js +18 -16
  5. package/dist/components/Items/BaseItem/BaseItem.d.ts +8 -4
  6. package/dist/components/Items/BaseItem/BaseItem.js +20 -14
  7. package/dist/components/Items/BaseItem/styles.module.css +20 -119
  8. package/dist/components/Items/GroupItem/GroupItem.d.ts +5 -1
  9. package/dist/components/Items/GroupItem/GroupItem.js +1 -3
  10. package/dist/components/Items/GroupSelectItem/GroupSelectItem.d.ts +4 -0
  11. package/dist/components/Items/GroupSelectItem/GroupSelectItem.js +18 -0
  12. package/dist/components/Items/GroupSelectItem/index.d.ts +1 -0
  13. package/dist/components/Items/GroupSelectItem/index.js +1 -0
  14. package/dist/components/Items/NextListItem/NextListItem.d.ts +6 -2
  15. package/dist/components/Items/NextListItem/NextListItem.js +55 -45
  16. package/dist/components/Items/NextListItem/constants.d.ts +2 -0
  17. package/dist/components/Items/NextListItem/constants.js +10 -0
  18. package/dist/components/Items/PinGroupItem/PinGroupItem.js +7 -3
  19. package/dist/components/Items/PinGroupItem/styles.module.css +26 -14
  20. package/dist/components/Items/SearchItem/SearchItem.d.ts +3 -1
  21. package/dist/components/Items/SearchItem/SearchItem.js +5 -8
  22. package/dist/components/Items/hooks.d.ts +11 -10
  23. package/dist/components/Items/hooks.js +64 -46
  24. package/dist/components/Items/index.d.ts +1 -5
  25. package/dist/components/Items/index.js +1 -5
  26. package/dist/components/Items/styles.module.css +33 -26
  27. package/dist/components/Items/types.d.ts +68 -34
  28. package/dist/components/Items/utils.d.ts +34 -16
  29. package/dist/components/Items/utils.js +135 -37
  30. package/dist/components/Lists/Droplist/DropList.d.ts +1 -1
  31. package/dist/components/Lists/Droplist/DropList.js +78 -50
  32. package/dist/components/Lists/List/List.d.ts +5 -9
  33. package/dist/components/Lists/List/List.js +61 -54
  34. package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +6 -20
  35. package/dist/components/Lists/ListPrivate/ListPrivate.js +9 -8
  36. package/dist/components/Lists/ListPrivate/styles.module.css +1 -0
  37. package/dist/components/Lists/contexts/CollapseProvider.d.ts +13 -1
  38. package/dist/components/Lists/contexts/CollapseProvider.js +2 -0
  39. package/dist/components/Lists/contexts/FocusListProvider.d.ts +9 -0
  40. package/dist/components/Lists/contexts/FocusListProvider.js +5 -0
  41. package/dist/components/Lists/contexts/NewListProvider.d.ts +32 -0
  42. package/dist/components/Lists/contexts/{ListProvider.js → NewListProvider.js} +10 -5
  43. package/dist/components/Lists/contexts/SelectionProvider.d.ts +25 -15
  44. package/dist/components/Lists/contexts/SelectionProvider.js +17 -13
  45. package/dist/components/Lists/contexts/index.d.ts +3 -2
  46. package/dist/components/Lists/contexts/index.js +3 -2
  47. package/dist/components/Lists/hooks.d.ts +10 -12
  48. package/dist/components/Lists/hooks.js +73 -40
  49. package/dist/components/Lists/index.d.ts +2 -1
  50. package/dist/components/Lists/index.js +1 -0
  51. package/dist/components/Lists/types.d.ts +12 -14
  52. package/dist/components/index.d.ts +2 -2
  53. package/dist/components/index.js +1 -1
  54. package/dist/helperComponents/ItemContent/ItemContent.d.ts +15 -0
  55. package/dist/helperComponents/ItemContent/ItemContent.js +23 -0
  56. package/dist/helperComponents/ItemContent/index.d.ts +1 -0
  57. package/dist/helperComponents/ItemContent/index.js +1 -0
  58. package/dist/helperComponents/ItemContent/styles.module.css +117 -0
  59. package/dist/helperComponents/ListEmptyState/ListEmptyState.d.ts +2 -2
  60. package/dist/helperComponents/ListEmptyState/ListEmptyState.js +13 -4
  61. package/dist/helperComponents/Separator/Separator.d.ts +8 -2
  62. package/dist/helperComponents/Separator/Separator.js +22 -2
  63. package/dist/helperComponents/Separator/constants.d.ts +2 -0
  64. package/dist/helperComponents/Separator/constants.js +5 -0
  65. package/dist/helperComponents/Separator/styles.module.css +20 -1
  66. package/dist/helperComponents/index.d.ts +1 -0
  67. package/dist/helperComponents/index.js +1 -0
  68. package/dist/index.d.ts +2 -2
  69. package/dist/index.js +2 -2
  70. package/dist/types.d.ts +1 -1
  71. package/package.json +13 -18
  72. package/src/components/Items/AccordionItem/AccordionItem.tsx +24 -20
  73. package/src/components/Items/BaseItem/BaseItem.tsx +91 -82
  74. package/src/components/Items/BaseItem/styles.module.scss +19 -77
  75. package/src/components/Items/GroupItem/GroupItem.tsx +5 -5
  76. package/src/components/Items/GroupSelectItem/GroupSelectItem.tsx +44 -0
  77. package/src/components/Items/GroupSelectItem/index.ts +1 -0
  78. package/src/components/Items/NextListItem/NextListItem.tsx +88 -85
  79. package/src/components/Items/NextListItem/constants.ts +12 -0
  80. package/src/components/Items/PinGroupItem/PinGroupItem.tsx +14 -4
  81. package/src/components/Items/PinGroupItem/styles.module.scss +14 -14
  82. package/src/components/Items/SearchItem/SearchItem.tsx +6 -8
  83. package/src/components/Items/hooks.tsx +86 -66
  84. package/src/components/Items/index.ts +1 -5
  85. package/src/components/Items/styles.module.scss +36 -25
  86. package/src/components/Items/types.ts +91 -42
  87. package/src/components/Items/utils.ts +199 -52
  88. package/src/components/Lists/Droplist/DropList.tsx +155 -98
  89. package/src/components/Lists/List/List.tsx +129 -104
  90. package/src/components/Lists/ListPrivate/ListPrivate.tsx +19 -20
  91. package/src/components/Lists/ListPrivate/styles.module.scss +1 -0
  92. package/src/components/Lists/contexts/CollapseProvider.tsx +22 -0
  93. package/src/components/Lists/contexts/FocusListProvider.tsx +15 -0
  94. package/src/components/Lists/contexts/NewListProvider.tsx +54 -0
  95. package/src/components/Lists/contexts/SelectionProvider.tsx +56 -39
  96. package/src/components/Lists/contexts/index.ts +3 -2
  97. package/src/components/Lists/hooks.ts +124 -75
  98. package/src/components/Lists/index.ts +2 -2
  99. package/src/components/Lists/types.ts +20 -24
  100. package/src/components/index.ts +17 -2
  101. package/src/helperComponents/ItemContent/ItemContent.tsx +63 -0
  102. package/src/helperComponents/ItemContent/index.ts +1 -0
  103. package/src/helperComponents/ItemContent/styles.module.scss +78 -0
  104. package/src/helperComponents/ListEmptyState/ListEmptyState.tsx +26 -12
  105. package/src/helperComponents/Separator/Separator.tsx +49 -3
  106. package/src/helperComponents/Separator/constants.ts +7 -0
  107. package/src/helperComponents/Separator/styles.module.scss +41 -1
  108. package/src/helperComponents/index.ts +1 -0
  109. package/src/index.ts +4 -2
  110. package/src/types.ts +1 -1
  111. package/dist/components/Items/NextListItem/hooks.d.ts +0 -19
  112. package/dist/components/Items/NextListItem/hooks.js +0 -80
  113. package/dist/components/Lists/contexts/ListProvider.d.ts +0 -17
  114. package/dist/components/Lists/contexts/ParentListProvider.d.ts +0 -18
  115. package/dist/components/Lists/contexts/ParentListProvider.js +0 -12
  116. package/dist/hooks.d.ts +0 -5
  117. package/dist/hooks.js +0 -15
  118. package/dist/utils.d.ts +0 -30
  119. package/dist/utils.js +0 -115
  120. package/src/components/Items/NextListItem/hooks.ts +0 -105
  121. package/src/components/Lists/contexts/CollapseProvider.ts +0 -9
  122. package/src/components/Lists/contexts/ListProvider.tsx +0 -28
  123. package/src/components/Lists/contexts/ParentListProvider.tsx +0 -35
  124. package/src/hooks.ts +0 -27
  125. package/src/utils.ts +0 -175
@@ -7,12 +7,12 @@ import { extractSupportProps } from '@snack-uikit/utils';
7
7
 
8
8
  import { ListEmptyState, useEmptyState } from '../../../helperComponents';
9
9
  import { PinBottomGroupItem, PinTopGroupItem, SearchItem, useRenderItems } from '../../Items';
10
- import { ListContextProvider } from '../contexts';
10
+ import { useNewListContext } from '../contexts';
11
11
  import commonStyles from '../styles.module.scss';
12
12
  import { ListPrivateProps } from '../types';
13
13
  import styles from './styles.module.scss';
14
14
 
15
- export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
15
+ export const ListPrivate = forwardRef(
16
16
  (
17
17
  {
18
18
  items,
@@ -26,30 +26,29 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
26
26
  scroll,
27
27
  nested,
28
28
  search,
29
+ searchItem,
29
30
  scrollRef,
30
31
  scrollContainerRef,
31
32
  footer,
32
33
  loading,
33
- size = 's',
34
- marker = true,
35
34
  limitedScrollHeight,
36
35
  className,
37
- parent = 'list',
38
36
  noDataState,
39
37
  noResultsState,
40
38
  errorDataState,
41
39
  dataError,
42
40
  dataFiltered,
43
41
  ...props
44
- },
45
- ref,
42
+ }: ListPrivateProps,
43
+ ref: ForwardedRef<HTMLElement>,
46
44
  ) => {
45
+ const { size = 's' } = useNewListContext();
46
+
47
47
  const itemsJSX = useRenderItems(items);
48
- const itemsPinTopJSX = useRenderItems(pinTop ?? []);
49
- const itemsPinBottomJSX = useRenderItems(pinBottom ?? []);
48
+ const itemsPinTopJSX = useRenderItems(pinTop);
49
+ const itemsPinBottomJSX = useRenderItems(pinBottom);
50
50
 
51
51
  const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
52
-
53
52
  const hasNoItems = items.length === 0;
54
53
 
55
54
  const loadingJSX = useMemo(
@@ -79,12 +78,12 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
79
78
  loading={loading}
80
79
  dataError={dataError}
81
80
  emptyStates={emptyStates}
82
- itemsLength={items.length}
81
+ hasNoItems={hasNoItems}
83
82
  dataFiltered={dataFiltered ?? Boolean(search?.value)}
84
83
  />
85
84
  </div>
86
85
  ),
87
- [dataError, dataFiltered, emptyStates, items.length, itemsJSX, loading, loadingJSX, search?.value],
86
+ [dataError, dataFiltered, emptyStates, hasNoItems, itemsJSX, loading, loadingJSX, search?.value],
88
87
  );
89
88
 
90
89
  const listJSX = (
@@ -93,7 +92,7 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
93
92
  ref={ref as ForwardedRef<HTMLUListElement>}
94
93
  onKeyDown={onKeyDown}
95
94
  tabIndex={tabIndex}
96
- onFocus={!nested ? onFocus : undefined}
95
+ onFocus={onFocus}
97
96
  onBlur={onBlur}
98
97
  data-active={active || undefined}
99
98
  role='menu'
@@ -101,7 +100,7 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
101
100
  >
102
101
  {(Number(pinTop?.length) > 0 || search) && (
103
102
  <PinTopGroupItem>
104
- {search && <SearchItem search={search} />}
103
+ {search && <SearchItem search={search} {...searchItem} />}
105
104
 
106
105
  {Number(pinTop?.length) > 0 && itemsPinTopJSX}
107
106
  </PinTopGroupItem>
@@ -128,16 +127,16 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
128
127
 
129
128
  {Number(pinBottom?.length) > 0 && <PinBottomGroupItem>{itemsPinBottomJSX}</PinBottomGroupItem>}
130
129
 
131
- {footer && <div className={styles.footer}>{footer}</div>}
130
+ {footer && (
131
+ <div className={styles.footer} onFocus={e => e.stopPropagation()}>
132
+ {footer}
133
+ </div>
134
+ )}
132
135
  </ul>
133
136
  );
134
137
 
135
138
  if (!nested) {
136
- return (
137
- <ListContextProvider size={size} marker={marker} parent={parent}>
138
- {listJSX}
139
- </ListContextProvider>
140
- );
139
+ return listJSX;
141
140
  }
142
141
 
143
142
  return (
@@ -47,5 +47,6 @@ $loader-height: (
47
47
  }
48
48
 
49
49
  .content {
50
+ overflow: hidden;
50
51
  flex-grow: 1;
51
52
  }
@@ -0,0 +1,22 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ import { ItemId } from '../../Items';
4
+
5
+ export type CollapseLevelContextType = {
6
+ level?: number;
7
+ };
8
+ export const CollapseLevelContext = createContext<CollapseLevelContextType>({});
9
+ export const useCollapseLevelContext = () => useContext(CollapseLevelContext);
10
+
11
+ export type CollapseContextType = {
12
+ openCollapseItems?: ItemId[];
13
+ toggleOpenCollapseItem?(id: ItemId): void;
14
+ };
15
+ export const CollapseContext = createContext<CollapseContextType>({});
16
+ export const useCollapseContext = () => useContext(CollapseContext);
17
+
18
+ export type CollapseState = {
19
+ value?: ItemId[];
20
+ onChange?(value?: ItemId[]): void;
21
+ defaultValue?: ItemId[];
22
+ };
@@ -0,0 +1,15 @@
1
+ import { createContext, KeyboardEvent, useContext } from 'react';
2
+
3
+ import { ItemId } from '../../Items';
4
+
5
+ export type FocusListContextType = {
6
+ activeItemId?: ItemId;
7
+ forceUpdateActiveItemId?(itemId: ItemId): void;
8
+ handleListKeyDownFactory: (ids: ItemId[], expandedIds: ItemId[]) => (e: KeyboardEvent<HTMLElement>) => void;
9
+ };
10
+
11
+ export const FocusListContext = createContext<FocusListContextType>({
12
+ handleListKeyDownFactory: () => () => {},
13
+ });
14
+
15
+ export const useFocusListContext = () => useContext(FocusListContext);
@@ -0,0 +1,54 @@
1
+ import { createContext, ReactNode, useContext } from 'react';
2
+
3
+ import { ItemContentProps } from '../../../helperComponents';
4
+ import { FlattenItem, FocusFlattenItem, ItemId } from '../../Items';
5
+
6
+ type ContentRenderProps = {
7
+ id?: ItemId;
8
+ content?: ItemContentProps | ReactNode;
9
+ };
10
+
11
+ export type PublicListContextType = {
12
+ /** Размер списка */
13
+ size?: 's' | 'm' | 'l';
14
+ /** Отображать ли маркер у выбранного жлемента списка */
15
+ marker?: boolean;
16
+ /**
17
+ * Рендер функция основного контента айтема
18
+ */
19
+ contentRender?(props: ContentRenderProps): ReactNode;
20
+ };
21
+
22
+ export type PrivateListContextType = {
23
+ flattenItems: Record<string, FlattenItem>;
24
+ focusFlattenItems: Record<string, FocusFlattenItem>;
25
+ };
26
+
27
+ type Child = {
28
+ children: ReactNode;
29
+ };
30
+
31
+ type ListContextType = PublicListContextType & PrivateListContextType;
32
+
33
+ export const ListContext = createContext({
34
+ flattenItems: {},
35
+ focusFlattenItems: {},
36
+ });
37
+
38
+ export function useNewListContext() {
39
+ return useContext<ListContextType>(ListContext);
40
+ }
41
+
42
+ function extractListProps<T extends ListContextType>({
43
+ size,
44
+ marker,
45
+ flattenItems,
46
+ focusFlattenItems,
47
+ contentRender,
48
+ }: T) {
49
+ return { size, marker, parent, contentRender, flattenItems, focusFlattenItems };
50
+ }
51
+
52
+ export function NewListContextProvider({ children, ...props }: ListContextType & Child) {
53
+ return <ListContext.Provider value={extractListProps(props)}>{children}</ListContext.Provider>;
54
+ }
@@ -1,23 +1,26 @@
1
1
  import { createContext, ReactNode, useCallback, useContext } from 'react';
2
- import { useUncontrolledProp } from 'uncontrollable';
3
2
 
4
- export type SelectionSingleValueType = string | number | undefined;
3
+ import { useValueControl } from '@snack-uikit/utils';
4
+
5
+ import { AnyType, ItemId } from '../../Items';
6
+
7
+ export type OnChangeHandler<T = AnyType> = (value: T) => void;
8
+
9
+ export type SelectionSingleValueType = ItemId;
5
10
 
6
11
  export type SelectionSingleState = {
7
12
  /** Начальное состояние */
8
- defaultValue?: SelectionSingleValueType;
13
+ defaultValue?: ItemId;
9
14
  /** Controlled состояние */
10
- value?: SelectionSingleValueType;
15
+ value?: ItemId;
11
16
  /** Controlled обработчик измения состояния */
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- onChange?(value: any): void;
17
+ onChange?: OnChangeHandler;
14
18
  /** Режим выбора */
15
19
  mode: 'single';
16
20
  };
17
21
 
18
22
  export type SelectionSingleProps = {
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- setValue?(value: any): void;
23
+ setValue?(value: AnyType): void;
21
24
  /** Режим выбора single */
22
25
  isSelectionSingle: true;
23
26
  /** Режим выбора multi */
@@ -26,19 +29,17 @@ export type SelectionSingleProps = {
26
29
 
27
30
  export type SelectionMultipleState = {
28
31
  /** Начальное состояние */
29
- defaultValue?: SelectionSingleValueType[];
32
+ defaultValue?: ItemId[];
30
33
  /** Controlled состояние */
31
- value?: SelectionSingleValueType[];
34
+ value?: ItemId[];
32
35
  /** Controlled обработчик измения состояния */
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- onChange?(value: any): void;
36
+ onChange?: OnChangeHandler;
35
37
  /** Режим выбора */
36
38
  mode: 'multiple';
37
39
  };
38
40
 
39
41
  export type SelectionMultipleProps = {
40
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
- setValue?(value: any): void;
42
+ setValue?(value: AnyType): void;
42
43
  /** Режим выбора single */
43
44
  isSelectionSingle: false;
44
45
  /** Режим выбора multi */
@@ -46,7 +47,7 @@ export type SelectionMultipleProps = {
46
47
  } & SelectionMultipleState;
47
48
 
48
49
  type SelectionNoneProps = {
49
- mode?: undefined;
50
+ mode?: 'none';
50
51
  value?: undefined;
51
52
  onChange?: undefined;
52
53
  setValue?: undefined;
@@ -55,14 +56,14 @@ type SelectionNoneProps = {
55
56
  isSelectionMultiple?: undefined;
56
57
  };
57
58
 
58
- type SelectionProviderProps = SelectionSingleProps | SelectionMultipleProps | SelectionNoneProps;
59
-
60
59
  type SelectionContextType =
61
60
  | Omit<SelectionNoneProps, 'defaultValue'>
62
61
  | Omit<SelectionSingleProps, 'defaultValue'>
63
62
  | Omit<SelectionMultipleProps, 'defaultValue'>;
64
63
 
65
- export type SelectionState = { selection?: SelectionSingleState | SelectionMultipleState };
64
+ export type SelectionState = {
65
+ selection?: SelectionSingleState | SelectionMultipleState;
66
+ };
66
67
 
67
68
  export const SelectionContext = createContext<SelectionContextType>({
68
69
  value: undefined,
@@ -70,13 +71,10 @@ export const SelectionContext = createContext<SelectionContextType>({
70
71
  mode: undefined,
71
72
  });
72
73
 
73
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
- export function isSelectionMultipleProps(props: any): props is SelectionMultipleProps {
74
+ export function isSelectionMultipleProps(props: AnyType): props is SelectionMultipleProps {
75
75
  return 'mode' in props && props['mode'] === 'multiple';
76
76
  }
77
-
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- export function isSelectionSingleProps(props: any): props is SelectionSingleProps {
77
+ export function isSelectionSingleProps(props: AnyType): props is SelectionSingleProps {
80
78
  return 'mode' in props && props['mode'] === 'single';
81
79
  }
82
80
 
@@ -84,20 +82,32 @@ type Child = {
84
82
  children: ReactNode;
85
83
  };
86
84
 
85
+ function SelectionNoneProvider({ children }: SelectionNoneProps & Child) {
86
+ return (
87
+ <SelectionContext.Provider
88
+ value={{
89
+ mode: 'none',
90
+ }}
91
+ >
92
+ {children}
93
+ </SelectionContext.Provider>
94
+ );
95
+ }
96
+
87
97
  function SelectionSingleProvider({
88
98
  value: valueProp,
89
99
  defaultValue,
90
100
  onChange: onChangeProp,
91
101
  children,
92
102
  }: SelectionSingleProps & Child) {
93
- const [value, setValue] = useUncontrolledProp<SelectionSingleValueType>(valueProp, defaultValue, newValue => {
94
- const result = typeof newValue === 'function' ? newValue(value) : newValue;
95
-
96
- onChangeProp?.(result);
103
+ const [value, setValue] = useValueControl<ItemId | undefined>({
104
+ value: valueProp,
105
+ defaultValue,
106
+ onChange: onChangeProp,
97
107
  });
98
108
 
99
109
  const onChange = useCallback(
100
- (newValue: SelectionSingleValueType) =>
110
+ (newValue: SelectionSingleValueType | undefined) =>
101
111
  setValue((oldValue: SelectionSingleValueType) => {
102
112
  if (newValue !== oldValue) {
103
113
  return newValue;
@@ -130,10 +140,10 @@ function SelectionMultipleProvider({
130
140
  onChange: onChangeProp,
131
141
  children,
132
142
  }: SelectionMultipleProps & Child) {
133
- const [value, setValue] = useUncontrolledProp<SelectionSingleValueType[]>(valueProp, defaultValue, newValue => {
134
- const result = typeof newValue === 'function' ? newValue(value) : newValue;
135
-
136
- onChangeProp?.(result);
143
+ const [value, setValue] = useValueControl<ItemId[] | undefined>({
144
+ value: valueProp,
145
+ defaultValue,
146
+ onChange: onChangeProp,
137
147
  });
138
148
 
139
149
  const onChange = useCallback(
@@ -173,6 +183,17 @@ function SelectionMultipleProvider({
173
183
  );
174
184
  }
175
185
 
186
+ type SelectionProviderProps = {
187
+ /** Начальное состояние */
188
+ defaultValue?: AnyType;
189
+ /** Controlled состояние */
190
+ value?: AnyType;
191
+ /** Controlled обработчик измения состояния */
192
+ onChange?: OnChangeHandler;
193
+ /** Режим выбора */
194
+ mode?: 'multiple' | 'single' | 'none';
195
+ };
196
+
176
197
  export function SelectionProvider({ children, ...props }: SelectionProviderProps & Child) {
177
198
  if (isSelectionSingleProps(props)) {
178
199
  return <SelectionSingleProvider {...props}>{children}</SelectionSingleProvider>;
@@ -182,13 +203,9 @@ export function SelectionProvider({ children, ...props }: SelectionProviderProps
182
203
  return <SelectionMultipleProvider {...props}>{children}</SelectionMultipleProvider>;
183
204
  }
184
205
 
185
- return <SelectionContext.Provider value={{}}>{children}</SelectionContext.Provider>;
206
+ return <SelectionNoneProvider>{children}</SelectionNoneProvider>;
186
207
  }
187
208
 
188
- export const useSelectionContext = () => useContext(SelectionContext);
189
-
190
- export function extractSelectionProps<T extends SelectionState>({ selection }: T) {
191
- return {
192
- ...(selection ?? {}),
193
- } as SelectionProviderProps;
209
+ export function useSelectionContext() {
210
+ return useContext(SelectionContext);
194
211
  }
@@ -1,4 +1,5 @@
1
- export * from './ListProvider';
2
1
  export * from './SelectionProvider';
3
- export * from './ParentListProvider';
4
2
  export * from './CollapseProvider';
3
+ export * from './NewListProvider';
4
+ export * from './SelectionProvider';
5
+ export * from './FocusListProvider';
@@ -1,99 +1,148 @@
1
- import { KeyboardEvent, RefObject, useEffect, useState } from 'react';
1
+ import { KeyboardEvent, RefObject, useCallback, useRef, useState } from 'react';
2
2
 
3
- export type UseKeyboardNavigationProps<T extends HTMLElement> = {
4
- ids: Array<number | string>;
5
- expandedIds: Array<number | string>;
6
- parentRef?: RefObject<T>;
3
+ import { FocusFlattenItem, ItemId } from '../Items';
4
+
5
+ type UseNewKeyboardNavigationProps<T extends HTMLElement> = {
6
+ mainRef?: RefObject<T>;
7
7
  btnRef?: RefObject<HTMLButtonElement>;
8
- itemRefs?: RefObject<HTMLElement>[];
8
+ focusFlattenItems: Record<string, FocusFlattenItem>;
9
9
  };
10
10
 
11
- export function useKeyboardNavigation<T extends HTMLElement>({
12
- ids,
13
- itemRefs,
14
- expandedIds,
15
- parentRef,
11
+ export function useNewKeyboardNavigation<T extends HTMLElement>({
12
+ mainRef,
16
13
  btnRef,
17
- }: UseKeyboardNavigationProps<T>) {
18
- const [activeFocusIndex, setActiveFocusIndex] = useState<number>(-1);
19
- const [openNestedIndex, setOpenNestedIndex] = useState<number>(-1);
20
-
21
- const handleListKeyDown = (e: KeyboardEvent<T>) => {
22
- switch (e.key) {
23
- case 'ArrowDown': {
24
- setActiveFocusIndex(activeIndex => Math.min(activeIndex + 1, ids.length - 1));
25
-
26
- // TODO: go to loader from last item
27
- // if (activeFocusIndex === ids.length - 2) {
28
- // return;
29
- // }
30
-
31
- e.stopPropagation();
32
- e.preventDefault();
33
- return;
34
- }
35
- case 'ArrowUp': {
36
- if (activeFocusIndex === 0) {
37
- parentRef?.current?.focus();
38
- setActiveFocusIndex(-1);
14
+ focusFlattenItems,
15
+ }: UseNewKeyboardNavigationProps<T>) {
16
+ const [activeItemId, setActiveItemId] = useState<ItemId | undefined>();
17
+
18
+ const activeItemIdRef = useRef<ItemId | undefined>();
19
+
20
+ const handleListKeyDownFactory = useCallback(
21
+ (ids: ItemId[], expandedIds: ItemId[]) => (e: KeyboardEvent<T>) => {
22
+ switch (e.key) {
23
+ case 'ArrowDown': {
24
+ if (activeItemIdRef.current !== undefined) {
25
+ const activeIndex = ids.findIndex(id => id === activeItemIdRef.current);
26
+
27
+ const nextId = Math.min(activeIndex + 1, ids.length - 1);
28
+ const itemId = ids[nextId];
29
+ const item = focusFlattenItems[itemId];
30
+
31
+ activeItemIdRef.current = itemId;
32
+ setActiveItemId(itemId);
33
+
34
+ if (item.type !== 'group') {
35
+ item.itemRef?.current?.focus();
36
+ }
37
+ } else {
38
+ const itemId = ids[0];
39
+ const item = focusFlattenItems[itemId];
40
+
41
+ activeItemIdRef.current = itemId;
42
+ setActiveItemId(itemId);
43
+
44
+ if (item.type !== 'group') {
45
+ item.itemRef?.current?.focus();
46
+ }
47
+ }
48
+
49
+ e.stopPropagation();
50
+ e.preventDefault();
51
+ return;
52
+ }
53
+ case 'ArrowUp': {
54
+ if (ids[0] === activeItemIdRef.current) {
55
+ const item = focusFlattenItems[ids[0]];
56
+
57
+ if (item.parentId === '~main') {
58
+ activeItemIdRef.current = undefined;
59
+ setActiveItemId(undefined);
60
+ mainRef?.current?.focus();
61
+ }
62
+ return;
63
+ }
64
+
65
+ if (activeItemIdRef.current !== undefined) {
66
+ const activeIndex = ids.findIndex(id => id === activeItemIdRef.current);
67
+ const nextId = Math.max(activeIndex - 1, 0);
68
+ const itemId = ids[nextId];
69
+ const item = focusFlattenItems[itemId];
70
+
71
+ activeItemIdRef.current = itemId;
72
+ setActiveItemId(itemId);
73
+
74
+ if (item.type !== 'group') {
75
+ item.itemRef?.current?.focus();
76
+ }
77
+ }
78
+
79
+ e.stopPropagation();
80
+ e.preventDefault();
39
81
  return;
40
82
  }
41
83
 
42
- setActiveFocusIndex(activeIndex => Math.max(activeIndex - 1, 0));
84
+ case 'ArrowRight': {
85
+ if (activeItemIdRef.current !== undefined && expandedIds.includes(activeItemIdRef.current)) {
86
+ const item = focusFlattenItems[activeItemIdRef.current];
43
87
 
44
- e.stopPropagation();
45
- e.preventDefault();
46
- return;
47
- }
48
- case 'ArrowRight': {
49
- if (expandedIds.includes(ids[activeFocusIndex])) {
50
- setOpenNestedIndex(activeFocusIndex);
51
- }
88
+ const newItemId = item.items[0];
89
+ const newItem = focusFlattenItems[newItemId];
52
90
 
53
- e.stopPropagation();
54
- e.preventDefault();
91
+ activeItemIdRef.current = newItemId;
92
+ setActiveItemId(newItemId);
55
93
 
56
- return;
57
- }
58
- case 'Tab': {
59
- if (parentRef && activeFocusIndex !== -1) {
94
+ setTimeout(() => newItem.itemRef?.current?.focus(), 0);
95
+ }
96
+
97
+ e.stopPropagation();
60
98
  e.preventDefault();
61
99
 
62
- parentRef?.current?.focus();
63
- } else if (activeFocusIndex === -1) {
64
- btnRef && !e.shiftKey ? btnRef?.current?.focus() : parentRef?.current?.focus();
100
+ return;
65
101
  }
66
102
 
67
- setOpenNestedIndex(-1);
68
- setActiveFocusIndex(-1);
103
+ case 'Tab': {
104
+ if (activeItemIdRef.current !== undefined) {
105
+ e.preventDefault();
106
+ e.stopPropagation();
69
107
 
70
- return;
71
- }
72
- default: {
73
- return;
108
+ activeItemIdRef.current = undefined;
109
+ setActiveItemId(undefined);
110
+ mainRef?.current?.focus();
111
+ } else {
112
+ btnRef && !e.shiftKey ? btnRef?.current?.focus() : mainRef?.current?.focus();
113
+ }
114
+
115
+ return;
116
+ }
117
+ default: {
118
+ return;
119
+ }
74
120
  }
75
- }
76
- };
121
+ },
122
+ [focusFlattenItems, mainRef, btnRef],
123
+ );
77
124
 
78
- const resetNestedIndex = () => {
79
- setActiveFocusIndex(openNestedIndex);
80
- setOpenNestedIndex(-1);
81
- };
125
+ const resetActiveItemId = useCallback(() => {
126
+ setActiveItemId(undefined);
127
+ activeItemIdRef.current = undefined;
128
+ }, []);
82
129
 
83
- const resetActiveFocusIndex = () => {
84
- setActiveFocusIndex(-1);
85
- };
130
+ const forceUpdateActiveItemId = useCallback(
131
+ (itemId: ItemId) => {
132
+ setActiveItemId(itemId);
133
+ activeItemIdRef.current = itemId;
134
+
135
+ const item = focusFlattenItems[itemId];
86
136
 
87
- useEffect(() => {
88
- activeFocusIndex !== -1 && openNestedIndex === -1 && itemRefs?.[activeFocusIndex]?.current?.focus();
89
- }, [activeFocusIndex, itemRefs, openNestedIndex]);
137
+ item?.itemRef?.current?.focus();
138
+ },
139
+ [focusFlattenItems],
140
+ );
90
141
 
91
142
  return {
92
- activeFocusIndex,
93
- setActiveFocusIndex,
94
- openNestedIndex,
95
- resetNestedIndex,
96
- resetActiveFocusIndex,
97
- handleListKeyDown,
143
+ resetActiveItemId,
144
+ activeItemId,
145
+ forceUpdateActiveItemId,
146
+ handleListKeyDownFactory,
98
147
  };
99
148
  }
@@ -7,7 +7,7 @@ export type {
7
7
  SelectionMultipleState,
8
8
  SelectionSingleProps,
9
9
  SelectionMultipleProps,
10
- isSelectionSingleProps,
11
- isSelectionMultipleProps,
12
10
  SelectionSingleValueType,
13
11
  } from './contexts';
12
+
13
+ export { useSelectionContext, useCollapseContext, isSelectionSingleProps, isSelectionMultipleProps } from './contexts';