@cloud-ru/ds-list 2.0.4-preview-5926424e.0 → 2.0.4

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.4 (2026-08-28)
7
+
8
+ **Note:** Version bump only for package @ds/list
9
+
6
10
  ## 2.0.3 (2026-08-28)
7
11
 
8
12
  **Note:** Version bump only for package @ds/list
package/README.md CHANGED
@@ -900,7 +900,6 @@ export function DroplistWithHeader() {
900
900
  | `selection` | `SelectionMultipleState` \| `SelectionSingleState` | — | Настройки выбора элементов. `mode: 'single'` — один выбранный элемент (`value: ItemId`), <br/> `mode: 'multiple'` — множественный выбор (`value: ItemId[]`). Без `selection` выбора нет — <br/> клик вызывает только `onClick` элемента. |
901
901
  | `size` | `"l"` \| `"m"` \| `"s"` | — | Размер списка |
902
902
  | `slotAfterTitle` | `ReactNode` | — | Только mobile (`BottomSheet`): slot справа от заголовка. |
903
- | `snapPoints` | `SnapPoint` | — | Массив фиксированных позиций sheet'а от меньшей к большей. По дефолту `undefined` — <br/> sheet `height: auto` с одним snap'ом по высоте контента. <br/> Пример: `[0.5, 1]` — sheet открывается на половину экрана, drag вверх раскрывает <br/> до full-viewport; drag вниз ниже `0.5` ведёт к закрытию. <br/> Контракт массива (движок не сортирует и не дедуплицирует — порядок и различимость на <br/> стороне потребителя): <br/> - строго по возрастанию: индекс `0` — самая компактная позиция, последний — top / expanded; <br/> - значения должны резолвиться в различные высоты (`['50%', 0.5]` на типичном вьюпорте дадут <br/> одну высоту → дубль-индекс будет недостижим свайпом); <br/> - `'fit-content'` имеет смысл только как ЕДИНСТВЕННЫЙ snap (без `snapPoints`); внутри массива <br/> фиксированных позиций его «контентная» высота не определена. |
904
903
  | `trigger` | `"click"` \| `"clickAndFocusVisible"` \| `"focus"` \| `"focusVisible"` \| `"hover"` \| `"hoverAndFocus"` \| `"hoverAndFocusVisible"` | — | Условие отображения поповера: <br/> - `click` - открывать по клику <br/> - `hover` - открывать по ховеру <br/> - `focusVisible` - открывать по focus-visible <br/> - `focus` - открывать по фокусу <br/> - `hoverAndFocusVisible` - открывать по ховеру и focus-visible <br/> - `hoverAndFocus` - открывать по ховеру и фокусу <br/> - `clickAndFocusVisible` - открывать по клику и focus-visible |
905
904
  | `triggerClassName` | `string` | — | CSS-класс триггера |
906
905
  | `triggerElemRef` | `RefObject<HTMLElement>` | — | Ссылка на элемент-триггер для дроплиста |
@@ -963,6 +962,8 @@ export function DroplistWithHeader() {
963
962
 
964
963
  - `Item` = `BaseItem | GroupItem | GroupSelectItem | NextListItem | AccordionItem`
965
964
 
965
+ - `ItemId` = `string | number`
966
+
966
967
  **ScrollProps**
967
968
 
968
969
  | Prop | Type | Default | Description |
@@ -1,5 +1,4 @@
1
1
  import { DragEndEvent } from '@dnd-kit/core';
2
- import { BottomSheetProps } from '@cloud-ru/ds-bottom-sheet';
3
2
  import { DropdownProps } from '@cloud-ru/ds-dropdown';
4
3
  import { WithSupportProps } from '@cloud-ru/ds-utils';
5
4
  import { FocusEvent, KeyboardEvent, ReactNode, RefObject } from 'react';
@@ -162,7 +161,7 @@ type DroplistMobileSlots = {
162
161
  * (список size `l` в `BottomSheet`), иначе — `DesktopDroplist` (анкорный popover). Mobile-слоты
163
162
  * применяются только на mobile.
164
163
  */
165
- export type DroplistProps = BaseDroplistProps & DroplistMobileSlots & Pick<BottomSheetProps, 'snapPoints'>;
164
+ export type DroplistProps = BaseDroplistProps & DroplistMobileSlots;
166
165
  /**
167
166
  * Props адаптивного `ReorderableDroplist`: props `ReorderableList` + popover/mobile-обвязка
168
167
  * `Droplist`. Отдельный компонент по той же причине, что и `ReorderableList`.
@@ -174,7 +173,7 @@ export type DroplistImplProps = DesktopDroplistProps & DroplistMobileSlots;
174
173
  * @internal Props mobile-реализации (`BottomSheet`): реализация дроплиста без popover-пропов
175
174
  * + mobile-слоты.
176
175
  */
177
- export type MobileDroplistProps = Omit<DesktopDroplistProps, 'trigger' | 'placement' | 'widthStrategy' | 'triggerElemRef' | 'listRef' | 'triggerClassName'> & Pick<BottomSheetProps, 'snapPoints'> & DroplistMobileSlots;
176
+ export type MobileDroplistProps = Omit<DesktopDroplistProps, 'trigger' | 'placement' | 'widthStrategy' | 'triggerElemRef' | 'listRef' | 'triggerClassName'> & DroplistMobileSlots;
178
177
  export type ListPrivateProps = Omit<ListImplProps, 'pinTop' | 'pinBottom' | 'items' | 'hasListInFocusChain' | 'onItemsReorder' | 'virtualized'> & {
179
178
  nested?: boolean;
180
179
  active?: boolean;
@@ -6,4 +6,4 @@ import { MobileDroplistProps } from '../../components/Lists/types';
6
6
  * вложенного списка, в шапке появляется его заголовок и кнопка «назад». Internal — наружу не реэкспортится;
7
7
  * рендерится адаптивным `Droplist` на `layoutType='mobile'`.
8
8
  */
9
- export declare function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size, snapPoints, footer, onItemsReorder, ...rest }: MobileDroplistProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size, footer, onItemsReorder, ...rest }: MobileDroplistProps): import("react/jsx-runtime").JSX.Element;
@@ -20,7 +20,7 @@ const utils_2 = require("./utils");
20
20
  * вложенного списка, в шапке появляется его заголовок и кнопка «назад». Internal — наружу не реэкспортится;
21
21
  * рендерится адаптивным `Droplist` на `layoutType='mobile'`.
22
22
  */
23
- function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size, snapPoints,
23
+ function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size,
24
24
  // `footer` уводим в sticky-футер `BottomSheet` (кнопки действия прилипают к низу sheet'а, а не
25
25
  // скроллятся вместе со списком). `header` + `headerDivider` / `footerDivider` НЕ деструктурируем —
26
26
  // они уходят в `...rest` на корневой `List` (он рендерит шапку и её divider).
@@ -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);
@@ -143,6 +138,6 @@ footer, onItemsReorder, ...rest }) {
143
138
  container: container?.current ?? containerRef, open: isRoot ? open : true,
144
139
  // Dismiss (backdrop / swipe / Esc): на корне — закрыть весь droplist; глубже — вернуться на уровень вверх,
145
140
  // как ждёт пользователь на mobile (а не закрывать всю цепочку разом).
146
- onClose: isRoot ? handleClose : popLevel, withDividers: true, title: title, onBackButtonClick: handleBack, actionButton: isRoot ? actionButton : undefined, slotAfterTitle: isRoot ? slotAfterTitle : undefined, content: content, footer: isRoot ? footer : undefined, snapPoints: expanded ? [1] : snapPoints, closeOnPopstate: closeOnPopstate ?? true }, isRoot ? 'root' : String(item?.id ?? levelIndex)));
141
+ onClose: isRoot ? handleClose : popLevel, withDividers: true, title: title, onBackButtonClick: handleBack, actionButton: isRoot ? actionButton : undefined, slotAfterTitle: isRoot ? slotAfterTitle : undefined, content: content, footer: isRoot ? footer : undefined, snapPoints: expanded ? [1] : undefined, closeOnPopstate: closeOnPopstate ?? true }, isRoot ? 'root' : String(item?.id ?? levelIndex)));
147
142
  })] }));
