@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,104 +1,124 @@
1
- import { createRef, RefObject, useEffect, useMemo } from 'react';
1
+ import { createRef, RefObject, useMemo } from 'react';
2
2
 
3
- import { extractAllChildIds, extractChildIds } from '../../utils';
4
- import { useSelectionContext } from '../Lists/contexts';
3
+ import { useNewListContext, useSelectionContext } from '../Lists/contexts';
5
4
  import { AccordionItem } from './AccordionItem';
6
5
  import { BaseItem } from './BaseItem';
7
6
  import { GroupItem } from './GroupItem';
7
+ import { GroupSelectItem } from './GroupSelectItem';
8
8
  import { NextListItem } from './NextListItem';
9
- import { ItemProps } from './types';
10
- import { addItemsIds, isAccordionItemProps, isGroupItemProps, isNextListItemProps } from './utils';
9
+ import {
10
+ Flatten,
11
+ FlattenAccordionItem,
12
+ FlattenBaseItem,
13
+ FlattenGroupSelectListItem,
14
+ FlattenNextListItem,
15
+ ItemId,
16
+ } from './types';
17
+ import { isAccordionItem, isGroupItem, isGroupSelectItem, isNextListItem } from './utils';
18
+
19
+ export function useRenderItems(focusCloseChildIds?: ItemId[]) {
20
+ const { focusFlattenItems, flattenItems } = useNewListContext();
21
+ const { isSelectionSingle } = useSelectionContext();
22
+
23
+ return useMemo(() => {
24
+ if (!focusCloseChildIds) {
25
+ return [null];
26
+ }
11
27
 
12
- export function useRenderItems(items: ItemProps[]) {
13
- return useMemo(
14
- () =>
15
- items.map(item => {
16
- if (isGroupItemProps(item)) {
17
- return <GroupItem {...item} key={item.label} />;
18
- }
28
+ return focusCloseChildIds.map(id => {
29
+ const { itemRef, key, originalId, items } = focusFlattenItems[id];
19
30
 
20
- if (isAccordionItemProps(item)) {
21
- return <AccordionItem {...item} key={item.key} />;
22
- }
31
+ const flattenItem = flattenItems[originalId];
23
32
 
24
- if (isNextListItemProps(item)) {
25
- return <NextListItem {...item} key={item.key} />;
26
- }
33
+ if (
34
+ isGroupItem(flattenItem) ||
35
+ (isSelectionSingle && isGroupSelectItem<FlattenGroupSelectListItem>(flattenItem))
36
+ ) {
37
+ return <GroupItem {...flattenItem} items={items} key={key} />;
38
+ }
39
+ if (isGroupSelectItem<FlattenGroupSelectListItem>(flattenItem)) {
40
+ return <GroupSelectItem {...flattenItem} items={items} itemRef={itemRef} key={key} />;
41
+ }
42
+ if (isAccordionItem<FlattenAccordionItem>(flattenItem)) {
43
+ return <AccordionItem {...flattenItem} items={items} itemRef={itemRef} key={key} />;
44
+ }
45
+ if (isNextListItem<FlattenNextListItem>(flattenItem)) {
46
+ return <NextListItem {...flattenItem} focusId={id} items={items} itemRef={itemRef} key={key} />;
47
+ }
27
48
 
28
- return <BaseItem {...item} key={item.key} />;
29
- }),
30
- [items],
31
- );
49
+ return <BaseItem {...flattenItem} itemRef={itemRef} key={key} />;
50
+ });
51
+ }, [flattenItems, focusCloseChildIds, focusFlattenItems, isSelectionSingle]);
32
52
  }
33
53
 
34
- type UseItemsWithIdsProps = {
54
+ type UseCreateBaseItemsProps = {
35
55
  search?: boolean;
36
56
  footerActiveElementsRefs?: RefObject<HTMLElement>[];
37
57
  };
38
58
 
39
- export function useItemsWithIds({ search, footerActiveElementsRefs }: UseItemsWithIdsProps) {
59
+ export function useCreateBaseItems({ footerActiveElementsRefs }: UseCreateBaseItemsProps): {
60
+ searchItem: Flatten<FlattenBaseItem, 'itemRef'>;
61
+ footerItems: Flatten<FlattenBaseItem, 'itemRef'>[];
62
+ } {
40
63
  return useMemo(
41
64
  () => ({
42
- search: addItemsIds(
43
- search ? ([{ itemRef: createRef<HTMLInputElement>() }] as unknown as ItemProps[]) : [],
44
- 'search',
45
- ),
46
- footerRefs: addItemsIds(
47
- footerActiveElementsRefs
48
- ? (footerActiveElementsRefs?.map(ref => ({ itemRef: ref })) as unknown as ItemProps[])
49
- : [],
50
- 'footer',
51
- ),
65
+ searchItem: {
66
+ itemRef: createRef<HTMLInputElement>(),
67
+ id: '~search',
68
+ parentId: '~main',
69
+ items: [],
70
+ allChildIds: [],
71
+ },
72
+ footerItems:
73
+ footerActiveElementsRefs?.map((itemRef, idx) => ({
74
+ id: `~footer__${idx}`,
75
+ itemRef,
76
+ parentId: '~main',
77
+ items: [],
78
+ allChildIds: [],
79
+ })) ?? [],
52
80
  }),
53
-
54
- [footerActiveElementsRefs, search],
81
+ [footerActiveElementsRefs],
55
82
  );
56
83
  }
57
84
 
58
85
  type UseGroupItemSelectionProps = {
59
- items: ItemProps[];
60
- id?: string | number;
86
+ allChildIds: ItemId[];
87
+ items: ItemId[];
88
+ id: ItemId;
61
89
  disabled?: boolean;
62
90
  };
63
91
 
64
- export function useGroupItemSelection({ id, items, disabled }: UseGroupItemSelectionProps) {
92
+ export function useGroupItemSelection({ id, allChildIds }: UseGroupItemSelectionProps) {
65
93
  const { value, setValue, isSelectionMultiple } = useSelectionContext();
66
- const { childIds, allChildIds } = useMemo(
67
- () => ({ childIds: extractChildIds({ items }), allChildIds: extractAllChildIds({ items }) }),
68
- [items],
94
+ const { flattenItems } = useNewListContext();
95
+
96
+ const baseChildIds = useMemo(
97
+ () =>
98
+ allChildIds.filter(itemId => {
99
+ const item = flattenItems[itemId];
100
+
101
+ return item && !('type' in item);
102
+ }),
103
+ [allChildIds, flattenItems],
69
104
  );
70
105
 
71
- const isIndeterminate = isSelectionMultiple
72
- ? allChildIds.some(childId => value?.includes(childId))
73
- : allChildIds.includes(value ?? '');
74
- const checked = isSelectionMultiple ? allChildIds.every(childId => value?.includes(childId)) : undefined;
106
+ const checked = isSelectionMultiple
107
+ ? value && Boolean(value.length) && baseChildIds.every(childId => value?.includes(childId))
108
+ : undefined;
75
109
 
76
- useEffect(() => {
77
- if (isSelectionMultiple) {
78
- if (checked && !value?.includes(id)) {
79
- setValue?.((value: Array<number | string>) => (value ?? []).concat([id ?? '']));
80
- }
81
- if (!checked && value?.includes(id)) {
82
- setValue?.((value: Array<number | string>) => (value ?? []).filter(itemId => itemId !== id));
83
- }
84
- }
85
- }, [checked, disabled, id, isSelectionMultiple, setValue, value]);
110
+ const indeterminate = isSelectionMultiple
111
+ ? !checked && baseChildIds.some(childId => value?.includes(childId))
112
+ : baseChildIds.includes(value ?? '');
86
113
 
87
114
  const handleOnSelect = () => {
88
115
  if (checked) {
89
- setValue?.((value: Array<string | number>) =>
90
- (value ?? []).filter(itemId => itemId !== id && !childIds.includes(itemId)),
91
- );
92
- return;
93
- }
94
-
95
- if (isIndeterminate) {
96
- setValue?.((value: Array<string | number>) => Array.from(new Set([...(value ?? []), ...childIds, id])));
116
+ setValue?.((value: ItemId[]) => (value ?? []).filter(itemId => itemId !== id && !baseChildIds.includes(itemId)));
97
117
  return;
98
118
  }
99
119
 
100
- setValue?.((value: Array<string | number>) => (value ?? []).concat([...childIds, id ?? '']));
120
+ setValue?.((value: ItemId[]) => Array.from(new Set([...(value ?? []), ...baseChildIds])));
101
121
  };
102
122
 
103
- return { checked, isIndeterminate, handleOnSelect };
123
+ return { checked, indeterminate, handleOnSelect };
104
124
  }
@@ -1,9 +1,5 @@
1
- export * from './BaseItem';
2
- export * from './NextListItem';
3
- export * from './AccordionItem';
4
- export * from './GroupItem';
5
- export * from './SearchItem';
6
1
  export * from './hooks';
7
2
  export * from './types';
8
3
  export * from './utils';
4
+ export * from './SearchItem';
9
5
  export * from './PinGroupItem';
@@ -3,26 +3,17 @@ import { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, RefObject } from 'rea
3
3
  import { TruncateStringProps } from '@snack-uikit/truncate-string';
4
4
  import { WithSupportProps } from '@snack-uikit/utils';
5
5
 
6
- import { SeparatorProps } from '../../helperComponents';
7
- import { ScrollProps, SearchState } from '../../types';
6
+ import { ItemContentProps } from '../../helperComponents';
7
+ import { ScrollProps } from '../../types';
8
8
 
9
- export type ItemContentProps = {
10
- option: string;
11
- caption?: string;
12
- description?: string;
13
- truncate?: {
14
- option?: number;
15
- description?: number;
16
- variant?: TruncateStringProps['variant'];
17
- };
18
- };
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export type AnyType = any;
19
11
 
20
- export type BaseItemPrivateProps = {
21
- expandIcon?: ReactNode;
22
- open?: boolean;
23
- };
12
+ export type ItemId = string | number;
24
13
 
25
- export type BaseItemProps = WithSupportProps<{
14
+ type ItemContent = ItemContentProps;
15
+
16
+ export type BaseItem = WithSupportProps<{
26
17
  /**
27
18
  * Слот до основного контента
28
19
  * @type ReactElement
@@ -33,9 +24,10 @@ export type BaseItemProps = WithSupportProps<{
33
24
  * @type ReactElement
34
25
  */
35
26
  afterContent?: ReactNode;
36
- /** Основной контент айтема */
37
- content: ItemContentProps;
38
-
27
+ /**
28
+ * Основной контент айтема
29
+ */
30
+ content?: ItemContent | ReactNode;
39
31
  /** Колбек обработки клика */
40
32
  onClick?(e: MouseEvent<HTMLElement>): void;
41
33
  /** Колбек обработки нажатия кнопки мыши */
@@ -46,16 +38,14 @@ export type BaseItemProps = WithSupportProps<{
46
38
  onFocus?(e: FocusEvent<HTMLElement>): void;
47
39
  /** Колбек обработки блюра */
48
40
  onBlur?(e: FocusEvent<HTMLElement>): void;
49
-
50
41
  /** Уникальный идентификатор */
51
- id?: string | number;
52
-
42
+ id?: ItemId;
53
43
  /** Флаг неактивности элемента */
54
44
  disabled?: boolean;
55
45
 
56
46
  itemRef?: RefObject<HTMLElement>;
57
- className?: string;
58
47
 
48
+ className?: string;
59
49
  /**
60
50
  * Флаг отображения отключения реакции на любое css состояние (hover/focus и тд)
61
51
  * <br>
@@ -67,31 +57,90 @@ export type BaseItemProps = WithSupportProps<{
67
57
  */
68
58
  switch?: boolean;
69
59
 
70
- key?: string | number;
71
-
72
60
  itemWrapRender?(item: ReactNode): ReactNode;
73
61
  }>;
74
62
 
75
- type BaseItemsWithoutNonGroupProps = Omit<BaseItemProps, 'switch' | 'inactive'>;
63
+ type BaseItemWithoutNonGroup = Omit<BaseItem, 'switch' | 'inactive'>;
76
64
 
77
65
  // eslint-disable-next-line no-use-before-define
78
- export type ItemProps = BaseItemProps | AccordionItemProps | NextListItemProps | GroupItemProps;
66
+ export type Item = BaseItem | AccordionItem | NextListItem | GroupItem | GroupSelectItem;
79
67
 
80
- export type AccordionItemProps = BaseItemsWithoutNonGroupProps & {
81
- items: ItemProps[];
68
+ export type AccordionItem = BaseItemWithoutNonGroup & {
69
+ items: Item[];
82
70
  type: 'collapse';
83
71
  };
84
72
 
85
- export type NextListItemProps = BaseItemsWithoutNonGroupProps & {
86
- items: ItemProps[];
87
- type: 'next-list';
88
- placement?: 'right-start' | 'left-start' | 'left' | 'right' | 'left-end' | 'right-end';
89
- search?: SearchState;
90
- onSublistOpenChanged?(open: boolean, id?: string | number): void;
91
- loading?: boolean;
92
- } & ScrollProps;
93
-
94
- export type GroupItemProps = Omit<SeparatorProps, 'size'> & {
95
- items: ItemProps[];
96
- id?: string | number;
73
+ export type NextListItem = BaseItemWithoutNonGroup &
74
+ ScrollProps & {
75
+ items: Item[];
76
+ type: 'next-list';
77
+
78
+ onSublistOpenChanged?(open: boolean, id?: ItemId): void;
79
+
80
+ loading?: boolean;
81
+ dataError?: boolean;
82
+ dataFiltered?: boolean;
83
+
84
+ placement?: 'right-start' | 'left-start' | 'left' | 'right' | 'left-end' | 'right-end';
85
+ };
86
+
87
+ type CommonGroupItem = {
88
+ label?: string;
89
+ truncate?: {
90
+ variant?: TruncateStringProps['variant'];
91
+ };
92
+ mode?: 'primary' | 'secondary';
93
+ divider?: boolean;
94
+ };
95
+
96
+ export type GroupItem = CommonGroupItem & {
97
+ items: Item[];
98
+ type: 'group';
99
+ };
100
+
101
+ export type GroupSelectItem = CommonGroupItem & {
102
+ items: Item[];
103
+ type: 'group-select';
104
+
105
+ id?: ItemId;
106
+ itemRef?: RefObject<HTMLElement>;
97
107
  };
108
+
109
+ type RequiredFields<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
110
+ export type Flatten<T, K extends keyof T> = RequiredFields<T, K>;
111
+
112
+ export type CommonFlattenProps = {
113
+ items: ItemId[];
114
+ allChildIds: ItemId[];
115
+ };
116
+
117
+ export type FlattenBaseItem = Flatten<BaseItem, 'id'>;
118
+ export type FlattenNextListItem = Flatten<Omit<NextListItem, 'items'>, 'id'>;
119
+ export type FlattenGroupListItem = Omit<GroupItem, 'items'> & { id: ItemId };
120
+ export type FlattenGroupSelectListItem = Flatten<Omit<GroupSelectItem, 'items'>, 'id'>;
121
+ export type FlattenAccordionItem = Flatten<Omit<AccordionItem, 'items'>, 'id'>;
122
+
123
+ export type FlattenItem =
124
+ | FlattenBaseItem
125
+ | ((FlattenNextListItem | FlattenGroupListItem | FlattenAccordionItem | FlattenGroupSelectListItem) &
126
+ CommonFlattenProps);
127
+
128
+ export type FocusFlattenItem = {
129
+ key: ItemId;
130
+ id: ItemId;
131
+
132
+ originalId: ItemId;
133
+ parentId?: ItemId;
134
+
135
+ itemRef?: RefObject<HTMLElement>;
136
+ type?: 'next-list' | 'collapse' | 'group' | 'group-select';
137
+
138
+ disabled?: boolean;
139
+ } & CommonFlattenProps;
140
+
141
+ export type ItemProps = Item;
142
+ export type BaseItemProps = BaseItem;
143
+ export type GroupItemProps = GroupItem;
144
+ export type NextListItemProps = NextListItem;
145
+ export type AccordionItemProps = AccordionItem;
146
+ export type GroupSelectItemProps = GroupSelectItem;
@@ -1,71 +1,212 @@
1
1
  import { createRef } from 'react';
2
2
 
3
- import { AccordionItemProps, BaseItemProps, GroupItemProps, ItemProps, NextListItemProps } from './types';
3
+ import { ItemContentProps } from '../../helperComponents';
4
+ import {
5
+ AccordionItem,
6
+ AnyType,
7
+ BaseItem,
8
+ FlattenItem,
9
+ FocusFlattenItem,
10
+ GroupItem,
11
+ GroupSelectItem,
12
+ Item,
13
+ ItemId,
14
+ NextListItem,
15
+ } from './types';
4
16
 
5
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
- export function isBaseItemProps(item: any): item is BaseItemProps {
7
- return 'content' in item;
17
+ export function isBaseItem<ReturnType = BaseItem>(item: AnyType): item is ReturnType {
18
+ return item && !('items' in item);
8
19
  }
9
-
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- export function isAccordionItemProps(item: any): item is AccordionItemProps {
12
- return 'items' in item && item['type'] === 'collapse';
20
+ export function isAccordionItem<ReturnType = AccordionItem>(item: AnyType): item is ReturnType {
21
+ return item && 'items' in item && item['type'] === 'collapse';
13
22
  }
14
-
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
- export function isNextListItemProps(item: any): item is NextListItemProps {
17
- return 'items' in item && item['type'] === 'next-list';
23
+ export function isNextListItem<ReturnType = NextListItem>(item: AnyType): item is ReturnType {
24
+ return item && 'items' in item && item['type'] === 'next-list';
18
25
  }
19
-
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- export function isGroupItemProps(item: any): item is GroupItemProps {
22
- return 'items' in item && item['type'] === undefined;
26
+ export function isGroupItem<ReturnType = GroupItem>(item: AnyType): item is ReturnType {
27
+ return item && 'items' in item && item['type'] === 'group';
28
+ }
29
+ export function isGroupSelectItem<ReturnType = GroupSelectItem>(item: AnyType): item is ReturnType {
30
+ return item && 'items' in item && item['type'] === 'group-select';
31
+ }
32
+ export function isContentItem(item: AnyType): item is ItemContentProps {
33
+ return typeof item === 'object' && item['option'] !== undefined;
23
34
  }
24
35
 
25
- export function getSlicedItems({
26
- items,
27
- hasSearch,
28
- pinTop,
29
- pinBottom,
30
- footerRefs,
31
- }: {
32
- items: ItemProps[];
33
- hasSearch: boolean;
34
- footerRefs: ItemProps[];
35
- pinTop?: ItemProps[];
36
- pinBottom?: ItemProps[];
37
- }) {
38
- const searchShift = hasSearch ? 1 : 0;
39
- const pinTopNumber = pinTop?.length ?? 0;
40
- const pinBottomNumber = pinBottom?.length ?? 0;
41
- const footerElementsNumber = footerRefs?.length ?? 0;
36
+ export const isBaseItemProps = isBaseItem;
37
+ export const isAccordionItemProps = isAccordionItem;
38
+ export const isNextListItemProps = isNextListItem;
39
+ export const isGroupItemProps = isGroupItem;
42
40
 
43
- return {
44
- pinTop: items.slice(searchShift, pinTopNumber + searchShift),
45
- items: items.slice(pinTopNumber + searchShift, items.length - pinBottomNumber - footerElementsNumber),
46
- pinBottom: items.slice(items.length - pinBottomNumber - footerElementsNumber, items.length - footerElementsNumber),
47
- };
48
- }
41
+ type FlattenProps = {
42
+ item: Item;
43
+ idx: number;
44
+ prefix?: ItemId;
45
+ parentId?: ItemId;
46
+ };
49
47
 
50
- export function addItemsIds(itemsProp: ItemProps[], prefix?: string | number): ItemProps[] {
51
- return itemsProp.map((item, idx) => {
52
- const autoId = prefix !== undefined ? [prefix, idx].join('-') : String(idx);
53
- const itemId = item.id ?? autoId;
48
+ type KindFlattenItemsProps = {
49
+ items: Item[];
50
+ prefix?: ItemId;
51
+ parentId?: ItemId;
52
+ };
54
53
 
55
- if (isGroupItemProps(item)) {
56
- return { key: autoId, ...item, id: itemId, items: addItemsIds(item.items, itemId) };
57
- }
54
+ export function kindFlattenItems({ items, prefix, parentId }: KindFlattenItemsProps) {
55
+ const flattenItems: Record<string, FlattenItem> = {};
56
+ const focusFlattenItems: Record<string, FocusFlattenItem> = {};
58
57
 
59
- if (isAccordionItemProps(item) || isNextListItemProps(item)) {
60
- return {
61
- key: autoId,
58
+ function flatten({ item, idx, prefix, parentId = '~main' }: FlattenProps): {
59
+ id: ItemId;
60
+ children: ItemId[];
61
+ focusChildren: ItemId[];
62
+ autoId: ItemId;
63
+ } {
64
+ const autoId = prefix !== undefined ? [prefix, idx].join('-') : String(idx);
65
+ const itemId = (!isGroupItem(item) ? item.id : undefined) ?? autoId;
66
+
67
+ if (isBaseItem(item)) {
68
+ flattenItems[itemId] = {
62
69
  ...item,
70
+ items: [],
71
+ allChildIds: [],
63
72
  id: itemId,
64
- items: addItemsIds(item.items, itemId),
73
+ };
74
+
75
+ focusFlattenItems[autoId] = {
76
+ key: autoId,
77
+ originalId: itemId,
78
+ id: autoId,
79
+ disabled: item.disabled,
80
+ parentId,
81
+ items: [],
82
+ allChildIds: [],
65
83
  itemRef: item.itemRef || createRef<HTMLElement>(),
66
84
  };
85
+
86
+ return { id: itemId, children: [itemId], autoId, focusChildren: [autoId] };
67
87
  }
68
88
 
69
- return { key: autoId, ...item, id: itemId, itemRef: item.itemRef || createRef<HTMLElement>() };
70
- });
89
+ let allChildIds: ItemId[] = [];
90
+ let allFocusChildIds: ItemId[] = [];
91
+ const closeChildIds: ItemId[] = [];
92
+ const autoChildIds: ItemId[] = [];
93
+
94
+ const { items, ...rest } = item;
95
+ const childActiveParent = isGroupItem(item) ? parentId ?? '~main' : autoId;
96
+
97
+ for (let idx = 0; idx < items.length; idx++) {
98
+ const { id, children, autoId, focusChildren } = flatten({
99
+ item: items[idx],
100
+ idx,
101
+ prefix: itemId,
102
+ parentId: childActiveParent,
103
+ });
104
+
105
+ autoChildIds.push(autoId);
106
+ closeChildIds.push(id);
107
+ allChildIds = allChildIds.concat(children);
108
+ allFocusChildIds = allFocusChildIds.concat(focusChildren);
109
+ }
110
+
111
+ const children = [...new Set(allChildIds.concat(closeChildIds))];
112
+ const focusChildren = [...new Set(allFocusChildIds.concat(autoChildIds))];
113
+
114
+ flattenItems[itemId] = {
115
+ ...rest,
116
+ id: itemId,
117
+ items: [],
118
+ allChildIds: children,
119
+ };
120
+
121
+ focusFlattenItems[autoId] = {
122
+ key: autoId,
123
+ originalId: itemId,
124
+ id: autoId,
125
+ parentId,
126
+ items: autoChildIds,
127
+ allChildIds: focusChildren,
128
+ disabled: (item.type === 'collapse' || item.type === 'next-list') && item.disabled,
129
+ type: item.type,
130
+ itemRef: (!isGroupItem(item) ? item.itemRef : undefined) ?? createRef<HTMLElement>(),
131
+ };
132
+
133
+ return { id: itemId, children, autoId, focusChildren };
134
+ }
135
+
136
+ const closeChildIds: ItemId[] = [];
137
+ const autoChildIds: ItemId[] = [];
138
+ let allChildIds: ItemId[] = [];
139
+
140
+ for (let idx = 0; idx < items.length; idx++) {
141
+ const { id, children, autoId } = flatten({ item: items[idx], idx, prefix, parentId });
142
+
143
+ autoChildIds.push(autoId);
144
+ closeChildIds.push(id);
145
+ allChildIds.push(id);
146
+ allChildIds = allChildIds.concat(children);
147
+ }
148
+
149
+ const children = [...new Set(allChildIds)];
150
+
151
+ return {
152
+ focusCloseChildIds: autoChildIds,
153
+ allChildIds: children,
154
+ flattenItems,
155
+ focusFlattenItems,
156
+ };
157
+ }
158
+
159
+ type ExtractActiveItemsProps = {
160
+ focusFlattenItems: Record<string, FocusFlattenItem>;
161
+ focusCloseChildIds: ItemId[];
162
+ openCollapseItems: ItemId[];
163
+ };
164
+
165
+ type ExtractActiveItemsReturnType = {
166
+ ids: ItemId[];
167
+ expandedIds: ItemId[];
168
+ };
169
+
170
+ export function extractActiveItems({
171
+ focusFlattenItems,
172
+ focusCloseChildIds,
173
+ openCollapseItems,
174
+ }: ExtractActiveItemsProps): ExtractActiveItemsReturnType {
175
+ const ids: ItemId[] = [];
176
+ const expandedIds: ItemId[] = [];
177
+
178
+ function internalFn(focusCloseChildIds: ItemId[]) {
179
+ focusCloseChildIds.forEach(id => {
180
+ const child = focusFlattenItems[id];
181
+
182
+ if (child.type === 'group') {
183
+ internalFn(child.items);
184
+ return;
185
+ }
186
+
187
+ if (!child.disabled) {
188
+ ids.push(child.id);
189
+
190
+ if (child.type === 'group-select') {
191
+ internalFn(child.items);
192
+ return;
193
+ }
194
+
195
+ if (child.type) {
196
+ expandedIds.push(id);
197
+
198
+ if (openCollapseItems.includes(id)) {
199
+ internalFn(child.items);
200
+ }
201
+ }
202
+ }
203
+ });
204
+ }
205
+
206
+ internalFn(focusCloseChildIds);
207
+
208
+ return {
209
+ ids,
210
+ expandedIds,
211
+ };
71
212
  }