@cloud-ru/ds-fields 2.2.3-preview-a0540d7f.0 → 2.2.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -3
  3. package/dist/cjs/components/FieldSelect/FieldSelect.js +43 -98
  4. package/dist/cjs/components/FieldSelect/constants.d.ts +0 -11
  5. package/dist/cjs/components/FieldSelect/constants.js +1 -20
  6. package/dist/cjs/components/FieldSelect/types.d.ts +3 -26
  7. package/dist/cjs/components/FieldSelect/utils.d.ts +19 -0
  8. package/dist/cjs/components/FieldSelect/utils.js +103 -0
  9. package/dist/esm/components/FieldSelect/FieldSelect.js +45 -100
  10. package/dist/esm/components/FieldSelect/constants.d.ts +0 -11
  11. package/dist/esm/components/FieldSelect/constants.js +0 -19
  12. package/dist/esm/components/FieldSelect/types.d.ts +3 -26
  13. package/dist/esm/components/FieldSelect/utils.d.ts +19 -0
  14. package/dist/esm/components/FieldSelect/utils.js +92 -0
  15. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  16. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  17. package/package.json +20 -20
  18. package/src/components/FieldSelect/FieldSelect.tsx +63 -151
  19. package/src/components/FieldSelect/constants.ts +0 -22
  20. package/src/components/FieldSelect/types.ts +48 -82
  21. package/src/components/FieldSelect/utils.ts +132 -0
  22. package/dist/cjs/components/FieldSelect/hooks.d.ts +0 -33
  23. package/dist/cjs/components/FieldSelect/hooks.js +0 -95
  24. package/dist/cjs/components/FieldSelect/utils/customOption.d.ts +0 -21
  25. package/dist/cjs/components/FieldSelect/utils/customOption.js +0 -55
  26. package/dist/cjs/components/FieldSelect/utils/extract.d.ts +0 -11
  27. package/dist/cjs/components/FieldSelect/utils/extract.js +0 -39
  28. package/dist/cjs/components/FieldSelect/utils/filter.d.ts +0 -3
  29. package/dist/cjs/components/FieldSelect/utils/filter.js +0 -40
  30. package/dist/cjs/components/FieldSelect/utils/index.d.ts +0 -6
  31. package/dist/cjs/components/FieldSelect/utils/index.js +0 -22
  32. package/dist/cjs/components/FieldSelect/utils/items.d.ts +0 -9
  33. package/dist/cjs/components/FieldSelect/utils/items.js +0 -47
  34. package/dist/cjs/components/FieldSelect/utils/selection.d.ts +0 -11
  35. package/dist/cjs/components/FieldSelect/utils/selection.js +0 -18
  36. package/dist/cjs/components/FieldSelect/utils/tagSize.d.ts +0 -4
  37. package/dist/cjs/components/FieldSelect/utils/tagSize.js +0 -5
  38. package/dist/esm/components/FieldSelect/hooks.d.ts +0 -33
  39. package/dist/esm/components/FieldSelect/hooks.js +0 -92
  40. package/dist/esm/components/FieldSelect/utils/customOption.d.ts +0 -21
  41. package/dist/esm/components/FieldSelect/utils/customOption.js +0 -48
  42. package/dist/esm/components/FieldSelect/utils/extract.d.ts +0 -11
  43. package/dist/esm/components/FieldSelect/utils/extract.js +0 -34
  44. package/dist/esm/components/FieldSelect/utils/filter.d.ts +0 -3
  45. package/dist/esm/components/FieldSelect/utils/filter.js +0 -36
  46. package/dist/esm/components/FieldSelect/utils/index.d.ts +0 -6
  47. package/dist/esm/components/FieldSelect/utils/index.js +0 -6
  48. package/dist/esm/components/FieldSelect/utils/items.d.ts +0 -9
  49. package/dist/esm/components/FieldSelect/utils/items.js +0 -41
  50. package/dist/esm/components/FieldSelect/utils/selection.d.ts +0 -11
  51. package/dist/esm/components/FieldSelect/utils/selection.js +0 -14
  52. package/dist/esm/components/FieldSelect/utils/tagSize.d.ts +0 -4
  53. package/dist/esm/components/FieldSelect/utils/tagSize.js +0 -2
  54. package/src/components/FieldSelect/hooks.ts +0 -177
  55. package/src/components/FieldSelect/utils/customOption.ts +0 -77
  56. package/src/components/FieldSelect/utils/extract.ts +0 -49
  57. package/src/components/FieldSelect/utils/filter.ts +0 -51
  58. package/src/components/FieldSelect/utils/index.ts +0 -6
  59. package/src/components/FieldSelect/utils/items.ts +0 -58
  60. package/src/components/FieldSelect/utils/selection.ts +0 -25
  61. package/src/components/FieldSelect/utils/tagSize.ts +0 -5
