@snack-uikit/fields 0.51.11 → 0.51.12

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,14 @@
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
+ ## <small>0.51.12 (2025-12-01)</small>
7
+
8
+ * feat(PDS-3204): added onClearButtonClick in FieldText ([2a0526b](https://github.com/cloud-ru-tech/snack-uikit/commit/2a0526b))
9
+
10
+
11
+
12
+
13
+
6
14
  ## <small>0.51.11 (2025-11-28)</small>
7
15
 
8
16
  ### Only dependencies have been changed
package/README.md CHANGED
@@ -473,6 +473,7 @@ FieldStepper в основном предназначен для работы с
473
473
  | showCopyButton | `boolean` | - | Отображение кнопки Копировать для поля (актуально только для `readonly = true`) |
474
474
  | onCopyButtonClick | `() => void` | - | Колбек клика по кнопке Копировать для поля |
475
475
  | showClearButton | `boolean` | true | Отображение кнопки очистки поля |
476
+ | onClearButtonClick | `() => void` | - | Колбек клика по кнопке очистки поля |
476
477
  | allowMoreThanMaxLength | `boolean` | - | Можно ли вводить больше разрешённого кол-ва символов |
477
478
  | prefixIcon | `ReactElement<any, string \| JSXElementConstructor<any>>` | - | Иконка-префикс для поля |
478
479
  | prefix | `ReactNode` | - | Произвольный префикс для поля |
@@ -15,6 +15,8 @@ type FieldTextOwnProps = {
15
15
  * @default true
16
16
  */
17
17
  showClearButton?: boolean;
18
+ /** Колбек клика по кнопке очистки поля */
19
+ onClearButtonClick?(): void;
18
20
  /** Можно ли вводить больше разрешённого кол-ва символов */
19
21
  allowMoreThanMaxLength?: boolean;
20
22
  /** Иконка-префикс для поля */
@@ -44,6 +44,7 @@ exports.FieldText = (0, react_1.forwardRef)((_a, ref) => {
44
44
  onFocus,
45
45
  onBlur,
46
46
  onCopyButtonClick,
47
+ onClearButtonClick,
47
48
  className,
48
49
  label,
49
50
  labelTooltip,
@@ -67,7 +68,7 @@ exports.FieldText = (0, react_1.forwardRef)((_a, ref) => {
67
68
  inputMode,
68
69
  spellCheck
69
70
  } = _a,
70
- rest = __rest(_a, ["id", "name", "value", "placeholder", "maxLength", "disabled", "readonly", "showCopyButton", "showClearButton", "allowMoreThanMaxLength", "onChange", "onFocus", "onBlur", "onCopyButtonClick", "className", "label", "labelTooltip", "labelTooltipPlacement", "required", "caption", "hint", "showHintIcon", "size", "validationState", "error", "autoComplete", "autoFocus", "prefixIcon", "prefix", "postfix", "button", "onPaste", "onKeyDown", "type", "inputMode", "spellCheck"]);
71
+ rest = __rest(_a, ["id", "name", "value", "placeholder", "maxLength", "disabled", "readonly", "showCopyButton", "showClearButton", "allowMoreThanMaxLength", "onChange", "onFocus", "onBlur", "onCopyButtonClick", "onClearButtonClick", "className", "label", "labelTooltip", "labelTooltipPlacement", "required", "caption", "hint", "showHintIcon", "size", "validationState", "error", "autoComplete", "autoFocus", "prefixIcon", "prefix", "postfix", "button", "onPaste", "onKeyDown", "type", "inputMode", "spellCheck"]);
71
72
  const [value = '', onChange] = (0, hooks_1.useValueControl)({
72
73
  value: valueProp,
73
74
  defaultValue: '',
@@ -98,18 +99,26 @@ exports.FieldText = (0, react_1.forwardRef)((_a, ref) => {
98
99
  const containerVariant = (0, helpers_1.getContainerVariant)({
99
100
  button
100
101
  });
101
- const onClear = () => {
102
+ const [isFieldFocused, setIsFieldFocused] = (0, react_1.useState)(false);
103
+ const focusedRef = (0, react_1.useRef)(false);
104
+ const onClear = e => {
102
105
  var _a;
106
+ e.preventDefault();
103
107
  onChange('');
104
- if (required) {
108
+ onClearButtonClick === null || onClearButtonClick === void 0 ? void 0 : onClearButtonClick();
109
+ if (focusedRef.current) {
105
110
  (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
106
111
  }
107
112
  };
113
+ const handleClearClickDown = () => {
114
+ focusedRef.current = isFieldFocused;
115
+ };
108
116
  const clearButtonSettings = (0, input_private_1.useClearButton)({
109
117
  clearButtonRef,
110
118
  showClearButton,
111
- size,
112
- onClear
119
+ onClear,
120
+ onDown: handleClearClickDown,
121
+ size
113
122
  });
114
123
  const copyButtonSettings = (0, hooks_1.useCopyButton)({
115
124
  copyButtonRef,
@@ -164,6 +173,20 @@ exports.FieldText = (0, react_1.forwardRef)((_a, ref) => {
164
173
  onInputKeyDown(event);
165
174
  onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
166
175
  };
176
+ const handleBlur = event => {
177
+ const nextTarget = event.relatedTarget;
178
+ // если фокус ушёл на кнопку очистки — игнорируем внешний onBlur
179
+ if (nextTarget && nextTarget === clearButtonRef.current) {
180
+ setIsFieldFocused(false);
181
+ return;
182
+ }
183
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
184
+ setIsFieldFocused(false);
185
+ };
186
+ const handleFocus = event => {
187
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
188
+ setIsFieldFocused(true);
189
+ };
167
190
  return (0, jsx_runtime_1.jsx)(FieldDecorator_1.FieldDecorator, Object.assign({
168
191
  className: className,
169
192
  label: label,
@@ -199,8 +222,8 @@ exports.FieldText = (0, react_1.forwardRef)((_a, ref) => {
199
222
  "data-size": size,
200
223
  value: value,
201
224
  onChange: onChange,
202
- onFocus: onFocus,
203
- onBlur: onBlur,
225
+ onFocus: handleFocus,
226
+ onBlur: handleBlur,
204
227
  tabIndex: inputTabIndex,
205
228
  onKeyDown: handleKeyDown,
206
229
  onPaste: onPaste,
@@ -15,6 +15,8 @@ type FieldTextOwnProps = {
15
15
  * @default true
16
16
  */
17
17
  showClearButton?: boolean;
18
+ /** Колбек клика по кнопке очистки поля */
19
+ onClearButtonClick?(): void;
18
20
  /** Можно ли вводить больше разрешённого кол-ва символов */
19
21
  allowMoreThanMaxLength?: boolean;
20
22
  /** Иконка-префикс для поля */
@@ -21,7 +21,7 @@ import { getValidationState } from '../../utils/getValidationState';
21
21
  import { FieldDecorator } from '../FieldDecorator';
22
22
  import { getContainerVariant } from './helpers';
23
23
  export const FieldText = forwardRef((_a, ref) => {
24
- var { id, name, value: valueProp, placeholder, maxLength, disabled = false, readonly = false, showCopyButton: showCopyButtonProp = true, showClearButton: showClearButtonProp = true, allowMoreThanMaxLength = false, onChange: onChangeProp, onFocus, onBlur, onCopyButtonClick, className, label, labelTooltip, labelTooltipPlacement, required = false, caption, hint, showHintIcon, size = SIZE.S, validationState = VALIDATION_STATE.Default, error, autoComplete, autoFocus, prefixIcon, prefix, postfix, button: buttonProp, onPaste, onKeyDown, type = 'text', inputMode, spellCheck } = _a, rest = __rest(_a, ["id", "name", "value", "placeholder", "maxLength", "disabled", "readonly", "showCopyButton", "showClearButton", "allowMoreThanMaxLength", "onChange", "onFocus", "onBlur", "onCopyButtonClick", "className", "label", "labelTooltip", "labelTooltipPlacement", "required", "caption", "hint", "showHintIcon", "size", "validationState", "error", "autoComplete", "autoFocus", "prefixIcon", "prefix", "postfix", "button", "onPaste", "onKeyDown", "type", "inputMode", "spellCheck"]);
24
+ var { id, name, value: valueProp, placeholder, maxLength, disabled = false, readonly = false, showCopyButton: showCopyButtonProp = true, showClearButton: showClearButtonProp = true, allowMoreThanMaxLength = false, onChange: onChangeProp, onFocus, onBlur, onCopyButtonClick, onClearButtonClick, className, label, labelTooltip, labelTooltipPlacement, required = false, caption, hint, showHintIcon, size = SIZE.S, validationState = VALIDATION_STATE.Default, error, autoComplete, autoFocus, prefixIcon, prefix, postfix, button: buttonProp, onPaste, onKeyDown, type = 'text', inputMode, spellCheck } = _a, rest = __rest(_a, ["id", "name", "value", "placeholder", "maxLength", "disabled", "readonly", "showCopyButton", "showClearButton", "allowMoreThanMaxLength", "onChange", "onFocus", "onBlur", "onCopyButtonClick", "onClearButtonClick", "className", "label", "labelTooltip", "labelTooltipPlacement", "required", "caption", "hint", "showHintIcon", "size", "validationState", "error", "autoComplete", "autoFocus", "prefixIcon", "prefix", "postfix", "button", "onPaste", "onKeyDown", "type", "inputMode", "spellCheck"]);
25
25
  const [value = '', onChange] = useValueControl({
26
26
  value: valueProp,
27
27
  defaultValue: '',
@@ -41,14 +41,27 @@ export const FieldText = forwardRef((_a, ref) => {
41
41
  setTimeout(() => { var _a; return (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
42
42
  } }) }) : undefined, [buttonProp]);
43
43
  const containerVariant = getContainerVariant({ button });
44
- const onClear = () => {
44
+ const [isFieldFocused, setIsFieldFocused] = useState(false);
45
+ const focusedRef = useRef(false);
46
+ const onClear = e => {
45
47
  var _a;
48
+ e.preventDefault();
46
49
  onChange('');
47
- if (required) {
50
+ onClearButtonClick === null || onClearButtonClick === void 0 ? void 0 : onClearButtonClick();
51
+ if (focusedRef.current) {
48
52
  (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
49
53
  }
50
54
  };
51
- const clearButtonSettings = useClearButton({ clearButtonRef, showClearButton, size, onClear });
55
+ const handleClearClickDown = () => {
56
+ focusedRef.current = isFieldFocused;
57
+ };
58
+ const clearButtonSettings = useClearButton({
59
+ clearButtonRef,
60
+ showClearButton,
61
+ onClear,
62
+ onDown: handleClearClickDown,
63
+ size,
64
+ });
52
65
  const copyButtonSettings = useCopyButton({
53
66
  copyButtonRef,
54
67
  showCopyButton,
@@ -91,5 +104,19 @@ export const FieldText = forwardRef((_a, ref) => {
91
104
  onInputKeyDown(event);
92
105
  onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
93
106
  };
94
- return (_jsx(FieldDecorator, Object.assign({ className: className, label: label, labelTooltip: labelTooltip, labelTooltipPlacement: labelTooltipPlacement, labelFor: id, required: required, caption: caption, length: maxLength ? { max: maxLength, current: value.length } : undefined, hint: hint, disabled: disabled, readonly: readonly, showHintIcon: showHintIcon, size: size, validationState: fieldValidationState, error: error }, extractSupportProps(rest), { children: _jsx(FieldContainerPrivate, { size: size, validationState: fieldValidationState, disabled: disabled, readonly: readonly, variant: containerVariant, inputRef: localRef, prefix: prefixButtons, postfix: postfixButtons, disableFocus: isButtonFocused, children: _jsx(InputPrivate, { ref: mergeRefs(ref, localRef), "data-size": size, value: value, onChange: onChange, onFocus: onFocus, onBlur: onBlur, tabIndex: inputTabIndex, onKeyDown: handleKeyDown, onPaste: onPaste, placeholder: placeholder, disabled: disabled, readonly: readonly, type: type, inputMode: inputMode, maxLength: allowMoreThanMaxLength ? undefined : maxLength || undefined, id: id, name: name, autoComplete: autoComplete, autoFocus: autoFocus, spellCheck: spellCheck, "data-test-id": 'field-text__input' }) }) })));
107
+ const handleBlur = event => {
108
+ const nextTarget = event.relatedTarget;
109
+ // если фокус ушёл на кнопку очистки — игнорируем внешний onBlur
110
+ if (nextTarget && nextTarget === clearButtonRef.current) {
111
+ setIsFieldFocused(false);
112
+ return;
113
+ }
114
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
115
+ setIsFieldFocused(false);
116
+ };
117
+ const handleFocus = event => {
118
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
119
+ setIsFieldFocused(true);
120
+ };
121
+ return (_jsx(FieldDecorator, Object.assign({ className: className, label: label, labelTooltip: labelTooltip, labelTooltipPlacement: labelTooltipPlacement, labelFor: id, required: required, caption: caption, length: maxLength ? { max: maxLength, current: value.length } : undefined, hint: hint, disabled: disabled, readonly: readonly, showHintIcon: showHintIcon, size: size, validationState: fieldValidationState, error: error }, extractSupportProps(rest), { children: _jsx(FieldContainerPrivate, { size: size, validationState: fieldValidationState, disabled: disabled, readonly: readonly, variant: containerVariant, inputRef: localRef, prefix: prefixButtons, postfix: postfixButtons, disableFocus: isButtonFocused, children: _jsx(InputPrivate, { ref: mergeRefs(ref, localRef), "data-size": size, value: value, onChange: onChange, onFocus: handleFocus, onBlur: handleBlur, tabIndex: inputTabIndex, onKeyDown: handleKeyDown, onPaste: onPaste, placeholder: placeholder, disabled: disabled, readonly: readonly, type: type, inputMode: inputMode, maxLength: allowMoreThanMaxLength ? undefined : maxLength || undefined, id: id, name: name, autoComplete: autoComplete, autoFocus: autoFocus, spellCheck: spellCheck, "data-test-id": 'field-text__input' }) }) })));
95
122
  });
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Fields",
7
- "version": "0.51.11",
7
+ "version": "0.51.12",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -37,13 +37,13 @@
37
37
  "scripts": {},
38
38
  "dependencies": {
39
39
  "@snack-uikit/button": "0.19.16",
40
- "@snack-uikit/calendar": "0.13.10",
41
- "@snack-uikit/color-picker": "0.3.49",
40
+ "@snack-uikit/calendar": "0.13.11",
41
+ "@snack-uikit/color-picker": "0.3.50",
42
42
  "@snack-uikit/divider": "3.2.10",
43
43
  "@snack-uikit/dropdown": "0.5.3",
44
44
  "@snack-uikit/icons": "0.27.4",
45
- "@snack-uikit/input-private": "4.8.4",
46
- "@snack-uikit/list": "0.32.9",
45
+ "@snack-uikit/input-private": "4.8.5",
46
+ "@snack-uikit/list": "0.32.10",
47
47
  "@snack-uikit/scroll": "0.10.5",
48
48
  "@snack-uikit/skeleton": "0.6.9",
49
49
  "@snack-uikit/slider": "0.3.30",
@@ -66,5 +66,5 @@
66
66
  "peerDependencies": {
67
67
  "@snack-uikit/locale": "*"
68
68
  },
69
- "gitHead": "6b39e180766635f8190da903bf560d1fbcbfaf52"
69
+ "gitHead": "e1b3bee4ec326c0c7e9c87b2714634a8ff113ded"
70
70
  }
@@ -1,7 +1,9 @@
1
1
  import mergeRefs from 'merge-refs';
2
2
  import {
3
+ FocusEventHandler,
3
4
  forwardRef,
4
5
  KeyboardEventHandler,
6
+ MouseEventHandler,
5
7
  ReactElement,
6
8
  ReactNode,
7
9
  useCallback,
@@ -66,6 +68,8 @@ type FieldTextOwnProps = {
66
68
  * @default true
67
69
  */
68
70
  showClearButton?: boolean;
71
+ /** Колбек клика по кнопке очистки поля */
72
+ onClearButtonClick?(): void;
69
73
  /** Можно ли вводить больше разрешённого кол-ва символов */
70
74
  allowMoreThanMaxLength?: boolean;
71
75
  /** Иконка-префикс для поля */
@@ -100,6 +104,7 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
100
104
  onFocus,
101
105
  onBlur,
102
106
  onCopyButtonClick,
107
+ onClearButtonClick,
103
108
  className,
104
109
  label,
105
110
  labelTooltip,
@@ -158,15 +163,31 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
158
163
 
159
164
  const containerVariant = getContainerVariant({ button });
160
165
 
161
- const onClear = () => {
166
+ const [isFieldFocused, setIsFieldFocused] = useState(false);
167
+ const focusedRef = useRef(false);
168
+
169
+ const onClear: MouseEventHandler<HTMLButtonElement> = e => {
170
+ e.preventDefault();
162
171
  onChange('');
172
+ onClearButtonClick?.();
163
173
 
164
- if (required) {
174
+ if (focusedRef.current) {
165
175
  localRef.current?.focus();
166
176
  }
167
177
  };
168
178
 
169
- const clearButtonSettings = useClearButton({ clearButtonRef, showClearButton, size, onClear });
179
+ const handleClearClickDown = () => {
180
+ focusedRef.current = isFieldFocused;
181
+ };
182
+
183
+ const clearButtonSettings = useClearButton({
184
+ clearButtonRef,
185
+ showClearButton,
186
+ onClear,
187
+ onDown: handleClearClickDown,
188
+ size,
189
+ });
190
+
170
191
  const copyButtonSettings = useCopyButton({
171
192
  copyButtonRef,
172
193
  showCopyButton,
@@ -218,6 +239,24 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
218
239
  onKeyDown?.(event);
219
240
  };
220
241
 
242
+ const handleBlur: FocusEventHandler<HTMLInputElement> = event => {
243
+ const nextTarget = event.relatedTarget as HTMLElement | null;
244
+
245
+ // если фокус ушёл на кнопку очистки — игнорируем внешний onBlur
246
+ if (nextTarget && nextTarget === clearButtonRef.current) {
247
+ setIsFieldFocused(false);
248
+ return;
249
+ }
250
+
251
+ onBlur?.(event);
252
+ setIsFieldFocused(false);
253
+ };
254
+
255
+ const handleFocus: FocusEventHandler<HTMLInputElement> = event => {
256
+ onFocus?.(event);
257
+ setIsFieldFocused(true);
258
+ };
259
+
221
260
  return (
222
261
  <FieldDecorator
223
262
  className={className}
@@ -253,8 +292,8 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
253
292
  data-size={size}
254
293
  value={value}
255
294
  onChange={onChange}
256
- onFocus={onFocus}
257
- onBlur={onBlur}
295
+ onFocus={handleFocus}
296
+ onBlur={handleBlur}
258
297
  tabIndex={inputTabIndex}
259
298
  onKeyDown={handleKeyDown}
260
299
  onPaste={onPaste}