@cloud-ru/ds-utils 1.0.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.
- package/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +222 -0
- package/dist/cjs/hooks/index.d.ts +13 -0
- package/dist/cjs/hooks/index.js +29 -0
- package/dist/cjs/hooks/useCopyToClipboard.d.ts +9 -0
- package/dist/cjs/hooks/useCopyToClipboard.js +27 -0
- package/dist/cjs/hooks/useDataPersist/index.d.ts +2 -0
- package/dist/cjs/hooks/useDataPersist/index.js +18 -0
- package/dist/cjs/hooks/useDataPersist/sources/baseSource.d.ts +11 -0
- package/dist/cjs/hooks/useDataPersist/sources/baseSource.js +35 -0
- package/dist/cjs/hooks/useDataPersist/sources/index.d.ts +3 -0
- package/dist/cjs/hooks/useDataPersist/sources/index.js +19 -0
- package/dist/cjs/hooks/useDataPersist/sources/localStorageSource.d.ts +5 -0
- package/dist/cjs/hooks/useDataPersist/sources/localStorageSource.js +19 -0
- package/dist/cjs/hooks/useDataPersist/sources/queryParamSource.d.ts +5 -0
- package/dist/cjs/hooks/useDataPersist/sources/queryParamSource.js +21 -0
- package/dist/cjs/hooks/useDataPersist/useDataPersist.d.ts +16 -0
- package/dist/cjs/hooks/useDataPersist/useDataPersist.js +26 -0
- package/dist/cjs/hooks/useDataPersist/useSource.d.ts +8 -0
- package/dist/cjs/hooks/useDataPersist/useSource.js +15 -0
- package/dist/cjs/hooks/useDebounce.d.ts +6 -0
- package/dist/cjs/hooks/useDebounce.js +25 -0
- package/dist/cjs/hooks/useDynamicList.d.ts +18 -0
- package/dist/cjs/hooks/useDynamicList.js +99 -0
- package/dist/cjs/hooks/useEventHandler.d.ts +6 -0
- package/dist/cjs/hooks/useEventHandler.js +23 -0
- package/dist/cjs/hooks/useHierarchicalSelection.d.ts +12 -0
- package/dist/cjs/hooks/useHierarchicalSelection.js +11 -0
- package/dist/cjs/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
- package/dist/cjs/hooks/useIsomorphicLayoutEffect.js +12 -0
- package/dist/cjs/hooks/useLocalStorage.d.ts +1 -0
- package/dist/cjs/hooks/useLocalStorage.js +15 -0
- package/dist/cjs/hooks/useModalOpenState.d.ts +18 -0
- package/dist/cjs/hooks/useModalOpenState.js +27 -0
- package/dist/cjs/hooks/usePopstateSubscription.d.ts +1 -0
- package/dist/cjs/hooks/usePopstateSubscription.js +14 -0
- package/dist/cjs/hooks/useSwipeable.d.ts +21 -0
- package/dist/cjs/hooks/useSwipeable.js +50 -0
- package/dist/cjs/hooks/useUncontrolledProp.d.ts +21 -0
- package/dist/cjs/hooks/useUncontrolledProp.js +23 -0
- package/dist/cjs/hooks/useValueControl.d.ts +20 -0
- package/dist/cjs/hooks/useValueControl.js +20 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types/index.d.ts +2 -0
- package/dist/cjs/types/index.js +18 -0
- package/dist/cjs/types/polymorphic.d.ts +3 -0
- package/dist/cjs/types/polymorphic.js +2 -0
- package/dist/cjs/types/valueOf.d.ts +5 -0
- package/dist/cjs/types/valueOf.js +2 -0
- package/dist/cjs/utils/componentPropsProcessors.d.ts +19 -0
- package/dist/cjs/utils/componentPropsProcessors.js +43 -0
- package/dist/cjs/utils/copyToClipboard.d.ts +12 -0
- package/dist/cjs/utils/copyToClipboard.js +47 -0
- package/dist/cjs/utils/debounce.d.ts +11 -0
- package/dist/cjs/utils/debounce.js +24 -0
- package/dist/cjs/utils/hierarchicalSelection/applySelectionDiff.d.ts +4 -0
- package/dist/cjs/utils/hierarchicalSelection/applySelectionDiff.js +16 -0
- package/dist/cjs/utils/hierarchicalSelection/checkGroupSelection.d.ts +5 -0
- package/dist/cjs/utils/hierarchicalSelection/checkGroupSelection.js +18 -0
- package/dist/cjs/utils/hierarchicalSelection/createHierarchicalSelectionHandlers.d.ts +19 -0
- package/dist/cjs/utils/hierarchicalSelection/createHierarchicalSelectionHandlers.js +20 -0
- package/dist/cjs/utils/hierarchicalSelection/getNodeSelectionState.d.ts +8 -0
- package/dist/cjs/utils/hierarchicalSelection/getNodeSelectionState.js +22 -0
- package/dist/cjs/utils/hierarchicalSelection/index.d.ts +6 -0
- package/dist/cjs/utils/hierarchicalSelection/index.js +22 -0
- package/dist/cjs/utils/hierarchicalSelection/toggleHierarchicalSelection.d.ts +5 -0
- package/dist/cjs/utils/hierarchicalSelection/toggleHierarchicalSelection.js +69 -0
- package/dist/cjs/utils/hierarchicalSelection/types.d.ts +23 -0
- package/dist/cjs/utils/hierarchicalSelection/types.js +2 -0
- package/dist/cjs/utils/index.d.ts +10 -0
- package/dist/cjs/utils/index.js +26 -0
- package/dist/cjs/utils/innerRef.d.ts +26 -0
- package/dist/cjs/utils/innerRef.js +37 -0
- package/dist/cjs/utils/isBrowser.d.ts +6 -0
- package/dist/cjs/utils/isBrowser.js +12 -0
- package/dist/cjs/utils/keyboard.d.ts +41 -0
- package/dist/cjs/utils/keyboard.js +66 -0
- package/dist/cjs/utils/preventEvent.d.ts +4 -0
- package/dist/cjs/utils/preventEvent.js +15 -0
- package/dist/cjs/utils/stopEventPropagation.d.ts +2 -0
- package/dist/cjs/utils/stopEventPropagation.js +6 -0
- package/dist/cjs/utils/throttle.d.ts +11 -0
- package/dist/cjs/utils/throttle.js +43 -0
- package/dist/cjs/utils/typeGuards.d.ts +5 -0
- package/dist/cjs/utils/typeGuards.js +13 -0
- package/dist/esm/hooks/index.d.ts +13 -0
- package/dist/esm/hooks/index.js +13 -0
- package/dist/esm/hooks/useCopyToClipboard.d.ts +9 -0
- package/dist/esm/hooks/useCopyToClipboard.js +24 -0
- package/dist/esm/hooks/useDataPersist/index.d.ts +2 -0
- package/dist/esm/hooks/useDataPersist/index.js +2 -0
- package/dist/esm/hooks/useDataPersist/sources/baseSource.d.ts +11 -0
- package/dist/esm/hooks/useDataPersist/sources/baseSource.js +31 -0
- package/dist/esm/hooks/useDataPersist/sources/index.d.ts +3 -0
- package/dist/esm/hooks/useDataPersist/sources/index.js +3 -0
- package/dist/esm/hooks/useDataPersist/sources/localStorageSource.d.ts +5 -0
- package/dist/esm/hooks/useDataPersist/sources/localStorageSource.js +15 -0
- package/dist/esm/hooks/useDataPersist/sources/queryParamSource.d.ts +5 -0
- package/dist/esm/hooks/useDataPersist/sources/queryParamSource.js +17 -0
- package/dist/esm/hooks/useDataPersist/useDataPersist.d.ts +16 -0
- package/dist/esm/hooks/useDataPersist/useDataPersist.js +22 -0
- package/dist/esm/hooks/useDataPersist/useSource.d.ts +8 -0
- package/dist/esm/hooks/useDataPersist/useSource.js +11 -0
- package/dist/esm/hooks/useDebounce.d.ts +6 -0
- package/dist/esm/hooks/useDebounce.js +22 -0
- package/dist/esm/hooks/useDynamicList.d.ts +18 -0
- package/dist/esm/hooks/useDynamicList.js +96 -0
- package/dist/esm/hooks/useEventHandler.d.ts +6 -0
- package/dist/esm/hooks/useEventHandler.js +20 -0
- package/dist/esm/hooks/useHierarchicalSelection.d.ts +12 -0
- package/dist/esm/hooks/useHierarchicalSelection.js +8 -0
- package/dist/esm/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
- package/dist/esm/hooks/useIsomorphicLayoutEffect.js +9 -0
- package/dist/esm/hooks/useLocalStorage.d.ts +1 -0
- package/dist/esm/hooks/useLocalStorage.js +12 -0
- package/dist/esm/hooks/useModalOpenState.d.ts +18 -0
- package/dist/esm/hooks/useModalOpenState.js +24 -0
- package/dist/esm/hooks/usePopstateSubscription.d.ts +1 -0
- package/dist/esm/hooks/usePopstateSubscription.js +11 -0
- package/dist/esm/hooks/useSwipeable.d.ts +21 -0
- package/dist/esm/hooks/useSwipeable.js +46 -0
- package/dist/esm/hooks/useUncontrolledProp.d.ts +21 -0
- package/dist/esm/hooks/useUncontrolledProp.js +20 -0
- package/dist/esm/hooks/useValueControl.d.ts +20 -0
- package/dist/esm/hooks/useValueControl.js +17 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/types/index.d.ts +2 -0
- package/dist/esm/types/index.js +2 -0
- package/dist/esm/types/polymorphic.d.ts +3 -0
- package/dist/esm/types/polymorphic.js +1 -0
- package/dist/esm/types/valueOf.d.ts +5 -0
- package/dist/esm/types/valueOf.js +1 -0
- package/dist/esm/utils/componentPropsProcessors.d.ts +19 -0
- package/dist/esm/utils/componentPropsProcessors.js +38 -0
- package/dist/esm/utils/copyToClipboard.d.ts +12 -0
- package/dist/esm/utils/copyToClipboard.js +44 -0
- package/dist/esm/utils/debounce.d.ts +11 -0
- package/dist/esm/utils/debounce.js +21 -0
- package/dist/esm/utils/hierarchicalSelection/applySelectionDiff.d.ts +4 -0
- package/dist/esm/utils/hierarchicalSelection/applySelectionDiff.js +13 -0
- package/dist/esm/utils/hierarchicalSelection/checkGroupSelection.d.ts +5 -0
- package/dist/esm/utils/hierarchicalSelection/checkGroupSelection.js +15 -0
- package/dist/esm/utils/hierarchicalSelection/createHierarchicalSelectionHandlers.d.ts +19 -0
- package/dist/esm/utils/hierarchicalSelection/createHierarchicalSelectionHandlers.js +17 -0
- package/dist/esm/utils/hierarchicalSelection/getNodeSelectionState.d.ts +8 -0
- package/dist/esm/utils/hierarchicalSelection/getNodeSelectionState.js +19 -0
- package/dist/esm/utils/hierarchicalSelection/index.d.ts +6 -0
- package/dist/esm/utils/hierarchicalSelection/index.js +6 -0
- package/dist/esm/utils/hierarchicalSelection/toggleHierarchicalSelection.d.ts +5 -0
- package/dist/esm/utils/hierarchicalSelection/toggleHierarchicalSelection.js +66 -0
- package/dist/esm/utils/hierarchicalSelection/types.d.ts +23 -0
- package/dist/esm/utils/hierarchicalSelection/types.js +1 -0
- package/dist/esm/utils/index.d.ts +10 -0
- package/dist/esm/utils/index.js +10 -0
- package/dist/esm/utils/innerRef.d.ts +26 -0
- package/dist/esm/utils/innerRef.js +32 -0
- package/dist/esm/utils/isBrowser.d.ts +6 -0
- package/dist/esm/utils/isBrowser.js +9 -0
- package/dist/esm/utils/keyboard.d.ts +41 -0
- package/dist/esm/utils/keyboard.js +58 -0
- package/dist/esm/utils/preventEvent.d.ts +4 -0
- package/dist/esm/utils/preventEvent.js +10 -0
- package/dist/esm/utils/stopEventPropagation.d.ts +2 -0
- package/dist/esm/utils/stopEventPropagation.js +3 -0
- package/dist/esm/utils/throttle.d.ts +11 -0
- package/dist/esm/utils/throttle.js +40 -0
- package/dist/esm/utils/typeGuards.d.ts +5 -0
- package/dist/esm/utils/typeGuards.js +5 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +38 -0
- package/src/hooks/index.ts +13 -0
- package/src/hooks/useCopyToClipboard.ts +38 -0
- package/src/hooks/useDataPersist/index.ts +2 -0
- package/src/hooks/useDataPersist/sources/baseSource.ts +36 -0
- package/src/hooks/useDataPersist/sources/index.ts +3 -0
- package/src/hooks/useDataPersist/sources/localStorageSource.ts +17 -0
- package/src/hooks/useDataPersist/sources/queryParamSource.ts +19 -0
- package/src/hooks/useDataPersist/useDataPersist.ts +51 -0
- package/src/hooks/useDataPersist/useSource.ts +23 -0
- package/src/hooks/useDebounce.ts +25 -0
- package/src/hooks/useDynamicList.ts +137 -0
- package/src/hooks/useEventHandler.ts +26 -0
- package/src/hooks/useHierarchicalSelection.ts +13 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +11 -0
- package/src/hooks/useLocalStorage.ts +19 -0
- package/src/hooks/useModalOpenState.ts +37 -0
- package/src/hooks/usePopstateSubscription.ts +14 -0
- package/src/hooks/useSwipeable.ts +85 -0
- package/src/hooks/useUncontrolledProp.ts +65 -0
- package/src/hooks/useValueControl.ts +41 -0
- package/src/index.ts +3 -0
- package/src/types/index.ts +2 -0
- package/src/types/polymorphic.ts +4 -0
- package/src/types/valueOf.ts +5 -0
- package/src/utils/componentPropsProcessors.ts +55 -0
- package/src/utils/copyToClipboard.ts +46 -0
- package/src/utils/debounce.ts +31 -0
- package/src/utils/hierarchicalSelection/applySelectionDiff.ts +20 -0
- package/src/utils/hierarchicalSelection/checkGroupSelection.ts +22 -0
- package/src/utils/hierarchicalSelection/createHierarchicalSelectionHandlers.ts +35 -0
- package/src/utils/hierarchicalSelection/getNodeSelectionState.ts +29 -0
- package/src/utils/hierarchicalSelection/index.ts +6 -0
- package/src/utils/hierarchicalSelection/toggleHierarchicalSelection.ts +109 -0
- package/src/utils/hierarchicalSelection/types.ts +26 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/innerRef.ts +37 -0
- package/src/utils/isBrowser.ts +9 -0
- package/src/utils/keyboard.ts +68 -0
- package/src/utils/stopEventPropagation.ts +5 -0
- package/src/utils/throttle.ts +55 -0
- package/src/utils/typeGuards.ts +9 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { checkGroupSelection } from './checkGroupSelection';
|
|
2
|
+
import { ToggleHierarchicalSelectionParams } from './types';
|
|
3
|
+
|
|
4
|
+
function resolveIsSelected<TId extends string | number>({
|
|
5
|
+
nodeId,
|
|
6
|
+
descendantIds,
|
|
7
|
+
selectableDescendantIds,
|
|
8
|
+
childIds,
|
|
9
|
+
selectedIds,
|
|
10
|
+
includeParentsInValue,
|
|
11
|
+
}: ToggleHierarchicalSelectionParams<TId>): boolean {
|
|
12
|
+
const hasDescendants = descendantIds.length > 0;
|
|
13
|
+
|
|
14
|
+
if (!includeParentsInValue && hasDescendants) {
|
|
15
|
+
const idsForChecked = childIds ?? descendantIds;
|
|
16
|
+
const selectable = selectableDescendantIds ?? descendantIds;
|
|
17
|
+
const allChildrenSelected = idsForChecked.length > 0 && idsForChecked.every(id => selectedIds.includes(id));
|
|
18
|
+
const allEnabledSelected = selectable.length > 0 && selectable.every(id => selectedIds.includes(id));
|
|
19
|
+
|
|
20
|
+
return allChildrenSelected || allEnabledSelected;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (hasDescendants) {
|
|
24
|
+
const { allSelected, someSelected } = checkGroupSelection(descendantIds, selectedIds);
|
|
25
|
+
|
|
26
|
+
return allSelected || someSelected;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return selectedIds.includes(nodeId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toggleWithParentsInValue<TId extends string | number>({
|
|
33
|
+
nodeId,
|
|
34
|
+
descendantIds,
|
|
35
|
+
selectedIds,
|
|
36
|
+
ancestors,
|
|
37
|
+
isSelected,
|
|
38
|
+
}: ToggleHierarchicalSelectionParams<TId> & { isSelected: boolean }): TId[] {
|
|
39
|
+
const idsFromNode = [nodeId, ...descendantIds];
|
|
40
|
+
|
|
41
|
+
let updatedSelectedIds = isSelected
|
|
42
|
+
? selectedIds.filter(id => !idsFromNode.includes(id))
|
|
43
|
+
: [...selectedIds, ...idsFromNode.filter(id => !selectedIds.includes(id))];
|
|
44
|
+
|
|
45
|
+
if (!ancestors?.length) {
|
|
46
|
+
return updatedSelectedIds;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const ancestor of ancestors) {
|
|
50
|
+
if (!ancestor.childIds.length) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parentSelection = checkGroupSelection(ancestor.childIds, updatedSelectedIds);
|
|
55
|
+
|
|
56
|
+
if (isSelected) {
|
|
57
|
+
if (!parentSelection.allSelected) {
|
|
58
|
+
updatedSelectedIds = updatedSelectedIds.filter(id => id !== ancestor.id);
|
|
59
|
+
}
|
|
60
|
+
} else if (parentSelection.allSelected && !updatedSelectedIds.includes(ancestor.id)) {
|
|
61
|
+
updatedSelectedIds = [...updatedSelectedIds, ancestor.id];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return updatedSelectedIds;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toggleWithoutParentsInValue<TId extends string | number>({
|
|
69
|
+
nodeId,
|
|
70
|
+
descendantIds,
|
|
71
|
+
selectableDescendantIds,
|
|
72
|
+
selectedIds,
|
|
73
|
+
isSelected,
|
|
74
|
+
}: ToggleHierarchicalSelectionParams<TId> & { isSelected: boolean }): TId[] {
|
|
75
|
+
if (!descendantIds.length) {
|
|
76
|
+
if (isSelected) {
|
|
77
|
+
return selectedIds.filter(id => id !== nodeId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (selectedIds.includes(nodeId)) {
|
|
81
|
+
return selectedIds;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return [...selectedIds, nodeId];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const selectable = selectableDescendantIds ?? descendantIds;
|
|
88
|
+
|
|
89
|
+
if (isSelected) {
|
|
90
|
+
return selectedIds.filter(id => id !== nodeId && !selectable.includes(id));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return [...new Set([...selectedIds, ...selectable])];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Пересчитывает выбранные id после toggle узла с каскадом вниз и опциональным walk вверх.
|
|
98
|
+
*/
|
|
99
|
+
export function toggleHierarchicalSelection<TId extends string | number>(
|
|
100
|
+
params: ToggleHierarchicalSelectionParams<TId>,
|
|
101
|
+
): TId[] {
|
|
102
|
+
const isSelected = resolveIsSelected(params);
|
|
103
|
+
|
|
104
|
+
if (params.includeParentsInValue) {
|
|
105
|
+
return toggleWithParentsInValue({ ...params, isSelected });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return toggleWithoutParentsInValue({ ...params, isSelected });
|
|
109
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type GroupSelectionState = {
|
|
2
|
+
allSelected: boolean;
|
|
3
|
+
someSelected: boolean;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type HierarchicalAncestor<TId extends string | number = string> = {
|
|
7
|
+
id: TId;
|
|
8
|
+
childIds: TId[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type GetNodeSelectionStateParams<TId extends string | number = string> = {
|
|
12
|
+
nodeId: TId;
|
|
13
|
+
childIds: TId[];
|
|
14
|
+
selectedIds: TId[];
|
|
15
|
+
includeParentsInValue: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ToggleHierarchicalSelectionParams<TId extends string | number = string> = {
|
|
19
|
+
nodeId: TId;
|
|
20
|
+
descendantIds: TId[];
|
|
21
|
+
selectableDescendantIds?: TId[];
|
|
22
|
+
childIds?: TId[];
|
|
23
|
+
selectedIds: TId[];
|
|
24
|
+
ancestors?: HierarchicalAncestor<TId>[];
|
|
25
|
+
includeParentsInValue: boolean;
|
|
26
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './componentPropsProcessors';
|
|
2
|
+
export * from './debounce';
|
|
3
|
+
export * from './hierarchicalSelection';
|
|
4
|
+
export * from './innerRef';
|
|
5
|
+
export * from './isBrowser';
|
|
6
|
+
export * from './copyToClipboard';
|
|
7
|
+
export * from './keyboard';
|
|
8
|
+
export * from './throttle';
|
|
9
|
+
export * from './typeGuards';
|
|
10
|
+
export * from './stopEventPropagation';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Маркер «компонент принимает DOM-ноду своего корня через проп `innerRef`».
|
|
3
|
+
*
|
|
4
|
+
* Нужен потому, что интроспекция пропсов функционального компонента в рантайме невозможна: обёртки вроде
|
|
5
|
+
* `Popover` / `Tooltip` / `Dropdown` получают триггер как `ReactElement` и должны решить, каким каналом отдать
|
|
6
|
+
* ему reference-ноду. У `forwardRef`-компонента это `ref` (определяется через `react-is`), у обычной функции —
|
|
7
|
+
* проп `innerRef`, и узнать о его поддержке можно только по явной пометке.
|
|
8
|
+
*
|
|
9
|
+
* Ключ — `Symbol.for`, поэтому маркер общий для всех копий `@cloud-ru/ds-utils` в дереве зависимостей.
|
|
10
|
+
*/
|
|
11
|
+
export const SUPPORTS_INNER_REF = Symbol.for('@cloud-ru/ds-supports-inner-ref');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Помечает компонент как принимающий `innerRef`. Вызывается один раз рядом с объявлением компонента:
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* export function Button<T extends ElementType = 'button'>({ innerRef, ...rest }: ButtonProps<T>) { … }
|
|
18
|
+
* withInnerRefSupport(Button);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Помечать нужно только те компоненты, которые прокидывают `innerRef` на **корневой** DOM-узел — именно его
|
|
22
|
+
* ждёт позиционирование поповера. Компоненты, у которых `innerRef` ведёт на внутренний слот (например, на
|
|
23
|
+
* нативный `input`), не помечаем.
|
|
24
|
+
*/
|
|
25
|
+
export function withInnerRefSupport<T extends object>(component: T): T {
|
|
26
|
+
Object.defineProperty(component, SUPPORTS_INNER_REF, { value: true });
|
|
27
|
+
|
|
28
|
+
return component;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Принимает ли тип элемента DOM-ноду через проп `innerRef`. */
|
|
32
|
+
export function supportsInnerRef(type: unknown): boolean {
|
|
33
|
+
return (
|
|
34
|
+
(typeof type === 'function' || (typeof type === 'object' && type !== null)) &&
|
|
35
|
+
Boolean((type as Record<symbol, unknown>)[SUPPORTS_INNER_REF])
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Функция проверки текущей среды выполнения кода,
|
|
3
|
+
* нужна для корректной работы SSR
|
|
4
|
+
* @function helper
|
|
5
|
+
*/
|
|
6
|
+
export function isBrowser() {
|
|
7
|
+
// eslint-disable-next-line @cloud-ru/ssr-safe-react/domApi
|
|
8
|
+
return Boolean(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
9
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Клавиши-стрелки. Внутри компонентов с собственной клавиатурной навигацией (списки, сетка
|
|
3
|
+
* календаря, roving-кнопки) их нативное поведение — прокрутка страницы — нужно гасить, иначе
|
|
4
|
+
* событие «улетает» за компонент и скроллит документ.
|
|
5
|
+
*/
|
|
6
|
+
export const ARROW_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'] as const;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Вертикальные стрелки. Для текстовых полей гасить нужно только их (горизонтальные `ArrowLeft` /
|
|
10
|
+
* `ArrowRight` двигают каретку — их перехватывать нельзя), тогда как в сетках/списках/roving-кнопках
|
|
11
|
+
* без текста гасятся все четыре (см. {@link ARROW_KEYS}).
|
|
12
|
+
*/
|
|
13
|
+
export const VERTICAL_ARROW_KEYS = ['ArrowUp', 'ArrowDown'] as const;
|
|
14
|
+
|
|
15
|
+
export type ArrowKey = (typeof ARROW_KEYS)[number];
|
|
16
|
+
|
|
17
|
+
/** `true`, если клавиша — одна из стрелок навигации. */
|
|
18
|
+
export function isArrowKey(key: string): key is ArrowKey {
|
|
19
|
+
return (ARROW_KEYS as readonly string[]).includes(key);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** `true`, если клавиша — вертикальная стрелка (`ArrowUp` / `ArrowDown`). */
|
|
23
|
+
export function isVerticalArrowKey(key: string): boolean {
|
|
24
|
+
return (VERTICAL_ARROW_KEYS as readonly string[]).includes(key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Гасит нативную прокрутку страницы для клавиш-стрелок: если нажата стрелка — вызывает
|
|
29
|
+
* `preventDefault()` и возвращает `true`. Использовать в начале keydown-обработчика компонента,
|
|
30
|
+
* который сам обрабатывает стрелочную навигацию.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const handleKeyDown = (e) => {
|
|
34
|
+
* preventScrollOnArrowKeys(e);
|
|
35
|
+
* // …собственная навигация
|
|
36
|
+
* };
|
|
37
|
+
*/
|
|
38
|
+
export function preventScrollOnArrowKeys(event: Pick<KeyboardEvent, 'key' | 'preventDefault'>): boolean {
|
|
39
|
+
if (isArrowKey(event.key)) {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Как {@link preventScrollOnArrowKeys}, но только для вертикальных стрелок (`ArrowUp` / `ArrowDown`).
|
|
49
|
+
* Для текстовых полей: гасит прокрутку страницы по Up/Down, не трогая горизонтальные стрелки —
|
|
50
|
+
* каретка по тексту продолжает двигаться.
|
|
51
|
+
*/
|
|
52
|
+
export function preventScrollOnVerticalArrows(event: Pick<KeyboardEvent, 'key' | 'preventDefault'>): boolean {
|
|
53
|
+
if (isVerticalArrowKey(event.key)) {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Программный фокус без авто-прокрутки страницы к элементу (`focus({ preventScroll: true })`).
|
|
63
|
+
* Применять при перемещении фокуса по клавиатуре внутри уже видимого компонента (ячейка
|
|
64
|
+
* календаря, элемент списка), иначе браузер скроллит документ к новому фокусу.
|
|
65
|
+
*/
|
|
66
|
+
export function focusWithoutScroll(element: HTMLElement | null | undefined): void {
|
|
67
|
+
element?.focus({ preventScroll: true });
|
|
68
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Функция-обёртка `throttle`: вызывается как исходная, плюс умеет отменить отложенный вызов. */
|
|
2
|
+
export type Throttled<TArgs extends unknown[]> = {
|
|
3
|
+
(...args: TArgs): void;
|
|
4
|
+
/** Отменяет запланированный вызов по заднему фронту. */
|
|
5
|
+
cancel(): void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ограничивает частоту вызовов `callback` до одного раза в `wait` мс.
|
|
10
|
+
* Срабатывает по обоим фронтам: первый вызов проходит сразу, последний в окне — по его истечении.
|
|
11
|
+
*/
|
|
12
|
+
export function throttle<TArgs extends unknown[]>(callback: (...args: TArgs) => void, wait = 0): Throttled<TArgs> {
|
|
13
|
+
let lastInvokeTime = 0;
|
|
14
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
15
|
+
let pendingArgs: TArgs | undefined;
|
|
16
|
+
|
|
17
|
+
const invoke = (args: TArgs) => {
|
|
18
|
+
lastInvokeTime = Date.now();
|
|
19
|
+
callback(...args);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const cancel = () => {
|
|
23
|
+
if (timer !== undefined) {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
timer = undefined;
|
|
26
|
+
}
|
|
27
|
+
pendingArgs = undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const throttled = (...args: TArgs): void => {
|
|
31
|
+
const remaining = wait - (Date.now() - lastInvokeTime);
|
|
32
|
+
|
|
33
|
+
if (remaining <= 0) {
|
|
34
|
+
cancel();
|
|
35
|
+
invoke(args);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pendingArgs = args;
|
|
40
|
+
|
|
41
|
+
if (timer === undefined) {
|
|
42
|
+
timer = setTimeout(() => {
|
|
43
|
+
timer = undefined;
|
|
44
|
+
|
|
45
|
+
if (pendingArgs !== undefined) {
|
|
46
|
+
const args = pendingArgs;
|
|
47
|
+
pendingArgs = undefined;
|
|
48
|
+
invoke(args);
|
|
49
|
+
}
|
|
50
|
+
}, remaining);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return Object.assign(throttled, { cancel });
|
|
55
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const isNil = (value: unknown): value is undefined | null => value === undefined || value === null;
|
|
2
|
+
|
|
3
|
+
export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';
|
|
4
|
+
|
|
5
|
+
export const isString = (value: unknown): value is string => typeof value === 'string';
|
|
6
|
+
|
|
7
|
+
export const isArray = <T>(value: unknown): value is Array<T> => Array.isArray(value);
|
|
8
|
+
|
|
9
|
+
export const isObject = (value: unknown): value is object => typeof value === 'object' && !isArray(value);
|