@sima-land/ui-nucleons 42.1.1 → 42.2.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.
@@ -66,15 +66,13 @@ const Autocomplete = (_a) => {
66
66
  const rootRef = (0, react_1.useRef)(null);
67
67
  const fieldRef = (0, react_1.useRef)();
68
68
  const menuRef = (0, react_1.useRef)();
69
- const [needDropdown, toggleDropdown] = (0, react_1.useState)(false);
69
+ const [withMenu, toggleMenu] = (0, react_1.useState)(false);
70
70
  const [realValue, setRealValue] = (0, react_1.useState)(value || defaultValue);
71
71
  const [activeIndex, setActiveIndex] = (0, react_1.useState)(null);
72
- const canShowDropdown = preset === 'filled-only-list'
73
- ? needDropdown && realValue && realValue.length > 0
74
- : needDropdown;
72
+ const canShowDropdown = preset === 'filled-only-list' ? withMenu && realValue && realValue.length > 0 : withMenu;
75
73
  const endAdornment = preset === 'default' ? react_1.default.createElement(down_1.default, { fill: colors_1.COLORS.get('gray38') }) : undefined;
76
74
  (0, hooks_1.useOutsideClick)(rootRef, () => {
77
- toggleDropdown(false);
75
+ toggleMenu(false);
78
76
  });
79
77
  (0, react_1.useEffect)(() => {
80
78
  setRealValue(value);
@@ -88,7 +86,7 @@ const Autocomplete = (_a) => {
88
86
  const osInstance = (_a = osComponentRef.current) === null || _a === void 0 ? void 0 : _a.osInstance();
89
87
  if (menu && osInstance && activeIndex !== null) {
90
88
  const child = menu.querySelectorAll('[role="menuitem"]')[activeIndex];
91
- child &&
89
+ child instanceof HTMLElement &&
92
90
  osInstance.scroll({
93
91
  el: child,
94
92
  scroll: { y: 'ifneeded' },
@@ -96,11 +94,11 @@ const Autocomplete = (_a) => {
96
94
  }
97
95
  }, [activeIndex]);
98
96
  return (react_1.default.createElement("div", { ref: rootRef, style: style, className: cx('root', className), "data-testid": dataTestId },
99
- react_1.default.createElement(text_field_1.TextField, Object.assign({}, restProps, { ref: fieldRef, focused: needDropdown, endAdornment: endAdornment, "data-testid": 'autocomplete:field', variant: 'desktop', value: value, multiline: false, onClick: e => {
100
- toggleDropdown(true);
97
+ react_1.default.createElement(text_field_1.TextField, Object.assign({}, restProps, { ref: fieldRef, focused: withMenu, endAdornment: endAdornment, "data-testid": 'autocomplete:field', variant: 'desktop', value: value, multiline: false, onClick: e => {
98
+ toggleMenu(true);
101
99
  onClick && onClick(e);
102
100
  }, onChange: (e) => {
103
- toggleDropdown(true);
101
+ toggleMenu(true);
104
102
  setRealValue(e.target.value);
105
103
  setActiveIndex(0);
106
104
  onChange && onChange(e);
@@ -117,7 +115,7 @@ const Autocomplete = (_a) => {
117
115
  case 'Enter':
118
116
  if (items && !(0, lodash_1.isNull)(activeIndex) && items.length > activeIndex) {
119
117
  onSelect && onSelect(items[activeIndex]);
120
- toggleDropdown(false);
118
+ toggleMenu(false);
121
119
  }
122
120
  break;
123
121
  }
@@ -127,7 +125,7 @@ const Autocomplete = (_a) => {
127
125
  canShowDropdown && (react_1.default.createElement(dropdown_1.Dropdown, Object.assign({}, (0, dropdown_2.placeDropdown)(restProps.size), { ref: menuRef, "data-testid": 'autocomplete:menu', className: cx('menu'), role: 'menu', customScrollbarProps: { osComponentRef } }), loading ? (react_1.default.createElement(dropdown_loading_1.DropdownLoading, { "data-testid": 'autocomplete:loading-area' })) : (react_1.default.createElement(react_1.default.Fragment, null, items && items.length > 0 ? (items.map((item, index) => (react_1.default.createElement(dropdown_item_1.DropdownItem, { size: itemSize, key: index, role: 'menuitem', checked: index === activeIndex, onClick: () => {
128
126
  onSelect && onSelect(item);
129
127
  setActiveIndex(null);
130
- toggleDropdown(false);
128
+ toggleMenu(false);
131
129
  fieldRef.current && fieldRef.current.focus();
132
130
  } }, renderItem(item))))) : (react_1.default.createElement(dropdown_item_1.DropdownItem, { size: 'm', noHover: true }, "\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E"))))))));
133
131
  };
package/hooks/index.js CHANGED
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.useIsomorphicLayoutEffect = exports.useOutsideClick = exports.useInfiniteScroll = exports.useIsTouchDevice = void 0;
7
7
  const react_1 = require("react");
8
8
  const is_touch_device_1 = require("../helpers/is-touch-device");
9
- const lodash_1 = require("lodash");
10
9
  const on_1 = __importDefault(require("../helpers/on"));
11
10
  const identity_1 = require("./identity");
12
11
  /**
@@ -51,16 +50,14 @@ exports.useInfiniteScroll = useInfiniteScroll;
51
50
  * @param callback Сработает при клике вне элемента.
52
51
  */
53
52
  const useOutsideClick = (elementRef, callback) => {
54
- const callbackRef = (0, react_1.useRef)(callback);
55
- callbackRef.current = callback;
56
- (0, react_1.useEffect)(() => (0, on_1.default)(document.documentElement, 'click', (event) => {
53
+ const callbackRef = (0, identity_1.useIdentityRef)(callback);
54
+ (0, react_1.useEffect)(() => (0, on_1.default)(document.documentElement, 'click', event => {
57
55
  const { current: element } = elementRef;
58
56
  const { current: handleClick } = callbackRef;
59
57
  element &&
60
58
  element !== event.target &&
61
59
  !element.contains(event.target) &&
62
- (0, lodash_1.isFunction)(handleClick) &&
63
- handleClick(event);
60
+ (handleClick === null || handleClick === void 0 ? void 0 : handleClick(event));
64
61
  }), []);
65
62
  };
66
63
  exports.useOutsideClick = useOutsideClick;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sima-land/ui-nucleons",
3
- "version": "42.1.1",
3
+ "version": "42.2.0",
4
4
  "repository": "ssh://git@github.com:sima-land/ui-nucleons.git",
5
5
  "author": "www.sima-land.ru team",
6
6
  "license": "Apache-2.0",
@@ -5,7 +5,7 @@ import { Country } from './presets';
5
5
  interface PhoneInputStyle extends React.CSSProperties {
6
6
  '--phone-input-width'?: number | string;
7
7
  }
8
- export interface PhoneInputProps extends Omit<TextFieldProps, 'onChange' | 'onBlur' | 'value' | 'ref' | 'style'> {
8
+ export interface PhoneInputProps extends Omit<TextFieldProps, 'onChange' | 'onBlur' | 'value' | 'ref' | 'style' | 'multiline'> {
9
9
  /** Сработает при "blur". */
10
10
  onBlur?: (e: React.FocusEvent<HTMLInputElement>, s: MaskState & {
11
11
  ready?: boolean;
@@ -35,7 +35,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.PhoneInput = void 0;
37
37
  const react_1 = __importStar(require("react"));
38
- const bind_1 = __importDefault(require("classnames/bind"));
39
38
  const down_1 = __importDefault(require("@sima-land/ui-quarks/icons/16x16/Stroked/Arrows/down"));
40
39
  const up_1 = __importDefault(require("@sima-land/ui-quarks/icons/16x16/Stroked/Arrows/up"));
41
40
  const dropdown_1 = require("../dropdown");
@@ -44,10 +43,9 @@ const text_field_1 = require("../text-field");
44
43
  const masked_field_1 = require("../masked-field");
45
44
  const hooks_1 = require("../hooks");
46
45
  const presets_1 = require("./presets");
47
- const sizes_1 = require("../styling/sizes");
48
- const colors_1 = require("../colors");
49
- const phone_input_module_scss_1 = __importDefault(require("./phone-input.module.scss"));
50
46
  const utils_1 = require("./utils");
47
+ const bind_1 = __importDefault(require("classnames/bind"));
48
+ const phone_input_module_scss_1 = __importDefault(require("./phone-input.module.scss"));
51
49
  const cx = bind_1.default.bind(phone_input_module_scss_1.default);
52
50
  /**
53
51
  * Удаляет из значения код переданной страны.
@@ -66,23 +64,32 @@ const PhoneInput = (_a) => {
66
64
  // маску определяем автоматически только при старте
67
65
  const [country, setCountry] = (0, react_1.useState)((0, utils_1.defineCountry)(value));
68
66
  const [cleanValue, setCleanValue] = (0, react_1.useState)(formatValue(value, country));
69
- const [isPopupOpen, togglePopup] = (0, react_1.useState)(false);
70
- const dropdownRef = (0, react_1.useRef)(null);
67
+ const [withMenu, toggleMenu] = (0, react_1.useState)(false);
68
+ const rootRef = (0, react_1.useRef)(null);
69
+ const openerRef = (0, react_1.useRef)(null);
71
70
  (0, react_1.useEffect)(() => {
72
71
  setCleanValue(formatValue(value, country));
73
72
  }, [value]);
74
- (0, hooks_1.useOutsideClick)(dropdownRef, event => {
75
- event.stopPropagation();
76
- togglePopup(false);
73
+ (0, hooks_1.useOutsideClick)(rootRef, () => {
74
+ toggleMenu(false);
77
75
  });
78
- const AdornmentSVG = isPopupOpen ? up_1.default : down_1.default;
79
- const commonFieldProps = Object.assign(Object.assign({}, restProps), { label, multiline: false, className: cx('field'), endAdornment: (react_1.default.createElement("div", { className: cx('adornment'), "data-testid": 'phone-input:dropdown-opener', onClick: event => {
80
- event.stopPropagation();
81
- togglePopup(true);
76
+ const AdornmentSVG = withMenu ? up_1.default : down_1.default;
77
+ const commonFieldProps = Object.assign(Object.assign({}, restProps), { label, multiline: false, className: cx('field'), endAdornment: (react_1.default.createElement("div", { ref: openerRef, className: cx('adornment'), "data-testid": 'phone-input:dropdown-opener', onClick: () => {
78
+ toggleMenu(true);
82
79
  } },
83
80
  react_1.default.createElement("img", { alt: '', width: 24, height: 24, src: country.imageSrc }),
84
- react_1.default.createElement(AdornmentSVG, { fill: colors_1.COLORS.get('gray38'), className: (0, sizes_1.marginLeft)(1) }))) });
85
- return (react_1.default.createElement("div", { "data-testid": testId, style: style, className: cx('root', className) },
81
+ react_1.default.createElement(AdornmentSVG, { className: cx('arrow') }))), onClick: event => {
82
+ const opener = openerRef.current;
83
+ if (opener instanceof Node &&
84
+ event.target instanceof Node &&
85
+ (opener === event.target || opener.contains(event.target))) {
86
+ event.preventDefault(); // чтобы не фокусировалось на поле
87
+ }
88
+ else {
89
+ toggleMenu(false);
90
+ }
91
+ } });
92
+ return (react_1.default.createElement("div", { ref: rootRef, "data-testid": testId, style: style, className: cx('root', className) },
86
93
  country.id === presets_1.IDS.other ? (react_1.default.createElement(text_field_1.TextField, Object.assign({}, commonFieldProps, { onChange: (e) => {
87
94
  e.target.value = e.target.value.replace(/\D/g, '').slice(0, 20);
88
95
  onChange && onChange(e);
@@ -101,8 +108,8 @@ const PhoneInput = (_a) => {
101
108
  // вставляем код страны обратно в чистое значение
102
109
  cleanValue: `${country.codeChars}${state.cleanValue}` }));
103
110
  } }))),
104
- isPopupOpen && (react_1.default.createElement(dropdown_1.Dropdown, { ref: dropdownRef, role: 'menu', className: cx('popup'), "data-testid": 'phone-input:dropdown' }, presets_1.countriesList.map((item, index) => item.id === country.id ? null : (react_1.default.createElement(dropdown_item_1.DropdownItem, { key: index, size: 'l', role: 'menuitem', className: cx('popup-item'), onClick: () => {
105
- togglePopup(false);
111
+ withMenu && (react_1.default.createElement(dropdown_1.Dropdown, { role: 'menu', className: cx('menu'), "data-testid": 'phone-input:dropdown' }, presets_1.countriesList.map((item, index) => item.id === country.id ? null : (react_1.default.createElement(dropdown_item_1.DropdownItem, { key: index, size: 'l', role: 'menuitem', className: cx('menu-item'), onClick: () => {
112
+ toggleMenu(false);
106
113
  setCountry(item);
107
114
  onCountrySelect && onCountrySelect(item);
108
115
  } },
@@ -17,7 +17,14 @@
17
17
  cursor: pointer;
18
18
  }
19
19
 
20
- .popup {
20
+ .arrow {
21
+ display: block;
22
+ fill: colors.$gray38;
23
+ margin-left: 4px;
24
+ pointer-events: none; // игнорируем клики т.к. после них элемент стрелки подменяется и считается что он сработал вне элемента родителя
25
+ }
26
+
27
+ .menu {
21
28
  position: absolute;
22
29
  width: 100%;
23
30
  top: 56px;
@@ -25,7 +32,7 @@
25
32
  z-index: 2;
26
33
  }
27
34
 
28
- .popup-item {
35
+ .menu-item {
29
36
  font-size: 16px;
30
37
  line-height: 24px;
31
38
  .item-name {
package/select/index.js CHANGED
@@ -83,10 +83,9 @@ const Select = (_a) => {
83
83
  onKeyDown: e => {
84
84
  e.key === 'Enter' && toggleMenu(true);
85
85
  },
86
- onClick: e => {
87
- e.preventDefault();
88
- onClick && onClick(e);
89
- },
86
+ }, onClick: e => {
87
+ e.preventDefault();
88
+ onClick && onClick(e);
90
89
  }, className: select_module_scss_1.default.field, "data-testid": 'select:field', label: label, focused: opened, baseInputProps: {
91
90
  // чтобы не ловить фокус на поле
92
91
  style: { pointerEvents: 'none' },
@@ -47,7 +47,7 @@ export interface TextFieldProps extends Omit<React.InputHTMLAttributes<HTMLInput
47
47
  /** Вариант отображения. */
48
48
  variant?: Variant;
49
49
  /** Свойства элемента-блока. */
50
- blockProps?: React.HTMLProps<HTMLDivElement>;
50
+ blockProps?: Omit<React.HTMLProps<HTMLDivElement>, 'onClick'>;
51
51
  /** Идентификатор для систем автоматизированного тестирования. */
52
52
  'data-testid'?: string;
53
53
  }
@@ -68,9 +68,9 @@ exports.TextField = (0, react_1.forwardRef)(function TextField({ autoFocus, capt
68
68
  variant,
69
69
  });
70
70
  (0, react_1.useImperativeHandle)(ref, () => baseInputRef.current);
71
- (0, react_1.useEffect)(() => toggleHasValue(Boolean(baseInputRef.current.value)));
71
+ (0, react_1.useEffect)(() => { var _a; return toggleHasValue(Boolean((_a = baseInputRef.current) === null || _a === void 0 ? void 0 : _a.value)); });
72
72
  return (react_1.default.createElement("div", { "data-testid": dataTestId, className: cx('root', className, classes.root), style: style },
73
- react_1.default.createElement("div", Object.assign({ "data-testid": 'text-field:block', className: cx('reset', 'input-block', commonModifyClasses, size && `size-${size}`, focused && classes.blockFocused, multiline && 'multiline', withLabel && 'with-label', variant === 'desktop' && rounds !== 'none' && shapes_1.SmallRounds[rounds], blockProps === null || blockProps === void 0 ? void 0 : blockProps.className), onClick: event => {
73
+ react_1.default.createElement("div", Object.assign({}, blockProps, { "data-testid": 'text-field:block', className: cx('reset', 'input-block', commonModifyClasses, size && `size-${size}`, focused && classes.blockFocused, multiline && 'multiline', withLabel && 'with-label', variant === 'desktop' && rounds !== 'none' && shapes_1.SmallRounds[rounds], blockProps === null || blockProps === void 0 ? void 0 : blockProps.className), onClick: event => {
74
74
  const input = baseInputRef.current;
75
75
  onClick === null || onClick === void 0 ? void 0 : onClick(event);
76
76
  input &&
@@ -78,7 +78,7 @@ exports.TextField = (0, react_1.forwardRef)(function TextField({ autoFocus, capt
78
78
  !event.defaultPrevented &&
79
79
  input !== document.activeElement &&
80
80
  input.focus();
81
- } }, blockProps),
81
+ } }),
82
82
  !multiline && Boolean(startAdornment) && (react_1.default.createElement(box_1.Box, { display: 'flex', alignItems: 'center', marginRight: 4 }, startAdornment)),
83
83
  react_1.default.createElement("div", { className: cx('main-column') },
84
84
  withLabel && (react_1.default.createElement("label", { className: cx('label', commonModifyClasses, hasValue && 'filled', labelAsPlaceholder && 'as-placeholder'), htmlFor: id, children: label, "data-testid": 'text-field:label' })),