@cloud-ru/ds-fields 2.2.2 → 2.2.3-preview-0c4119af.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.
Files changed (60) hide show
  1. package/README.md +3 -1
  2. package/dist/cjs/components/FieldSelect/FieldSelect.js +81 -41
  3. package/dist/cjs/components/FieldSelect/constants.d.ts +11 -0
  4. package/dist/cjs/components/FieldSelect/constants.js +20 -1
  5. package/dist/cjs/components/FieldSelect/hooks.d.ts +32 -0
  6. package/dist/cjs/components/FieldSelect/hooks.js +83 -0
  7. package/dist/cjs/components/FieldSelect/types.d.ts +26 -3
  8. package/dist/cjs/components/FieldSelect/utils/customOption.d.ts +21 -0
  9. package/dist/cjs/components/FieldSelect/utils/customOption.js +55 -0
  10. package/dist/cjs/components/FieldSelect/utils/extract.d.ts +11 -0
  11. package/dist/cjs/components/FieldSelect/utils/extract.js +39 -0
  12. package/dist/cjs/components/FieldSelect/utils/filter.d.ts +3 -0
  13. package/dist/cjs/components/FieldSelect/utils/filter.js +40 -0
  14. package/dist/cjs/components/FieldSelect/utils/index.d.ts +6 -0
  15. package/dist/cjs/components/FieldSelect/utils/index.js +22 -0
  16. package/dist/cjs/components/FieldSelect/utils/items.d.ts +9 -0
  17. package/dist/cjs/components/FieldSelect/utils/items.js +47 -0
  18. package/dist/cjs/components/FieldSelect/utils/selection.d.ts +11 -0
  19. package/dist/cjs/components/FieldSelect/utils/selection.js +18 -0
  20. package/dist/cjs/components/FieldSelect/utils/tagSize.d.ts +4 -0
  21. package/dist/cjs/components/FieldSelect/utils/tagSize.js +5 -0
  22. package/dist/esm/components/FieldSelect/FieldSelect.js +82 -42
  23. package/dist/esm/components/FieldSelect/constants.d.ts +11 -0
  24. package/dist/esm/components/FieldSelect/constants.js +19 -0
  25. package/dist/esm/components/FieldSelect/hooks.d.ts +32 -0
  26. package/dist/esm/components/FieldSelect/hooks.js +80 -0
  27. package/dist/esm/components/FieldSelect/types.d.ts +26 -3
  28. package/dist/esm/components/FieldSelect/utils/customOption.d.ts +21 -0
  29. package/dist/esm/components/FieldSelect/utils/customOption.js +48 -0
  30. package/dist/esm/components/FieldSelect/utils/extract.d.ts +11 -0
  31. package/dist/esm/components/FieldSelect/utils/extract.js +34 -0
  32. package/dist/esm/components/FieldSelect/utils/filter.d.ts +3 -0
  33. package/dist/esm/components/FieldSelect/utils/filter.js +36 -0
  34. package/dist/esm/components/FieldSelect/utils/index.d.ts +6 -0
  35. package/dist/esm/components/FieldSelect/utils/index.js +6 -0
  36. package/dist/esm/components/FieldSelect/utils/items.d.ts +9 -0
  37. package/dist/esm/components/FieldSelect/utils/items.js +41 -0
  38. package/dist/esm/components/FieldSelect/utils/selection.d.ts +11 -0
  39. package/dist/esm/components/FieldSelect/utils/selection.js +14 -0
  40. package/dist/esm/components/FieldSelect/utils/tagSize.d.ts +4 -0
  41. package/dist/esm/components/FieldSelect/utils/tagSize.js +2 -0
  42. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  43. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  44. package/package.json +20 -20
  45. package/src/components/FieldSelect/FieldSelect.tsx +129 -61
  46. package/src/components/FieldSelect/constants.ts +22 -0
  47. package/src/components/FieldSelect/hooks.ts +162 -0
  48. package/src/components/FieldSelect/types.ts +82 -48
  49. package/src/components/FieldSelect/utils/customOption.ts +77 -0
  50. package/src/components/FieldSelect/utils/extract.ts +49 -0
  51. package/src/components/FieldSelect/utils/filter.ts +51 -0
  52. package/src/components/FieldSelect/utils/index.ts +6 -0
  53. package/src/components/FieldSelect/utils/items.ts +58 -0
  54. package/src/components/FieldSelect/utils/selection.ts +25 -0
  55. package/src/components/FieldSelect/utils/tagSize.ts +5 -0
  56. package/dist/cjs/components/FieldSelect/utils.d.ts +0 -19
  57. package/dist/cjs/components/FieldSelect/utils.js +0 -103
  58. package/dist/esm/components/FieldSelect/utils.d.ts +0 -19
  59. package/dist/esm/components/FieldSelect/utils.js +0 -92
  60. package/src/components/FieldSelect/utils.ts +0 -132
