@snack-uikit/search 0.7.22 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
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
+ # 0.8.0 (2024-05-13)
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+
12
+ * **FF-0000:** change droplist -> list package ([54f146b](https://github.com/cloud-ru-tech/snack-uikit/commit/54f146b520b43e2cc5976f2d8c62320b8c23176b))
13
+
14
+
15
+
16
+
6
17
  ## 0.7.22 (2024-05-08)
7
18
 
8
19
  ### Only dependencies have been changed
package/README.md CHANGED
@@ -42,7 +42,7 @@ import { Search, SearchPrivate } from "@snack-uikit/search";
42
42
  | loading | `boolean` | - | Состояние загрузки |
43
43
  | onSubmit | `(value: string) => void` | - | Колбек на подтверждение поиска по строке |
44
44
  | outline | `boolean` | - | Внешний бордер |
45
- | options | `Pick<DroplistItemSingleProps, "option" \| "caption" \| "description" \| "tagLabel" \| "icon" \| "avatar">[]` | - | Элементы выпадающие в Droplist в режиме Autocomplete. На нажатие 'Space', 'Enter' или клике по элементу будет вызываться onSubmit. В режиме FieldText options отсутсвуют |
45
+ | options | `BaseItem[]` | - | Элементы выпадающие в Droplist в режиме Autocomplete. На нажатие 'Space', 'Enter' или клике по элементу будет вызываться onSubmit. В режиме FieldText options отсутсвуют |
46
46
  | autocomplete | `boolean` | - | Работа в режиме Autocomplete в значении true Работа в режиме FieldText в значении false \| undefined |
47
47
  | ref | `Ref<HTMLInputElement>` | - | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom |
48
48
  | key | `Key` | - | |
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { ItemSingleProps } from '@snack-uikit/droplist';
2
+ import { BaseItemProps } from '@snack-uikit/list';
3
3
  import { SearchPrivateProps } from '@snack-uikit/search-private';
4
4
  export type SearchAutocompleteProps = Omit<SearchPrivateProps, 'onKeyDown'> & {
5
5
  /**
@@ -7,7 +7,7 @@ export type SearchAutocompleteProps = Omit<SearchPrivateProps, 'onKeyDown'> & {
7
7
  *
8
8
  * На нажатие 'Space', 'Enter' или клике по элементу будет вызываться onSubmit.
9
9
  */
10
- options: Pick<ItemSingleProps, 'option' | 'description' | 'tagLabel' | 'icon' | 'caption' | 'avatar'>[];
10
+ options: BaseItemProps[];
11
11
  /** Внешний бордер */
12
12
  outline?: boolean;
13
13
  };
@@ -17,7 +17,7 @@ export declare const SearchAutocomplete: import("react").ForwardRefExoticCompone
17
17
  *
18
18
  * На нажатие 'Space', 'Enter' или клике по элементу будет вызываться onSubmit.
19
19
  */
20
- options: Pick<ItemSingleProps, 'option' | 'description' | 'tagLabel' | 'icon' | 'caption' | 'avatar'>[];
20
+ options: BaseItemProps[];
21
21
  /** Внешний бордер */
22
22
  outline?: boolean | undefined;
23
23
  } & import("react").RefAttributes<HTMLInputElement>>;
@@ -10,44 +10,69 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
- import { createElement as _createElement } from "react";
14
13
  import cn from 'classnames';
15
14
  import mergeRefs from 'merge-refs';
16
- import { forwardRef, useCallback, useRef, useState } from 'react';
17
- import { Droplist } from '@snack-uikit/droplist';
15
+ import { forwardRef, useCallback, useMemo, useRef, useState, } from 'react';
16
+ import { Droplist } from '@snack-uikit/list';
18
17
  import { SearchPrivate } from '@snack-uikit/search-private';
19
18
  import { PRIVATE_SEARCH_TEST_IDS, SIZE, TEST_IDS } from '../../constants';
20
19
  import { SearchDecorator } from '../SearchDecorator';
21
20
  import styles from './styles.module.css';
22
21
  export const SearchAutocomplete = forwardRef(function SearchAutocomplete(_a, ref) {
23
- var _b, _c;
24
22
  var { size = SIZE.S, value, onChange, placeholder, options = [], loading, outline, onSubmit, onFocus, className } = _a, rest = __rest(_a, ["size", "value", "onChange", "placeholder", "options", "loading", "outline", "onSubmit", "onFocus", "className"]);
25
23
  const scrollRef = useRef(null);
24
+ const localRef = useRef(null);
26
25
  const [isOpen, setIsOpen] = useState(false);
27
- const { firstElementRefCallback, handleDroplistFocusLeave, handleDroplistItemClick, handleTriggerKeyDown, handleDroplistItemKeyDown, triggerElementRef, } = Droplist.useKeyboardNavigation({
28
- setDroplistOpen: setIsOpen,
29
- triggerType: 'input',
30
- });
31
- const handleOptionKeyDown = useCallback((event) => {
26
+ const handleOptionKeyDown = useCallback((e) => {
32
27
  var _a, _b;
33
- event.stopPropagation();
34
- // ignoring special keys (tab, arrows, backspace, etc.)
35
- if (event.key.length === 1) {
36
- (_a = triggerElementRef.current) === null || _a === void 0 ? void 0 : _a.focus();
28
+ if (e.key.length === 1) {
29
+ e.stopPropagation();
30
+ (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
37
31
  (_b = scrollRef.current) === null || _b === void 0 ? void 0 : _b.scroll(0, 0);
32
+ return;
33
+ }
34
+ // ignoring special keys (tab, arrows, backspace, etc.)
35
+ if (!['ArrowUp', 'ArrowDown'].includes(e.key)) {
36
+ e.stopPropagation();
37
+ }
38
+ else {
39
+ e.preventDefault();
40
+ }
41
+ }, [scrollRef]);
42
+ const handleKeyDown = (cb) => (e) => {
43
+ if (e.key === ' ') {
44
+ e.stopPropagation();
38
45
  }
39
- }, [triggerElementRef, scrollRef]);
40
- const onKeyDown = (e) => {
41
- handleTriggerKeyDown(e);
42
46
  if (e.key.length === 1) {
43
47
  setIsOpen(true);
44
48
  }
49
+ cb === null || cb === void 0 ? void 0 : cb(e);
45
50
  };
46
- return (_jsx("div", Object.assign({ className: cn(styles.wrap, className) }, rest, { children: _jsx(Droplist, { open: Boolean((_b = triggerElementRef.current) === null || _b === void 0 ? void 0 : _b.value) && isOpen && options.length > 0, firstElementRefCallback: firstElementRefCallback, useScroll: true, size: size, onFocusLeave: handleDroplistFocusLeave, onOpenChange: setIsOpen, "data-test-id": TEST_IDS.droplist, triggerClassName: styles.triggerClassName, scrollRef: scrollRef, triggerRef: triggerElementRef, triggerElement: _jsx(SearchDecorator, { size: size, outline: outline || undefined, focused: (isOpen && Boolean((_c = triggerElementRef.current) === null || _c === void 0 ? void 0 : _c.value)) || undefined, "data-test-id": TEST_IDS.decorator, children: _jsx(SearchPrivate, { loading: loading, value: value, onChange: onChange, onSubmit: onSubmit, placeholder: placeholder, ref: mergeRefs(ref, triggerElementRef), onKeyDown: onKeyDown, onFocus: onFocus, size: size, "data-test-id": PRIVATE_SEARCH_TEST_IDS.field }) }), children: options.map(item => (_createElement(Droplist.ItemSingle, Object.assign({}, item, { key: item.option, onClick: e => {
47
- var _a;
48
- handleDroplistItemClick(e);
49
- onChange === null || onChange === void 0 ? void 0 : onChange(item.option);
50
- onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(item.option);
51
- (_a = triggerElementRef.current) === null || _a === void 0 ? void 0 : _a.blur();
52
- }, onKeyDown: e => handleDroplistItemKeyDown(e, handleOptionKeyDown), "data-test-id": TEST_IDS.option })))) }) })));
51
+ const items = useMemo(() => options.map((_a, idx) => {
52
+ var _b;
53
+ var { onClick } = _a, item = __rest(_a, ["onClick"]);
54
+ return (Object.assign(Object.assign({}, item), { onClick: (e) => {
55
+ var _a;
56
+ // Dirty hack: by default list items call onClick by 'Space' || 'Enter'
57
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
58
+ // @ts-expect-error
59
+ if (!(e.type === 'keydown' && (e === null || e === void 0 ? void 0 : e.key) === ' ')) {
60
+ onClick === null || onClick === void 0 ? void 0 : onClick(e);
61
+ if (item.id) {
62
+ onChange === null || onChange === void 0 ? void 0 : onChange(String(item.id));
63
+ onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(String(item.id));
64
+ }
65
+ else if (typeof item.content === 'string') {
66
+ onChange === null || onChange === void 0 ? void 0 : onChange(item.content);
67
+ onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(item.content);
68
+ }
69
+ setIsOpen(false);
70
+ (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
71
+ }
72
+ }, onKeyDown: handleOptionKeyDown, 'data-test-id': `${TEST_IDS.option}__${(_b = item.id) !== null && _b !== void 0 ? _b : idx}` }));
73
+ }), [handleOptionKeyDown, onChange, onSubmit, options]);
74
+ return (_jsx("div", Object.assign({ className: cn(styles.wrap, className) }, rest, { children: _jsx(Droplist, { open: isOpen && options.length > 0, scroll: true, size: size, onOpenChange: setIsOpen, "data-test-id": TEST_IDS.droplist, triggerClassName: styles.triggerClassName, scrollRef: scrollRef, triggerElemRef: localRef, items: items, loading: loading, children: ({ onKeyDown }) => {
75
+ var _a;
76
+ return (_jsx(SearchDecorator, { size: size, outline: outline || undefined, focused: (isOpen && Boolean((_a = localRef.current) === null || _a === void 0 ? void 0 : _a.value)) || undefined, "data-test-id": TEST_IDS.decorator, children: _jsx(SearchPrivate, { loading: loading, value: value, onChange: onChange, onSubmit: onSubmit, placeholder: placeholder, ref: mergeRefs(ref, localRef), onKeyDown: handleKeyDown(onKeyDown), onFocus: onFocus, size: size, "data-test-id": PRIVATE_SEARCH_TEST_IDS.field }) }));
77
+ } }) })));
53
78
  });
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Search",
7
- "version": "0.7.22",
7
+ "version": "0.8.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -32,7 +32,7 @@
32
32
  "license": "Apache-2.0",
33
33
  "scripts": {},
34
34
  "dependencies": {
35
- "@snack-uikit/droplist": "0.13.18",
35
+ "@snack-uikit/list": "0.13.0",
36
36
  "@snack-uikit/search-private": "0.2.1",
37
37
  "@snack-uikit/utils": "3.3.0",
38
38
  "classnames": "2.3.2",
@@ -41,5 +41,5 @@
41
41
  "devDependencies": {
42
42
  "@types/merge-refs": "1.0.0"
43
43
  },
44
- "gitHead": "59bd2ac46a341357d543ada864a34e09745b3e42"
44
+ "gitHead": "d36fe553ce72b92deafee581e70090458827f8ac"
45
45
  }
@@ -1,8 +1,17 @@
1
1
  import cn from 'classnames';
2
2
  import mergeRefs from 'merge-refs';
3
- import { forwardRef, KeyboardEvent, useCallback, useRef, useState } from 'react';
3
+ import {
4
+ forwardRef,
5
+ KeyboardEvent,
6
+ KeyboardEventHandler,
7
+ MouseEvent,
8
+ useCallback,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
4
13
 
5
- import { Droplist, ItemSingleProps } from '@snack-uikit/droplist';
14
+ import { BaseItemProps, Droplist } from '@snack-uikit/list';
6
15
  import { SearchPrivate, SearchPrivateProps } from '@snack-uikit/search-private';
7
16
 
8
17
  import { PRIVATE_SEARCH_TEST_IDS, SIZE, TEST_IDS } from '../../constants';
@@ -15,7 +24,7 @@ export type SearchAutocompleteProps = Omit<SearchPrivateProps, 'onKeyDown'> & {
15
24
  *
16
25
  * На нажатие 'Space', 'Enter' или клике по элементу будет вызываться onSubmit.
17
26
  */
18
- options: Pick<ItemSingleProps, 'option' | 'description' | 'tagLabel' | 'icon' | 'caption' | 'avatar'>[];
27
+ options: BaseItemProps[];
19
28
  /** Внешний бордер */
20
29
  outline?: boolean;
21
30
  };
@@ -37,60 +46,90 @@ export const SearchAutocomplete = forwardRef<HTMLInputElement, SearchAutocomplet
37
46
  ref,
38
47
  ) {
39
48
  const scrollRef = useRef<HTMLElement>(null);
49
+ const localRef = useRef<HTMLInputElement>(null);
40
50
 
41
51
  const [isOpen, setIsOpen] = useState(false);
42
52
 
43
- const {
44
- firstElementRefCallback,
45
- handleDroplistFocusLeave,
46
- handleDroplistItemClick,
47
- handleTriggerKeyDown,
48
- handleDroplistItemKeyDown,
49
- triggerElementRef,
50
- } = Droplist.useKeyboardNavigation<HTMLInputElement>({
51
- setDroplistOpen: setIsOpen,
52
- triggerType: 'input',
53
- });
54
-
55
53
  const handleOptionKeyDown = useCallback(
56
- (event: KeyboardEvent<HTMLButtonElement>) => {
57
- event.stopPropagation();
54
+ (e: KeyboardEvent<HTMLButtonElement>) => {
55
+ if (e.key.length === 1) {
56
+ e.stopPropagation();
57
+ localRef.current?.focus();
58
+ scrollRef.current?.scroll(0, 0);
59
+
60
+ return;
61
+ }
58
62
 
59
63
  // ignoring special keys (tab, arrows, backspace, etc.)
60
- if (event.key.length === 1) {
61
- triggerElementRef.current?.focus();
62
- scrollRef.current?.scroll(0, 0);
64
+ if (!['ArrowUp', 'ArrowDown'].includes(e.key)) {
65
+ e.stopPropagation();
66
+ } else {
67
+ e.preventDefault();
63
68
  }
64
69
  },
65
- [triggerElementRef, scrollRef],
70
+ [scrollRef],
66
71
  );
67
72
 
68
- const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
69
- handleTriggerKeyDown(e);
73
+ const handleKeyDown = (cb?: KeyboardEventHandler<HTMLInputElement>) => (e: KeyboardEvent<HTMLInputElement>) => {
74
+ if (e.key === ' ') {
75
+ e.stopPropagation();
76
+ }
70
77
 
71
78
  if (e.key.length === 1) {
72
79
  setIsOpen(true);
73
80
  }
81
+
82
+ cb?.(e);
74
83
  };
75
84
 
85
+ const items: BaseItemProps[] = useMemo(
86
+ () =>
87
+ options.map(({ onClick, ...item }, idx) => ({
88
+ ...item,
89
+ onClick: (e: MouseEvent<HTMLElement>) => {
90
+ // Dirty hack: by default list items call onClick by 'Space' || 'Enter'
91
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
92
+ // @ts-expect-error
93
+ if (!(e.type === 'keydown' && e?.key === ' ')) {
94
+ onClick?.(e);
95
+
96
+ if (item.id) {
97
+ onChange?.(String(item.id));
98
+ onSubmit?.(String(item.id));
99
+ } else if (typeof item.content === 'string') {
100
+ onChange?.(item.content);
101
+ onSubmit?.(item.content);
102
+ }
103
+
104
+ setIsOpen(false);
105
+ localRef.current?.focus();
106
+ }
107
+ },
108
+ onKeyDown: handleOptionKeyDown,
109
+ 'data-test-id': `${TEST_IDS.option}__${item.id ?? idx}`,
110
+ })),
111
+ [handleOptionKeyDown, onChange, onSubmit, options],
112
+ );
113
+
76
114
  return (
77
115
  <div className={cn(styles.wrap, className)} {...rest}>
78
116
  <Droplist
79
- open={Boolean(triggerElementRef.current?.value) && isOpen && options.length > 0}
80
- firstElementRefCallback={firstElementRefCallback}
81
- useScroll
117
+ open={isOpen && options.length > 0}
118
+ scroll
82
119
  size={size}
83
- onFocusLeave={handleDroplistFocusLeave}
84
120
  onOpenChange={setIsOpen}
85
121
  data-test-id={TEST_IDS.droplist}
86
122
  triggerClassName={styles.triggerClassName}
87
123
  scrollRef={scrollRef}
88
- triggerRef={triggerElementRef}
89
- triggerElement={
124
+ triggerElemRef={localRef}
125
+ items={items}
126
+ loading={loading}
127
+ >
128
+ {({ onKeyDown }) => (
90
129
  <SearchDecorator
91
130
  size={size}
92
131
  outline={outline || undefined}
93
- focused={(isOpen && Boolean(triggerElementRef.current?.value)) || undefined}
132
+ focused={(isOpen && Boolean(localRef.current?.value)) || undefined}
94
133
  data-test-id={TEST_IDS.decorator}
95
134
  >
96
135
  <SearchPrivate
@@ -99,29 +138,14 @@ export const SearchAutocomplete = forwardRef<HTMLInputElement, SearchAutocomplet
99
138
  onChange={onChange}
100
139
  onSubmit={onSubmit}
101
140
  placeholder={placeholder}
102
- ref={mergeRefs(ref, triggerElementRef)}
103
- onKeyDown={onKeyDown}
141
+ ref={mergeRefs(ref, localRef)}
142
+ onKeyDown={handleKeyDown(onKeyDown)}
104
143
  onFocus={onFocus}
105
144
  size={size}
106
145
  data-test-id={PRIVATE_SEARCH_TEST_IDS.field}
107
146
  />
108
147
  </SearchDecorator>
109
- }
110
- >
111
- {options.map(item => (
112
- <Droplist.ItemSingle
113
- {...item}
114
- key={item.option}
115
- onClick={e => {
116
- handleDroplistItemClick(e);
117
- onChange?.(item.option);
118
- onSubmit?.(item.option);
119
- triggerElementRef.current?.blur();
120
- }}
121
- onKeyDown={e => handleDroplistItemKeyDown(e, handleOptionKeyDown)}
122
- data-test-id={TEST_IDS.option}
123
- />
124
- ))}
148
+ )}
125
149
  </Droplist>
126
150
  </div>
127
151
  );