@snack-uikit/list 0.10.0 → 0.10.1-preview-69abc1d3.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.
Files changed (127) hide show
  1. package/README.md +56 -24
  2. package/dist/components/Items/AccordionItem/AccordionItem.d.ts +4 -2
  3. package/dist/components/Items/AccordionItem/AccordionItem.js +18 -16
  4. package/dist/components/Items/BaseItem/BaseItem.d.ts +8 -4
  5. package/dist/components/Items/BaseItem/BaseItem.js +20 -14
  6. package/dist/components/Items/BaseItem/styles.module.css +9 -122
  7. package/dist/components/Items/GroupItem/GroupItem.d.ts +5 -1
  8. package/dist/components/Items/GroupItem/GroupItem.js +1 -3
  9. package/dist/components/Items/GroupSelectItem/GroupSelectItem.d.ts +4 -0
  10. package/dist/components/Items/GroupSelectItem/GroupSelectItem.js +18 -0
  11. package/dist/components/Items/GroupSelectItem/index.d.ts +1 -0
  12. package/dist/components/Items/GroupSelectItem/index.js +1 -0
  13. package/dist/components/Items/NextListItem/NextListItem.d.ts +6 -2
  14. package/dist/components/Items/NextListItem/NextListItem.js +53 -45
  15. package/dist/components/Items/NextListItem/constants.d.ts +2 -0
  16. package/dist/components/Items/NextListItem/constants.js +10 -0
  17. package/dist/components/Items/SearchItem/SearchItem.d.ts +3 -1
  18. package/dist/components/Items/SearchItem/SearchItem.js +5 -8
  19. package/dist/components/Items/hooks.d.ts +11 -10
  20. package/dist/components/Items/hooks.js +64 -46
  21. package/dist/components/Items/index.d.ts +1 -5
  22. package/dist/components/Items/index.js +1 -5
  23. package/dist/components/Items/types.d.ts +68 -34
  24. package/dist/components/Items/utils.d.ts +33 -16
  25. package/dist/components/Items/utils.js +132 -37
  26. package/dist/components/Lists/Droplist/DropList.d.ts +1 -1
  27. package/dist/components/Lists/Droplist/DropList.js +73 -50
  28. package/dist/components/Lists/List/List.d.ts +5 -9
  29. package/dist/components/Lists/List/List.js +56 -54
  30. package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +6 -20
  31. package/dist/components/Lists/ListPrivate/ListPrivate.js +9 -8
  32. package/dist/components/Lists/ListPrivate/styles.module.css +1 -0
  33. package/dist/components/Lists/contexts/CollapseProvider.d.ts +13 -1
  34. package/dist/components/Lists/contexts/CollapseProvider.js +2 -0
  35. package/dist/components/Lists/contexts/FocusListProvider.d.ts +9 -0
  36. package/dist/components/Lists/contexts/FocusListProvider.js +5 -0
  37. package/dist/components/Lists/contexts/NewListProvider.d.ts +32 -0
  38. package/dist/components/Lists/contexts/{ListProvider.js → NewListProvider.js} +10 -5
  39. package/dist/components/Lists/contexts/SelectionProvider.d.ts +25 -15
  40. package/dist/components/Lists/contexts/SelectionProvider.js +17 -13
  41. package/dist/components/Lists/contexts/index.d.ts +2 -2
  42. package/dist/components/Lists/contexts/index.js +2 -2
  43. package/dist/components/Lists/hooks.d.ts +10 -12
  44. package/dist/components/Lists/hooks.js +73 -40
  45. package/dist/components/Lists/types.d.ts +12 -14
  46. package/dist/components/index.d.ts +2 -2
  47. package/dist/components/index.js +1 -1
  48. package/dist/helperComponents/ItemContent/ItemContent.d.ts +15 -0
  49. package/dist/helperComponents/ItemContent/ItemContent.js +23 -0
  50. package/dist/helperComponents/ItemContent/index.d.ts +1 -0
  51. package/dist/helperComponents/ItemContent/index.js +1 -0
  52. package/dist/helperComponents/ItemContent/styles.module.css +117 -0
  53. package/dist/helperComponents/ListEmptyState/ListEmptyState.d.ts +2 -2
  54. package/dist/helperComponents/ListEmptyState/ListEmptyState.js +13 -4
  55. package/dist/helperComponents/Separator/Separator.d.ts +8 -2
  56. package/dist/helperComponents/Separator/Separator.js +22 -2
  57. package/dist/helperComponents/Separator/constants.d.ts +2 -0
  58. package/dist/helperComponents/Separator/constants.js +5 -0
  59. package/dist/helperComponents/Separator/styles.module.css +20 -1
  60. package/dist/helperComponents/index.d.ts +1 -0
  61. package/dist/helperComponents/index.js +1 -0
  62. package/dist/hooks.d.ts +1 -1
  63. package/dist/hooks.js +15 -10
  64. package/dist/index.d.ts +3 -2
  65. package/dist/index.js +3 -2
  66. package/dist/legacy/components/Items/hooks.d.ts +12 -0
  67. package/dist/legacy/components/Items/hooks.js +33 -0
  68. package/dist/legacy/components/index.d.ts +1 -0
  69. package/dist/legacy/components/index.js +1 -0
  70. package/dist/legacy/hooks.d.ts +5 -0
  71. package/dist/legacy/hooks.js +15 -0
  72. package/dist/legacy/index.d.ts +3 -0
  73. package/dist/legacy/index.js +3 -0
  74. package/dist/{utils.d.ts → legacy/utils.d.ts} +2 -3
  75. package/dist/{utils.js → legacy/utils.js} +4 -12
  76. package/dist/types.d.ts +1 -1
  77. package/package.json +14 -14
  78. package/src/components/Items/AccordionItem/AccordionItem.tsx +24 -20
  79. package/src/components/Items/BaseItem/BaseItem.tsx +48 -43
  80. package/src/components/Items/BaseItem/styles.module.scss +8 -70
  81. package/src/components/Items/GroupItem/GroupItem.tsx +5 -5
  82. package/src/components/Items/GroupSelectItem/GroupSelectItem.tsx +44 -0
  83. package/src/components/Items/GroupSelectItem/index.ts +1 -0
  84. package/src/components/Items/NextListItem/NextListItem.tsx +86 -85
  85. package/src/components/Items/NextListItem/constants.ts +12 -0
  86. package/src/components/Items/SearchItem/SearchItem.tsx +6 -8
  87. package/src/components/Items/hooks.tsx +86 -66
  88. package/src/components/Items/index.ts +1 -5
  89. package/src/components/Items/types.ts +91 -42
  90. package/src/components/Items/utils.ts +193 -52
  91. package/src/components/Lists/Droplist/DropList.tsx +151 -98
  92. package/src/components/Lists/List/List.tsx +124 -104
  93. package/src/components/Lists/ListPrivate/ListPrivate.tsx +19 -20
  94. package/src/components/Lists/ListPrivate/styles.module.scss +1 -0
  95. package/src/components/Lists/contexts/CollapseProvider.tsx +22 -0
  96. package/src/components/Lists/contexts/FocusListProvider.tsx +16 -0
  97. package/src/components/Lists/contexts/NewListProvider.tsx +54 -0
  98. package/src/components/Lists/contexts/SelectionProvider.tsx +56 -39
  99. package/src/components/Lists/contexts/index.ts +3 -2
  100. package/src/components/Lists/hooks.ts +124 -75
  101. package/src/components/Lists/types.ts +20 -24
  102. package/src/components/index.ts +16 -2
  103. package/src/helperComponents/ItemContent/ItemContent.tsx +63 -0
  104. package/src/helperComponents/ItemContent/index.ts +1 -0
  105. package/src/helperComponents/ItemContent/styles.module.scss +78 -0
  106. package/src/helperComponents/ListEmptyState/ListEmptyState.tsx +26 -12
  107. package/src/helperComponents/Separator/Separator.tsx +49 -3
  108. package/src/helperComponents/Separator/constants.ts +7 -0
  109. package/src/helperComponents/Separator/styles.module.scss +41 -1
  110. package/src/helperComponents/index.ts +1 -0
  111. package/src/hooks.ts +21 -15
  112. package/src/index.ts +5 -2
  113. package/src/legacy/components/Items/hooks.tsx +53 -0
  114. package/src/legacy/components/index.ts +1 -0
  115. package/src/legacy/hooks.ts +27 -0
  116. package/src/legacy/index.ts +3 -0
  117. package/src/{utils.ts → legacy/utils.ts} +4 -13
  118. package/src/types.ts +1 -1
  119. package/dist/components/Items/NextListItem/hooks.d.ts +0 -19
  120. package/dist/components/Items/NextListItem/hooks.js +0 -80
  121. package/dist/components/Lists/contexts/ListProvider.d.ts +0 -17
  122. package/dist/components/Lists/contexts/ParentListProvider.d.ts +0 -18
  123. package/dist/components/Lists/contexts/ParentListProvider.js +0 -12
  124. package/src/components/Items/NextListItem/hooks.ts +0 -105
  125. package/src/components/Lists/contexts/CollapseProvider.ts +0 -9
  126. package/src/components/Lists/contexts/ListProvider.tsx +0 -28
  127. package/src/components/Lists/contexts/ParentListProvider.tsx +0 -35
