@cloud-ru/ds-fields 1.0.8-preview-04507fc5.0 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-ru/ds-fields",
3
- "version": "1.0.8-preview-04507fc5.0",
3
+ "version": "2.0.0",
4
4
  "description": "Поля ввода с единой обёрткой label/caption/hint/error и общими осями size/validationState — текст, пароль, число, ползунок, дата, время, выбор, цвет.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,24 +39,24 @@
39
39
  "classnames": "^2.5.1",
40
40
  "merge-refs": "^2.0.0",
41
41
  "react-textarea-autosize": "^8.5.9",
42
- "@cloud-ru/ds-adaptive": "1.0.1",
43
- "@cloud-ru/ds-bottom-sheet": "1.0.2",
44
- "@cloud-ru/ds-button": "1.0.1",
45
- "@cloud-ru/ds-calendar": "1.0.7-preview-04507fc5.0",
46
- "@cloud-ru/ds-color-picker": "1.0.2",
47
- "@cloud-ru/ds-dropdown": "1.0.3-preview-04507fc5.0",
48
- "@cloud-ru/ds-divider": "1.0.1",
49
- "@cloud-ru/ds-field-decorator": "1.0.2",
50
- "@cloud-ru/ds-icons": "1.0.1",
51
- "@cloud-ru/ds-input-private": "1.0.1",
52
- "@cloud-ru/ds-list": "2.0.1-preview-04507fc5.0",
53
- "@cloud-ru/ds-loader": "1.0.1",
54
- "@cloud-ru/ds-materials": "1.0.1",
55
- "@cloud-ru/ds-scroll": "1.0.1",
56
- "@cloud-ru/ds-skeleton": "1.0.1",
57
- "@cloud-ru/ds-slider": "1.0.2",
58
- "@cloud-ru/ds-tag": "1.0.4-preview-04507fc5.0",
59
- "@cloud-ru/ds-tooltip": "1.0.2",
60
- "@cloud-ru/ds-utils": "1.1.0"
42
+ "@cloud-ru/ds-adaptive": "^1.0.1",
43
+ "@cloud-ru/ds-bottom-sheet": "^1.0.3",
44
+ "@cloud-ru/ds-color-picker": "^1.0.3",
45
+ "@cloud-ru/ds-dropdown": "^1.0.3",
46
+ "@cloud-ru/ds-divider": "^1.0.1",
47
+ "@cloud-ru/ds-button": "^1.0.1",
48
+ "@cloud-ru/ds-icons": "^1.0.2",
49
+ "@cloud-ru/ds-calendar": "^1.0.7",
50
+ "@cloud-ru/ds-loader": "^1.0.1",
51
+ "@cloud-ru/ds-input-private": "^1.0.2",
52
+ "@cloud-ru/ds-list": "^2.0.1",
53
+ "@cloud-ru/ds-materials": "^1.0.1",
54
+ "@cloud-ru/ds-skeleton": "^1.0.1",
55
+ "@cloud-ru/ds-slider": "^1.0.3",
56
+ "@cloud-ru/ds-scroll": "^1.0.2",
57
+ "@cloud-ru/ds-utils": "^1.1.0",
58
+ "@cloud-ru/ds-tag": "^1.0.4",
59
+ "@cloud-ru/ds-tooltip": "^1.0.3",
60
+ "@cloud-ru/ds-field-decorator": "^1.0.3"
61
61
  }
62
62
  }
@@ -141,15 +141,42 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
141
141
  // строкой при переключении selection) не должен ронять компонент на `.map` строки.
142
142
  multiple && Array.isArray(props.defaultValue) ? (props.defaultValue as ItemId[]) : [],
143
143
  );
144
- const singleValue = useMemo<ItemId | undefined>(
145
- () => (!multiple ? ((props.value as ItemId | undefined) ?? singleLocal) : undefined),
146
- [multiple, props.value, singleLocal],
147
- );
144
+
145
+ // Controlled-режим «залипает»: как только потребитель прислал `value`, локальный стейт больше
146
+ // не читается. Иначе очистка (потребитель ставит `undefined`) откатывалась бы на устаревший
147
+ // локальный выбор и воскрешала прошлое значение.
148
+ const singleControlled = useRef(false);
149
+ const multipleControlled = useRef(false);
150
+
151
+ if (!multiple && props.value !== undefined) {
152
+ singleControlled.current = true;
153
+ }
154
+
155
+ if (multiple && Array.isArray(props.value)) {
156
+ multipleControlled.current = true;
157
+ }
158
+
159
+ const singleValue = useMemo<ItemId | undefined>(() => {
160
+ if (multiple) {
161
+ return undefined;
162
+ }
163
+
164
+ const raw = singleControlled.current ? (props.value as ItemId | undefined) : singleLocal;
165
+
166
+ // `''` = «значение не выбрано»: формы отдают пустое значение строкой.
167
+ return raw === '' ? undefined : raw;
168
+ }, [multiple, props.value, singleLocal]);
169
+
148
170
  const multipleValue = useMemo<ItemId[]>(() => {
149
171
  if (!multiple) {
150
172
  return [];
151
173
  }
152
- return Array.isArray(props.value) ? (props.value as ItemId[]) : multipleLocal;
174
+
175
+ if (multipleControlled.current) {
176
+ return Array.isArray(props.value) ? (props.value as ItemId[]) : [];
177
+ }
178
+
179
+ return multipleLocal;
153
180
  }, [multiple, props.value, multipleLocal]);