@@ -0,0 +1,132 @@
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
+
5
+ import { SELECTION_MODE } from './constants';
6
+ import { FieldSelectItem, FieldSelectMultipleProps, FieldSelectProps } from './types';
7
+
8
+ export const TAG_SIZE_MAP: Record<Size, TagSize> = { s: 'xs', m: 'xs', l: 's' };
9
+
10
+ export type WithIdContent = { id?: ItemId; content?: unknown; disabled?: boolean; appearance?: TagAppearance };
11
+
12
+ export function extractLabel(item: WithIdContent): string {
13
+ const { content, id } = item;
14
+
15
+ if (!content) {
16
+ return String(id ?? '');
17
+ }
18
+
19
+ if (typeof content === 'string' || typeof content === 'number') {
20
+ return String(content);
21
+ }
22
+
23
+ if (typeof content === 'object' && content !== null && 'label' in content) {
24
+ return String((content as { label: unknown }).label);
25
+ }
26
+
27
+ return String(id ?? '');
28
+ }
29
+
30
+ // Текст для поиска: лейбл (option) + caption + description — паритет с легаси `useSearch`,
31
+ // который матчил запрос по всем трём полям контента, а не только по лейблу.
32
+ export function extractSearchText(item: WithIdContent): string {
33
+ const parts = [extractLabel(item)];
34
+ const { content } = item;
35
+
36
+ if (content && typeof content === 'object') {
37
+ const c = content as { caption?: unknown; description?: unknown };
38
+
39
+ if (typeof c.caption === 'string') {
40
+ parts.push(c.caption);
41
+ }
42
+
43
+ if (typeof c.description === 'string') {
44
+ parts.push(c.description);
45
+ }
46
+ }
47
+
48
+ return parts.join(' ');
49
+ }
50
+
51
+ // Цвет тега выбранного значения (multiple) — паритет с легаси `option.appearance`.
52
+ // Проверка типа нужна и при типизированном поле: items часто приходят из ответа бэкенда.
53
+ export function extractAppearance(item?: WithIdContent): TagAppearance | undefined {
54
+ return typeof item?.appearance === 'string' ? item.appearance : undefined;
55
+ }
56
+
57
+ export function flatten(items: FieldSelectItem[]): WithIdContent[] {
58
+ const out: WithIdContent[] = [];
59
+
60
+ for (const item of items) {
61
+ out.push(item as WithIdContent);
62
+
63
+ if ('items' in item && Array.isArray(item.items)) {
64
+ out.push(...flatten(item.items as FieldSelectItem[]));
65
+ }
66
+ }
67
+
68
+ return out;
69
+ }
70
+
71
+ export function findItem(items: FieldSelectItem[], id: ItemId): WithIdContent | undefined {
72
+ for (const item of flatten(items)) {
73
+ if (item.id === id) {
74
+ return item;
75
+ }
76
+ }
77
+
78
+ return undefined;
79
+ }
80
+
81
+ // Subsequence-fuzzy: символы запроса должны встречаться в строке в исходном порядке.
82
+ // Пример: query=`lge` matches `Large` (L → r → arg → e).
83
+ export function isFuzzyMatch(haystack: string, needle: string): boolean {
84
+ if (!needle) {
85
+ return true;
86
+ }
87
+
88
+ let i = 0;
89
+
90
+ for (const ch of haystack) {
91
+ if (ch === needle[i]) {
92
+ i += 1;
93
+
94
+ if (i === needle.length) {
95
+ return true;
96
+ }
97
+ }
98
+ }
99
+
100
+ return false;
101
+ }
102
+
103
+ export function filterItems(items: FieldSelectItem[], query: string, fuzzy: boolean): FieldSelectItem[] {
104
+ if (!query) {
105
+ return items;
106
+ }
107
+
108
+ const q = query.toLowerCase();
109
+
110
+ const matches = (item: FieldSelectItem): boolean => {
111
+ const text = extractSearchText(item as WithIdContent).toLowerCase();
112
+
113
+ return fuzzy ? isFuzzyMatch(text, q) : text.includes(q);
114
+ };
115
+
116
+ const walk = (list: FieldSelectItem[]): FieldSelectItem[] =>
117
+ list.flatMap(item => {
118
+ if ('items' in item && Array.isArray(item.items)) {
119
+ const filteredChildren = walk(item.items as FieldSelectItem[]);
120
+
121
+ return filteredChildren.length > 0 ? [{ ...item, items: filteredChildren } as FieldSelectItem] : [];
122
+ }
123
+
124
+ return matches(item) ? [item] : [];
125
+ });
126
+
127
+ return walk(items);
128
+ }
129
+
130
+ export function isMultiple(props: FieldSelectProps): props is FieldSelectMultipleProps {
131
+ return props.selection === SELECTION_MODE.Multiple;
132
+ }
@@ -1,33 +0,0 @@
1
- import { ItemId } from '@cloud-ru/ds-list';
2
- import { FocusEvent, KeyboardEvent, RefObject } from 'react';
3
- import { FieldSelectAddCustomOptionTrigger } from './types';
4
- /** Результат blur: фокус остался внутри поля, ввод зафиксирован, или коммитить было нечего. */
5
- type CustomOptionBlurResult = 'inside' | 'committed' | 'skipped';
6
- type UseCustomOptionParams = {
7
- allowCustomOption: boolean;
8
- addCustomOptionTriggers: readonly FieldSelectAddCustomOptionTrigger[] | undefined;
9
- addOptionByEnter: boolean;
10
- multiple: boolean;
11
- inputValue: string;
12
- typing: boolean;
13
- multipleValue: ItemId[];
14
- commitSingle(value: ItemId | undefined): void;
15
- commitMultiple(value: ItemId[]): void;
16
- resetSearch(): void;
17
- inputElementRef: RefObject<HTMLInputElement | null>;
18
- clearButtonRef: RefObject<HTMLButtonElement | null>;
19
- copyButtonRef: RefObject<HTMLButtonElement | null>;
20
- footerActiveElementsRefs?: RefObject<HTMLElement>[];
21
- };
22
- /** Фиксация произвольного ввода как выбранной опции: триггеры, blur, закрытие списка. */
23
- export declare function useCustomOption({ allowCustomOption, addCustomOptionTriggers, addOptionByEnter, multiple, inputValue, typing, multipleValue, commitSingle, commitMultiple, resetSearch, inputElementRef, clearButtonRef, copyButtonRef, footerActiveElementsRefs, }: UseCustomOptionParams): {
24
- listRef: RefObject<HTMLElement>;
25
- suppressCloseCommit: () => void;
26
- enableCloseCommit: () => void;
27
- tryCommitOnClose: () => boolean;
28
- tryCommitOnBlur: (event: FocusEvent<HTMLInputElement>) => CustomOptionBlurResult;
29
- tryCommitFromKeyboard: (event: KeyboardEvent<HTMLInputElement>) => boolean;
30
- handleListSingleChange: (next: ItemId | undefined) => void;
31
- handleListMultipleChange: (next: ItemId[]) => void;
32
- };
33
- export {};
@@ -1,95 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useCustomOption = useCustomOption;
4
- const utils_1 = require("@cloud-ru/ds-utils");
5
- const react_1 = require("react");
6
- const constants_1 = require("./constants");
7
- const utils_2 = require("./utils");
8
- /** Фиксация произвольного ввода как выбранной опции: триггеры, blur, закрытие списка. */
9
- function useCustomOption({ allowCustomOption, addCustomOptionTriggers, addOptionByEnter, multiple, inputValue, typing, multipleValue, commitSingle, commitMultiple, resetSearch, inputElementRef, clearButtonRef, copyButtonRef, footerActiveElementsRefs, }) {
10
- const listRef = (0, react_1.useRef)(null);
11
- const skipCloseCommitRef = (0, react_1.useRef)(false);
12
- const skipBlurCommitRef = (0, react_1.useRef)(false);
13
- const resolvedTriggers = (0, react_1.useMemo)(() => (0, utils_2.resolveAddCustomOptionTriggers)({
14
- allowCustomOption,
15
- addCustomOptionTriggers,
16
- addOptionByEnter,
17
- defaultTriggers: multiple ? constants_1.ADD_CUSTOM_OPTION_TRIGGERS_MULTIPLE : constants_1.ADD_CUSTOM_OPTION_TRIGGERS_SINGLE,
18
- }), [allowCustomOption, addCustomOptionTriggers, addOptionByEnter, multiple]);
19
- const tryCommit = (0, react_1.useCallback)((trigger) => {
20
- if (!typing || !(0, utils_2.shouldHandleCustomOptionTrigger)(trigger, resolvedTriggers) || inputValue === '') {
21
- return false;
22
- }
23
- if (multiple) {
24
- if (multipleValue.includes(inputValue)) {
25
- resetSearch();
26
- }
27
- else {
28
- commitMultiple([...multipleValue, inputValue]);
29
- }
30
- }
31
- else {
32
- commitSingle(inputValue);
33
- }
34
- return true;
35
- }, [typing, resolvedTriggers, inputValue, multiple, multipleValue, commitMultiple, commitSingle, resetSearch]);
36
- const suppressCloseCommit = (0, react_1.useCallback)(() => {
37
- skipCloseCommitRef.current = true;
38
- skipBlurCommitRef.current = true;
39
- }, []);
40
- const enableCloseCommit = (0, react_1.useCallback)(() => {
41
- skipCloseCommitRef.current = false;
42
- skipBlurCommitRef.current = false;
43
- }, []);
44
- const tryCommitOnClose = (0, react_1.useCallback)(() => {
45
- const skipped = skipCloseCommitRef.current;
46
- const shouldCommit = (0, utils_2.shouldCommitCustomOptionOnClose)({
47
- skip: skipped,
48
- inputElement: inputElementRef.current,
49
- activeElement: (0, utils_1.isBrowser)() ? document.activeElement : null,
50
- });
51
- if (skipped) {
52
- queueMicrotask(() => {
53
- skipCloseCommitRef.current = false;
54
- });
55
- }
56
- return shouldCommit && tryCommit(constants_1.ADD_CUSTOM_OPTION_TRIGGER.Blur);
57
- }, [inputElementRef, tryCommit]);
58
- const tryCommitOnBlur = (0, react_1.useCallback)((event) => {
59
- if (skipBlurCommitRef.current) {
60
- skipBlurCommitRef.current = false;
61
- return 'skipped';
62
- }
63
- if ((0, utils_2.isCustomOptionBlurInsideField)(event.relatedTarget, [
64
- listRef.current,
65
- clearButtonRef.current,
66
- copyButtonRef.current,
67
- ...(footerActiveElementsRefs?.map(ref => ref.current) ?? []),
68
- ])) {
69
- return 'inside';
70
- }
71
- return tryCommit(constants_1.ADD_CUSTOM_OPTION_TRIGGER.Blur) ? 'committed' : 'skipped';
72
- }, [clearButtonRef, copyButtonRef, footerActiveElementsRefs, tryCommit]);
73
- const tryCommitFromKeyboard = (0, react_1.useCallback)((event) => {
74
- const trigger = (0, utils_2.getCustomOptionTriggerByCode)(event.code, event.key);
75
- return trigger !== undefined && tryCommit(trigger);
76
- }, [tryCommit]);
77
- const handleListSingleChange = (0, react_1.useCallback)((next) => {
78
- skipCloseCommitRef.current = true;
79
- commitSingle(next);
80
- }, [commitSingle]);
81
- const handleListMultipleChange = (0, react_1.useCallback)((next) => {
82
- skipCloseCommitRef.current = true;
83
- commitMultiple(next);
84
- }, [commitMultiple]);
85
- return {
86
- listRef,
87
- suppressCloseCommit,
88
- enableCloseCommit,
89
- tryCommitOnClose,
90
- tryCommitOnBlur,
91
- tryCommitFromKeyboard,
92
- handleListSingleChange,
93
- handleListMultipleChange,
94
- };
95
- }
@@ -1,21 +0,0 @@
1
- import { FieldSelectAddCustomOptionTrigger, FieldSelectMultipleAddCustomOptionTrigger } from '../types';
2
- /**
3
- * Сопоставляет клавишу с триггером кастомной опции (`enter` / `space` / `comma`).
4
- * Сначала `KeyboardEvent.code`, затем `key` — если `code` пустой или не из словаря (`','` без `Comma`).
5
- */
6
- export declare function getCustomOptionTriggerByCode(code: string, key?: string): FieldSelectMultipleAddCustomOptionTrigger | undefined;
7
- export declare function shouldHandleCustomOptionTrigger(trigger: FieldSelectAddCustomOptionTrigger | undefined, availableTriggers: readonly FieldSelectAddCustomOptionTrigger[]): trigger is FieldSelectAddCustomOptionTrigger;
8
- export declare function resolveAddCustomOptionTriggers(params: {
9
- allowCustomOption: boolean;
10
- addCustomOptionTriggers: readonly FieldSelectAddCustomOptionTrigger[] | undefined;
11
- addOptionByEnter: boolean;
12
- defaultTriggers: readonly FieldSelectAddCustomOptionTrigger[];
13
- }): FieldSelectAddCustomOptionTrigger[];
14
- /** Фокус ушёл на дроплист, футер или postfix — кастомную опцию на blur фиксировать не нужно. */
15
- export declare function isCustomOptionBlurInsideField(relatedTarget: EventTarget | null, nodes: (Node | null | undefined)[]): boolean;
16
- /** Клик снаружи закрывает список, не снимая фокус с input — blur не придёт. */
17
- export declare function shouldCommitCustomOptionOnClose(params: {
18
- skip: boolean;
19
- inputElement: Element | null;
20
- activeElement: Element | null;
21
- }): boolean;
@@ -1,55 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getCustomOptionTriggerByCode = getCustomOptionTriggerByCode;
4
- exports.shouldHandleCustomOptionTrigger = shouldHandleCustomOptionTrigger;
5
- exports.resolveAddCustomOptionTriggers = resolveAddCustomOptionTriggers;
6
- exports.isCustomOptionBlurInsideField = isCustomOptionBlurInsideField;
7
- exports.shouldCommitCustomOptionOnClose = shouldCommitCustomOptionOnClose;
8
- const constants_1 = require("../constants");
9
- /**
10
- * Сопоставляет клавишу с триггером кастомной опции (`enter` / `space` / `comma`).
11
- * Сначала `KeyboardEvent.code`, затем `key` — если `code` пустой или не из словаря (`','` без `Comma`).
12
- */
13
- function getCustomOptionTriggerByCode(code, key) {
14
- switch (code) {
15
- case 'Enter':
16
- return constants_1.ADD_CUSTOM_OPTION_TRIGGER.Enter;
17
- case 'Space':
18
- return constants_1.ADD_CUSTOM_OPTION_TRIGGER.Space;
19
- case 'Comma':
20
- return constants_1.ADD_CUSTOM_OPTION_TRIGGER.Comma;
21
- default:
22
- break;
23
- }
24
- switch (key) {
25
- case 'Enter':
26
- return constants_1.ADD_CUSTOM_OPTION_TRIGGER.Enter;
27
- case ' ':
28
- return constants_1.ADD_CUSTOM_OPTION_TRIGGER.Space;
29
- case ',':
30
- return constants_1.ADD_CUSTOM_OPTION_TRIGGER.Comma;
31
- default:
32
- return undefined;
33
- }
34
- }
35
- function shouldHandleCustomOptionTrigger(trigger, availableTriggers) {
36
- return trigger !== undefined && availableTriggers.includes(trigger);
37
- }
38
- function resolveAddCustomOptionTriggers(params) {
39
- if (params.allowCustomOption) {
40
- const source = params.addCustomOptionTriggers !== undefined ? params.addCustomOptionTriggers : params.defaultTriggers;
41
- return source.filter(trigger => params.defaultTriggers.includes(trigger));
42
- }
43
- if (params.addOptionByEnter) {
44
- return [constants_1.ADD_CUSTOM_OPTION_TRIGGER.Enter];
45
- }
46
- return [];
47
- }
48
- /** Фокус ушёл на дроплист, футер или postfix — кастомную опцию на blur фиксировать не нужно. */
49
- function isCustomOptionBlurInsideField(relatedTarget, nodes) {
50
- return relatedTarget instanceof Node && nodes.some(node => Boolean(node?.contains(relatedTarget)));
51
- }
52
- /** Клик снаружи закрывает список, не снимая фокус с input — blur не придёт. */
53
- function shouldCommitCustomOptionOnClose(params) {
54
- return !params.skip && params.activeElement !== null && params.activeElement === params.inputElement;
55
- }
@@ -1,11 +0,0 @@
1
- import { ItemId } from '@cloud-ru/ds-list';
2
- import { Appearance as TagAppearance } from '@cloud-ru/ds-tag';
3
- export type WithIdContent = {
4
- id?: ItemId;
5
- content?: unknown;
6
- disabled?: boolean;
7
- appearance?: TagAppearance;
8
- };
9
- export declare function extractLabel(item: WithIdContent): string;
10
- export declare function extractSearchText(item: WithIdContent): string;
11
- export declare function extractAppearance(item?: WithIdContent): TagAppearance | undefined;
@@ -1,39 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractLabel = extractLabel;
4
- exports.extractSearchText = extractSearchText;
5
- exports.extractAppearance = extractAppearance;
6
- function extractLabel(item) {
7
- const { content, id } = item;
8
- if (!content) {
9
- return String(id ?? '');
10
- }
11
- if (typeof content === 'string' || typeof content === 'number') {
12
- return String(content);
13
- }
14
- if (typeof content === 'object' && content !== null && 'label' in content) {
15
- return String(content.label);
16
- }
17
- return String(id ?? '');
18
- }
19
- // Текст для поиска: лейбл (option) + caption + description — паритет с легаси `useSearch`,
20
- // который матчил запрос по всем трём полям контента, а не только по лейблу.
21
- function extractSearchText(item) {
22
- const parts = [extractLabel(item)];
23
- const { content } = item;
24
- if (content && typeof content === 'object') {
25
- const c = content;
26
- if (typeof c.caption === 'string') {
27
- parts.push(c.caption);
28
- }
29
- if (typeof c.description === 'string') {
30
- parts.push(c.description);
31
- }
32
- }
33
- return parts.join(' ');
34
- }
35
- // Цвет тега выбранного значения (multiple) — паритет с легаси `option.appearance`.
36
- // Проверка типа нужна и при типизированном поле: items часто приходят из ответа бэкенда.
37
- function extractAppearance(item) {
38
- return typeof item?.appearance === 'string' ? item.appearance : undefined;
39
- }
@@ -1,3 +0,0 @@
1
- import { FieldSelectItem } from '../types';
2
- export declare function isFuzzyMatch(haystack: string, needle: string): boolean;
3
- export declare function filterItems(items: FieldSelectItem[], query: string, fuzzy: boolean): FieldSelectItem[];
@@ -1,40 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isFuzzyMatch = isFuzzyMatch;
4
- exports.filterItems = filterItems;
5
- const extract_1 = require("./extract");
6
- // Subsequence-fuzzy: символы запроса должны встречаться в строке в исходном порядке.
7
- // Пример: query=`lge` matches `Large` (L → r → arg → e).
8
- function isFuzzyMatch(haystack, needle) {
9
- if (!needle) {
10
- return true;
11
- }
12
- let i = 0;
13
- for (const ch of haystack) {
14
- if (ch === needle[i]) {
15
- i += 1;
16
- if (i === needle.length) {
17
- return true;
18
- }
19
- }
20
- }
21
- return false;
22
- }
23
- function filterItems(items, query, fuzzy) {
24
- if (!query) {
25
- return items;
26
- }
27
- const q = query.toLowerCase();
28
- const matches = (item) => {
29
- const text = (0, extract_1.extractSearchText)(item).toLowerCase();
30
- return fuzzy ? isFuzzyMatch(text, q) : text.includes(q);
31
- };
32
- const walk = (list) => list.flatMap(item => {
33
- if ('items' in item && Array.isArray(item.items)) {
34
- const filteredChildren = walk(item.items);
35
- return filteredChildren.length > 0 ? [{ ...item, items: filteredChildren }] : [];
36
- }
37
- return matches(item) ? [item] : [];
38
- });
39
- return walk(items);
40
- }
@@ -1,6 +0,0 @@
1
- export * from './customOption';
2
- export * from './extract';
3
- export * from './filter';
4
- export * from './items';
5
- export * from './selection';
6
- export * from './tagSize';
@@ -1,22 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./customOption"), exports);
18
- __exportStar(require("./extract"), exports);
19
- __exportStar(require("./filter"), exports);
20
- __exportStar(require("./items"), exports);
21
- __exportStar(require("./selection"), exports);
22
- __exportStar(require("./tagSize"), exports);
@@ -1,9 +0,0 @@
1
- import { ItemId } from '@cloud-ru/ds-list';
2
- import { FieldSelectItem } from '../types';
3
- import { WithIdContent } from './extract';
4
- export declare function flatten(items: FieldSelectItem[]): WithIdContent[];
5
- export declare function findItem(items: FieldSelectItem[], id: ItemId): WithIdContent | undefined;
6
- /** Айтем для выбранного значения, которого нет в `items` (кастомная опция, ленивая подгрузка). */
7
- export declare function createPlaceholderItem(id: ItemId): FieldSelectItem;
8
- /** Выбранные id, которых нет в дереве айтемов — в дроплист их нужно добавить отдельно. */
9
- export declare function getMissingSelectedItems(selectedIds: ItemId[], allItems: FieldSelectItem[]): FieldSelectItem[];
@@ -1,47 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.flatten = flatten;
4
- exports.findItem = findItem;
5
- exports.createPlaceholderItem = createPlaceholderItem;
6
- exports.getMissingSelectedItems = getMissingSelectedItems;
7
- function flatten(items) {
8
- const out = [];
9
- for (const item of items) {
10
- out.push(item);
11
- if ('items' in item && Array.isArray(item.items)) {
12
- out.push(...flatten(item.items));
13
- }
14
- }
15
- return out;
16
- }
17
- function findItem(items, id) {
18
- for (const item of flatten(items)) {
19
- if (item.id === id) {
20
- return item;
21
- }
22
- }
23
- return undefined;
24
- }
25
- /** Айтем для выбранного значения, которого нет в `items` (кастомная опция, ленивая подгрузка). */
26
- function createPlaceholderItem(id) {
27
- return { id, content: { label: String(id) } };
28
- }
29
- /** Выбранные id, которых нет в дереве айтемов — в дроплист их нужно добавить отдельно. */
30
- function getMissingSelectedItems(selectedIds, allItems) {
31
- if (selectedIds.length === 0) {
32
- return [];
33
- }
34
- const existingIds = new Set();
35
- for (const item of flatten(allItems)) {
36
- if (item.id !== undefined) {
37
- existingIds.add(item.id);
38
- }
39
- }
40
- const missing = [];
41
- for (const id of selectedIds) {
42
- if (id !== '' && !existingIds.has(id)) {
43
- missing.push(createPlaceholderItem(id));
44
- }
45
- }
46
- return missing;
47
- }
@@ -1,11 +0,0 @@
1
- import { ItemId } from '@cloud-ru/ds-list';
2
- import { FieldSelectMultipleProps, Selection } from '../types';
3
- export declare function isMultiple(props: {
4
- selection?: Selection;
5
- }): props is FieldSelectMultipleProps;
6
- /** Выбранные id в нормализованном виде: массив и для `single`, и для `multiple`. */
7
- export declare function getSelectedIds(params: {
8
- multiple: boolean;
9
- multipleValue: ItemId[];
10
- singleValue: ItemId | undefined;
11
- }): ItemId[];
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isMultiple = isMultiple;
4
- exports.getSelectedIds = getSelectedIds;
5
- const constants_1 = require("../constants");
6
- function isMultiple(props) {
7
- return props.selection === constants_1.SELECTION_MODE.Multiple;
8
- }
9
- /** Выбранные id в нормализованном виде: массив и для `single`, и для `multiple`. */
10
- function getSelectedIds(params) {
11
- if (params.multiple) {
12
- return params.multipleValue;
13
- }
14
- if (params.singleValue === undefined) {
15
- return [];
16
- }
17
- return [params.singleValue];
18
- }
@@ -1,4 +0,0 @@
1
- import { Size } from '@cloud-ru/ds-field-decorator';
2
- import { Size as TagSize } from '@cloud-ru/ds-tag';
3
- /** Соответствие размера поля размеру чипа выбранного значения (`selection='multiple'`). */
4
- export declare const TAG_SIZE_MAP: Record<Size, TagSize>;
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TAG_SIZE_MAP = void 0;
4
- /** Соответствие размера поля размеру чипа выбранного значения (`selection='multiple'`). */
5
- exports.TAG_SIZE_MAP = { s: 'xs', m: 'xs', l: 's' };
@@ -1,33 +0,0 @@
1
- import { ItemId } from '@cloud-ru/ds-list';
2
- import { FocusEvent, KeyboardEvent, RefObject } from 'react';
3
- import { FieldSelectAddCustomOptionTrigger } from './types.js';
4
- /** Результат blur: фокус остался внутри поля, ввод зафиксирован, или коммитить было нечего. */
5
- type CustomOptionBlurResult = 'inside' | 'committed' | 'skipped';
6
- type UseCustomOptionParams = {
7
- allowCustomOption: boolean;
8
- addCustomOptionTriggers: readonly FieldSelectAddCustomOptionTrigger[] | undefined;
9
- addOptionByEnter: boolean;
10
- multiple: boolean;
11
- inputValue: string;
12
- typing: boolean;
13
- multipleValue: ItemId[];
14
- commitSingle(value: ItemId | undefined): void;
15
- commitMultiple(value: ItemId[]): void;
16
- resetSearch(): void;
17
- inputElementRef: RefObject<HTMLInputElement | null>;
18
- clearButtonRef: RefObject<HTMLButtonElement | null>;
19
- copyButtonRef: RefObject<HTMLButtonElement | null>;
20
- footerActiveElementsRefs?: RefObject<HTMLElement>[];
21
- };
22
- /** Фиксация произвольного ввода как выбранной опции: триггеры, blur, закрытие списка. */
23
- export declare function useCustomOption({ allowCustomOption, addCustomOptionTriggers, addOptionByEnter, multiple, inputValue, typing, multipleValue, commitSingle, commitMultiple, resetSearch, inputElementRef, clearButtonRef, copyButtonRef, footerActiveElementsRefs, }: UseCustomOptionParams): {
24
- listRef: RefObject<HTMLElement>;
25
- suppressCloseCommit: () => void;
26
- enableCloseCommit: () => void;
27
- tryCommitOnClose: () => boolean;
28
- tryCommitOnBlur: (event: FocusEvent<HTMLInputElement>) => CustomOptionBlurResult;
29
- tryCommitFromKeyboard: (event: KeyboardEvent<HTMLInputElement>) => boolean;
30
- handleListSingleChange: (next: ItemId | undefined) => void;
31
- handleListMultipleChange: (next: ItemId[]) => void;
32
- };
33
- export {};