@@ -5,11 +5,41 @@ import { ValueOf } from '@cloud-ru/ds-utils';
5
5
  import { FocusEvent, KeyboardEvent, ReactNode } from 'react';
6
6
 
7
7
  import { FieldLayoutPresets } from '../../hooks';
8
- import { SELECTION_MODE } from './constants';
8
+ import {
9
+ ADD_CUSTOM_OPTION_TRIGGER,
10
+ ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE,
11
+ ADD_CUSTOM_OPTION_TRIGGERS_SINGLE,
12
+ SELECTION_MODE,
13
+ } from './constants';
9
14
 
10
15
  /** Режим выбора FieldSelect: одно значение или несколько. */
11
16
  export type Selection = ValueOf<typeof SELECTION_MODE>;
12
17
 
18
+ /** Событие, по которому произвольный ввод фиксируется как выбранная опция. */
19
+ export type FieldSelectAddCustomOptionTrigger = ValueOf<typeof ADD_CUSTOM_OPTION_TRIGGER>;
20
+
21
+ /** Триггеры кастомной опции в режиме `single`. */
22
+ export type FieldSelectSingleAddCustomOptionTrigger = (typeof ADD_CUSTOM_OPTION_TRIGGERS_SINGLE)[number];
23
+
24
+ /** Триггеры кастомной опции в режиме `multiple`. */
25
+ export type FieldSelectMultipleAddCustomOptionTrigger = (typeof ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE)[number];
26
+
27
+ type CustomOptionProps<TTrigger extends FieldSelectAddCustomOptionTrigger> = {
28
+ /**
29
+ * Разрешить фиксировать произвольный ввод из строки поиска как выбранную опцию
30
+ * (значение, которого нет в `items`).
31
+ * @default false
32
+ */
33
+ allowCustomOption?: boolean;
34
+ /**
35
+ * События, по которым произвольный ввод фиксируется как выбранная опция.
36
+ * Учитывается только при `allowCustomOption={true}`.
37
+ * Если не задан — используются все триггеры режима (`single`: enter, blur; `multiple`: enter, blur, space, comma).
38
+ * Триггеры, недоступные в текущем `selection`, игнорируются.
39
+ */
40
+ addCustomOptionTriggers?: TTrigger[];
41
+ };
42
+
13
43
  // ItemProps — union с рекурсивными группами, поэтому раздаём поле по всем веткам.
14
44
  type WithTagAppearance<T> = T extends { items: ItemProps[] }
15
45
  ? Omit<T, 'items'> & { appearance?: TagAppearance; items: WithTagAppearance<ItemProps>[] }
@@ -115,6 +145,8 @@ type CommonSelectProps = FieldSelectDecoratorProps &
115
145
  autocomplete?: boolean;
116
146
  /**
117
147
  * Зафиксировать введённый текст как новый выбор по `Enter` (создание опции «на лету»).
148
+ *
149
+ * @deprecated Используйте `allowCustomOption`. `true` эквивалентен `allowCustomOption` с триггером `enter`.
118
150
  * @default false
119
151
  */
120
152
  addOptionByEnter?: boolean;
@@ -169,53 +201,55 @@ type CommonSelectProps = FieldSelectDecoratorProps &
169
201
  'data-test-id'?: string;
170
202
  };
171
203
 
