@snack-uikit/list 0.11.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.
- package/CHANGELOG.md +13 -0
- package/README.md +22 -61
- package/dist/components/Items/AccordionItem/AccordionItem.d.ts +4 -2
- package/dist/components/Items/AccordionItem/AccordionItem.js +18 -16
- package/dist/components/Items/BaseItem/BaseItem.d.ts +8 -4
- package/dist/components/Items/BaseItem/BaseItem.js +20 -14
- package/dist/components/Items/BaseItem/styles.module.css +20 -119
- package/dist/components/Items/GroupItem/GroupItem.d.ts +5 -1
- package/dist/components/Items/GroupItem/GroupItem.js +1 -3
- package/dist/components/Items/GroupSelectItem/GroupSelectItem.d.ts +4 -0
- package/dist/components/Items/GroupSelectItem/GroupSelectItem.js +18 -0
- package/dist/components/Items/GroupSelectItem/index.d.ts +1 -0
- package/dist/components/Items/GroupSelectItem/index.js +1 -0
- package/dist/components/Items/NextListItem/NextListItem.d.ts +6 -2
- package/dist/components/Items/NextListItem/NextListItem.js +55 -45
- package/dist/components/Items/NextListItem/constants.d.ts +2 -0
- package/dist/components/Items/NextListItem/constants.js +10 -0
- package/dist/components/Items/PinGroupItem/PinGroupItem.js +7 -3
- package/dist/components/Items/PinGroupItem/styles.module.css +26 -14
- package/dist/components/Items/SearchItem/SearchItem.d.ts +3 -1
- package/dist/components/Items/SearchItem/SearchItem.js +5 -8
- package/dist/components/Items/hooks.d.ts +11 -10
- package/dist/components/Items/hooks.js +64 -46
- package/dist/components/Items/index.d.ts +1 -5
- package/dist/components/Items/index.js +1 -5
- package/dist/components/Items/styles.module.css +33 -26
- package/dist/components/Items/types.d.ts +68 -34
- package/dist/components/Items/utils.d.ts +34 -16
- package/dist/components/Items/utils.js +135 -37
- package/dist/components/Lists/Droplist/DropList.d.ts +1 -1
- package/dist/components/Lists/Droplist/DropList.js +78 -50
- package/dist/components/Lists/List/List.d.ts +5 -9
- package/dist/components/Lists/List/List.js +61 -54
- package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +6 -20
- package/dist/components/Lists/ListPrivate/ListPrivate.js +9 -8
- package/dist/components/Lists/ListPrivate/styles.module.css +1 -0
- package/dist/components/Lists/contexts/CollapseProvider.d.ts +13 -1
- package/dist/components/Lists/contexts/CollapseProvider.js +2 -0
- package/dist/components/Lists/contexts/FocusListProvider.d.ts +9 -0
- package/dist/components/Lists/contexts/FocusListProvider.js +5 -0
- package/dist/components/Lists/contexts/NewListProvider.d.ts +32 -0
- package/dist/components/Lists/contexts/{ListProvider.js → NewListProvider.js} +10 -5
- package/dist/components/Lists/contexts/SelectionProvider.d.ts +25 -15
- package/dist/components/Lists/contexts/SelectionProvider.js +17 -13
- package/dist/components/Lists/contexts/index.d.ts +3 -2
- package/dist/components/Lists/contexts/index.js +3 -2
- package/dist/components/Lists/hooks.d.ts +10 -12
- package/dist/components/Lists/hooks.js +73 -40
- package/dist/components/Lists/index.d.ts +2 -1
- package/dist/components/Lists/index.js +1 -0
- package/dist/components/Lists/types.d.ts +12 -14
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.js +1 -1
- package/dist/helperComponents/ItemContent/ItemContent.d.ts +15 -0
- package/dist/helperComponents/ItemContent/ItemContent.js +23 -0
- package/dist/helperComponents/ItemContent/index.d.ts +1 -0
- package/dist/helperComponents/ItemContent/index.js +1 -0
- package/dist/helperComponents/ItemContent/styles.module.css +117 -0
- package/dist/helperComponents/ListEmptyState/ListEmptyState.d.ts +2 -2
- package/dist/helperComponents/ListEmptyState/ListEmptyState.js +13 -4
- package/dist/helperComponents/Separator/Separator.d.ts +8 -2
- package/dist/helperComponents/Separator/Separator.js +22 -2
- package/dist/helperComponents/Separator/constants.d.ts +2 -0
- package/dist/helperComponents/Separator/constants.js +5 -0
- package/dist/helperComponents/Separator/styles.module.css +20 -1
- package/dist/helperComponents/index.d.ts +1 -0
- package/dist/helperComponents/index.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/types.d.ts +1 -1
- package/package.json +13 -18
- package/src/components/Items/AccordionItem/AccordionItem.tsx +24 -20
- package/src/components/Items/BaseItem/BaseItem.tsx +91 -82
- package/src/components/Items/BaseItem/styles.module.scss +19 -77
- package/src/components/Items/GroupItem/GroupItem.tsx +5 -5
- package/src/components/Items/GroupSelectItem/GroupSelectItem.tsx +44 -0
- package/src/components/Items/GroupSelectItem/index.ts +1 -0
- package/src/components/Items/NextListItem/NextListItem.tsx +88 -85
- package/src/components/Items/NextListItem/constants.ts +12 -0
- package/src/components/Items/PinGroupItem/PinGroupItem.tsx +14 -4
- package/src/components/Items/PinGroupItem/styles.module.scss +14 -14
- package/src/components/Items/SearchItem/SearchItem.tsx +6 -8
- package/src/components/Items/hooks.tsx +86 -66
- package/src/components/Items/index.ts +1 -5
- package/src/components/Items/styles.module.scss +36 -25
- package/src/components/Items/types.ts +91 -42
- package/src/components/Items/utils.ts +199 -52
- package/src/components/Lists/Droplist/DropList.tsx +155 -98
- package/src/components/Lists/List/List.tsx +129 -104
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +19 -20
- package/src/components/Lists/ListPrivate/styles.module.scss +1 -0
- package/src/components/Lists/contexts/CollapseProvider.tsx +22 -0
- package/src/components/Lists/contexts/FocusListProvider.tsx +15 -0
- package/src/components/Lists/contexts/NewListProvider.tsx +54 -0
- package/src/components/Lists/contexts/SelectionProvider.tsx +56 -39
- package/src/components/Lists/contexts/index.ts +3 -2
- package/src/components/Lists/hooks.ts +124 -75
- package/src/components/Lists/index.ts +2 -2
- package/src/components/Lists/types.ts +20 -24
- package/src/components/index.ts +17 -2
- package/src/helperComponents/ItemContent/ItemContent.tsx +63 -0
- package/src/helperComponents/ItemContent/index.ts +1 -0
- package/src/helperComponents/ItemContent/styles.module.scss +78 -0
- package/src/helperComponents/ListEmptyState/ListEmptyState.tsx +26 -12
- package/src/helperComponents/Separator/Separator.tsx +49 -3
- package/src/helperComponents/Separator/constants.ts +7 -0
- package/src/helperComponents/Separator/styles.module.scss +41 -1
- package/src/helperComponents/index.ts +1 -0
- package/src/index.ts +4 -2
- package/src/types.ts +1 -1
- package/dist/components/Items/NextListItem/hooks.d.ts +0 -19
- package/dist/components/Items/NextListItem/hooks.js +0 -80
- package/dist/components/Lists/contexts/ListProvider.d.ts +0 -17
- package/dist/components/Lists/contexts/ParentListProvider.d.ts +0 -18
- package/dist/components/Lists/contexts/ParentListProvider.js +0 -12
- package/dist/hooks.d.ts +0 -5
- package/dist/hooks.js +0 -15
- package/dist/utils.d.ts +0 -30
- package/dist/utils.js +0 -115
- package/src/components/Items/NextListItem/hooks.ts +0 -105
- package/src/components/Lists/contexts/CollapseProvider.ts +0 -9
- package/src/components/Lists/contexts/ListProvider.tsx +0 -28
- package/src/components/Lists/contexts/ParentListProvider.tsx +0 -35
- package/src/hooks.ts +0 -27
- 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 {
|
|
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
|
|
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
|
-
|
|
81
|
+
hasNoItems={hasNoItems}
|
|
83
82
|
dataFiltered={dataFiltered ?? Boolean(search?.value)}
|
|
84
83
|
/>
|
|
85
84
|
</div>
|
|
86
85
|
),
|
|
87
|
-
[dataError, dataFiltered, emptyStates,
|
|
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={
|
|
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 &&
|
|
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 (
|
|
@@ -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
|
-
|
|
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?:
|
|
13
|
+
defaultValue?: ItemId;
|
|
9
14
|
/** Controlled состояние */
|
|
10
|
-
value?:
|
|
15
|
+
value?: ItemId;
|
|
11
16
|
/** Controlled обработчик измения состояния */
|
|
12
|
-
|
|
13
|
-
onChange?(value: any): void;
|
|
17
|
+
onChange?: OnChangeHandler;
|
|
14
18
|
/** Режим выбора */
|
|
15
19
|
mode: 'single';
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
export type SelectionSingleProps = {
|
|
19
|
-
|
|
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?:
|
|
32
|
+
defaultValue?: ItemId[];
|
|
30
33
|
/** Controlled состояние */
|
|
31
|
-
value?:
|
|
34
|
+
value?: ItemId[];
|
|
32
35
|
/** Controlled обработчик измения состояния */
|
|
33
|
-
|
|
34
|
-
onChange?(value: any): void;
|
|
36
|
+
onChange?: OnChangeHandler;
|
|
35
37
|
/** Режим выбора */
|
|
36
38
|
mode: 'multiple';
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
export type SelectionMultipleProps = {
|
|
40
|
-
|
|
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?:
|
|
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 = {
|
|
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
|
-
|
|
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] =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
onChangeProp
|
|
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] =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
onChangeProp
|
|
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 <
|
|
206
|
+
return <SelectionNoneProvider>{children}</SelectionNoneProvider>;
|
|
186
207
|
}
|
|
187
208
|
|
|
188
|
-
export
|
|
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,99 +1,148 @@
|
|
|
1
|
-
import { KeyboardEvent, RefObject,
|
|
1
|
+
import { KeyboardEvent, RefObject, useCallback, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { FocusFlattenItem, ItemId } from '../Items';
|
|
4
|
+
|
|
5
|
+
type UseNewKeyboardNavigationProps<T extends HTMLElement> = {
|
|
6
|
+
mainRef?: RefObject<T>;
|
|
7
7
|
btnRef?: RefObject<HTMLButtonElement>;
|
|
8
|
-
|
|
8
|
+
focusFlattenItems: Record<string, FocusFlattenItem>;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export function
|
|
12
|
-
|
|
13
|
-
itemRefs,
|
|
14
|
-
expandedIds,
|
|
15
|
-
parentRef,
|
|
11
|
+
export function useNewKeyboardNavigation<T extends HTMLElement>({
|
|
12
|
+
mainRef,
|
|
16
13
|
btnRef,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const [
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
84
|
+
case 'ArrowRight': {
|
|
85
|
+
if (activeItemIdRef.current !== undefined && expandedIds.includes(activeItemIdRef.current)) {
|
|
86
|
+
const item = focusFlattenItems[activeItemIdRef.current];
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
54
|
-
|
|
91
|
+
activeItemIdRef.current = newItemId;
|
|
92
|
+
setActiveItemId(newItemId);
|
|
55
93
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
setTimeout(() => newItem.itemRef?.current?.focus(), 0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
e.stopPropagation();
|
|
60
98
|
e.preventDefault();
|
|
61
99
|
|
|
62
|
-
|
|
63
|
-
} else if (activeFocusIndex === -1) {
|
|
64
|
-
btnRef && !e.shiftKey ? btnRef?.current?.focus() : parentRef?.current?.focus();
|
|
100
|
+
return;
|
|
65
101
|
}
|
|
66
102
|
|
|
67
|
-
|
|
68
|
-
|
|
103
|
+
case 'Tab': {
|
|
104
|
+
if (activeItemIdRef.current !== undefined) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
e.stopPropagation();
|
|
69
107
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
125
|
+
const resetActiveItemId = useCallback(() => {
|
|
126
|
+
setActiveItemId(undefined);
|
|
127
|
+
activeItemIdRef.current = undefined;
|
|
128
|
+
}, []);
|
|
82
129
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
const forceUpdateActiveItemId = useCallback(
|
|
131
|
+
(itemId: ItemId) => {
|
|
132
|
+
setActiveItemId(itemId);
|
|
133
|
+
activeItemIdRef.current = itemId;
|
|
134
|
+
|
|
135
|
+
const item = focusFlattenItems[itemId];
|
|
86
136
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
137
|
+
item?.itemRef?.current?.focus();
|
|
138
|
+
},
|
|
139
|
+
[focusFlattenItems],
|
|
140
|
+
);
|
|
90
141
|
|
|
91
142
|
return {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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';
|