154
181
 
155
182
  const effectiveValidationState = useMemo(
@@ -547,7 +574,7 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
547
574
  className={cn(fieldStyles.fieldInput, !searchable && styles.readonlyCursor)}
548
575
  value={inputValue}
549
576
  onChange={searchable ? handleInputChange : undefined}
550
- placeholder={selectedLabel ? '' : placeholder}
577
+ placeholder={selectedLabel || hasChips ? '' : placeholder}
551
578
  disabled={disabled}
552
579
  readonly={!searchable || readOnly}
553
580
  id={id}
@@ -163,9 +163,9 @@ type CommonSelectProps = FieldSelectDecoratorProps &
163
163
  export type FieldSelectSingleProps = CommonSelectProps & {
164
164
  /** Режим выбора. По умолчанию `'single'`. */
165
165
  selection?: typeof SELECTION_MODE.Single;
166
- /** Управляемое значение */
166
+ /** Управляемое значение. Пустая строка трактуется как «значение не выбрано». */
167
167
  value?: ItemId;
168
- /** Неуправляемое значение по умолчанию */
168
+ /** Неуправляемое значение по умолчанию. Пустая строка трактуется как «значение не выбрано». */
169
169
  defaultValue?: ItemId;
170
170
  /** Колбек смены значения */
171
171
  onChange?(value: ItemId | undefined): void;
@@ -105,10 +105,16 @@ type FieldTextAreaOwnProps = {
105
105
  */
106
106
  showClearButton?: boolean;
107
107
  /**
108
- * Кнопка копирования (видна при value && !disabled, независимо от readonly)
108
+ * Кнопка копирования (видна при непустом value в режиме readonly, как у остальных полей)
109
109
  * @default true
110
110
  */
111
111
  showCopyButton?: boolean;
112
+ /**
113
+ * Показывать кнопку копирования и в обычном (не readonly) режиме — рядом с кнопкой очистки.
114
+ * Опция только для textarea: в многострочном поле копирование значения осмысленно и при вводе.
115
+ * @default false
116
+ */
117
+ showCopyButtonInEditMode?: boolean;
112
118
  /** Колбек после копирования */
113
119
  onCopyButtonClick?(): void;
114
120
  /** Колбек фокуса */
@@ -157,6 +163,7 @@ export const FieldTextArea = forwardRef<HTMLTextAreaElement, FieldTextAreaProps>
157
163
  footer,
158
164
  showClearButton: showClearButtonProp = true,
159
165
  showCopyButton: showCopyButtonProp = true,
166
+ showCopyButtonInEditMode = false,
160
167
  onCopyButtonClick,
161
168
  onFocus: onFocusProp,
162
169
  onBlur: onBlurProp,
@@ -192,9 +199,9 @@ export const FieldTextArea = forwardRef<HTMLTextAreaElement, FieldTextAreaProps>
192
199
  const stringValue = String(value ?? '');
193
200
  const isResizable = resizable && !disabled && !readOnly;
194
201
  const showClearUi = Boolean(showClearButtonProp && stringValue && !readOnly && !disabled);
195
- // Copy не завязан на readonly (паритет с snack-uikit master): виден при любом непустом значении.
196
- // В editable-режиме показываются обе кнопки (clear + copy), в readonly — только copy.
197
- const showCopyUi = Boolean(showCopyButtonProp && stringValue && !disabled);
202
+ // По умолчанию copy только в readonly, как у остальных полей. В editable-режиме кнопка
203
+ // появляется лишь по явному `showCopyButtonInEditMode`; тогда видны обе (clear + copy).
204
+ const showCopyUi = Boolean(showCopyButtonProp && stringValue && !disabled && (readOnly || showCopyButtonInEditMode));
198
205
 
199
206
  const onClear = useCallback(() => {
200
207
  setValue('');