172
- export type FieldSelectSingleProps = CommonSelectProps & {
173
- /** Режим выбора. По умолчанию `'single'`. */
174
- selection?: typeof SELECTION_MODE.Single;
175
- /** Управляемое значение. Пустая строка трактуется как «значение не выбрано». */
176
- value?: ItemId;
177
- /** Неуправляемое значение по умолчанию. Пустая строка трактуется как «значение не выбрано». */
178
- defaultValue?: ItemId;
179
- /** Колбек смены значения */
180
- onChange?(value: ItemId | undefined): void;
181
- /**
182
- * Закрывать дроплист после клика на айтем.
183
- * @default true
184
- */
185
- closeDroplistOnItemClick?: boolean;
186
- };
204
+ export type FieldSelectSingleProps = CommonSelectProps &
205
+ CustomOptionProps<FieldSelectSingleAddCustomOptionTrigger> & {
206
+ /** Режим выбора. По умолчанию `'single'`. */
207
+ selection?: typeof SELECTION_MODE.Single;
208
+ /** Управляемое значение. Пустая строка трактуется как «значение не выбрано». */
209
+ value?: ItemId;
210
+ /** Неуправляемое значение по умолчанию. Пустая строка трактуется как «значение не выбрано». */
211
+ defaultValue?: ItemId;
212
+ /** Колбек смены значения */
213
+ onChange?(value: ItemId | undefined): void;
214
+ /**
215
+ * Закрывать дроплист после клика на айтем.
216
+ * @default true
217
+ */
218
+ closeDroplistOnItemClick?: boolean;
219
+ };
187
220
 
188
- export type FieldSelectMultipleProps = CommonSelectProps & {
189
- /** Режим выбора */
190
- selection: typeof SELECTION_MODE.Multiple;
191
- /** Управляемые значения */
192
- value?: ItemId[];
193
- /** Неуправляемые значения по умолчанию */
194
- defaultValue?: ItemId[];
195
- /** Колбек смены значений */
196
- onChange?(value: ItemId[]): void;
197
- /**
198
- * Форматтер строки выбранных значений (используется, если `chips=false`).
199
- * @default список лейблов через `, `
200
- */
201
- formatSelected?(selected: { id: ItemId; label: string }[]): string;
202
- /**
203
- * Отображать выбранные значения как чипы (`@cloud-ru/ds-tag`) внутри поля. Если `false`,
204
- * показывает строку из `formatSelected` либо comma-joined.
205
- * @default true
206
- */
207
- chips?: boolean;
208
- /**
209
- * Удалять последний чип по `Backspace`, когда строка ввода пустая.
210
- * Работает только при `chips=true` и `searchable=true`.
211
- * @default true
212
- */
213
- removeByBackspace?: boolean;
214
- /**
215
- * Закрывать дроплист после клика на айтем.
216
- * @default false
217
- */
218
- closeDroplistOnItemClick?: boolean;
219
- };
221
+ export type FieldSelectMultipleProps = CommonSelectProps &
222
+ CustomOptionProps<FieldSelectMultipleAddCustomOptionTrigger> & {
223
+ /** Режим выбора */
224
+ selection: typeof SELECTION_MODE.Multiple;
225
+ /** Управляемые значения */
226
+ value?: ItemId[];
227
+ /** Неуправляемые значения по умолчанию */
228
+ defaultValue?: ItemId[];
229
+ /** Колбек смены значений */
230
+ onChange?(value: ItemId[]): void;
231
+ /**
232
+ * Форматтер строки выбранных значений (используется, если `chips=false`).
233
+ * @default — список лейблов через `, `
234
+ */
235
+ formatSelected?(selected: { id: ItemId; label: string }[]): string;
236
+ /**
237
+ * Отображать выбранные значения как чипы (`@cloud-ru/ds-tag`) внутри поля. Если `false`,
238
+ * показывает строку из `formatSelected` либо comma-joined.
239
+ * @default true
240
+ */
241
+ chips?: boolean;
242
+ /**
243
+ * Удалять последний чип по `Backspace`, когда строка ввода пустая.
244
+ * Работает только при `chips=true` и `searchable=true`.
245
+ * @default true
246
+ */
247
+ removeByBackspace?: boolean;
248
+ /**
249
+ * Закрывать дроплист после клика на айтем.
250
+ * @default false
251
+ */
252
+ closeDroplistOnItemClick?: boolean;
253
+ };
220
254
 
221
255
  export type FieldSelectProps = FieldSelectSingleProps | FieldSelectMultipleProps;
