@cloud-ru/ds-list 2.0.3-preview-6a131ef8.0 → 2.0.3

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,10 @@
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
+ ## 2.0.3 (2026-08-28)
7
+
8
+ **Note:** Version bump only for package @ds/list
9
+
6
10
  ## 2.0.2 (2026-08-27)
7
11
 
8
12
  ### Bug Fixes
@@ -29,9 +29,7 @@ footer, onItemsReorder, ...rest }) {
29
29
  const portalContext = (0, portal_context_1.usePortalContext)();
30
30
  const scrollRef = (0, react_1.useRef)(null);
31
31
  const [open = false, setIsOpen] = (0, utils_1.useValueControl)({ value: openProp, onChange: onOpenChange });
32
- // Стек заходов во вложенные next-list'ы: индексный путь от корня на каждый уровень.
33
- // Пустой — корневой уровень. Хранится путь, а не сам айтем: айтемы пересобираются на каждом
34
- // рендере, и сохранённая копия оставила бы открытый sheet в устаревшем состоянии.
32
+ // Стек заходов во вложенные next-list'ы. Пустой корневой уровень.
35
33
  const [path, setPath] = (0, react_1.useState)([]);
36
34
  const handleClose = () => {
37
35
  setIsOpen(false);
@@ -93,10 +91,7 @@ footer, onItemsReorder, ...rest }) {
93
91
  const containerRef = portalContext.current ?? undefined;
94
92
  // Каскад sheet'ов: корень + по одному sheet'у на каждый заход в next-list. Каждый накладывается
95
93
  // поверх предыдущего (свой backdrop), а не заменяет его содержимое. Назад — закрывает верхний.
96
- const sheets = [{ source: items, title: label, item: undefined }].concat(path.map(indexPath => {
97
- const item = (0, utils_2.resolveItemByPath)(items, indexPath);
98
- return { source: (item?.items ?? []), title: (0, utils_2.nextListOption)(item), item };
99
- }));
94
+ const sheets = [{ source: items, title: label, item: undefined }].concat(path.map(item => ({ source: item.items, title: (0, utils_2.nextListOption)(item), item })));
100
95
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [trigger, sheets.map(({ source, title, item }, levelIndex) => {
101
96
  const isRoot = levelIndex === 0;
102
97
  // Режим переупорядочивания (`onItemsReorder`) — только корень: дерево
@@ -105,7 +100,7 @@ footer, onItemsReorder, ...rest }) {
105
100
  const reorderRoot = Boolean(isRoot && onItemsReorder);
106
101
  const levelItems = reorderRoot
107
102
  ? undefined
108
- : (0, utils_2.buildLevelItems)(source, relativePath => setPath(prev => [...prev.slice(0, levelIndex), [...(prev[levelIndex - 1] ?? []), ...relativePath]]), handleClose, closeOnClick);
103
+ : (0, utils_2.buildLevelItems)(source, next => setPath(prev => [...prev.slice(0, levelIndex), next]), handleClose, closeOnClick);
109
104
  // Поиск/виртуализация/footer/actions — только на корне; sublists всегда статичны по контенту.
110
105
  const searchable = isRoot && Boolean(search);
111
106
  const expanded = searchable || (isRoot && virtualized);
@@ -1,18 +1,11 @@
1
1
  import { Item, NextListItem } from '../../components/Items';
2
- /**
3
- * Айтем уровня по индексному пути от корня. Путь разрешается на каждом рендере по актуальным
4
- * `items`: иначе открытый sheet сохранял бы копию айтема, полученную при заходе во вложенный
5
- * список, и не отражал бы её последующие изменения (например, смену выбранной строки).
6
- */
7
- export declare function resolveItemByPath(items: Item[], indexPath: number[]): NextListItem | undefined;
8
2
  /** Текст `label` next-list-айтема — заголовок шапки sheet'а при заходе во вложенный список. */
9
- export declare function nextListOption(item?: NextListItem): string | undefined;
3
+ export declare function nextListOption(item: NextListItem): string | undefined;
10
4
  /**
11
5
  * Готовит айтемы текущего уровня sheet'а:
12
6
  * - `next-list` превращается в базовый айтем с шевроном `>`, клик по которому уводит на уровень вложенного
13
7
  * списка (drill-down), а не открывает второй BottomSheet (десктопный nested-popover на mobile неприменим);
14
- * позиция айтема передаётся в `onDrill` индексным путём — уровень разрешается по актуальным `items`;
15
8
  * - `group` / `group-select` / `collapse` рекурсивно обрабатываются (внутри них тоже может быть `next-list`);
16
9
  * - базовые айтемы (action-меню без `selection`) при `closeOnClick` закрывают sheet по клику.
17
10
  */
18
- export declare function buildLevelItems(items: Item[], onDrill: (indexPath: number[]) => void, onClose: () => void, closeOnClick: boolean, basePath?: number[]): Item[];
11
+ export declare function buildLevelItems(items: Item[], onDrill: (item: NextListItem) => void, onClose: () => void, closeOnClick: boolean): Item[];
@@ -1,32 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveItemByPath = resolveItemByPath;
4
3
  exports.nextListOption = nextListOption;
5
4
  exports.buildLevelItems = buildLevelItems;
6
5
  const jsx_runtime_1 = require("react/jsx-runtime");
7
6
  const system_1 = require("@cloud-ru/ds-icons/interface/system");
8
7
  const constants_1 = require("../../constants");
9
- /**
10
- * Айтем уровня по индексному пути от корня. Путь разрешается на каждом рендере по актуальным
11
- * `items`: иначе открытый sheet сохранял бы копию айтема, полученную при заходе во вложенный
12
- * список, и не отражал бы её последующие изменения (например, смену выбранной строки).
13
- */
14
- function resolveItemByPath(items, indexPath) {
15
- let source = items;
16
- let node;
17
- for (const index of indexPath) {
18
- node = source?.[index];
19
- if (!node || typeof node !== 'object' || !('items' in node)) {
20
- source = undefined;
21
- continue;
22
- }
23
- source = node.items;
24
- }
25
- return node;
26
- }
27
8
  /** Текст `label` next-list-айтема — заголовок шапки sheet'а при заходе во вложенный список. */
28
9
  function nextListOption(item) {
29
- const content = item?.content;
10
+ const { content } = item;
30
11
  if (content && typeof content === 'object' && 'label' in content) {
31
12
  return String(content.label);
32
13
  }
@@ -36,13 +17,11 @@ function nextListOption(item) {
36
17
  * Готовит айтемы текущего уровня sheet'а:
37
18
  * - `next-list` превращается в базовый айтем с шевроном `>`, клик по которому уводит на уровень вложенного
38
19
  * списка (drill-down), а не открывает второй BottomSheet (десктопный nested-popover на mobile неприменим);
39
- * позиция айтема передаётся в `onDrill` индексным путём — уровень разрешается по актуальным `items`;
40
20
  * - `group` / `group-select` / `collapse` рекурсивно обрабатываются (внутри них тоже может быть `next-list`);
41
21
  * - базовые айтемы (action-меню без `selection`) при `closeOnClick` закрывают sheet по клику.
42
22
  */
43
- function buildLevelItems(items, onDrill, onClose, closeOnClick, basePath = []) {
44
- return items.map((item, index) => {
45
- const indexPath = [...basePath, index];
23
+ function buildLevelItems(items, onDrill, onClose, closeOnClick) {
24
+ return items.map(item => {
46
25
  if (item && typeof item === 'object' && 'type' in item) {
47
26
  if (item.type === constants_1.ITEM_TYPE.NextList) {
48
27
  const next = item;
@@ -63,12 +42,12 @@ function buildLevelItems(items, onDrill, onClose, closeOnClick, basePath = []) {
63
42
  afterContent: (0, jsx_runtime_1.jsx)(system_1.ChevronRightSVG, {}),
64
43
  onClick: (event) => {
65
44
  next.onClick?.(event);
66
- onDrill(indexPath);
45
+ onDrill(next);
67
46
  },
68
47
  };
69
48
  }
70
49
  if (item.type === constants_1.ITEM_TYPE.Group || item.type === constants_1.ITEM_TYPE.GroupSelect || item.type === constants_1.ITEM_TYPE.Collapse) {
71
- return { ...item, items: buildLevelItems(item.items, onDrill, onClose, closeOnClick, indexPath) };
50
+ return { ...item, items: buildLevelItems(item.items, onDrill, onClose, closeOnClick) };
72
51
  }
73
52
  }
74
53
  if (closeOnClick && item && typeof item === 'object' && 'onClick' in item) {
@@ -6,7 +6,7 @@ import { cloneElement, isValidElement, useMemo, useRef, useState } from 'react';
6
6
  import { List, ReorderableList } from '../../components/Lists/List/index.js';
7
7
  import { TEST_IDS } from '../../constants.js';
8
8
  import styles from './MobileDroplist.module.css';
9
- import { buildLevelItems, nextListOption, resolveItemByPath } from './utils.js';
9
+ import { buildLevelItems, nextListOption } from './utils.js';
10
10
  /**
11
11
  * Mobile-вариант `Droplist`: рендерит `List` (в переданном `size`) внутри `BottomSheet` из `@cloud-ru/ds-bottom-sheet`.
12
12
  * Триггер (`children`) клонируется для открытия sheet'а по клику/Enter/Space. `next-list`-айтемы на mobile
@@ -23,9 +23,7 @@ footer, onItemsReorder, ...rest }) {
23
23
  const portalContext = usePortalContext();
24
24
  const scrollRef = useRef(null);
25
25
  const [open = false, setIsOpen] = useValueControl({ value: openProp, onChange: onOpenChange });
26
- // Стек заходов во вложенные next-list'ы: индексный путь от корня на каждый уровень.
27
- // Пустой — корневой уровень. Хранится путь, а не сам айтем: айтемы пересобираются на каждом
28
- // рендере, и сохранённая копия оставила бы открытый sheet в устаревшем состоянии.
26
+ // Стек заходов во вложенные next-list'ы. Пустой корневой уровень.
29
27
  const [path, setPath] = useState([]);
30
28
  const handleClose = () => {
31
29
  setIsOpen(false);
@@ -87,10 +85,7 @@ footer, onItemsReorder, ...rest }) {
87
85
  const containerRef = portalContext.current ?? undefined;
88
86
  // Каскад sheet'ов: корень + по одному sheet'у на каждый заход в next-list. Каждый накладывается
89
87
  // поверх предыдущего (свой backdrop), а не заменяет его содержимое. Назад — закрывает верхний.
90
- const sheets = [{ source: items, title: label, item: undefined }].concat(path.map(indexPath => {
91
- const item = resolveItemByPath(items, indexPath);
92
- return { source: (item?.items ?? []), title: nextListOption(item), item };
93
- }));
88
+ const sheets = [{ source: items, title: label, item: undefined }].concat(path.map(item => ({ source: item.items, title: nextListOption(item), item })));
94
89
  return (_jsxs(_Fragment, { children: [trigger, sheets.map(({ source, title, item }, levelIndex) => {
95
90
  const isRoot = levelIndex === 0;
96
91
  // Режим переупорядочивания (`onItemsReorder`) — только корень: дерево
@@ -99,7 +94,7 @@ footer, onItemsReorder, ...rest }) {
99
94
  const reorderRoot = Boolean(isRoot && onItemsReorder);
100
95
  const levelItems = reorderRoot
101
96
  ? undefined
102
- : buildLevelItems(source, relativePath => setPath(prev => [...prev.slice(0, levelIndex), [...(prev[levelIndex - 1] ?? []), ...relativePath]]), handleClose, closeOnClick);
97
+ : buildLevelItems(source, next => setPath(prev => [...prev.slice(0, levelIndex), next]), handleClose, closeOnClick);
103
98
  // Поиск/виртуализация/footer/actions — только на корне; sublists всегда статичны по контенту.
104
99
  const searchable = isRoot && Boolean(search);
105
100
  const expanded = searchable || (isRoot && virtualized);
@@ -1,18 +1,11 @@
1
1
  import { Item, NextListItem } from '../../components/Items/index.js';
2
- /**
3
- * Айтем уровня по индексному пути от корня. Путь разрешается на каждом рендере по актуальным
4
- * `items`: иначе открытый sheet сохранял бы копию айтема, полученную при заходе во вложенный
5
- * список, и не отражал бы её последующие изменения (например, смену выбранной строки).
6
- */
7
- export declare function resolveItemByPath(items: Item[], indexPath: number[]): NextListItem | undefined;
8
2
  /** Текст `label` next-list-айтема — заголовок шапки sheet'а при заходе во вложенный список. */
9
- export declare function nextListOption(item?: NextListItem): string | undefined;
3
+ export declare function nextListOption(item: NextListItem): string | undefined;
10
4
  /**
11
5
  * Готовит айтемы текущего уровня sheet'а:
12
6
  * - `next-list` превращается в базовый айтем с шевроном `>`, клик по которому уводит на уровень вложенного
13
7
  * списка (drill-down), а не открывает второй BottomSheet (десктопный nested-popover на mobile неприменим);
14
- * позиция айтема передаётся в `onDrill` индексным путём — уровень разрешается по актуальным `items`;
15
8
  * - `group` / `group-select` / `collapse` рекурсивно обрабатываются (внутри них тоже может быть `next-list`);
16
9
  * - базовые айтемы (action-меню без `selection`) при `closeOnClick` закрывают sheet по клику.
17
10
  */
18
- export declare function buildLevelItems(items: Item[], onDrill: (indexPath: number[]) => void, onClose: () => void, closeOnClick: boolean, basePath?: number[]): Item[];
11
+ export declare function buildLevelItems(items: Item[], onDrill: (item: NextListItem) => void, onClose: () => void, closeOnClick: boolean): Item[];
@@ -1,27 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ChevronRightSVG } from '@cloud-ru/ds-icons/interface/system';
3
3
  import { ITEM_TYPE } from '../../constants.js';
4
- /**
5
- * Айтем уровня по индексному пути от корня. Путь разрешается на каждом рендере по актуальным
6
- * `items`: иначе открытый sheet сохранял бы копию айтема, полученную при заходе во вложенный
7
- * список, и не отражал бы её последующие изменения (например, смену выбранной строки).
8
- */
9
- export function resolveItemByPath(items, indexPath) {
10
- let source = items;
11
- let node;
12
- for (const index of indexPath) {
13
- node = source?.[index];
14
- if (!node || typeof node !== 'object' || !('items' in node)) {
15
- source = undefined;
16
- continue;
17
- }
18
- source = node.items;
19
- }
20
- return node;
21
- }
22
4
  /** Текст `label` next-list-айтема — заголовок шапки sheet'а при заходе во вложенный список. */
23
5
  export function nextListOption(item) {
24
- const content = item?.content;
6
+ const { content } = item;
25
7
  if (content && typeof content === 'object' && 'label' in content) {
26
8
  return String(content.label);
27
9
  }
@@ -31,13 +13,11 @@ export function nextListOption(item) {
31
13
  * Готовит айтемы текущего уровня sheet'а:
32
14
  * - `next-list` превращается в базовый айтем с шевроном `>`, клик по которому уводит на уровень вложенного
33
15
  * списка (drill-down), а не открывает второй BottomSheet (десктопный nested-popover на mobile неприменим);
34
- * позиция айтема передаётся в `onDrill` индексным путём — уровень разрешается по актуальным `items`;
35
16
  * - `group` / `group-select` / `collapse` рекурсивно обрабатываются (внутри них тоже может быть `next-list`);
36
17
  * - базовые айтемы (action-меню без `selection`) при `closeOnClick` закрывают sheet по клику.
37
18
  */
38
- export function buildLevelItems(items, onDrill, onClose, closeOnClick, basePath = []) {
39
- return items.map((item, index) => {
40
- const indexPath = [...basePath, index];
19
+ export function buildLevelItems(items, onDrill, onClose, closeOnClick) {
20
+ return items.map(item => {
41
21
  if (item && typeof item === 'object' && 'type' in item) {
42
22
  if (item.type === ITEM_TYPE.NextList) {
43
23
  const next = item;
@@ -58,12 +38,12 @@ export function buildLevelItems(items, onDrill, onClose, closeOnClick, basePath
58
38
  afterContent: _jsx(ChevronRightSVG, {}),
59
39
  onClick: (event) => {
60
40
  next.onClick?.(event);
61
- onDrill(indexPath);
41
+ onDrill(next);
62
42
  },
63
43
  };
64
44
  }
65
45
  if (item.type === ITEM_TYPE.Group || item.type === ITEM_TYPE.GroupSelect || item.type === ITEM_TYPE.Collapse) {
66
- return { ...item, items: buildLevelItems(item.items, onDrill, onClose, closeOnClick, indexPath) };
46
+ return { ...item, items: buildLevelItems(item.items, onDrill, onClose, closeOnClick) };
67
47
  }
68
48
  }
69
49
  if (closeOnClick && item && typeof item === 'object' && 'onClick' in item) {