148
143
  }
@@ -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) {
@@ -1,5 +1,4 @@
1
1
  import { DragEndEvent } from '@dnd-kit/core';
2
- import { BottomSheetProps } from '@cloud-ru/ds-bottom-sheet';
3
2
  import { DropdownProps } from '@cloud-ru/ds-dropdown';
4
3
  import { WithSupportProps } from '@cloud-ru/ds-utils';
5
4
  import { FocusEvent, KeyboardEvent, ReactNode, RefObject } from 'react';
@@ -162,7 +161,7 @@ type DroplistMobileSlots = {
162
161
  * (список size `l` в `BottomSheet`), иначе — `DesktopDroplist` (анкорный popover). Mobile-слоты
163
162
  * применяются только на mobile.
164
163
  */
165
- export type DroplistProps = BaseDroplistProps & DroplistMobileSlots & Pick<BottomSheetProps, 'snapPoints'>;
164
+ export type DroplistProps = BaseDroplistProps & DroplistMobileSlots;
166
165
  /**
167
166
  * Props адаптивного `ReorderableDroplist`: props `ReorderableList` + popover/mobile-обвязка
168
167
  * `Droplist`. Отдельный компонент по той же причине, что и `ReorderableList`.
@@ -174,7 +173,7 @@ export type DroplistImplProps = DesktopDroplistProps & DroplistMobileSlots;
174
173
  * @internal Props mobile-реализации (`BottomSheet`): реализация дроплиста без popover-пропов
175
174
  * + mobile-слоты.
176
175
  */
177
- export type MobileDroplistProps = Omit<DesktopDroplistProps, 'trigger' | 'placement' | 'widthStrategy' | 'triggerElemRef' | 'listRef' | 'triggerClassName'> & Pick<BottomSheetProps, 'snapPoints'> & DroplistMobileSlots;
176
+ export type MobileDroplistProps = Omit<DesktopDroplistProps, 'trigger' | 'placement' | 'widthStrategy' | 'triggerElemRef' | 'listRef' | 'triggerClassName'> & DroplistMobileSlots;
178
177
  export type ListPrivateProps = Omit<ListImplProps, 'pinTop' | 'pinBottom' | 'items' | 'hasListInFocusChain' | 'onItemsReorder' | 'virtualized'> & {
179
178
  nested?: boolean;
180
179
  active?: boolean;
@@ -6,4 +6,4 @@ import { MobileDroplistProps } from '../../components/Lists/types.js';
6
6
  * вложенного списка, в шапке появляется его заголовок и кнопка «назад». Internal — наружу не реэкспортится;
7
7
  * рендерится адаптивным `Droplist` на `layoutType='mobile'`.
8
8
  */
9
- export declare function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size, snapPoints, footer, onItemsReorder, ...rest }: MobileDroplistProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size, footer, onItemsReorder, ...rest }: MobileDroplistProps): import("react/jsx-runtime").JSX.Element;
@@ -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
@@ -14,7 +14,7 @@ import { buildLevelItems, nextListOption, resolveItemByPath } from './utils.js';
14
14
  * вложенного списка, в шапке появляется его заголовок и кнопка «назад». Internal — наружу не реэкспортится;