@@ -0,0 +1,77 @@
1
+ import { ADD_CUSTOM_OPTION_TRIGGER } from '../constants';
2
+ import { FieldSelectAddCustomOptionTrigger, FieldSelectMultipleAddCustomOptionTrigger } from '../types';
3
+
4
+ /**
5
+ * Сопоставляет клавишу с триггером кастомной опции (`enter` / `space` / `comma`).
6
+ * Сначала `KeyboardEvent.code`, затем `key` — если `code` пустой или не из словаря (`','` без `Comma`).
7
+ */
8
+ export function getCustomOptionTriggerByCode(
9
+ code: string,
10
+ key?: string,
11
+ ): FieldSelectMultipleAddCustomOptionTrigger | undefined {
12
+ switch (code) {
13
+ case 'Enter':
14
+ return ADD_CUSTOM_OPTION_TRIGGER.Enter;
15
+ case 'Space':
16
+ return ADD_CUSTOM_OPTION_TRIGGER.Space;
17
+ case 'Comma':
18
+ return ADD_CUSTOM_OPTION_TRIGGER.Comma;
19
+ default:
20
+ break;
21
+ }
22
+
23
+ switch (key) {
24
+ case 'Enter':
25
+ return ADD_CUSTOM_OPTION_TRIGGER.Enter;
26
+ case ' ':
27
+ return ADD_CUSTOM_OPTION_TRIGGER.Space;
28
+ case ',':
29
+ return ADD_CUSTOM_OPTION_TRIGGER.Comma;
30
+ default:
31
+ return undefined;
32
+ }
33
+ }
34
+
35
+ export function shouldHandleCustomOptionTrigger(
36
+ trigger: FieldSelectAddCustomOptionTrigger | undefined,
37
+ availableTriggers: readonly FieldSelectAddCustomOptionTrigger[],
38
+ ): trigger is FieldSelectAddCustomOptionTrigger {
39
+ return trigger !== undefined && availableTriggers.includes(trigger);
40
+ }
41
+
42
+ export function resolveAddCustomOptionTriggers(params: {
43
+ allowCustomOption: boolean;
44
+ addCustomOptionTriggers: readonly FieldSelectAddCustomOptionTrigger[] | undefined;
45
+ addOptionByEnter: boolean;
46
+ defaultTriggers: readonly FieldSelectAddCustomOptionTrigger[];
47
+ }): FieldSelectAddCustomOptionTrigger[] {
48
+ if (params.allowCustomOption) {
49
+ const source =
50
+ params.addCustomOptionTriggers !== undefined ? params.addCustomOptionTriggers : params.defaultTriggers;
51
+
52
+ return source.filter(trigger => params.defaultTriggers.includes(trigger));
53
+ }
54
+
55
+ if (params.addOptionByEnter) {
56
+ return [ADD_CUSTOM_OPTION_TRIGGER.Enter];
57
+ }
58
+
59
+ return [];
60
+ }
61
+
62
+ /** Фокус ушёл на дроплист, футер или postfix — кастомную опцию на blur фиксировать не нужно. */
63
+ export function isCustomOptionBlurInsideField(
64
+ relatedTarget: EventTarget | null,
65
+ nodes: (Node | null | undefined)[],
66
+ ): boolean {
67
+ return relatedTarget instanceof Node && nodes.some(node => Boolean(node?.contains(relatedTarget)));
68
+ }
69
+
70
+ /** Клик снаружи закрывает список, не снимая фокус с input — blur не придёт. */
71
+ export function shouldCommitCustomOptionOnClose(params: {
72
+ skip: boolean;
73
+ inputElement: Element | null;
74
+ activeElement: Element | null;
75
+ }): boolean {
76
+ return !params.skip && params.activeElement !== null && params.activeElement === params.inputElement;
77
+ }
@@ -0,0 +1,49 @@
1
+ import { ItemId } from '@cloud-ru/ds-list';
2
+ import { Appearance as TagAppearance } from '@cloud-ru/ds-tag';
3
+
4
+ export type WithIdContent = { id?: ItemId; content?: unknown; disabled?: boolean; appearance?: TagAppearance };
5
+
6
+ export function extractLabel(item: WithIdContent): string {
7
+ const { content, id } = item;
8
+
9
+ if (!content) {
10
+ return String(id ?? '');
11
+ }
12
+
13
+ if (typeof content === 'string' || typeof content === 'number') {
14
+ return String(content);
15
+ }
16
+
17
+ if (typeof content === 'object' && content !== null && 'label' in content) {
18
+ return String((content as { label: unknown }).label);
19
+ }
20
+
21
+ return String(id ?? '');
22
+ }
23
+
24
+ // Текст для поиска: лейбл (option) + caption + description — паритет с легаси `useSearch`,
25
+ // который матчил запрос по всем трём полям контента, а не только по лейблу.
26
+ export function extractSearchText(item: WithIdContent): string {
27
+ const parts = [extractLabel(item)];
28
+ const { content } = item;
29
+
30
+ if (content && typeof content === 'object') {
31
+ const c = content as { caption?: unknown; description?: unknown };
32
+
33
+ if (typeof c.caption === 'string') {
34
+ parts.push(c.caption);
35
+ }
36
+
37
+ if (typeof c.description === 'string') {
38
+ parts.push(c.description);
39
+ }
40
+ }
41
+
42
+ return parts.join(' ');
43
+ }
44
+
45
+ // Цвет тега выбранного значения (multiple) — паритет с легаси `option.appearance`.
46
+ // Проверка типа нужна и при типизированном поле: items часто приходят из ответа бэкенда.
47
+ export function extractAppearance(item?: WithIdContent): TagAppearance | undefined {
48
+ return typeof item?.appearance === 'string' ? item.appearance : undefined;
49
+ }
@@ -0,0 +1,51 @@
1
+ import { FieldSelectItem } from '../types';
2
+ import { extractSearchText, WithIdContent } from './extract';
3
+
4
+ // Subsequence-fuzzy: символы запроса должны встречаться в строке в исходном порядке.
5
+ // Пример: query=`lge` matches `Large` (L → r → arg → e).
6
+ export function isFuzzyMatch(haystack: string, needle: string): boolean {
7
+ if (!needle) {
8
+ return true;
9
+ }
10
+
11
+ let i = 0;
12
+
13
+ for (const ch of haystack) {
14
+ if (ch === needle[i]) {
15
+ i += 1;
16
+
17
+ if (i === needle.length) {
18
+ return true;
19
+ }
20
+ }
21
+ }
22
+
23
+ return false;
24
+ }
25
+
26
+ export function filterItems(items: FieldSelectItem[], query: string, fuzzy: boolean): FieldSelectItem[] {
27
+ if (!query) {
28
+ return items;
29
+ }
30
+
31
+ const q = query.toLowerCase();
32
+
33
+ const matches = (item: FieldSelectItem): boolean => {
34
+ const text = extractSearchText(item as WithIdContent).toLowerCase();
35
+
36
+ return fuzzy ? isFuzzyMatch(text, q) : text.includes(q);
37
+ };
38
+
39
+ const walk = (list: FieldSelectItem[]): FieldSelectItem[] =>
40
+ list.flatMap(item => {
41
+ if ('items' in item && Array.isArray(item.items)) {
42
+ const filteredChildren = walk(item.items as FieldSelectItem[]);
43
+
44
+ return filteredChildren.length > 0 ? [{ ...item, items: filteredChildren } as FieldSelectItem] : [];
45
+ }
46
+
47
+ return matches(item) ? [item] : [];
48
+ });
49
+
50
+ return walk(items);
51
+ }
@@ -0,0 +1,6 @@
1
+ export * from './customOption';
2
+ export * from './extract';
3
+ export * from './filter';
4
+ export * from './items';
5
+ export * from './selection';
6
+ export * from './tagSize';
@@ -0,0 +1,58 @@
1
+ import { ItemId } from '@cloud-ru/ds-list';
2
+
3
+ import { FieldSelectItem } from '../types';
4
+ import { WithIdContent } from './extract';
5
+
6
+ export function flatten(items: FieldSelectItem[]): WithIdContent[] {
7
+ const out: WithIdContent[] = [];
8
+
9
+ for (const item of items) {
10
+ out.push(item as WithIdContent);
11
+
12
+ if ('items' in item && Array.isArray(item.items)) {
13
+ out.push(...flatten(item.items as FieldSelectItem[]));
14
+ }
15
+ }
16
+
17
+ return out;
18
+ }
19
+
20
+ export function findItem(items: FieldSelectItem[], id: ItemId): WithIdContent | undefined {
21
+ for (const item of flatten(items)) {
22
+ if (item.id === id) {
23
+ return item;
24
+ }
25
+ }
26
+
27
+ return undefined;
28
+ }
29
+
30
+ /** Айтем для выбранного значения, которого нет в `items` (кастомная опция, ленивая подгрузка). */
31
+ export function createPlaceholderItem(id: ItemId): FieldSelectItem {
32
+ return { id, content: { label: String(id) } };
33
+ }
34
+
35
+ /** Выбранные id, которых нет в дереве айтемов — в дроплист их нужно добавить отдельно. */
36
+ export function getMissingSelectedItems(selectedIds: ItemId[], allItems: FieldSelectItem[]): FieldSelectItem[] {
37
+ if (selectedIds.length === 0) {
38
+ return [];
39
+ }
40
+
41
+ const existingIds = new Set<ItemId>();
42
+
43
+ for (const item of flatten(allItems)) {
44
+ if (item.id !== undefined) {
45
+ existingIds.add(item.id);
46
+ }
47
+ }
48
+
49
+ const missing: FieldSelectItem[] = [];
50
+
51
+ for (const id of selectedIds) {
52
+ if (id !== '' && !existingIds.has(id)) {
53
+ missing.push(createPlaceholderItem(id));
54
+ }
55
+ }
56
+
57
+ return missing;
58
+ }
@@ -0,0 +1,25 @@
1
+ import { ItemId } from '@cloud-ru/ds-list';
2
+
3
+ import { SELECTION_MODE } from '../constants';
4
+ import { FieldSelectMultipleProps, Selection } from '../types';
5
+
6
+ export function isMultiple(props: { selection?: Selection }): props is FieldSelectMultipleProps {
7
+ return props.selection === SELECTION_MODE.Multiple;
8
+ }
9
+
10
+ /** Выбранные id в нормализованном виде: массив и для `single`, и для `multiple`. */
11
+ export function getSelectedIds(params: {
12
+ multiple: boolean;
13
+ multipleValue: ItemId[];
14
+ singleValue: ItemId | undefined;
15
+ }): ItemId[] {
16
+ if (params.multiple) {
17
+ return params.multipleValue;
18
+ }
19
+
20
+ if (params.singleValue === undefined) {
21
+ return [];
22
+ }
23
+
24
+ return [params.singleValue];
25
+ }
@@ -0,0 +1,5 @@
1
+ import { Size } from '@cloud-ru/ds-field-decorator';
2
+ import { Size as TagSize } from '@cloud-ru/ds-tag';
3
+
4
+ /** Соответствие размера поля размеру чипа выбранного значения (`selection='multiple'`). */
5
+ export const TAG_SIZE_MAP: Record<Size, TagSize> = { s: 'xs', m: 'xs', l: 's' };
@@ -1,19 +0,0 @@
1
- import { Size } from '@cloud-ru/ds-field-decorator';
2
- import { ItemId } from '@cloud-ru/ds-list';
3
- import { Appearance as TagAppearance, Size as TagSize } from '@cloud-ru/ds-tag';
4
- import { FieldSelectItem, FieldSelectMultipleProps, FieldSelectProps } from './types';
5
- export declare const TAG_SIZE_MAP: Record<Size, TagSize>;
6
- export type WithIdContent = {
7
- id?: ItemId;
8
- content?: unknown;
9
- disabled?: boolean;
10
- appearance?: TagAppearance;
11
- };
12
- export declare function extractLabel(item: WithIdContent): string;
13
- export declare function extractSearchText(item: WithIdContent): string;
14
- export declare function extractAppearance(item?: WithIdContent): TagAppearance | undefined;
15
- export declare function flatten(items: FieldSelectItem[]): WithIdContent[];
16
- export declare function findItem(items: FieldSelectItem[], id: ItemId): WithIdContent | undefined;
17
- export declare function isFuzzyMatch(haystack: string, needle: string): boolean;
18
- export declare function filterItems(items: FieldSelectItem[], query: string, fuzzy: boolean): FieldSelectItem[];
19
- export declare function isMultiple(props: FieldSelectProps): props is FieldSelectMultipleProps;
@@ -1,103 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TAG_SIZE_MAP = void 0;
4
- exports.extractLabel = extractLabel;
5
- exports.extractSearchText = extractSearchText;
6
- exports.extractAppearance = extractAppearance;
7
- exports.flatten = flatten;
8
- exports.findItem = findItem;
9
- exports.isFuzzyMatch = isFuzzyMatch;
10
- exports.filterItems = filterItems;
11
- exports.isMultiple = isMultiple;
12
- const constants_1 = require("./constants");
13
- exports.TAG_SIZE_MAP = { s: 'xs', m: 'xs', l: 's' };
14
- function extractLabel(item) {
15
- const { content, id } = item;
16
- if (!content) {
17
- return String(id ?? '');
18
- }
19
- if (typeof content === 'string' || typeof content === 'number') {
20
- return String(content);
21
- }
22
- if (typeof content === 'object' && content !== null && 'label' in content) {
23
- return String(content.label);
24
- }
25
- return String(id ?? '');
26
- }
27
- // Текст для поиска: лейбл (option) + caption + description — паритет с легаси `useSearch`,
28
- // который матчил запрос по всем трём полям контента, а не только по лейблу.
29
- function extractSearchText(item) {
30
- const parts = [extractLabel(item)];
31
- const { content } = item;
32
- if (content && typeof content === 'object') {
33
- const c = content;
34
- if (typeof c.caption === 'string') {
35
- parts.push(c.caption);
36
- }
37
- if (typeof c.description === 'string') {
38
- parts.push(c.description);
39
- }
40
- }
41
- return parts.join(' ');
42
- }
43
- // Цвет тега выбранного значения (multiple) — паритет с легаси `option.appearance`.
44
- // Проверка типа нужна и при типизированном поле: items часто приходят из ответа бэкенда.
45
- function extractAppearance(item) {
46
- return typeof item?.appearance === 'string' ? item.appearance : undefined;
47
- }
48
- function flatten(items) {
49
- const out = [];
50
- for (const item of items) {
51
- out.push(item);
52
- if ('items' in item && Array.isArray(item.items)) {
53
- out.push(...flatten(item.items));
54
- }
55
- }
56
- return out;
57
- }
58
- function findItem(items, id) {
59
- for (const item of flatten(items)) {
60
- if (item.id === id) {
61
- return item;
62
- }
63
- }
64
- return undefined;
65
- }
66
- // Subsequence-fuzzy: символы запроса должны встречаться в строке в исходном порядке.
67
- // Пример: query=`lge` matches `Large` (L → r → arg → e).
68
- function isFuzzyMatch(haystack, needle) {
69
- if (!needle) {
70
- return true;
71
- }
72
- let i = 0;
73
- for (const ch of haystack) {
74
- if (ch === needle[i]) {
75
- i += 1;
76
- if (i === needle.length) {
77
- return true;
78
- }
79
- }
80
- }
81
- return false;
82
- }
83
- function filterItems(items, query, fuzzy) {
84
- if (!query) {
85
- return items;
86
- }
87
- const q = query.toLowerCase();
88
- const matches = (item) => {
89
- const text = extractSearchText(item).toLowerCase();
90
- return fuzzy ? isFuzzyMatch(text, q) : text.includes(q);
91
- };
92
- const walk = (list) => list.flatMap(item => {
93
- if ('items' in item && Array.isArray(item.items)) {
94
- const filteredChildren = walk(item.items);
95
- return filteredChildren.length > 0 ? [{ ...item, items: filteredChildren }] : [];
96
- }
97
- return matches(item) ? [item] : [];
98
- });
99
- return walk(items);
100
- }
101
- function isMultiple(props) {
102
- return props.selection === constants_1.SELECTION_MODE.Multiple;
103
- }
@@ -1,19 +0,0 @@
1
- import { Size } from '@cloud-ru/ds-field-decorator';
2
- import { ItemId } from '@cloud-ru/ds-list';
3
- import { Appearance as TagAppearance, Size as TagSize } from '@cloud-ru/ds-tag';
4
- import { FieldSelectItem, FieldSelectMultipleProps, FieldSelectProps } from './types.js';
5
- export declare const TAG_SIZE_MAP: Record<Size, TagSize>;
6
- export type WithIdContent = {
7
- id?: ItemId;
8
- content?: unknown;
9
- disabled?: boolean;
10
- appearance?: TagAppearance;
11
- };
12
- export declare function extractLabel(item: WithIdContent): string;
13
- export declare function extractSearchText(item: WithIdContent): string;
14
- export declare function extractAppearance(item?: WithIdContent): TagAppearance | undefined;
15
- export declare function flatten(items: FieldSelectItem[]): WithIdContent[];
16
- export declare function findItem(items: FieldSelectItem[], id: ItemId): WithIdContent | undefined;
17
- export declare function isFuzzyMatch(haystack: string, needle: string): boolean;
18
- export declare function filterItems(items: FieldSelectItem[], query: string, fuzzy: boolean): FieldSelectItem[];
19
- export declare function isMultiple(props: FieldSelectProps): props is FieldSelectMultipleProps;
@@ -1,92 +0,0 @@
1
- import { SELECTION_MODE } from './constants.js';
2
- export const TAG_SIZE_MAP = { s: 'xs', m: 'xs', l: 's' };
3
- export function extractLabel(item) {
4
- const { content, id } = item;
5
- if (!content) {
6
- return String(id ?? '');
7
- }
8
- if (typeof content === 'string' || typeof content === 'number') {
9
- return String(content);
10
- }
11
- if (typeof content === 'object' && content !== null && 'label' in content) {
12
- return String(content.label);
13
- }
14
- return String(id ?? '');
15
- }
16
- // Текст для поиска: лейбл (option) + caption + description — паритет с легаси `useSearch`,
17
- // который матчил запрос по всем трём полям контента, а не только по лейблу.
18
- export function extractSearchText(item) {
19
- const parts = [extractLabel(item)];
20
- const { content } = item;
21
- if (content && typeof content === 'object') {
22
- const c = content;
23
- if (typeof c.caption === 'string') {
24
- parts.push(c.caption);
25
- }
26
- if (typeof c.description === 'string') {
27
- parts.push(c.description);
28
- }
29
- }
30
- return parts.join(' ');
31
- }
32
- // Цвет тега выбранного значения (multiple) — паритет с легаси `option.appearance`.
33
- // Проверка типа нужна и при типизированном поле: items часто приходят из ответа бэкенда.
34
- export function extractAppearance(item) {
35
- return typeof item?.appearance === 'string' ? item.appearance : undefined;
36
- }
37
- export function flatten(items) {
38
- const out = [];
39
- for (const item of items) {
40
- out.push(item);
41
- if ('items' in item && Array.isArray(item.items)) {
42
- out.push(...flatten(item.items));
43
- }
44
- }
45
- return out;
46
- }
47
- export function findItem(items, id) {
48
- for (const item of flatten(items)) {
49
- if (item.id === id) {
50
- return item;
51
- }
52
- }
53
- return undefined;
54
- }
55
- // Subsequence-fuzzy: символы запроса должны встречаться в строке в исходном порядке.
56
- // Пример: query=`lge` matches `Large` (L → r → arg → e).
57
- export function isFuzzyMatch(haystack, needle) {
58
- if (!needle) {
59
- return true;
60
- }
61
- let i = 0;
62
- for (const ch of haystack) {
63
- if (ch === needle[i]) {
64
- i += 1;
65
- if (i === needle.length) {
66
- return true;
67
- }
68
- }
69
- }
70
- return false;
71
- }
72
- export function filterItems(items, query, fuzzy) {
73
- if (!query) {
74
- return items;
75
- }
76
- const q = query.toLowerCase();
77
- const matches = (item) => {
78
- const text = extractSearchText(item).toLowerCase();
79
- return fuzzy ? isFuzzyMatch(text, q) : text.includes(q);
80
- };
81
- const walk = (list) => list.flatMap(item => {
82
- if ('items' in item && Array.isArray(item.items)) {
83
- const filteredChildren = walk(item.items);
84
- return filteredChildren.length > 0 ? [{ ...item, items: filteredChildren }] : [];
85
- }
86
- return matches(item) ? [item] : [];
87
- });
88
- return walk(items);
89
- }
90
- export function isMultiple(props) {
91
- return props.selection === SELECTION_MODE.Multiple;
92
- }