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