15
15
  * рендерится адаптивным `Droplist` на `layoutType='mobile'`.
16
16
  */
17
- export function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size, snapPoints,
17
+ export function MobileDroplist({ items, selection, open: openProp, onOpenChange, children, search, label, actionButton, slotAfterTitle, onBackButtonClick, virtualized, closeDroplistOnItemClick, scroll, container, closeOnPopstate, size,
18
18
  // `footer` уводим в sticky-футер `BottomSheet` (кнопки действия прилипают к низу sheet'а, а не
19
19
  // скроллятся вместе со списком). `header` + `headerDivider` / `footerDivider` НЕ деструктурируем —
20
20
  // они уходят в `...rest` на корневой `List` (он рендерит шапку и её divider).
@@ -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);
@@ -137,6 +132,6 @@ footer, onItemsReorder, ...rest }) {
137
132
  container: container?.current ?? containerRef, open: isRoot ? open : true,
138
133
  // Dismiss (backdrop / swipe / Esc): на корне — закрыть весь droplist; глубже — вернуться на уровень вверх,
139
134
  // как ждёт пользователь на mobile (а не закрывать всю цепочку разом).
140
- onClose: isRoot ? handleClose : popLevel, withDividers: true, title: title, onBackButtonClick: handleBack, actionButton: isRoot ? actionButton : undefined, slotAfterTitle: isRoot ? slotAfterTitle : undefined, content: content, footer: isRoot ? footer : undefined, snapPoints: expanded ? [1] : snapPoints, closeOnPopstate: closeOnPopstate ?? true }, isRoot ? 'root' : String(item?.id ?? levelIndex)));
135
+ onClose: isRoot ? handleClose : popLevel, withDividers: true, title: title, onBackButtonClick: handleBack, actionButton: isRoot ? actionButton : undefined, slotAfterTitle: isRoot ? slotAfterTitle : undefined, content: content, footer: isRoot ? footer : undefined, snapPoints: expanded ? [1] : undefined, closeOnPopstate: closeOnPopstate ?? true }, isRoot ? 'root' : String(item?.id ?? levelIndex)));
141
136
  })] }));
142
137
  }
@@ -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) {