@companix/formkit 0.0.29 → 0.0.30

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/README.md CHANGED
@@ -1,58 +1,320 @@
1
- ## Create form primitives
1
+ # @companix/formkit
2
+
3
+ Формы на React одной декларативной схемой. Поля, раскладка, условия и проверка описываются деревом из обычных вызовов
4
+ функций, а значения формы TypeScript выводит из самой схемы — без ручных интерфейсов, `useState` на каждое поле и
5
+ `register` в разметке.
2
6
 
3
7
  ```tsx
4
- const SelectForm = (name: string) => {
5
- // select form code
6
- }
7
- const InputForm = (name: string) => {
8
- // input form code
9
- }
8
+ const scheme = [
9
+ Label('Имя')(TextForm('name', { placeholder: 'Иван', $rules: { required: true } })),
10
+ CheckboxForm('hasCompany', { title: 'Представляю компанию' }),
11
+ Condition('hasCompany', { canActivate: (checked: boolean) => checked })(
12
+ Context('company')(Label('Компания')(TextForm('name', { placeholder: 'ООО «Ромашка»' })))
13
+ )
14
+ ]
15
+
16
+ // onSubmit получит { name: string; hasCompany: boolean; company?: { name: string } }
17
+ ```
18
+
19
+ `TextForm`, `CheckboxForm` и `Label` здесь — свои поле и раскладка проекта: как их завести, показано ниже.
20
+
21
+ ## Почему formkit
22
+
23
+ - **Форма — данные, а не разметка.** Схема — массив, который собирают функциями: общий блок полей пишется один раз и
24
+ вставляется в любую форму, вариант формы — другой набор элементов, а не ветвление в JSX.
25
+ - **Типы из схемы.** `onSubmit`, `onChange`, `defaultValues`, `setValue` и `useValue` знают имена и типы полей,
26
+ вложенные контексты и необязательность полей под условием.
27
+ - **Поле пишется один раз.** `createForm` превращает любой компонент ввода в поле: умолчание, «что считать пустым» и
28
+ отрисовка ошибки живут в нём, а не в каждой форме.
29
+ - **Условия и вложенность из коробки.** `Condition` показывает поля по значению другого поля, `Context` складывает их
30
+ в объект; скрытое поле не попадает ни в значения, ни в проверку.
31
+ - **Состояние вне React.** Значения держит менеджер формы: правка поля перерисовывает это поле и тех, кто на него
32
+ подписан, а не всю форму и не экран вокруг неё.
33
+ - **Изменённость.** Форма знает, что её правили, сообщает об этом и при отправке без изменений вызывает `onEqual`
34
+ вместо запроса.
35
+
36
+ ## Установка
37
+
38
+ ```sh
39
+ npm i @companix/formkit
10
40
  ```
11
41
 
12
- ## Create layouts
42
+ Нужен React 18.
43
+
44
+ ## Форма за минуту
13
45
 
14
46
  ```tsx
15
- export const Label = createLayout((label: React.ReactNode) => {
16
- return ({ children }) => {
17
- return <FormGroup label={label}>{children}</FormGroup>
47
+ import { useMemo } from 'react'
48
+ import { createForm, createLayout, useForm, FormLayout } from '@companix/formkit'
49
+
50
+ // поле: умолчание, проверка обязательности и компонент
51
+ const TextForm = createForm<string, { placeholder: string }>({
52
+ defaultValue: '',
53
+ getRequireCheck: () => (value) => {
54
+ if (value.trim() === '') {
55
+ throw ''
56
+ }
57
+ },
58
+ getForm: ({ placeholder }) => {
59
+ return ({ field, error, disabled }) => {
60
+ return (
61
+ <div className="field">
62
+ <input
63
+ ref={field.ref}
64
+ value={field.value}
65
+ placeholder={placeholder}
66
+ disabled={disabled}
67
+ onChange={(e) => field.onChange(e.target.value)}
68
+ />
69
+ {error?.messages?.map((message) => (
70
+ <p key={message}>{message}</p>
71
+ ))}
72
+ </div>
73
+ )
74
+ }
18
75
  }
19
76
  })
20
77
 
21
- export const Double = createLayout(() => {
78
+ // раскладка: подпись над полем
79
+ const Label = createLayout((text: string) => {
22
80
  return ({ children }) => {
23
- return <div className="form-double-primitive">{children}</div>
81
+ return (
82
+ <label className="label">
83
+ <span>{text}</span>
84
+ {children}
85
+ </label>
86
+ )
24
87
  }
25
88
  })
26
89
 
27
- export const Splitter = createLayout(() => {
28
- return () => {
29
- return <div className="form-splitter" />
90
+ export const ProfileForm = () => {
91
+ const scheme = useMemo(() => {
92
+ return [
93
+ Label('Имя')(TextForm('name', { placeholder: 'Иван', $rules: { required: true } })),
94
+ Label('Город')(TextForm('city', { placeholder: 'Москва' }))
95
+ ]
96
+ }, [])
97
+
98
+ const form = useForm(scheme, {
99
+ defaultValues: { city: 'Москва' },
100
+ onSubmit: async (values) => {
101
+ await api.saveProfile(values) // values: { name: string; city: string }
102
+ }
103
+ })
104
+
105
+ return (
106
+ <>
107
+ <FormLayout manager={form.manager} />
108
+ <button onClick={form.handleSubmit}>Сохранить</button>
109
+ </>
110
+ )
111
+ }
112
+ ```
113
+
114
+ Менеджер формы создаётся один раз: схему и `defaultValues` `useForm` читает при первом рендере, поэтому схему держат в
115
+ `useMemo`. `FormLayout` рисует схему и может стоять где угодно — в окне, на странице, в шаге мастера.
116
+
117
+ ## Поле — `createForm`
118
+
119
+ Поле — это компонент ввода плюс три ответа: какое у него значение по умолчанию, что для него значит «пусто» и как его
120
+ нарисовать по параметрам из схемы.
121
+
122
+ ```tsx
123
+ const SelectForm = <T,>(defaultValue: T | null) => {
124
+ return createForm<T | null, { options: { value: T; title: string }[] }>({
125
+ defaultValue,
126
+ getRequireCheck: () => (value) => {
127
+ if (value === null) {
128
+ throw ''
129
+ }
130
+ },
131
+ getForm: ({ options }) => {
132
+ return ({ field, error }) => (
133
+ <Select options={options} value={field.value} onChange={field.onChange} error={error} />
134
+ )
135
+ }
136
+ })
137
+ }
138
+
139
+ SelectForm<'day' | 'night'>('day')('mode', { options: MODES })
140
+ ```
141
+
142
+ Компонент получает `field` (`value`, `onChange`, `ref` для фокуса на ошибке), `error` и `disabled` формы. Умолчание,
143
+ которое зависит от места, задают фабрикой, как у `SelectForm` выше: `NumberInputForm(1)('count', …)`.
144
+
145
+ ## Раскладка — `createLayout`
146
+
147
+ Раскладка оборачивает поля разметкой. Параметр раскладки — один аргумент; раскладку без параметров вызывают сразу.
148
+
149
+ ```tsx
150
+ const Card = createLayout(({ title, hint }: { title: string; hint?: string }) => {
151
+ return ({ children }) => (
152
+ <section className="card">
153
+ <h3>{title}</h3>
154
+ {hint && <p>{hint}</p>}
155
+ {children}
156
+ </section>
157
+ )
158
+ })
159
+
160
+ const Double = createLayout(() => {
161
+ return ({ children }) => <div className="double">{children}</div>
162
+ })()
163
+
164
+ Card({ title: 'Контакты', hint: 'Видят только менеджеры' })(
165
+ Double(TextForm('phone', { placeholder: '+7' }), TextForm('email', { placeholder: 'mail@site.ru' }))
166
+ )
167
+ ```
168
+
169
+ ## Схема
170
+
171
+ | Элемент | Что делает | В значениях |
172
+ | ---------------------------------------- | ------------------------------------------------------------------------- | ------------------ |
173
+ | `XForm('name', params)` | поле | `name: V` |
174
+ | `Layout(params)(…)` | разметка вокруг полей | поля как есть |
175
+ | `Context('name')(…)` | вложенный объект: имена полей внутри — `name.field` | `name: { … }` |
176
+ | `Condition('field', { canActivate })(…)` | поля показаны, пока `canActivate(значение поля)` верно; имя — полный путь | поля необязательны |
177
+ | `Visablility({ hidden })(…)` | поля не рисуются, но несут значение из `defaultValues` | поля необязательны |
178
+ | `createExtraForm(…)` | поля, которые пользователь добавляет и убирает сам | поля необязательны |
179
+
180
+ Значения `onSubmit` и `onChange` выводятся из схемы сами, тип `defaultValues` схемы — `GetDefaultValues<typeof scheme>`.
181
+
182
+ ## Проверка
183
+
184
+ ```tsx
185
+ TextForm('inn', {
186
+ placeholder: '12 цифр',
187
+ $rules: {
188
+ required: true,
189
+ validate: (value) => {
190
+ if (!/^\d{12}$/.test(value)) {
191
+ throw 'ИНН — 12 цифр'
192
+ }
193
+ }
30
194
  }
31
195
  })
32
196
  ```
