indicator-ui 1.0.31 → 1.0.33

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.
@@ -71,4 +71,19 @@
71
71
  --teal-800: #125D56;
72
72
  --teal-900: #134E48;
73
73
  --teal-950: #0A2926;
74
+ }
75
+
76
+ :root {
77
+ --rose-25: #FFF5F6;
78
+ --rose-50: #FFF1F3;
79
+ --rose-100: #FFE4E8;
80
+ --rose-200: #FECDD6;
81
+ --rose-300: #FEA3B4;
82
+ --rose-400: #FD6F8E;
83
+ --rose-500: #F63D68;
84
+ --rose-600: #E31B54;
85
+ --rose-700: #C01048;
86
+ --rose-800: #A11043;
87
+ --rose-900: #89123E;
88
+ --rose-950: #510B24;
74
89
  }
@@ -0,0 +1,17 @@
1
+ import { ExtendFormPath, FormErrorsType, FormErrorType, FormPath, FormValue, UseFormServices } from '../types';
2
+ import { Form } from '../classes';
3
+ export declare function createFormServices<T>(deps: {
4
+ getForm: () => Form<T>;
5
+ setFormData: (data: T) => void;
6
+ getField: <P extends FormPath<T>>(path: P) => FormValue<T, P> | null | undefined;
7
+ getFieldSync: <P extends FormPath<T>>(path: P) => FormValue<T, P> | undefined;
8
+ setField: <P extends FormPath<T>>(path: P, value: FormValue<T, P> | undefined) => void;
9
+ clearField: (path: FormPath<T> | FormPath<T>[]) => void;
10
+ getError: <P extends FormPath<T>>(path: P) => FormErrorType | undefined;
11
+ setError: <P extends FormPath<T>>(path: P, error: FormErrorType) => void;
12
+ setErrors: (errors: FormErrorsType<T>) => void;
13
+ clearErrors: () => void;
14
+ isFieldValid: <P extends ExtendFormPath<T>>(path: P) => Promise<boolean>;
15
+ areFieldsValid: (paths: ExtendFormPath<T>[]) => Promise<boolean>;
16
+ isFormValid: () => Promise<boolean>;
17
+ }): UseFormServices<T>;
@@ -3,3 +3,4 @@ export * from './setDataByPath';
3
3
  export * from './getFormDataPaths';
4
4
  export * from './scheme';
5
5
  export * from './fieldsProps';
6
+ export * from './createFormServices';
@@ -1,2 +1,3 @@
1
1
  export * from './formTypes';
2
2
  export * from './scheme';
3
+ export * from './services';
@@ -0,0 +1,42 @@
1
+ import { ExtendFormPath, FormErrorsType, FormErrorType, FormPath, FormValue } from './formTypes';
2
+ import { Form } from '../classes';
3
+ export type UseFormServices<T> = {
4
+ /** Работа с данными формы */
5
+ form: {
6
+ /** Получить актуальные данные формы */
7
+ getData: () => Form<T>;
8
+ /** Полностью заменить данные формы */
9
+ setData: (data: T) => void;
10
+ };
11
+ /** Работа с полями */
12
+ fields: {
13
+ /** Получить значение поля (реактивное) */
14
+ get: <P extends FormPath<T>>(path: P) => FormValue<T, P> | null | undefined;
15
+ /** Получить значение поля (гарантированно актуальное) */
16
+ getSync: <P extends FormPath<T>>(path: P) => FormValue<T, P> | undefined;
17
+ /** Установить значение поля */
18
+ set: <P extends FormPath<T>>(path: P, value: FormValue<T, P> | undefined) => void;
19
+ /** Очистить одно или несколько полей */
20
+ clear: (path: FormPath<T> | FormPath<T>[]) => void;
21
+ };
22
+ /** Работа с ошибками */
23
+ errors: {
24
+ /** Получить ошибку поля */
25
+ get: <P extends FormPath<T>>(path: P) => FormErrorType | undefined;
26
+ /** Установить ошибку поля */
27
+ set: <P extends FormPath<T>>(path: P, error: FormErrorType) => void;
28
+ /** Массово установить ошибки (например, серверные) */
29
+ setAll: (errors: FormErrorsType<T>) => void;
30
+ /** Очистить все ошибки */
31
+ clear: () => void;
32
+ };
33
+ /** Валидация (без мутаций ошибок) */
34
+ validation: {
35
+ /** Проверить валидность поля */
36
+ isFieldValid: <P extends ExtendFormPath<T>>(path: P) => Promise<boolean>;
37
+ /** Проверить несколько полей */
38
+ areFieldsValid: (paths: ExtendFormPath<T>[]) => Promise<boolean>;
39
+ /** Проверить валидность всей формы */
40
+ isFormValid: () => Promise<boolean>;
41
+ };
42
+ };
@@ -1,120 +1,290 @@
1
1
  import { default as React, FormEvent } from 'react';
