@snack-uikit/utils 3.6.0 → 3.7.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 CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.7.0 (2025-01-23)
7
+
8
+
9
+ ### Features
10
+
11
+ * **PDS-926:** add useDynamicList hook ([1d95c09](https://github.com/cloud-ru-tech/snack-uikit/commit/1d95c0969ef96e7b891e927543a38b0929edc760))
12
+
13
+
14
+
15
+
16
+
6
17
  # 3.6.0 (2024-12-12)
7
18
 
8
19
 
package/README.md CHANGED
@@ -18,6 +18,11 @@
18
18
 
19
19
  Хук задерживает выполнение функции или обновление значения до тех пор,
20
20
  пока не пройдет определенный период времени без новых вызовов.
21
+ ## useDynamicList
22
+ `React hook`
23
+
24
+ Хук позволяет распределять элементы списка на две группы: видимые и невидимые,
25
+ в зависимости от ширины контейнера
21
26
  ## useEventHandler
22
27
  `React hook`
23
28
 
@@ -1,4 +1,5 @@
1
1
  export * from './useDebounce';
2
+ export * from './useDynamicList';
2
3
  export * from './useEventHandler';
3
4
  export * from './useIsomorphicLayoutEffect';
4
5
  export * from './useSwipeable';
@@ -23,6 +23,7 @@ Object.defineProperty(exports, "__esModule", {
23
23
  value: true
24
24
  });
25
25
  __exportStar(require("./useDebounce"), exports);
26
+ __exportStar(require("./useDynamicList"), exports);
26
27
  __exportStar(require("./useEventHandler"), exports);
27
28
  __exportStar(require("./useIsomorphicLayoutEffect"), exports);
28
29
  __exportStar(require("./useSwipeable"), exports);
@@ -0,0 +1,18 @@
1
+ import { RefObject } from 'react';
2
+ type UseDynamicListProps<T extends object> = {
3
+ items: T[];
4
+ resizingContainerRef?: RefObject<HTMLDivElement>;
5
+ parentContainerRef: RefObject<HTMLDivElement>;
6
+ maxVisibleItems?: number;
7
+ };
8
+ type UseDynamicListReturnType<T extends object> = {
9
+ visibleItems: T[];
10
+ hiddenItems: T[];
11
+ };
12
+ /**
13
+ * Хук позволяет распределять элементы списка на две группы: видимые и невидимые,
14
+ * в зависимости от ширины контейнера
15
+ * @function React hook
16
+ */
17
+ export declare function useDynamicList<T extends object>({ parentContainerRef, resizingContainerRef, items, maxVisibleItems, }: UseDynamicListProps<T>): UseDynamicListReturnType<T>;
18
+ export {};
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useDynamicList = useDynamicList;
7
+ const react_1 = require("react");
8
+ const useEventHandler_1 = require("./useEventHandler");
9
+ const useIsomorphicLayoutEffect_1 = require("./useIsomorphicLayoutEffect");
10
+ /**
11
+ * Хук позволяет распределять элементы списка на две группы: видимые и невидимые,
12
+ * в зависимости от ширины контейнера
13
+ * @function React hook
14
+ */
15
+ function useDynamicList(_ref) {
16
+ let {
17
+ parentContainerRef,
18
+ resizingContainerRef = parentContainerRef,
19
+ items,
20
+ maxVisibleItems = Infinity
21
+ } = _ref;
22
+ const adjustAmount = value => Math.min(maxVisibleItems, value);
23
+ const [visibleItemsAmount, setVisibleItemsAmount] = (0, react_1.useState)(adjustAmount(items.length));
24
+ const [width, setWidth] = (0, react_1.useState)(Infinity);
25
+ const widthRef = (0, react_1.useRef)(width);
26
+ const handleVisibleItemsAmount = param => {
27
+ if (typeof param === 'number') {
28
+ setVisibleItemsAmount(adjustAmount(param));
29
+ } else {
30
+ setVisibleItemsAmount(v => param(adjustAmount(v)));
31
+ }
32
+ };
33
+ const tryHidingItem = (0, useEventHandler_1.useEventHandler)(() => {
34
+ const container = parentContainerRef.current;
35
+ if (container && container.scrollWidth - container.offsetWidth > 0) {
36
+ const itemToHide = items[visibleItemsAmount - 1];
37
+ if (itemToHide) {
38
+ handleVisibleItemsAmount(value => value - 1);
39
+ }
40
+ }
41
+ });
42
+ const tryShowingItem = (0, useEventHandler_1.useEventHandler)(() => {
43
+ const itemToShow = items[visibleItemsAmount];
44
+ if (itemToShow) {
45
+ handleVisibleItemsAmount(value => value + 1);
46
+ }
47
+ });
48
+ const toggleItemWidth = (0, useEventHandler_1.useEventHandler)(_ref2 => {
49
+ let {
50
+ changedWidth,
51
+ initialWidth
52
+ } = _ref2;
53
+ if (changedWidth > initialWidth) {
54
+ //try to add extra item
55
+ if (visibleItemsAmount < maxVisibleItems) {
56
+ tryShowingItem();
57
+ }
58
+ } else if (changedWidth < initialWidth) {
59
+ // check if item should be hidden
60
+ tryHidingItem();
61
+ }
62
+ });
63
+ (0, react_1.useEffect)(() => {
64
+ const listener = () => {
65
+ tryHidingItem();
66
+ if (parentContainerRef.current) {
67
+ setWidth(parentContainerRef.current.scrollWidth);
68
+ }
69
+ };
70
+ document.fonts.addEventListener('loadingdone', listener);
71
+ return () => document.fonts.removeEventListener('loadingdone', listener);
72
+ }, [parentContainerRef, tryHidingItem]);
73
+ (0, react_1.useEffect)(() => {
74
+ const container = resizingContainerRef.current;
75
+ if (container) {
76
+ const observer = new ResizeObserver(entities => entities.forEach(entity => {
77
+ if (entity.target === container) {
78
+ const [{
79
+ inlineSize: newWidth
80
+ }] = entity.contentBoxSize;
81
+ setWidth(Math.floor(newWidth));
82
+ }
83
+ }));
84
+ observer.observe(container);
85
+ return () => observer.disconnect();
86
+ }
87
+ }, [resizingContainerRef]);
88
+ (0, useIsomorphicLayoutEffect_1.useLayoutEffect)(() => {
89
+ if (parentContainerRef.current) {
90
+ toggleItemWidth({
91
+ initialWidth: parentContainerRef.current.scrollWidth,
92
+ changedWidth: widthRef.current
93
+ });
94
+ }
95
+ }, [items, parentContainerRef, toggleItemWidth]);
96
+ (0, useIsomorphicLayoutEffect_1.useLayoutEffect)(() => {
97
+ toggleItemWidth({
98
+ initialWidth: widthRef.current,
99
+ changedWidth: width
100
+ });
101
+ widthRef.current = width;
102
+ }, [width, toggleItemWidth]);
103
+ (0, useIsomorphicLayoutEffect_1.useLayoutEffect)(() => {
104
+ tryHidingItem();
105
+ }, [tryHidingItem, visibleItemsAmount]);
106
+ return {
107
+ visibleItems: items.slice(0, visibleItemsAmount),
108
+ hiddenItems: items.slice(visibleItemsAmount)
109
+ };
110
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './useDebounce';
2
+ export * from './useDynamicList';
2
3
  export * from './useEventHandler';
3
4
  export * from './useIsomorphicLayoutEffect';
4
5
  export * from './useSwipeable';
@@ -1,4 +1,5 @@
1
1
  export * from './useDebounce';
2
+ export * from './useDynamicList';
2
3
  export * from './useEventHandler';
3
4
  export * from './useIsomorphicLayoutEffect';
4
5
  export * from './useSwipeable';
@@ -0,0 +1,18 @@
1
+ import { RefObject } from 'react';
2
+ type UseDynamicListProps<T extends object> = {
3
+ items: T[];
4
+ resizingContainerRef?: RefObject<HTMLDivElement>;
5
+ parentContainerRef: RefObject<HTMLDivElement>;
6
+ maxVisibleItems?: number;
7
+ };
8
+ type UseDynamicListReturnType<T extends object> = {
9
+ visibleItems: T[];
10
+ hiddenItems: T[];
11
+ };
12
+ /**
13
+ * Хук позволяет распределять элементы списка на две группы: видимые и невидимые,
14
+ * в зависимости от ширины контейнера
15
+ * @function React hook
16
+ */
17
+ export declare function useDynamicList<T extends object>({ parentContainerRef, resizingContainerRef, items, maxVisibleItems, }: UseDynamicListProps<T>): UseDynamicListReturnType<T>;
18
+ export {};
@@ -0,0 +1,94 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { useEventHandler } from './useEventHandler';
3
+ import { useLayoutEffect } from './useIsomorphicLayoutEffect';
4
+ /**
5
+ * Хук позволяет распределять элементы списка на две группы: видимые и невидимые,
6
+ * в зависимости от ширины контейнера
7
+ * @function React hook
8
+ */
9
+ export function useDynamicList({ parentContainerRef, resizingContainerRef = parentContainerRef, items, maxVisibleItems = Infinity, }) {
10
+ const adjustAmount = (value) => Math.min(maxVisibleItems, value);
11
+ const [visibleItemsAmount, setVisibleItemsAmount] = useState(adjustAmount(items.length));
12
+ const [width, setWidth] = useState(Infinity);
13
+ const widthRef = useRef(width);
14
+ const handleVisibleItemsAmount = param => {
15
+ if (typeof param === 'number') {
16
+ setVisibleItemsAmount(adjustAmount(param));
17
+ }
18
+ else {
19
+ setVisibleItemsAmount(v => param(adjustAmount(v)));
20
+ }
21
+ };
22
+ const tryHidingItem = useEventHandler(() => {
23
+ const container = parentContainerRef.current;
24
+ if (container && container.scrollWidth - container.offsetWidth > 0) {
25
+ const itemToHide = items[visibleItemsAmount - 1];
26
+ if (itemToHide) {
27
+ handleVisibleItemsAmount(value => value - 1);
28
+ }
29
+ }
30
+ });
31
+ const tryShowingItem = useEventHandler(() => {
32
+ const itemToShow = items[visibleItemsAmount];
33
+ if (itemToShow) {
34
+ handleVisibleItemsAmount(value => value + 1);
35
+ }
36
+ });
37
+ const toggleItemWidth = useEventHandler(({ changedWidth, initialWidth }) => {
38
+ if (changedWidth > initialWidth) {
39
+ //try to add extra item
40
+ if (visibleItemsAmount < maxVisibleItems) {
41
+ tryShowingItem();
42
+ }
43
+ }
44
+ else if (changedWidth < initialWidth) {
45
+ // check if item should be hidden
46
+ tryHidingItem();
47
+ }
48
+ });
49
+ useEffect(() => {
50
+ const listener = () => {
51
+ tryHidingItem();
52
+ if (parentContainerRef.current) {
53
+ setWidth(parentContainerRef.current.scrollWidth);
54
+ }
55
+ };
56
+ document.fonts.addEventListener('loadingdone', listener);
57
+ return () => document.fonts.removeEventListener('loadingdone', listener);
58
+ }, [parentContainerRef, tryHidingItem]);
59
+ useEffect(() => {
60
+ const container = resizingContainerRef.current;
61
+ if (container) {
62
+ const observer = new ResizeObserver(entities => entities.forEach(entity => {
63
+ if (entity.target === container) {
64
+ const [{ inlineSize: newWidth }] = entity.contentBoxSize;
65
+ setWidth(Math.floor(newWidth));
66
+ }
67
+ }));
68
+ observer.observe(container);
69
+ return () => observer.disconnect();
70
+ }
71
+ }, [resizingContainerRef]);
72
+ useLayoutEffect(() => {
73
+ if (parentContainerRef.current) {
74
+ toggleItemWidth({
75
+ initialWidth: parentContainerRef.current.scrollWidth,
76
+ changedWidth: widthRef.current,
77
+ });
78
+ }
79
+ }, [items, parentContainerRef, toggleItemWidth]);
80
+ useLayoutEffect(() => {
81
+ toggleItemWidth({
82
+ initialWidth: widthRef.current,
83
+ changedWidth: width,
84
+ });
85
+ widthRef.current = width;
86
+ }, [width, toggleItemWidth]);
87
+ useLayoutEffect(() => {
88
+ tryHidingItem();
89
+ }, [tryHidingItem, visibleItemsAmount]);
90
+ return {
91
+ visibleItems: items.slice(0, visibleItemsAmount),
92
+ hiddenItems: items.slice(visibleItemsAmount),
93
+ };
94
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Utils",
7
- "version": "3.6.0",
7
+ "version": "3.7.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -39,5 +39,5 @@
39
39
  "react-swipeable": "7.0.2",
40
40
  "uncontrollable": "8.0.4"
41
41
  },
42
- "gitHead": "ac8fa414e1a84f72594d9c509b0daca04b5e48be"
42
+ "gitHead": "3528e78aec23804dca44d311947c1dcd4ed3792b"
43
43
  }
@@ -1,4 +1,5 @@
1
1
  export * from './useDebounce';
2
+ export * from './useDynamicList';
2
3
  export * from './useEventHandler';
3
4
  export * from './useIsomorphicLayoutEffect';
4
5
  export * from './useSwipeable';
@@ -0,0 +1,135 @@
1
+ import { RefObject, useEffect, useRef, useState } from 'react';
2
+
3
+ import { useEventHandler } from './useEventHandler';
4
+ import { useLayoutEffect } from './useIsomorphicLayoutEffect';
5
+
6
+ type UseDynamicListProps<T extends object> = {
7
+ items: T[];
8
+ resizingContainerRef?: RefObject<HTMLDivElement>;
9
+ parentContainerRef: RefObject<HTMLDivElement>;
10
+ maxVisibleItems?: number;
11
+ };
12
+
13
+ type UseDynamicListReturnType<T extends object> = {
14
+ visibleItems: T[];
15
+ hiddenItems: T[];
16
+ };
17
+
18
+ /**
19
+ * Хук позволяет распределять элементы списка на две группы: видимые и невидимые,
20
+ * в зависимости от ширины контейнера
21
+ * @function React hook
22
+ */
23
+ export function useDynamicList<T extends object>({
24
+ parentContainerRef,
25
+ resizingContainerRef = parentContainerRef,
26
+ items,
27
+ maxVisibleItems = Infinity,
28
+ }: UseDynamicListProps<T>): UseDynamicListReturnType<T> {
29
+ const adjustAmount = (value: number) => Math.min(maxVisibleItems, value);
30
+
31
+ const [visibleItemsAmount, setVisibleItemsAmount] = useState<number>(adjustAmount(items.length));
32
+ const [width, setWidth] = useState(Infinity);
33
+ const widthRef = useRef(width);
34
+
35
+ const handleVisibleItemsAmount: typeof setVisibleItemsAmount = param => {
36
+ if (typeof param === 'number') {
37
+ setVisibleItemsAmount(adjustAmount(param));
38
+ } else {
39
+ setVisibleItemsAmount(v => param(adjustAmount(v)));
40
+ }
41
+ };
42
+
43
+ const tryHidingItem = useEventHandler(() => {
44
+ const container = parentContainerRef.current;
45
+
46
+ if (container && container.scrollWidth - container.offsetWidth > 0) {
47
+ const itemToHide = items[visibleItemsAmount - 1];
48
+
49
+ if (itemToHide) {
50
+ handleVisibleItemsAmount(value => value - 1);
51
+ }
52
+ }
53
+ });
54
+
55
+ const tryShowingItem = useEventHandler(() => {
56
+ const itemToShow = items[visibleItemsAmount];
57
+
58
+ if (itemToShow) {
59
+ handleVisibleItemsAmount(value => value + 1);
60
+ }
61
+ });
62
+
63
+ const toggleItemWidth = useEventHandler(
64
+ ({ changedWidth, initialWidth }: { changedWidth: number; initialWidth: number }) => {
65
+ if (changedWidth > initialWidth) {
66
+ //try to add extra item
67
+ if (visibleItemsAmount < maxVisibleItems) {
68
+ tryShowingItem();
69
+ }
70
+ } else if (changedWidth < initialWidth) {
71
+ // check if item should be hidden
72
+ tryHidingItem();
73
+ }
74
+ },
75
+ );
76
+
77
+ useEffect(() => {
78
+ const listener = () => {
79
+ tryHidingItem();
80
+
81
+ if (parentContainerRef.current) {
82
+ setWidth(parentContainerRef.current.scrollWidth);
83
+ }
84
+ };
85
+
86
+ document.fonts.addEventListener('loadingdone', listener);
87
+ return () => document.fonts.removeEventListener('loadingdone', listener);
88
+ }, [parentContainerRef, tryHidingItem]);
89
+
90
+ useEffect(() => {
91
+ const container = resizingContainerRef.current;
92
+
93
+ if (container) {
94
+ const observer = new ResizeObserver(entities =>
95
+ entities.forEach(entity => {
96
+ if (entity.target === container) {
97
+ const [{ inlineSize: newWidth }] = entity.contentBoxSize;
98
+ setWidth(Math.floor(newWidth));
99
+ }
100
+ }),
101
+ );
102
+
103
+ observer.observe(container);
104
+
105
+ return () => observer.disconnect();
106
+ }
107
+ }, [resizingContainerRef]);
108
+
109
+ useLayoutEffect(() => {
110
+ if (parentContainerRef.current) {
111
+ toggleItemWidth({
112
+ initialWidth: parentContainerRef.current.scrollWidth,
113
+ changedWidth: widthRef.current,
114
+ });
115
+ }
116
+ }, [items, parentContainerRef, toggleItemWidth]);
117
+
118
+ useLayoutEffect(() => {
119
+ toggleItemWidth({
120
+ initialWidth: widthRef.current,
121
+ changedWidth: width,
122
+ });
123
+
124
+ widthRef.current = width;
125
+ }, [width, toggleItemWidth]);
126
+
127
+ useLayoutEffect(() => {
128
+ tryHidingItem();
129
+ }, [tryHidingItem, visibleItemsAmount]);
130
+
131
+ return {
132
+ visibleItems: items.slice(0, visibleItemsAmount),
133
+ hiddenItems: items.slice(visibleItemsAmount),
134
+ };
135
+ }