33
197
 
34
- ## Create a scheme
198
+ - `required` проверяет поле его же `getRequireCheck`; `validate` — после него. Без `required` `validate` работает,
199
+ только если поле не пустое.
200
+ - Проверка бросает строку: `throw ''` подсвечивает поле, `throw 'текст'` — с сообщением, массив строк — несколько
201
+ сообщений. Ошибку рисует компонент поля из `error.messages`.
202
+ - `handleSubmit` проверяет поля, которые форма показывает, и ставит фокус на первое ошибочное. Поле скрытого условия
203
+ отправку не держит. После первой попытки поля проверяются на каждой правке.
204
+ - Ошибку от сервера ставит `setError` из второго аргумента `onSubmit`: `setError('inn', { message: 'ИНН занят' })`.
35
205
 
36
- ```ts
37
- const scheme = [
38
- Label('Имя')(
39
- SelectForm('name', {
40
- placeholder: 'Введите имя',
41
- options: []
42
- })
43
- ),
44
- Splitter(),
45
- Double()(
46
- Label('Фамилия')(
47
- InputForm('surname', {
48
- placeholder: 'Введите фамилию'
49
- })
50
- ),
51
- Label('Отчество')(
52
- InputForm('patronymic', {
53
- placeholder: 'Введите отчество'
54
- })
206
+ ## Значения, умолчания и условия
207
+
208
+ У поля два источника начального значения: умолчание компонента (`defaultValue` из `createForm`) и `defaultValues`
209
+ формы. Стартовое значение — `defaultValues`, а без них — умолчание компонента. От стартового значения считается
210
+ изменённость, и с него поле начинает:
211
+
212
+ - при первом показе;
213
+ - когда условие снова показывает поле: скрытое поле теряет значение и ошибку, вернувшееся — начинает заново со
214
+ стартового, а не с того, что ввели до скрытия.
215
+
216
+ Значения формы — `onChange`, `getValues()` и отправка — это то, что форма показывает. Поля скрытых условий в них не
217
+ входят, а поле, которое изменение только что открыло, входит сразу, ещё до монтирования, со стартовым значением.
218
+
219
+ Отсюда простое правило: `defaultValues` — только данные. Где данных нет, передавайте `undefined`, и поле возьмёт своё
220
+ умолчание; повторять умолчание поля в `defaultValues` не нужно.
221
+
222
+ ```tsx
223
+ defaultValues: {
224
+ hasEscorts: escorts !== null,
225
+ escorts: escorts ?? undefined // нет данных — поля начнут со своих умолчаний
226
+ }
227
+ ```
228
+
229
+ ## Живая форма и изменённость
230
+
231
+ ```tsx
232
+ const form = useForm(scheme, {
233
+ defaultValues,
234
+ // значения после каждой правки — расчёт и подсказки поверх полей, без кнопки отправки
235
+ onChange: (values) => setDraft(toDraft(values)),
236
+ // форма изменена или снова стартовая
237
+ onFormDirty: (isDirty) => setDirty(isDirty),
238
+ // отправили без изменений
239
+ onEqual: () => close()
240
+ })
241
+ ```
242
+
243
+ - `onDirty` сообщает о каждом поле, `onFormDirty` — о форме целиком: первое изменение и снятие последнего.
244
+ - `setAsDefault()` делает текущие значения стартовыми — после успешного сохранения.
245
+ - `reset(values)` ставит новые значения и делает их стартовыми; поле, которого нет в `values`, получает умолчание
246
+ компонента. `reset(values, { asDirty: true })` оставляет новые значения изменением.
247
+ - `setAsDefault()` и `reset(values)` сообщают «чисто» через `onDirty` и `onFormDirty`.
248
+
249
+ ## Управление формой
250
+
251
+ `useForm` возвращает:
252
+
253
+ | Метод | Что делает |
254
+ | ----------------------------- | ---------------------------------------------------------------------- |
255
+ | `manager` | менеджер для `FormLayout` |
256
+ | `handleSubmit()` | проверка показанных полей и `onSubmit` (или `onEqual`) |
257
+ | `getValues()` | значения формы — то, что она показывает |
258
+ | `getValue(name)` | значение поля по полному пути (`company.name`) |
259
+ | `setValue(name, value)` | запись поля, как ввод пользователя |
260
+ | `reset(values, options?)` | новые значения, по умолчанию — стартовые |
261
+ | `setAsDefault()` | текущие значения — стартовые |
262
+ | `setError(name, { message })` | ошибка поля |
263
+ | `setFocus(name)` | фокус на поле |
264
+ | `subscribe(name, callback)` | подписка на поле; возвращает отписку |
265
+ | `subscribeAny(callback)` | подписка на любую правку; возвращает отписку |
266
+ | `getNames()` | полные пути всех полей схемы |
267
+ | `useValue(name)` | хук внутри `FormLayout`: значение поля с перерисовкой и типом из схемы |
268
+
269
+ ## Внутри схемы: `useValue` и `useSetValue`
270
+
271
+ Компоненты раскладок и поля читают и пишут соседние поля по полному пути. Хуки берут форму из контекста, поэтому
272
+ работают внутри `FormLayout`:
273
+
274
+ ```tsx
275
+ // подсказка, которая зависит от выбранной причины
276
+ const ReasonHint = createLayout(() => {
277
+ return () => {
278
+ const reason = useValue<Reason | null>('reason')
279
+
280
+ return (
281
+ <p className="hint">
282
+ {reason === 'scan' ? 'Переснимут только отмеченные' : 'Анкета вернётся на шаг'}
283
+ </p>
55
284
  )
56
- )
57
- ]
285
+ }
286
+ })()
287
+
288
+ // подстановка из списка в поле контакта
289
+ const ManagerPick = ({ managers }: { managers: Manager[] }) => {
290
+ const setName = useSetValue<string>('contact.name')
291
+
292
+ return <Menu items={managers} onSelect={(manager) => setName(manager.name)} />
293
+ }
294
+ ```
295
+
296
+ Типизированный `useValue` для схемы — `createUseValue<typeof scheme>()`.
297
+
298
+ ## Поля, которые добавляет пользователь
299
+
300
+ ```tsx
301
+ // Removable — своя раскладка с кнопкой удаления, AddMenu — меню ещё не добавленных полей
302
+ const Optional = createExtraForm({
303
+ getItems: (registry) => [
304
+ registry('phone')((name, remove) => Removable(remove)(TextForm(name, { placeholder: 'Телефон' }))),
305
+ registry('email')((name, remove) => Removable(remove)(TextForm(name, { placeholder: 'Почта' })))
306
+ ],
307
+ getController: (append, visible) => <AddMenu hidden={visible} onSelect={append} />
308
+ })
58
309
  ```
310
+
311
+ Поле добавлено, пока у него есть значение: в `defaultValues` — показано сразу, убранное — изменение формы.
312
+
313
+ ## 0.0.30
314
+
315
+ - Поле, снова показанное условием, начинает со стартового значения (`defaultValues`), а не с умолчания компонента.
316
+ - `onChange`, `getValues()` и отправка включают поля, которые изменение только что показало, — со стартовым значением,
317
+ до монтирования.
318
+ - `handleSubmit` проверяет только показанные поля: скрытое поле отправку не держит. Ошибка скрытого поля сбрасывается.
319
+ - Скрытое условием поле не считается изменённым — изменение несёт поле условия.
320
+ - `setAsDefault()` и `reset()` сообщают «чисто» через `onDirty` и `onFormDirty`.
package/dist/bundle.es.js CHANGED
@@ -1,17 +1,17 @@
1
- import { jsx as b, Fragment as q, jsxs as P } from "react/jsx-runtime";
2
- import { createContext as O, useState as L, useContext as R, useMemo as p, useEffect as $, useCallback as j, useRef as U } from "react";
3
- import M from "fast-deep-equal";
4
- const A = O({}), N = (n) => {
5
- const [, r] = L([]), e = R(A);
1
+ import { jsx as g, Fragment as N, jsxs as U } from "react/jsx-runtime";
2
+ import { createContext as _, useState as B, useContext as T, useMemo as A, useEffect as m, useCallback as R, useRef as z } from "react";
3
+ import O from "fast-deep-equal";
4
+ const D = _({}), M = (n) => {
5
+ const [, r] = B([]), e = T(D);
6
6
  if (!e)
7
7
  throw new Error("useValue cannot be used outside Form context");
8
- const s = p(() => {
8
+ const s = A(() => {
9
9
  const t = e.getForm(n);
10
10
  if (!t)
11
11
  throw new Error(`form with name "${n}" doesnt exist in form`);
12
12
  return t;
13
13
  }, [e, n]);
14
- return $(() => {
14
+ return m(() => {
15
15
  const t = e.subscribeToForm(n, () => {
16
16
  r([]);
17
17
  });
@@ -19,181 +19,205 @@ const A = O({}), N = (n) => {
19
19
  t();
20
20
  };
21
21
  }, []), s.value;
22
- }, W = (n) => {
23
- const r = R(A);
22
+ }, Y = (n) => {
23
+ const r = T(D);
24
24
  if (!r)
25
25
  throw new Error("useSetValue cannot be used outside Form context");
26
26
  return (e) => {
27
27
  r.setValue(n, e);
28
28
  };
29
- }, X = () => (n) => N(n), _ = ({ canActivate: n, name: r, children: e }) => {
30
- const s = N(r);
31
- return n(s) ? /* @__PURE__ */ b(q, { children: e }) : null;
32
- }, z = ({ item: { Form: n }, name: r }) => {
33
- const [, e] = L([]), s = R(A), { unregistry: t } = p(() => s.registry(r, () => {
29
+ }, Z = () => (n) => M(n), G = ({ canActivate: n, name: r, children: e }) => {
30
+ const s = M(r);
31
+ return n(s) ? /* @__PURE__ */ g(N, { children: e }) : null;
32
+ }, H = ({ item: { Form: n }, name: r }) => {
33
+ const [, e] = B([]), s = T(D), { unregistry: t } = A(() => s.registry(r, () => {
34
34
  e([]);
35
- }), []), f = j(
36
- (d) => {
37
- if (d && d.focus) {
38
- const h = s.getForm(r);
39
- h.focus = () => {
40
- d.focus();
35
+ }), []), u = R(
36
+ (y) => {
37
+ if (y && y.focus) {
38
+ const v = s.getForm(r);
39
+ v.focus = () => {
40
+ y.focus();
41
41
  };
42
42
  }
43
43
  },
44
44
  [r]
45
45
  );
46
- $(() => () => {
46
+ m(() => () => {
47
47
  t();
48
48
  }, []);
49
- const { value: y, error: m } = s.getForm(r);
50
- return /* @__PURE__ */ b(
49
+ const { value: d, error: b } = s.getForm(r);
50
+ return /* @__PURE__ */ g(
51
51
  n,
52
52
  {
53
53
  field: {
54
- value: y,
55
- onChange: (d) => {
56
- s.onChange(r, d);
54
+ value: d,
55
+ onChange: (y) => {
56
+ s.onChange(r, y);
57
57
  },
58
58
  onBlur: () => {
59
59
  },
60
- ref: f
60
+ ref: u
61
61
  },
62
62
  isDirty: !1,
63
- error: m,
63
+ error: b,
64
64
  disabled: s.disabled
65
65
  }
66
66
  );
67
- }, G = ({ item: n, path: r }) => {
68
- const { getController: e, items: s } = n, t = R(A), f = p(() => {
67
+ }, J = ({ item: n, path: r }) => {
68
+ const { getController: e, items: s } = n, t = T(D), u = A(() => {
69
69
  const l = {};
70
- return s.forEach(({ name: i, getForm: x }) => {
71
- l[i] = x(i, () => D(i));
70
+ return s.forEach(({ name: i, getForm: F }) => {
71
+ l[i] = F(i, () => j(i));
72
72
  }), l;
73
- }, []), y = j(() => {
73
+ }, []), d = R(() => {
74
74
  const l = [];
75
75
  return s.forEach(({ name: i }) => {
76
76
  t.getForm([...r, i].join(".")).value !== void 0 && l.push(i);
77
77
  }), l;
78
- }, []), m = p(() => y(), []), [d, h] = L(m);
79
- $(() => {
78
+ }, []), b = A(() => d(), []), [y, v] = B(b);
79
+ m(() => {
80
80
  const { unregistry: l } = t.registryExtraForm(
81
81
  s.map(({ name: i }) => [...r, i].join(".")),
82
82
  () => {
83
- h(y());
83
+ v(d());
84
84
  }
85
85
  );
86
86
  return () => {
87
87
  l();
88
88
  };
89
89
  }, []);
90
- const v = j(
90
+ const x = R(
91
91
  (l) => {
92
- f[l] && !d.includes(l) && h((i) => [...i, l]);
92
+ u[l] && !y.includes(l) && v((i) => [...i, l]);
93
93
  },
94
- [d]
95
- ), D = j((l) => {
96
- h((i) => i.filter((x) => x !== l));
94
+ [y]
95
+ ), j = R((l) => {
96
+ v((i) => i.filter((F) => F !== l));
97
97
  }, []);
98
- return /* @__PURE__ */ P(q, { children: [
99
- d.map((l) => {
100
- const i = f[l];
101
- return /* @__PURE__ */ b(C, { path: r, scheme: [i] }, `param-form--${l}`);
98
+ return /* @__PURE__ */ U(N, { children: [
99
+ y.map((l) => {
100
+ const i = u[l];
101
+ return /* @__PURE__ */ g(p, { path: r, scheme: [i] }, `param-form--${l}`);
102
102
  }),
103
- e(v, d)
103
+ e(x, y)
104
104
  ] });
105
- }, C = ({ scheme: n, path: r }) => /* @__PURE__ */ b(q, { children: n.map((e, s) => e.type === "layout" ? /* @__PURE__ */ b(e.Layout, { children: /* @__PURE__ */ b(C, { path: r, scheme: e.childs }) }, `layout-form--${s}-${r.length}`) : e.type === "context" ? /* @__PURE__ */ b(
106
- C,
105
+ }, p = ({ scheme: n, path: r }) => /* @__PURE__ */ g(N, { children: n.map((e, s) => e.type === "layout" ? /* @__PURE__ */ g(e.Layout, { children: /* @__PURE__ */ g(p, { path: r, scheme: e.childs }) }, `layout-form--${s}-${r.length}`) : e.type === "context" ? /* @__PURE__ */ g(
106
+ p,
107
107
  {
108
108
  path: [...r, e.context],
109
109
  scheme: e.childs
110
110
  },
111
111
  `context-form--${s}-${e.context}-${r.length}`
112
- ) : e.type === "condition" ? /* @__PURE__ */ b(
113
- _,
112
+ ) : e.type === "condition" ? /* @__PURE__ */ g(
113
+ G,
114
114
  {
115
115
  name: e.depended,
116
116
  canActivate: e.canActivate,
117
- children: /* @__PURE__ */ b(C, { path: r, scheme: e.childs })
117
+ children: /* @__PURE__ */ g(p, { path: r, scheme: e.childs })
118
118
  },
119
119
  `condition--${s}-${e.depended}-${r.length}`
120
- ) : e.type === "extra" ? /* @__PURE__ */ b(G, { path: r, item: e }, `partial-item--${s}-${r.length}`) : e.type === "visability" ? e.hidden ? null : /* @__PURE__ */ b(C, { path: r, scheme: e.childs }, `visability--${s}-${r.length}`) : /* @__PURE__ */ b(
121
- z,
120
+ ) : e.type === "extra" ? /* @__PURE__ */ g(J, { path: r, item: e }, `partial-item--${s}-${r.length}`) : e.type === "visability" ? e.hidden ? null : /* @__PURE__ */ g(p, { path: r, scheme: e.childs }, `visability--${s}-${r.length}`) : /* @__PURE__ */ g(
121
+ H,
122
122
  {
123
123
  name: [...r, e.name].join("."),
124
124
  item: e
125
125
  },
126
126
  `form-item--${s}-${e.name}-${r.length}`
127
- )) }), S = (n, r, e, s) => {
127
+ )) }), $ = (n, r, e, s) => {
128
128
  n.forEach((t) => {
129
129
  if (t.type === "form" && s(t, [...r, t.name].join("."), e[t.name]), t.type === "context") {
130
- S(t.childs, [...r, t.context], e[t.context] ?? {}, s);
130
+ $(t.childs, [...r, t.context], e[t.context] ?? {}, s);
131
131
  return;
132
132
  }
133
133
  if (t.type === "layout" || t.type === "condition" || t.type === "visability") {
134
- S(t.childs, r, e, s);
134
+ $(t.childs, r, e, s);
135
135
  return;
136
136
  }
137
- t.type === "extra" && t.items.forEach(({ getForm: f, name: y }) => {
138
- S([f(y, () => {
137
+ t.type === "extra" && t.items.forEach(({ getForm: u, name: d }) => {
138
+ $([u(d, () => {
139
139
  })], r, e, s);
140
140
  });
141
141
  });
142
- }, E = ({ scheme: n, values: r, path: e, forms: s }) => {
142
+ }, C = (n, r, e, s) => {
143
143
  n.forEach((t) => {
144
- if (t.type === "form") {
145
- const f = [...e, t.name].join(".");
146
- s[f].value !== void 0 && (r[t.name] = s[f].value);
144
+ if (t.type === "form" && s([...r, t.name].join(".")), t.type === "context") {
145
+ C(t.childs, [...r, t.context], e, s);
146
+ return;
147
+ }
148
+ if (t.type === "layout") {
149
+ C(t.childs, r, e, s);
150
+ return;
151
+ }
152
+ if (t.type === "visability" && !t.hidden) {
153
+ C(t.childs, r, e, s);
154
+ return;
147
155
  }
148
- if (t.type === "context") {
149
- r[t.context] = {}, E({
150
- scheme: t.childs,
151
- path: [...e, t.context],
152
- values: r[t.context],
153
- forms: s
156
+ if (t.type === "condition" && t.canActivate(e[t.depended]?.value)) {
157
+ C(t.childs, r, e, s);
158
+ return;
159
+ }
160
+ t.type === "extra" && t.items.forEach(({ getForm: u, name: d }) => {
161
+ e[[...r, d].join(".")]?.value !== void 0 && C([u(d, () => {
162
+ })], r, e, s);
163
+ });
164
+ });
165
+ }, S = ({ scheme: n, values: r, path: e, forms: s, shown: t }) => {
166
+ n.forEach((u) => {
167
+ if (u.type === "form") {
168
+ const { value: d, startValue: b } = s[[...e, u.name].join(".")];
169
+ d !== void 0 && (r[u.name] = d), d === void 0 && t && (r[u.name] = b);
170
+ }
171
+ if (u.type === "context") {
172
+ r[u.context] = {}, S({
173
+ scheme: u.childs,
174
+ path: [...e, u.context],
175
+ values: r[u.context],
176
+ forms: s,
177
+ shown: t
154
178
  });
155
179
  return;
156
180
  }
157
- if (t.type === "layout") {
158
- E({ scheme: t.childs, path: e, values: r, forms: s });
181
+ if (u.type === "layout") {
182
+ S({ scheme: u.childs, path: e, values: r, forms: s, shown: t });
159
183
  return;
160
184
  }
161
- if (t.type === "visability") {
162
- E({ scheme: t.childs, path: e, values: r, forms: s });
185
+ if (u.type === "visability") {
186
+ S({ scheme: u.childs, path: e, values: r, forms: s, shown: t && !u.hidden });
163
187
  return;
164
188
  }
165
- if (t.type === "condition") {
166
- const f = s[t.depended]?.value;
167
- if (t.canActivate(f)) {
168
- E({ scheme: t.childs, path: e, values: r, forms: s });
189
+ if (u.type === "condition") {
190
+ const d = s[u.depended]?.value;
191
+ if (u.canActivate(d)) {
192
+ S({ scheme: u.childs, path: e, values: r, forms: s, shown: t });
169
193
  return;
170
194
  }
171
195
  }
172
- t.type === "extra" && t.items.forEach(({ getForm: f, name: y }) => {
173
- E({ scheme: [f(y, () => {
174
- })], path: e, values: r, forms: s });
196
+ u.type === "extra" && u.items.forEach(({ getForm: d, name: b }) => {
197
+ S({ scheme: [d(b, () => {
198
+ })], path: e, values: r, forms: s, shown: !1 });
175
199
  });
176
200
  });
177
- }, T = (n, r) => {
201
+ }, L = (n, r) => {
178
202
  const e = {};
179
- return E({ scheme: n, path: [], values: e, forms: r }), e;
180
- }, H = (n, r) => {
181
- const { onSubmit: e, onChange: s, onEqual: t, disabled: f, onDirty: y, onFormDirty: m, onChangeEvent: d, defaultValues: h } = r, v = { onSubmit: e }, D = { onChange: s }, l = {
203
+ return S({ scheme: n, path: [], values: e, forms: r, shown: !0 }), e;
204
+ }, K = (n, r) => {
205
+ const { onSubmit: e, onChange: s, onEqual: t, disabled: u, onDirty: d, onFormDirty: b, onChangeEvent: y, defaultValues: v } = r, x = { onSubmit: e }, j = { onChange: s }, l = {
182
206
  isActive: !1,
183
207
  isSubmitted: !1,
184
208
  dirtyCount: 0
185
- }, i = {}, x = /* @__PURE__ */ new Set(), V = {
209
+ }, i = {}, F = /* @__PURE__ */ new Set(), E = {
186
210
  subscribers: [],
187
211
  names: {}
188
212
  };
189
- S(n, [], h ?? {}, (o, c, u) => {
190
- i[c] = {
213
+ $(n, [], v ?? {}, (o, a, c) => {
214
+ i[a] = {
191
215
  error: null,
192
216
  isDirty: !1,
193
- value: u,
194
- // текущее (динамическое) состояние поля. Значение равно переданному дефолтному, либо: определяется как дефолтное значение компонента формы в момент его монтирования, а до этого - undefined.
195
- startValue: u ?? o.defaultValue,
196
- // начальное (стартовое) значение (равно переданному дефолтному, либо значению формы по умолчанию). Исходя из сравнения текущего значения со стартовым, можно понять, модифицированно ли поле
217
+ value: c,
218
+ // текущее (динамическое) состояние поля. Значение равно переданному дефолтному, либо: определяется как стартовое значение в момент монтирования поля, а до этого - undefined. Поле, которое скрыло условие, значения не имеет.
219
+ startValue: c ?? o.defaultValue,
220
+ // начальное (стартовое) значение (равно переданному дефолтному, либо значению формы по умолчанию). С него начинает поле при монтировании, в том числе снова показанное условием; исходя из сравнения текущего значения со стартовым, можно понять, модифицированно ли поле
197
221
  defaultValue: o.defaultValue,
198
222
  // дефолтное значение компонента формы
199
223
  rerender: () => {
@@ -202,98 +226,100 @@ const A = O({}), N = (n) => {
202
226
  validate: o.validate
203
227
  };
204
228
  });
205
- const B = (o, c) => {
206
- y?.({ name: o, isDirty: c }), c && l.dirtyCount === 0 && m?.(!0), !c && l.dirtyCount === 1 && m?.(!1), l.dirtyCount += c ? 1 : -1;
229
+ const q = (o, a) => {
230
+ d?.({ name: o, isDirty: a }), a && l.dirtyCount === 0 && b?.(!0), !a && l.dirtyCount === 1 && b?.(!1), l.dirtyCount += a ? 1 : -1;
231
+ }, P = () => {
232
+ for (const o in i)
233
+ i[o].isDirty && (q(o, !1), i[o].isDirty = !1);
207
234
  };
208
235
  return {
209
- submitRef: v,
210
- changeRef: D,
236
+ submitRef: x,
237
+ changeRef: j,
211
238
  scheme: n,
212
- disabled: f,
239
+ disabled: u,
213
240
  activate() {
214
241
  l.isActive = !0;
215
242
  },
216
243
  disactivate() {
217
244
  l.isActive = !1;
218
245
  },
219
- setError(o, c) {
220
- const u = this.getForm(o);
221
- u && (u.error = {
246
+ setError(o, a) {
247
+ const c = this.getForm(o);
248
+ c && (c.error = {
222
249
  error: !0,
223
- messages: [c.message]
224
- }, u.rerender());
250
+ messages: [a.message]
251
+ }, c.rerender());
225
252
  },
226
253
  setFocus(o) {
227
- const c = this.getForm(o);
228
- c && c.focus && c.focus();
254
+ const a = this.getForm(o);
255
+ a && a.focus && a.focus();
229
256
  },
230
- setValue(o, c) {
231
- const u = this.getForm(o);
232
- u && u.value !== c && this.onChange(o, c);
257
+ setValue(o, a) {
258
+ const c = this.getForm(o);
259
+ c && c.value !== a && this.onChange(o, a);
233
260
  },
234
261
  getValues() {
235
- return T(n, i);
262
+ return L(n, i);
236
263
  },
237
264
  async handleSubmit() {
238
- if (f)
265
+ if (u)
239
266
  return;
240
267
  l.isSubmitted = !0;
241
- let o = !1, c = !1;
242
- for (const u in i) {
243
- const a = i[u];
244
- if (a.value !== void 0) {
245
- const g = a.validate(a.value);
246
- g && (c = !0, a.error = g, a.rerender(), !o && a.focus && (a.focus(), o = !0));
268
+ let o = !1, a = !1;
269
+ if (C(n, [], i, (c) => {
270
+ const f = i[c];
271
+ if (f.value !== void 0) {
272
+ const h = f.validate(f.value);
273
+ h && (a = !0, f.error = h, f.rerender(), !o && f.focus && (f.focus(), o = !0));
247
274
  }
248
- }
249
- if (!c) {
275
+ }), !a) {
250
276
  if (l.dirtyCount === 0 && t) {
251
277
  t();
252
278
  return;
253
279
  }
254
- if (!v.onSubmit)
280
+ if (!x.onSubmit)
255
281
  return;
256
- const u = T(n, i);
257
- await v.onSubmit(u, {
258
- setError: (a, g) => {
259
- const F = this.getForm(a);
260
- F.error = {
282
+ const c = L(n, i);
283
+ await x.onSubmit(c, {
284
+ setError: (f, h) => {
285
+ const V = this.getForm(f);
286
+ V.error = {
261
287
  error: !0,
262
- messages: [g.message]
263
- }, F.rerender();
288
+ messages: [h.message]
289
+ }, V.rerender();
264
290
  }
265
- }).catch((a) => {
266
- console.log("[formkit] submit exception:", a);
291
+ }).catch((f) => {
292
+ console.log("[formkit] submit exception:", f);
267
293
  });
268
294
  }
269
295
  },
270
- reset(o, { asDirty: c }) {
271
- S(n, [], o, (u, a, g) => {
272
- if (g === void 0 && i[a].value === void 0)
296
+ reset(o, { asDirty: a }) {
297
+ $(n, [], o, (c, f, h) => {
298
+ if (h === void 0 && i[f].value === void 0)
273
299
  return;
274
- if (V.names[a] && g === void 0) {
275
- i[a].value = void 0;
300
+ if (E.names[f] && h === void 0) {
301
+ i[f].value = void 0;
276
302
  return;
277
303
  }
278
- const F = g ?? u.defaultValue;
279
- c || (i[a].startValue = F, i[a].isDirty = !1), i[a].value !== F && this.onChange(a, F);
280
- }), c || (l.dirtyCount = 0, l.isSubmitted = !1), V.subscribers.forEach(({ visualizeEvalueatedForms: u }) => {
281
- u();
304
+ const V = h ?? c.defaultValue;
305
+ a || (i[f].startValue = V), i[f].value !== V && this.onChange(f, V);
306
+ }), a || (P(), l.isSubmitted = !1), E.subscribers.forEach(({ visualizeEvalueatedForms: c }) => {
307
+ c();
282
308
  });
283
309
  },
284
310
  setAsDefault() {
285
- S(n, [], {}, (o, c) => {
286
- i[c].startValue = i[c].value, i[c].isDirty = !1;
287
- }), l.dirtyCount = 0, l.isSubmitted = !1;
311
+ $(n, [], {}, (o, a) => {
312
+ i[a].startValue = i[a].value ?? i[a].defaultValue;
313
+ }), P(), l.isSubmitted = !1;
288
314
  },
289
- registry(o, c) {
290
- return i[o].rerender = c, i[o].value === void 0 && (i[o].value = i[o].defaultValue), {
315
+ registry(o, a) {
316
+ return i[o].rerender = a, i[o].value === void 0 && (i[o].value = E.names[o] ? i[o].defaultValue : i[o].startValue), {
291
317
  unregistry() {
292
318
  if (i[o].rerender = () => {
293
319
  }, i[o].focus = void 0, l.isActive) {
294
- i[o].value = void 0;
295
- const u = !M(i[o].startValue, i[o].defaultValue);
296
- i[o].isDirty !== u && B(o, u), i[o].isDirty = u;
320
+ i[o].value = void 0, i[o].error = null;
321
+ const c = E.names[o] ? !O(i[o].startValue, i[o].defaultValue) : !1;
322
+ i[o].isDirty !== c && q(o, c), i[o].isDirty = c;
297
323
  }
298
324
  }
299
325
  };
@@ -301,52 +327,52 @@ const A = O({}), N = (n) => {
301
327
  getForm(o) {
302
328
  return i[o];
303
329
  },
304
- subscribeToForm(o, c) {
305
- const u = { callback: c }, a = this.getForm(o);
306
- return a.subscribers.push(u), () => {
307
- a.subscribers = a.subscribers.filter((g) => g !== u);
330
+ subscribeToForm(o, a) {
331
+ const c = { callback: a }, f = this.getForm(o);
332
+ return f.subscribers.push(c), () => {
333
+ f.subscribers = f.subscribers.filter((h) => h !== c);
308
334
  };
309
335
  },
310
336
  subscribeToAny(o) {
311
- return x.add(o), () => {
312
- x.delete(o);
337
+ return F.add(o), () => {
338
+ F.delete(o);
313
339
  };
314
340
  },
315
341
  getNames() {
316
342
  return Object.keys(i);
317
343
  },
318
- registryExtraForm(o, c) {
319
- const u = { visualizeEvalueatedForms: c };
320
- return o.forEach((a) => {
321
- V.names[a] = !0;
322
- }), V.subscribers.push(u), {
344
+ registryExtraForm(o, a) {
345
+ const c = { visualizeEvalueatedForms: a };
346
+ return o.forEach((f) => {
347
+ E.names[f] = !0;
348
+ }), E.subscribers.push(c), {
323
349
  unregistry() {
324
- V.subscribers = V.subscribers.filter((a) => a !== u);
350
+ E.subscribers = E.subscribers.filter((f) => f !== c);
325
351
  }
326
352
  };
327
353
  },
328
- onChange(o, c) {
329
- const u = this.getForm(o), a = !M(c, u.startValue);
330
- u.isDirty !== a && B(o, a), u.isDirty = a, d?.({ name: o, value: c }), (u.error || l.isSubmitted) && (u.error = u.validate(c) || null);
331
- const g = u.value;
332
- u.value = c, u.rerender(), u.subscribers.forEach(({ callback: F }) => {
333
- F(c, g);
334
- }), x.forEach((F) => {
335
- F();
336
- }), D.onChange?.(T(n, i));
354
+ onChange(o, a) {
355
+ const c = this.getForm(o), f = !O(a, c.startValue);
356
+ c.isDirty !== f && q(o, f), c.isDirty = f, y?.({ name: o, value: a }), (c.error || l.isSubmitted) && (c.error = c.validate(a) || null);
357
+ const h = c.value;
358
+ c.value = a, c.rerender(), c.subscribers.forEach(({ callback: V }) => {
359
+ V(a, h);
360
+ }), F.forEach((V) => {
361
+ V();
362
+ }), j.onChange?.(L(n, i));
337
363
  }
338
364
  };
339
- }, Y = (n, r) => {
340
- const e = U(null);
341
- e.current === null && (e.current = H(n, r));
365
+ }, k = (n, r) => {
366
+ const e = z(null);
367
+ e.current === null && (e.current = K(n, r));
342
368
  const s = e.current;
343
- return $(() => {
369
+ return m(() => {
344
370
  e.current && (e.current.submitRef.onSubmit = r.onSubmit, e.current.changeRef.onChange = r.onChange);
345
371
  }, [r.onSubmit, r.onChange]), {
346
372
  manager: s,
347
- useValue: (t) => N(t),
348
- reset: (t, f = {}) => {
349
- s.reset(t, f);
373
+ useValue: (t) => M(t),
374
+ reset: (t, u = {}) => {
375
+ s.reset(t, u);
350
376
  },
351
377
  setAsDefault: () => {
352
378
  s.setAsDefault();
@@ -354,11 +380,11 @@ const A = O({}), N = (n) => {
354
380
  setFocus: (t) => {
355
381
  s.setFocus(t);
356
382
  },
357
- setError: (t, f) => {
358
- s.setError(t, f);
383
+ setError: (t, u) => {
384
+ s.setError(t, u);
359
385
  },
360
- setValue: (t, f) => {
361
- s.setValue(t, f);
386
+ setValue: (t, u) => {
387
+ s.setValue(t, u);
362
388
  },
363
389
  getValue: (t) => s.getForm(t).value,
364
390
  getValues: () => s.getValues(),
@@ -366,17 +392,17 @@ const A = O({}), N = (n) => {
366
392
  await s.handleSubmit();
367
393
  },
368
394
  // prettier-ignore
369
- subscribe: (t, f) => s.subscribeToForm(t, f),
395
+ subscribe: (t, u) => s.subscribeToForm(t, u),
370
396
  // подписка на любое изменение значений формы (для гейтов поверх всех полей)
371
397
  subscribeAny: (t) => s.subscribeToAny(t),
372
398
  // плоский список имён всех полей (dotted-пути; вложенные context раскрыты)
373
399
  getNames: () => s.getNames()
374
400
  };
375
- }, Z = ({ manager: n }) => (p(() => {
401
+ }, I = ({ manager: n }) => (A(() => {
376
402
  n.activate();
377
- }, []), $(() => () => {
403
+ }, []), m(() => () => {
378
404
  n.disactivate();
379
- }, []), /* @__PURE__ */ b(A.Provider, { value: n, children: /* @__PURE__ */ b(C, { scheme: n.scheme, path: [] }) })), w = (n) => {
405
+ }, []), /* @__PURE__ */ g(D.Provider, { value: n, children: /* @__PURE__ */ g(p, { scheme: n.scheme, path: [] }) })), w = (n) => {
380
406
  try {
381
407
  n();
382
408
  } catch (r) {
@@ -390,67 +416,67 @@ const A = O({}), N = (n) => {
390
416
  error: !0
391
417
  };
392
418
  }
393
- }, k = (n) => (r, e) => {
394
- const s = n.getRequireCheck(e), { $rules: t = {}, ...f } = e, { validate: y, required: m } = t;
419
+ }, rr = (n) => (r, e) => {
420
+ const s = n.getRequireCheck(e), { $rules: t = {}, ...u } = e, { validate: d, required: b } = t;
395
421
  return {
396
422
  type: "form",
397
423
  name: r,
398
424
  defaultValue: n.defaultValue,
399
- Form: n.getForm(f),
400
- validate: (d) => {
401
- if (m) {
402
- const h = w(() => s(d));
403
- if (h)
404
- return h;
405
- if (y) {
406
- const v = w(() => y(d));
407
- if (v)
408
- return v;
425
+ Form: n.getForm(u),
426
+ validate: (y) => {
427
+ if (b) {
428
+ const v = w(() => s(y));
429
+ if (v)
430
+ return v;
431
+ if (d) {
432
+ const x = w(() => d(y));
433
+ if (x)
434
+ return x;
409
435
  }
410
436
  return;
411
437
  }
412
- if (y && !w(() => s(d))) {
413
- const v = w(() => y(d));
414
- if (v)
415
- return v;
438
+ if (d && !w(() => s(y))) {
439
+ const x = w(() => d(y));
440
+ if (x)
441
+ return x;
416
442
  }
417
443
  }
418
444
  };
419
- }, I = (n) => (r) => (...e) => ({
445
+ }, er = (n) => (r) => (...e) => ({
420
446
  type: "layout",
421
447
  Layout: n(r),
422
448
  childs: e
423
- }), rr = ({
449
+ }), tr = ({
424
450
  getItems: n,
425
451
  getController: r
426
452
  }) => ({
427
453
  type: "extra",
428
454
  items: n((e) => (s) => ({ type: "registry", name: e, getForm: s })),
429
455
  getController: r
430
- }), er = (n) => (...r) => ({
456
+ }), sr = (n) => (...r) => ({
431
457
  type: "context",
432
458
  context: n,
433
459
  childs: r
434
- }), tr = (n, r) => (...e) => ({
460
+ }), nr = (n, r) => (...e) => ({
435
461
  type: "condition",
436
462
  depended: n,
437
463
  canActivate: r.canActivate,
438
464
  childs: e
439
- }), sr = ({ hidden: n }) => (...r) => ({
465
+ }), or = ({ hidden: n }) => (...r) => ({
440
466
  type: "visability",
441
467
  hidden: n ?? !1,
442
468
  childs: r
443
469
  });
444
470
  export {
445
- tr as Condition,
446
- er as Context,
447
- Z as FormLayout,
448
- sr as Visablility,
449
- rr as createExtraForm,
450
- k as createForm,
451
- I as createLayout,
452
- X as createUseValue,
453
- Y as useForm,
454
- W as useSetValue,
455
- N as useValue
471
+ nr as Condition,
472
+ sr as Context,
473
+ I as FormLayout,
474
+ or as Visablility,
475
+ tr as createExtraForm,
476
+ rr as createForm,
477
+ er as createLayout,
478
+ Z as createUseValue,
479
+ k as useForm,
480
+ Y as useSetValue,
481
+ M as useValue
456
482
  };
@@ -1,5 +1,8 @@
1
+ import { Forms } from './manager';
1
2
  import { FieldValues } from '../types';
2
3
  import { SchemeItems } from '../core/types';
3
4
  type cb = (item: SchemeItems.Form, name: string, value?: any) => void;
5
+ type shownCb = (name: string) => void;
4
6
  declare const readScheme: (scheme: SchemeItems.All[], path: string[], values: FieldValues, callback: cb) => void;
5
- export { readScheme };
7
+ declare const readShown: (scheme: SchemeItems.All[], path: string[], forms: Forms, callback: shownCb) => void;
8
+ export { readScheme, readShown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companix/formkit",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "module": "./dist/bundle.es.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",