@@ -1,20 +1,23 @@
1
+ import cn from 'classnames';
1
2
  import { cloneElement, isValidElement, KeyboardEvent, ReactNode, useCallback, useMemo, useRef } from 'react';
2
- import { useUncontrolledProp } from 'uncontrollable';
3
3
 
4
- import { Dropdown } from '@snack-uikit/dropdown';
4
+ import { Dropdown, DropdownProps } from '@snack-uikit/dropdown';
5
+ import { useValueControl } from '@snack-uikit/utils';
5
6
 
6
- import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
7
- import { addItemsIds, getSlicedItems, useItemsWithIds } from '../../Items';
8
- import { extractSelectionProps, ParentListContext, SelectionProvider } from '../contexts';
9
- import { useKeyboardNavigation } from '../hooks';
7
+ import { extractActiveItems, ItemId, kindFlattenItems, useCreateBaseItems } from '../../Items';
8
+ import { CollapseContext, FocusListContext, NewListContextProvider, SelectionProvider } from '../contexts';
9
+ import { useNewKeyboardNavigation } from '../hooks';
10
10
  import { ListPrivate } from '../ListPrivate';
11
+ import styles from '../styles.module.scss';
11
12
  import { DroplistProps } from '../types';
12
13
 
14
+ const DEFAULT_FALLBACK_PLACEMENTS: DropdownProps['fallbackPlacements'] = ['top', 'right', 'bottom', 'left'];
15
+
13
16
  export function Droplist({
14
17
  items: itemsProp,
15
18
  search,
16
- pinBottom,
17
- pinTop,
19
+ pinBottom: pinBottomProp = [],
20
+ pinTop: pinTopProp = [],
18
21
  footerActiveElementsRefs,
19
22
  children,
20
23
  trigger,
@@ -24,65 +27,98 @@ export function Droplist({
24
27
  open: openProp,
25
28
  onOpenChange,
26
29
  collapse = {},
30
+ triggerClassName,
31
+ selection,
32
+ contentRender,
33
+ size = 's',
34
+ marker = true,
35
+ className,
27
36
  ...props
28
37
  }: DroplistProps) {
29
38
  const hasSearch = useMemo(() => Boolean(search), [search]);
30
39
 
31
- const memorizedItems = useMemo(
32
- () => addItemsIds((pinTop ?? []).concat(itemsProp).concat(pinBottom ?? [])),
33
- [itemsProp, pinBottom, pinTop],
34
- );
35
-
36
- const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp<Array<number | string>>(
37
- collapse.value,
38
- collapse.defaultValue ?? [],
39
- collapse.onChange,
40
+ const [openCollapseItems = [], setOpenCollapsedItems] = useValueControl<ItemId[] | undefined>(collapse);
41
+ const toggleOpenCollapseItem = useCallback(
42
+ (id: ItemId) =>
43
+ setOpenCollapsedItems((items: ItemId[]) =>
44
+ items?.includes(id) ? items.filter(item => item !== id) : (items ?? []).concat([id]),
45
+ ),
46
+ [setOpenCollapsedItems],
40
47
  );
41
48
 
42
- const { search: searchItem, footerRefs } = useItemsWithIds({
43
- search: hasSearch,
44
- footerActiveElementsRefs,
49
+ const [open = false, setOpen] = useValueControl<boolean>({
50
+ value: openProp,
51
+ defaultValue: false,
52
+ onChange: onOpenChange,
45
53
  });
46
54
 
47
- const { items, itemRefs, ids, expandedIds } = useMemo(() => {
48
- const res = withCollapsedItems({
49
- items: memorizedItems,
50
- openCollapsedItems,
55
+ const { searchItem, footerItems } = useCreateBaseItems({ footerActiveElementsRefs });
56
+ /**
57
+ * Объект с пропсами всех вложенных айтемов; ключ id
58
+ */
59
+ const { flattenItems, focusFlattenItems, ...memorizedItems } = useMemo(() => {
60
+ const pinTop = kindFlattenItems({ items: pinTopProp, prefix: '~pinTop', parentId: '~main' });
61
+ const items = kindFlattenItems({ items: itemsProp, prefix: '~main', parentId: '~main' });
62
+ const pinBottom = kindFlattenItems({ items: pinBottomProp, prefix: '~pinBottom', parentId: '~main' });
63
+
64
+ const flattenItems = { ...pinTop.flattenItems, ...pinBottom.flattenItems, ...items.flattenItems };
65
+ const focusFlattenItems = {
66
+ ...pinTop.focusFlattenItems,
67
+ ...pinBottom.focusFlattenItems,
68
+ ...items.focusFlattenItems,
69
+ };
70
+
71
+ [...footerItems, searchItem].forEach(item => {
72
+ flattenItems[item.id] = item;
73
+ focusFlattenItems[item.id] = { ...item, originalId: item.id, items: [], key: item.id, allChildIds: [] };
74
+ });
75
+
76
+ return { items, pinTop, pinBottom, flattenItems, focusFlattenItems };
77
+ }, [itemsProp, pinTopProp, pinBottomProp, searchItem, footerItems]);
78
+
79
+ const { ids, expandedIds } = useMemo(() => {
80
+ const { pinTop, items, pinBottom } = memorizedItems;
81
+
82
+ let ids: ItemId[] = [];
83
+ let expandedIds: ItemId[] = [];
84
+
85
+ if (hasSearch) {
86
+ ids.push(searchItem.id);
87
+ }
88
+
89
+ [pinTop, items, pinBottom].forEach(({ focusFlattenItems, focusCloseChildIds }) => {
90
+ const activeItems = extractActiveItems({ focusFlattenItems, focusCloseChildIds, openCollapseItems });
91
+
92
+ ids = ids.concat(activeItems.ids);
93
+ expandedIds = expandedIds.concat(activeItems.expandedIds);
51
94
  });
52
95
 
53
- const items = searchItem.concat(res.items).concat(footerRefs);
96
+ footerItems.forEach(footerItem => {
97
+ ids.push(footerItem.id);
98
+ });
54
99
 
55
100
  return {
56
- items,
57
- ids: extractItemIds(searchItem).concat(res.ids).concat(extractItemIds(footerRefs)),
58
- itemRefs: extractItemRefs(searchItem).concat(res.itemRefs).concat(extractItemRefs(footerRefs)),
59
- expandedIds: res.expandedIds,
101
+ ids,
102
+ expandedIds,
60
103
  };
61
- }, [footerRefs, memorizedItems, openCollapsedItems, searchItem]);
62
-
63
- const slicedItems = useMemo(
64
- () => getSlicedItems({ items, hasSearch, pinTop, pinBottom, footerRefs }),
65
- [items, hasSearch, pinTop, pinBottom, footerRefs],
66
- );
104
+ }, [footerItems, hasSearch, memorizedItems, openCollapseItems, searchItem.id]);
67
105
 
68
106
  const triggerElemRef = useRef<HTMLElement>(null);
69
- const listRef = useRef<HTMLUListElement>(null);
107
+ const listRef = useRef<HTMLElement>(null);
70
108
 
71
- const {
72
- activeFocusIndex,
73
- setActiveFocusIndex,
74
- openNestedIndex,
75
- handleListKeyDown,
76
- resetNestedIndex,
77
- resetActiveFocusIndex,
78
- } = useKeyboardNavigation({ ids, expandedIds, parentRef: triggerElemRefProp ?? triggerElemRef, itemRefs });
109
+ const { handleListKeyDownFactory, resetActiveItemId, activeItemId, forceUpdateActiveItemId } =
110
+ useNewKeyboardNavigation({
111
+ mainRef: triggerElemRefProp ?? triggerElemRef,
112
+ focusFlattenItems,
113
+ });
79
114
 
80
- const [open, setOpen] = useUncontrolledProp<boolean>(openProp, false, onOpenChange);
115
+ const handleListKeyDown = useCallback(
116
+ (e: KeyboardEvent<HTMLElement>) => handleListKeyDownFactory(ids, expandedIds)(e),
117
+ [handleListKeyDownFactory, ids, expandedIds],
118
+ );
81
119
 
82
120
  const handleOpenChange = (open: boolean) => {
83
- resetNestedIndex();
84
- resetActiveFocusIndex();
85
- setOpenCollapsedItems([]);
121
+ resetActiveItemId();
86
122
 
87
123
  setOpen(open);
88
124
  };
@@ -94,14 +130,18 @@ export function Droplist({
94
130
  setOpen(true);
95
131
 
96
132
  setTimeout(() => {
133
+ resetActiveItemId();
97
134
  listRef.current?.focus();
98
- setActiveFocusIndex(0);
99
135
  }, 0);
100
136
  }
101
137
 
138
+ if (e.key === 'ArrowUp') {
139
+ setOpen(false);
140
+ }
141
+
102
142
  cb?.(e);
103
143
  },
104
- [setActiveFocusIndex, setOpen],
144
+ [resetActiveItemId, setOpen],
105
145
  );
106
146
 
107
147
  const triggerElem: ReactNode = useMemo(() => {
@@ -124,54 +164,67 @@ export function Droplist({
124
164
  }, [onKeyDown, children]);
125
165
 
126
166
  return (
127
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
128
- // @ts-ignore
129
- <SelectionProvider {...extractSelectionProps(props)}>
130
- <ParentListContext.Provider
131
- value={{
132
- parentIds: ids,
133
- parentActiveFocusIndex: activeFocusIndex,
134
- parentExpandedIds: expandedIds,
135
- parentItemRefs: itemRefs,
136
- parentOpenNestedIndex: openNestedIndex,
137
- triggerRef: triggerElemRefProp ?? triggerElemRef,
138
- parentRef: listRef,
139
- parentResetNestedIndex: resetNestedIndex,
140
- parentResetActiveFocusIndex: resetActiveFocusIndex,
141
- openCollapsedItems,
142
- toggleOpenCollapsedItems: id =>
143
- setOpenCollapsedItems((items: Array<string | number> | undefined = []) =>
144
- items.includes(id) ? items.filter(item => item !== id) : items?.concat([id]),
145
- ),
146
- }}
147
- >
148
- <Dropdown
149
- content={
150
- <ListPrivate
151
- {...slicedItems}
152
- {...props}
153
- onKeyDown={handleListKeyDown}
154
- tabIndex={0}
155
- ref={listRef}
156
- search={search}
157
- limitedScrollHeight
158
- parent='droplist'
159
- />
160
- }
161
- trigger={trigger}
162
- placement={placement}
163
- widthStrategy={widthStrategy}
164
- {...(triggerElemRefProp
165
- ? {}
166
- : {
167
- triggerRef: triggerElemRef,
168
- })}
169
- open={open}
170
- onOpenChange={handleOpenChange}
167
+ <NewListContextProvider
168
+ flattenItems={flattenItems}
169
+ focusFlattenItems={focusFlattenItems}
170
+ contentRender={contentRender}
171
+ size={size}
172
+ marker={marker}
173
+ >
174
+ <SelectionProvider {...selection}>
175
+ <CollapseContext.Provider
176
+ value={{
177
+ openCollapseItems,
178
+ toggleOpenCollapseItem,
179
+ }}
171
180
  >
172
- {triggerElem}
173
- </Dropdown>
174
- </ParentListContext.Provider>
175
- </SelectionProvider>
181
+ <FocusListContext.Provider
182
+ value={{
183
+ activeItemId,
184
+ handleListKeyDownFactory,
185
+ forceUpdateActiveItemId,
186
+ }}
187
+ >
188
+ <Dropdown
189
+ content={
190
+ <div className={cn(styles.wrapper, className)}>
191
+ <ListPrivate
192
+ {...props}
193
+ items={memorizedItems.items.focusCloseChildIds}
194
+ pinTop={memorizedItems.pinTop.focusCloseChildIds}
195
+ pinBottom={memorizedItems.pinBottom.focusCloseChildIds}
196
+ onKeyDown={handleListKeyDown}
197
+ searchItem={searchItem}
198
+ tabIndex={0}
199
+ ref={listRef}
200
+ search={search}
201
+ onFocus={e => {
202
+ e.stopPropagation();
203
+
204
+ forceUpdateActiveItemId?.(ids[0]);
205
+ }}
206
+ limitedScrollHeight
207
+ />
208
+ </div>
209
+ }
210
+ triggerClassName={triggerClassName}
211
+ fallbackPlacements={DEFAULT_FALLBACK_PLACEMENTS}
212
+ trigger={trigger}
213
+ placement={placement}
214
+ widthStrategy={widthStrategy}
215
+ {...(triggerElemRefProp
216
+ ? {}
217
+ : {
218
+ triggerRef: triggerElemRef,
219
+ })}
220
+ open={open}
221
+ onOpenChange={handleOpenChange}
222
+ >
223
+ {triggerElem}
224
+ </Dropdown>
225
+ </FocusListContext.Provider>
226
+ </CollapseContext.Provider>
227
+ </SelectionProvider>
228
+ </NewListContextProvider>
176
229
  );
177
230
  }
@@ -1,149 +1,169 @@
1
1
  import cn from 'classnames';
2
2
  import mergeRefs from 'merge-refs';
3
- import { FocusEvent, forwardRef, KeyboardEvent, useMemo, useRef } from 'react';
4
- import { useUncontrolledProp } from 'uncontrollable';
3
+ import { ForwardedRef, forwardRef, KeyboardEvent, useCallback, useMemo, useRef } from 'react';
4
+
5
+ import { useValueControl } from '@snack-uikit/utils';
5
6
 
6
7
  import { HiddenTabButton } from '../../../helperComponents';
7
- import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
8
- import { addItemsIds, getSlicedItems, useItemsWithIds } from '../../Items';
9
- import { extractSelectionProps, ParentListContext, SelectionProvider, useParentListContext } from '../contexts';
10
- import { useKeyboardNavigation } from '../hooks';
8
+ import { extractActiveItems, ItemId, kindFlattenItems, useCreateBaseItems } from '../../Items';
9
+ import { CollapseContext, FocusListContext, NewListContextProvider, SelectionProvider } from '../contexts';
10
+ import { useNewKeyboardNavigation } from '../hooks';
11
11
  import { ListPrivate } from '../ListPrivate';
12
12
  import styles from '../styles.module.scss';
13
13
  import { ListProps } from '../types';
14
14
 
15
- export const List = forwardRef<HTMLElement, ListProps>(
15
+ export const List = forwardRef(
16
16
  (
17
17
  {
18
- items: itemsProp,
18
+ items: itemsProp = [],
19
19
  search,
20
- pinBottom,
21
- pinTop,
20
+ pinBottom: pinBottomProp = [],
21
+ pinTop: pinTopProp = [],
22
22
  footerActiveElementsRefs,
23
23
  onKeyDown,
24
24
  tabIndex = 0,
25
25
  className,
26
26
  collapse = {},
27
+ selection,
28
+ contentRender,
29
+ size = 's',
30
+ marker = true,
27
31
  ...props
28
- },
29
- ref,
32
+ }: ListProps,
33
+ ref: ForwardedRef<HTMLElement>,
30
34
  ) => {
31
35
  const hasSearch = useMemo(() => Boolean(search), [search]);
32
36
 
33
- const memorizedItems = useMemo(
34
- () => addItemsIds((pinTop ?? []).concat(itemsProp).concat(pinBottom ?? [])),
35
- [itemsProp, pinBottom, pinTop],
37
+ const [openCollapseItems = [], setOpenCollapsedItems] = useValueControl<ItemId[] | undefined>(collapse);
38
+ const toggleOpenCollapseItem = useCallback(
39
+ (id: ItemId) =>
40
+ setOpenCollapsedItems((items: ItemId[]) =>
41
+ items?.includes(id) ? items.filter(item => item !== id) : (items ?? []).concat([id]),
42
+ ),
43
+ [setOpenCollapsedItems],
36
44
  );
37
45
 
38
- const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp<Array<number | string>>(
39
- collapse.value,
40
- collapse.defaultValue ?? [],
41
- collapse.onChange,
42
- );
46
+ const { searchItem, footerItems } = useCreateBaseItems({ footerActiveElementsRefs });
47
+
48
+ /**
49
+ * Объект с пропсами всех вложенных айтемов; ключ id
50
+ */
51
+ const { flattenItems, focusFlattenItems, ...memorizedItems } = useMemo(() => {
52
+ const pinTop = kindFlattenItems({ items: pinTopProp, prefix: '~pinTop', parentId: '~main' });
53
+ const items = kindFlattenItems({ items: itemsProp, prefix: '~main', parentId: '~main' });
54
+ const pinBottom = kindFlattenItems({ items: pinBottomProp, prefix: '~pinBottom', parentId: '~main' });
55
+
56
+ const flattenItems = { ...pinTop.flattenItems, ...pinBottom.flattenItems, ...items.flattenItems };
57
+ const focusFlattenItems = {
58
+ ...pinTop.focusFlattenItems,
59
+ ...pinBottom.focusFlattenItems,
60
+ ...items.focusFlattenItems,
61
+ };
43
62
 
44
- const { search: searchItem, footerRefs } = useItemsWithIds({
45
- search: hasSearch,
46
- footerActiveElementsRefs,
47
- });
63
+ [...footerItems, searchItem].forEach(item => {
64
+ flattenItems[item.id] = item;
65
+ focusFlattenItems[item.id] = { ...item, originalId: item.id, items: [], key: item.id, allChildIds: [] };
66
+ });
67
+
68
+ return { items, pinTop, pinBottom, flattenItems, focusFlattenItems };
69
+ }, [itemsProp, pinTopProp, pinBottomProp, searchItem, footerItems]);
70
+
71
+ const { ids, expandedIds } = useMemo(() => {
72
+ const { pinTop, items, pinBottom } = memorizedItems;
73
+
74
+ let ids: ItemId[] = [];
75
+ let expandedIds: ItemId[] = [];
76
+
77
+ if (hasSearch) {
78
+ ids.push(searchItem.id);
79
+ }
48
80
 
49
- const { items, itemRefs, ids, expandedIds } = useMemo(() => {
50
- const res = withCollapsedItems({
51
- items: memorizedItems,
52
- openCollapsedItems,
81
+ [pinTop, items, pinBottom].forEach(({ focusFlattenItems, focusCloseChildIds }) => {
82
+ const activeItems = extractActiveItems({ focusFlattenItems, focusCloseChildIds, openCollapseItems });
83
+
84
+ ids = ids.concat(activeItems.ids);
85
+ expandedIds = expandedIds.concat(activeItems.expandedIds);
53
86
  });
54
87
 
55
- const items = searchItem.concat(res.items).concat(footerRefs);
88
+ footerItems.forEach(footerItem => {
89
+ ids.push(footerItem.id);
90
+ });
56
91
 
57
92
  return {
58
- items,
59
- ids: extractItemIds(searchItem).concat(res.ids).concat(extractItemIds(footerRefs)),
60
- itemRefs: extractItemRefs(searchItem).concat(res.itemRefs).concat(extractItemRefs(footerRefs)),
61
- expandedIds: res.expandedIds,
93
+ ids,
94
+ expandedIds,
62
95
  };
63
- }, [footerRefs, memorizedItems, openCollapsedItems, searchItem]);
64
-
65
- const slicedItems = useMemo(
66
- () => getSlicedItems({ items, hasSearch, pinTop, pinBottom, footerRefs }),
67
- [items, hasSearch, pinTop, pinBottom, footerRefs],
68
- );
96
+ }, [footerItems, hasSearch, memorizedItems, openCollapseItems, searchItem.id]);
69
97
 
70
98
  const listRef = useRef<HTMLElement>(null);
71
99
  const btnRef = useRef<HTMLButtonElement>(null);
72
100
 
73
- const { triggerRef } = useParentListContext();
74
-
75
- const {
76
- activeFocusIndex,
77
- openNestedIndex,
78
- handleListKeyDown,
79
- resetNestedIndex,
80
- resetActiveFocusIndex,
81
- setActiveFocusIndex,
82
- } = useKeyboardNavigation({
83
- ids,
84
- expandedIds,
85
- parentRef: listRef,
86
- btnRef,
87
- itemRefs,
88
- });
89
-
90
- const isActive = listRef.current === document.activeElement && activeFocusIndex === -1 && openNestedIndex === -1;
101
+ const { handleListKeyDownFactory, activeItemId, resetActiveItemId, forceUpdateActiveItemId } =
102
+ useNewKeyboardNavigation({
103
+ mainRef: listRef,
104
+ btnRef,
105
+ focusFlattenItems,
106
+ });
107
+
108
+ const handleListKeyDown = useCallback(
109
+ (e: KeyboardEvent<HTMLElement>) => handleListKeyDownFactory(ids, expandedIds)(e),
110
+ [handleListKeyDownFactory, ids, expandedIds],
111
+ );
112
+
113
+ const isActive = listRef.current === document.activeElement && activeItemId === undefined;
91
114
 
92
115
  const mergedHandlerKeyDown = (e: KeyboardEvent<HTMLElement>) => {
93
116
  onKeyDown?.(e);
94
117
  handleListKeyDown?.(e);
95
118
  };
96
119
 
97
- const handleOnFocus = (e: FocusEvent<HTMLElement>) => {
98
- if (
99
- e.relatedTarget === null ||
100
- (e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))
101
- ) {
102
- resetActiveFocusIndex();
103
- }
120
+ const handleOnFocus = () => {
121
+ resetActiveItemId();
104
122
  };
105
123
 
106
124
  return (
107
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108
- // @ts-ignore
109
- <SelectionProvider {...extractSelectionProps(props)}>
110
- <ParentListContext.Provider
111
- value={{
112
- parentIds: ids,
113
- parentActiveFocusIndex: activeFocusIndex,
114
- parentExpandedIds: expandedIds,
115
- parentItemRefs: itemRefs,
116
- parentOpenNestedIndex: openNestedIndex,
117
- triggerRef: triggerRef ?? listRef,
118
- parentRef: listRef,
119
-
120
- parentSetActiveFocusIndex: setActiveFocusIndex,
121
- parentResetNestedIndex: resetNestedIndex,
122
- parentResetActiveFocusIndex: resetActiveFocusIndex,
123
- openCollapsedItems,
124
- toggleOpenCollapsedItems: id =>
125
- setOpenCollapsedItems((items: Array<string | number> | undefined = []) =>
126
- items.includes(id) ? items.filter(item => item !== id) : items?.concat([id]),
127
- ),
128
- }}
129
- >
130
- <div className={cn(styles.wrapper, className)} data-active={isActive || undefined}>
131
- <ListPrivate
132
- {...props}
133
- {...slicedItems}
134
- ref={mergeRefs(ref, listRef)}
135
- onFocus={handleOnFocus}
136
- onKeyDown={mergedHandlerKeyDown}
137
- tabIndex={tabIndex}
138
- search={search}
139
- nested={false}
140
- parent='list'
141
- />
142
-
143
- <HiddenTabButton ref={btnRef} listRef={listRef} tabIndex={tabIndex} />
144
- </div>
145
- </ParentListContext.Provider>
146
- </SelectionProvider>
125
+ <NewListContextProvider
126
+ flattenItems={flattenItems}
127
+ focusFlattenItems={focusFlattenItems}
128
+ contentRender={contentRender}
129
+ size={size}
130
+ marker={marker}
131
+ >
132
+ <SelectionProvider {...selection}>
133
+ <CollapseContext.Provider
134
+ value={{
135
+ openCollapseItems,
136
+ toggleOpenCollapseItem,
137
+ }}
138
+ >
139
+ <FocusListContext.Provider
140
+ value={{
141
+ activeItemId,
142
+ handleListKeyDownFactory,
143
+ forceUpdateActiveItemId,
144
+ }}
145
+ >
146
+ <div className={cn(styles.wrapper, className)} data-active={isActive || undefined}>
147
+ <ListPrivate
148
+ {...props}
149
+ items={memorizedItems.items.focusCloseChildIds}
150
+ pinTop={memorizedItems.pinTop.focusCloseChildIds}
151
+ pinBottom={memorizedItems.pinBottom.focusCloseChildIds}
152
+ searchItem={searchItem}
153
+ ref={mergeRefs(ref, listRef)}
154
+ onFocus={handleOnFocus}
155
+ onKeyDown={mergedHandlerKeyDown}
156
+ tabIndex={tabIndex}
157
+ search={search}
158
+ nested={false}
159
+ />
160
+
161
+ <HiddenTabButton ref={btnRef} listRef={listRef} tabIndex={tabIndex} />
162
+ </div>
163
+ </FocusListContext.Provider>
164
+ </CollapseContext.Provider>
165
+ </SelectionProvider>
166
+ </NewListContextProvider>
147
167
  );
148
168
  },
149
169
  );