2
2
  import { ExtendFormPath, FieldPropsType, FormErrorsType, FormErrorType } from '..';
3
- import { Nullable, Undefinable } from '../../types';
4
- import { FormPath, FormSchemeType, FormValue } from './types';
3
+ import { InstanceRefAttributes, Nullable, Undefinable } from '../../types';
4
+ import { FormPath, FormSchemeType, FormValue, UseFormServices } from './types';
5
5
  type PropsType<T, Form> = [
6
6
  props?: {
7
7
  initFormData?: T;
8
8
  scheme?: FormSchemeType<T>;
9
- onSubmit?: (data: Form, event: FormEvent<HTMLFormElement>) => void | Promise<void>;
9
+ onSubmit?: (data: Form, event: FormEvent<HTMLFormElement>, services: UseFormServices<T>) => void | Promise<void>;
10
10
  /** Callback при ошибки валидации полей */
11
11
  onSubmitError?: (event: {
12
12
  errors: FormErrorsType<T>;
13
- }) => void;
13
+ }, services: UseFormServices<T>) => void;
14
14
  }
15
15
  ];
16
16
  /**
17
- * Хук `useForm` — универсальное решение для управления и валидации форм с полной статической типизацией.
17
+ * Хук `useForm` — универсальный инструмент для управления состоянием и валидации форм
18
+ * с глубокой статической типизацией и расширяемым API.
18
19
  *
19
- * При указании generic-типа `Form` (например, `useForm<MyFormType>()`) вы получаете:
20
- * - строгую типизацию всех полей формы;
21
- * - контроль значений, ошибок и валидации;
22
- * - удобную интеграцию с готовыми компонентами (`FormField`, `FormSelectField`, `FormRadioField`, `FormSwitcherField`, `FormTextareaField`);
23
- * - возможность легко использовать собственные поля.
20
+ * ---
21
+ * ## 🧠 Ключевая идея
22
+ *
23
+ * `useForm` инкапсулирует:
24
+ * - состояние формы;
25
+ * - ошибки;
26
+ * - правила валидации;
27
+ * - интеграцию с `<form>` и полями.
28
+ *
29
+ * При этом предоставляет:
30
+ * - декларативную работу через `register` / `registerForm`;
31
+ * - императивные сервисы для бизнес-логики (`services`);
32
+ * - строгую типизацию всех путей формы.
24
33
  *
25
34
  * ---
26
- * ### 💡 Основная идея
27
- * Все поля формы управляются внутри хука.
28
- * Для внешнего контроля состояния формы и ошибок можно использовать `setFormData` и `setError`.
35
+ * ## 🧩 Типизация
36
+ *
37
+ * При указании generic-типа `Form`:
38
+ *
39
+ * ```ts
40
+ * useForm<MyFormType>()
41
+ * ```
29
42
  *
30
- * При регистрации поля через `register`:
31
- * - создаются функции валидации;
32
- * - возвращаются пропсы (`FieldPropsType`), которые можно напрямую передавать в компонент поля.
43
+ * вы получаете:
44
+ * - строгую типизацию `path` для всех полей;
45
+ * - корректные типы значений и ошибок;
46
+ * - защиту от обращения к несуществующим полям.
33
47
  *
34
48
  * ---
35
- * ###Способы задания валидации
49
+ * ##Валидация
50
+ *
51
+ * Валидация может быть задана двумя способами:
52
+ *
53
+ * ### 1️⃣ Через `register`
36
54
  *
37
- * **1. Через `register`:**
38
55
  * ```tsx
39
56
  * <FormField
40
- * {...register('baseInput', { required: { setting: true, message: 'Обязательное поле' } })}
41
- * label="Base input"
42
- * hint="Обычная строка"
57
+ * {...register('email', {
58
+ * required: { setting: true, message: 'Обязательное поле' }
59
+ * })}
43
60
  * />
44
61
  * ```
45
62
  *
46
- * **2. Через схему `scheme`:**
63
+ * ### 2️⃣ Через схему `scheme`
64
+ *
47
65
  * ```ts
48
- * const scheme: FormSchemeType<TestFormType> = {
49
- * baseInput: {
50
- * minStr: { setting: 3, message: 'Минимум 3 символа' },
51
- * maxStr: 10,
52
- * },
53
- * textarea: {
54
- * custom: (value) =>
55
- * value?.includes('_') ? 'В тексте есть запрещенный символ' : false,
56
- * noSpace: true,
57
- * },
58
- * 'selects.select': {
59
- * required: { setting: true, message: 'Поле обязательное' },
66
+ * const scheme: FormSchemeType<FormType> = {
67
+ * email: {
68
+ * required: true,
69
+ * custom: value => value.includes('@') || 'Некорректный email',
60
70
  * },
61
71
  * };
62
72
  * ```
63
73
  *
64
- * Для более подробного ознакомления с полями схемы можно ознакомиться с типом {@link FormSchemeType}.
74
+ * Схема применяется один раз и синхронизируется с формой автоматически.
65
75
  *
66
76
  * ---
67
- * ### 🧩 Пример использования
77
+ * ## 🧠 `registerForm`
78
+ *
79
+ * `registerForm` подключает форму к системе валидации и управления состоянием.
80
+ *
81
+ * ```tsx
82
+ * <form {...registerForm()}>
83
+ * ```
84
+ *
85
+ * Поведение:
86
+ * - `onSubmit`:
87
+ * - предотвращает нативный submit;
88
+ * - проверяет валидность формы;
89
+ * - вызывает пользовательский `onSubmit` или `onSubmitError`;
90
+ * - `onReset`:
91
+ * - предотвращает нативный reset;
92
+ * - очищает форму и ошибки;
93
+ * - `noValidate: true`:
94
+ * - отключает HTML5-валидацию.
95
+ *
96
+ * ---
97
+ * ## 🔌 Callbacks: `onSubmit` и `onSubmitError`
98
+ *
99
+ * ```ts
100
+ * onSubmit(data, event, services)
101
+ * onSubmitError({ errors }, services)
102
+ * ```
103
+ *
104
+ * Помимо данных формы, колбэки получают объект `services`,
105
+ * который предоставляет **безопасный императивный API** для бизнес-логики.
106
+ *
107
+ * ---
108
+ * ## 🛠 `services`
109
+ *
110
+ * `services` — это **стабильный объект команд**, доступный только внутри
111
+ * `onSubmit` и `onSubmitError`.
112
+ *
113
+ * ### ✔️ Что в `services` есть:
114
+ * - чтение и замена данных формы;
115
+ * - работа с отдельными полями;
116
+ * - установка и очистка ошибок;
117
+ * - асинхронная валидация (без мутации ошибок).
118
+ *
119
+ * ### ❌ Чего в `services` НЕТ принципиально:
120
+ * - реактивных значений (`formData`, `errors`);
121
+ * - методов `submit`, `reset`, `register`;
122
+ * - UX-логики (подсветка, фокус, скролл);
123
+ * - возможностей повторно инициировать submit.
124
+ *
125
+ * Это гарантирует:
126
+ * - отсутствие циклов;
127
+ * - предсказуемость side-effect’ов;
128
+ * - стабильность публичного API.
129
+ *
130
+ * ---
131
+ * @template Form — полный тип формы.
132
+ * @template T — тип начальных данных (может быть частичным).
133
+ *
134
+ * @param params
135
+ * @param params.initFormData — начальные данные формы.
136
+ * @param params.scheme — схема валидации.
137
+ * @param params.onSubmit — вызывается при успешной валидации формы.
138
+ * @param params.onSubmitError — вызывается при ошибках валидации.
139
+ *
140
+ * @returns API управления формой.
141
+ * @example
142
+ *
143
+ * ### Полноценный пример использования `useForm`
144
+ *
145
+ * Форма демонстрирует:
146
+ * - вложенные структуры данных;
147
+ * - массивы и tuple-поля;
148
+ * - кастомную и схемную валидацию;
149
+ * - работу с датами и диапазонами;
150
+ * - проверку валидности формы и отдельных полей;
151
+ * - императивные действия (очистка, валидация).
152
+ *
153
+ * ---
154
+ *
68
155
  * ```tsx
69
156
  * type TestFormType = {
157
+ * phone: string;
70
158
  * baseInput: string;
71
159
  * textarea: string;
160
+ * date: string;
161
+ * time: string;
162
+ * datetime: string;
163
+ * dateRange: [string | undefined, string | undefined];
72
164
  * selects: {
73
- * select: number;
74
- * selectMulti: number[];
165
+ * select: 'test1' | 'test2' | 'test3';
166
+ * selectMulti: ('test1' | 'test2' | 'test3')[];
75
167
  * };
76
168
  * addition: [
77
169
  * { switcher: boolean },
78
- * { radio: { multi: string[]; default: string } },
170
+ * { radio: { multi: ('test1' | 'test2' | 'test3')[]; default: string } },
79
171
  * ];
172
+ * array: { test1: string; test2: string }[];
173
+ * };
174
+ *
175
+ * const scheme: FormSchemeType<TestFormType> = {
176
+ * phone: {
177
+ * custom: value =>
178
+ * value && value.length > 5 ? false : 'Ошибка',
179
+ * },
180
+ *
181
+ * baseInput: {
182
+ * default: 'test string',
183
+ * minStr: { setting: 3, message: 'Больше 3 символов' },
184
+ * maxStr: 15,
185
+ * },
186
+ *
187
+ * textarea: {
188
+ * default: 'test/textarea',
189
+ * noSpace: true,
190
+ * custom: (value, options) => {
191
+ * const radioValues = options.getField('addition[1].radio.multi');
192
+ * return value?.includes('_')
193
+ * ? 'В тексте есть запрещенный символ'
194
+ * : false;
195
+ * },
196
+ * },
197
+ *
198
+ * 'selects.select': {
199
+ * required: { setting: true, message: 'Поле обязательное' },
200
+ * },
201
+ *
202
+ * 'array[*]': {
203
+ * custom: {
204
+ * fun: () => '',
205
+ * },
206
+ * },
80
207
  * };
81
208
  *
82
209
  * export function FormPage() {
83
- * const { register, registerForm, formData, errors, isFormValid } = useForm<TestFormType>({
210
+ * const {
211
+ * formData,
212
+ * clearForm,
213
+ * clearErrors,
214
+ * highlightFormErrors,
215
+ * highlightField,
216
+ * isFormValid,
217
+ * isFieldValid,
218
+ * register,
219
+ * registerForm,
220
+ * } = useForm<TestFormType>({
84
221
  * scheme,
85
- * onSubmit: (data) => console.log('Submit data:', data),
222
+ * onSubmit: data => {
223
+ * console.log('Submit data:', data);
224
+ * },
86
225
  * });
87
226
  *
88
227
  * return (
89
228
  * <form {...registerForm()}>
229
+ *
230
+ * <FormDateRangeFieldBase {...register('dateRange')} />
231
+ *
232
+ * <FormDateField
233
+ * {...register('datetime')}
234
+ * inputFormat="dd.MM.yyyy HH:mm"
235
+ * outFormat="yyyy-MM-dd'T'HH:mmXXX"
236
+ * label="Дата и время"
237
+ * />
238
+ *
239
+ * <FormSelectField
240
+ * {...register('selects.selectMulti')}
241
+ * multiple
242
+ * options={selectOptions}
243
+ * label="selects.selectMulti"
244
+ * />
245
+ *
246
+ * <FormField
247
+ * {...register('phone')}
248
+ * label="Номер телефона"
249
+ * mask="+7 000 000-00-00"
250
+ * />
251
+ *
90
252
  * <FormField
91
- * {...register('baseInput', { required: { setting: true, message: 'Обязательное поле' } })}
92
- * label="base input"
93
- * hint="Обычная строка"
253
+ * {...register('baseInput')}
254
+ * label="Base input"
94
255
  * />
95
256
  *
96
257
  * <FormTextareaField {...register('textarea')} />
97
258
  *
98
259
  * <FormSwitcherField {...register('addition[0].switcher')} />
99
260
  *
100
- * <FormRadioField {...register('addition[1].radio.default')} options={radioOptions} />
101
- * <FormRadioField {...register('addition[1].radio.multi')} options={radioOptions} multiple />
261
+ * <FormRadioField
262
+ * {...register('addition[1].radio.default')}
263
+ * options={radioOptions}
264
+ * />
265
+ *
266
+ * <FormRadioField
267
+ * {...register('addition[1].radio.multi')}
268
+ * options={radioOptions}
269
+ * multiple
270
+ * />
102
271
  *
103
272
  * <FormSelectField
104
273
  * {...register('selects.select')}
105
274
  * options={selectOptions}
106
- * label="selects.select"
107
275
  * required
108
- * hint="Select с выбором только одного элемента"
109
276
  * />
110
277
  *
111
- * <FormSelectField
112
- * {...register('selects.selectMulti')}
113
- * multiple
114
- * required
115
- * options={selectOptions}
116
- * label="selects.selectMulti"
117
- * hint="Select с выбором нескольких элементов"
278
+ * <FormDateField
279
+ * {...register('date')}
280
+ * inputFormat="dd.MM.yyyy"
281
+ * outFormat="yyyy-MM-dd"
282
+ * />
283
+ *
284
+ * <FormDateField
285
+ * {...register('time')}
286
+ * inputFormat="HH:mm"
287
+ * outFormat="HH:mmXXX"
118
288
  * />
119
289
  *
120
290
  * <button type="submit">Отправить</button>
@@ -125,70 +295,28 @@ type PropsType<T, Form> = [
125
295
  * ```
126
296
  *
127
297
  * ---
128
- * ### ⚙️ Основные методы и значения
129
- *
130
- * | Переменная / метод | Тип | Назначение |
131
- * |---------------------|------|-------------|
132
- * | `formData` | `T` | Текущее состояние формы (реактивное) |
133
- * | `setFormData(data)` | `(data: T) => void` | Полностью заменяет состояние формы |
134
- * | `getFormData()` | `() => T` | Возвращает актуальные данные формы |
135
- * | `errors` | `FormErrorsType<T>` | Объект всех ошибок формы |
136
- * | `setErrors(errors)` | `(errors: FormErrorsType<T>) => void` | Полностью заменяет состояние ошибок |
137
- * | `getField(path)` | `(path) => value` | Возвращает текущее значение поля (возвращает стейт) |
138
- * | `setField(path, value)` | `(path, value) => void` | Устанавливает значение поля |
139
- * | `getFieldSync(path)` | `(path) => value` | Возвращает текущее значение поля (гарантировано последнее значение) |
140
- * | `getError(path)` | `(path) => FormErrorType` | Возвращает ошибку конкретного поля |
141
- * | `setError(path, error)` | `(path, error) => void` | Устанавливает ошибку вручную |
142
- * | `highlightField(path)` | `(path) => Promise<boolean>` | Проверяет конкретное поле и обновляет `errors`. Возвращает результат проверки. |
143
- * | `highlightFields(paths)` | `(paths) => Promise<boolean>` | Проверяет несколько полей и обновляет `errors`. Возвращает результат проверки. |
144
- * | `highlightFormErrors()` | `() => Promise<boolean>` | Проверяет всю форму и обновляет `errors`. Возвращает результат проверки. |
145
- * | `isFieldValid(path)` | `(path) => Promise<boolean>` | Проверяет валидность поля (без добавления `errors`) |
146
- * | `areFieldsValid(paths)` | `(paths) => Promise<boolean>` | Проверяет валидность полей (без добавления `errors`) |
147
- * | `isFormValid()` | `() => Promise<boolean>` | Проверяет, валидна ли вся форма (без добавления `errors`) |
148
- * | `clearForm()` | `() => void` | Сбрасывает все значения формы |
149
- * | `clearErrors()` | `() => void` | Очищает все ошибки |
150
- * | `clearField(path | paths)` | `(path | paths) => void` | Очищает поле или поля |
151
- * | `register(path, config?)` | `(path, config?) => FieldPropsType` | Регистрирует поле и возвращает пропсы для компонента |
152
- * | `registerForm()` | `() => Pick<React.ComponentProps<'form'>, 'onSubmit' | 'onReset' | 'noValidate'>` | Возвращает обработчики и настройки для `<form>` |
153
- * | `getValidForm()` | `() => Promise<T | null>` | Возвращает валидную форму, в случае если форма не валидна - null |
154
298
  *
155
- * ---
156
- * ### 🧠 `registerForm`
299
+ * ### Императивные действия
157
300
  *
158
- * Функция `registerForm` позволяет удобно подключить форму к системе валидации и управления состоянием,
159
- * не теряя контроль над поведением `onSubmit` и `onReset`.
301
+ * ```ts
302
+ * clearForm(); // очистить все значения формы
303
+ * clearErrors(); // очистить все ошибки
304
+ * await highlightFormErrors(); // подсветить ошибки всех полей
305
+ * await highlightField('selects.select');
160
306
  *
161
- * #### Пример:
162
- * ```tsx
163
- * const { register, registerForm } = useForm<MyFormType>({
164
- * onSubmit: (data) => console.log('Отправлено:', data),
165
- * });
166
- *
167
- * return (
168
- * <form {...registerForm()}>
169
- * <FormField {...register('username')} label="Имя" />
170
- * <FormField {...register('email')} label="Email" />
171
- * <button type="submit">Отправить</button>
172
- * <button type="reset">Сбросить</button>
173
- * </form>
174
- * );
307
+ * const isValid = await isFormValid();
308
+ * const isTextareaValid = await isFieldValid('textarea');
175
309
  * ```
176
310
  *
177
- * #### Поведение:
178
- * - `onSubmit`: выполняет валидацию всей формы (`isFormValid()`), при успехе вызывает переданный `onSubmit`;
179
- * - `onReset`: предотвращает нативный сброс, очищает значения (`clearForm()`) и ошибки (`clearErrors()`);
180
- * - `noValidate: true`: отключает нативную HTML5-валидацию, чтобы не мешала кастомной логике.
181
- *
182
311
  * ---
183
- * @template Form — объект, описывающий форму.
184
- * @template T — частичный тип формы (используется для инициализации, по умолчанию `Undefinable<Form>`).
185
312
  *
186
- * @param {Object} [params]
187
- * @param {T} [params.initFormData] — начальные данные формы.
188
- * @param {FormSchemeType<T>} [params.scheme] — схема валидации формы.
189
- * @param {(data: Form) => void | Promise<void>} [params.onSubmit] — колбэк, вызываемый при успешной отправке формы.
313
+ * ### Просмотр текущего состояния формы
190
314
  *
191
- * @returns {object} Объект с методами и состоянием формы (см. таблицу выше).
315
+ * ```tsx
316
+ * <pre>
317
+ * {JSON.stringify(formData, null, 2)}
318
+ * </pre>
319
+ * ```
192
320
  */
193
321
  export declare function useForm<Form, T extends Nullable<Undefinable<Form>> = Nullable<Undefinable<Form>>>(...args: PropsType<T, Form>): {
194
322
  formData: T | undefined;
@@ -214,7 +342,9 @@ export declare function useForm<Form, T extends Nullable<Undefinable<Form>> = Nu
214
342
  (): FieldPropsType<FormValue<T, "">>;
215
343
  <P extends FormPath<T>>(path: P, config?: FormSchemeType<T>[P]): FieldPropsType<FormValue<T, P>>;
216
344
  };
217
- registerForm: () => Pick<React.ComponentProps<"form">, "onSubmit" | "onReset" | "noValidate">;
345
+ registerForm: () => Pick<React.ComponentProps<"form">, "onSubmit" | "onReset" | "noValidate" | "ref"> & Pick<InstanceRefAttributes<React.ComponentRef<"form">>, "instanceRef">;
218
346
  getValidForm: () => Promise<Form | null>;
347
+ submitForm: () => void;
348
+ resetForm: () => void;
219
349
  };
220
350
  export {};
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "react-components",
12
12
  "ui-kit"
13
13
  ],
14
- "version": "1.0.31",
14
+ "version": "1.0.33",
15
15
  "exports": {
16
16
  ".": {
17
17
  "types": "./dist/types/index.d.ts",