@reformer/cdk 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/FormArray-CBT-1kKN.js +120 -0
- package/dist/FormWizard-DLDm4FJM.js +311 -0
- package/dist/Slot-YDt2BEtP.js +27 -0
- package/dist/components/form-array/FormArray.d.ts +223 -0
- package/dist/components/form-array/FormArrayAddButton.d.ts +6 -0
- package/dist/components/form-array/FormArrayContext.d.ts +137 -0
- package/dist/components/form-array/FormArrayCount.d.ts +17 -0
- package/dist/components/form-array/FormArrayEmpty.d.ts +22 -0
- package/dist/components/form-array/FormArrayItemIndex.d.ts +24 -0
- package/dist/components/form-array/FormArrayList.d.ts +26 -0
- package/dist/components/form-array/FormArrayRemoveButton.d.ts +26 -0
- package/dist/components/form-array/index.d.ts +13 -0
- package/dist/components/form-array/types.d.ts +77 -0
- package/dist/components/form-array/useFormArray.d.ts +95 -0
- package/dist/components/form-field/FormField.d.ts +107 -0
- package/dist/components/form-field/FormFieldContext.d.ts +56 -0
- package/dist/components/form-field/FormFieldControl.d.ts +35 -0
- package/dist/components/form-field/FormFieldDescription.d.ts +30 -0
- package/dist/components/form-field/FormFieldError.d.ts +36 -0
- package/dist/components/form-field/FormFieldLabel.d.ts +35 -0
- package/dist/components/form-field/FormFieldRoot.d.ts +32 -0
- package/dist/components/form-field/index.d.ts +10 -0
- package/dist/components/form-field/types.d.ts +114 -0
- package/dist/components/form-field/useFormField.d.ts +111 -0
- package/dist/components/form-wizard/FormWizard.d.ts +47 -0
- package/dist/components/form-wizard/FormWizardActions.d.ts +98 -0
- package/dist/components/form-wizard/FormWizardContext.d.ts +84 -0
- package/dist/components/form-wizard/FormWizardIndicator.d.ts +118 -0
- package/dist/components/form-wizard/FormWizardNext.d.ts +35 -0
- package/dist/components/form-wizard/FormWizardPrev.d.ts +35 -0
- package/dist/components/form-wizard/FormWizardProgress.d.ts +83 -0
- package/dist/components/form-wizard/FormWizardStep.d.ts +55 -0
- package/dist/components/form-wizard/FormWizardSubmit.d.ts +43 -0
- package/dist/components/form-wizard/Slot.d.ts +20 -0
- package/dist/components/form-wizard/Step.d.ts +24 -0
- package/dist/components/form-wizard/index.d.ts +21 -0
- package/dist/components/form-wizard/types.d.ts +108 -0
- package/dist/form-array.d.ts +2 -0
- package/dist/form-array.js +15 -0
- package/dist/form-field.d.ts +2 -0
- package/dist/form-field.js +12 -0
- package/dist/form-wizard.d.ts +2 -0
- package/dist/form-wizard.js +22 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +33 -0
- package/dist/useFormField-DV396Bxa.js +232 -0
- package/llms.txt +3294 -0
- package/package.json +90 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { FormFields, FormProxy } from '@reformer/core';
|
|
2
|
+
import { FormArrayList } from './FormArrayList';
|
|
3
|
+
import { FormArrayAddButton } from './FormArrayAddButton';
|
|
4
|
+
import { FormArrayRemoveButton } from './FormArrayRemoveButton';
|
|
5
|
+
import { FormArrayEmpty } from './FormArrayEmpty';
|
|
6
|
+
import { FormArrayCount } from './FormArrayCount';
|
|
7
|
+
import { FormArrayItemIndex } from './FormArrayItemIndex';
|
|
8
|
+
import { FormArrayRootProps } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* Handle exposed via ref for external control of {@link FormArray}.
|
|
11
|
+
*
|
|
12
|
+
* Имперо-API для случаев, когда триггер находится вне дерева `FormArray.Root`
|
|
13
|
+
* (тулбар страницы, диалог подтверждения, async-эффект). Получают через
|
|
14
|
+
* `useRef<FormArrayHandle<T>>(null)` и передают в `<FormArray.Root ref={...}>`.
|
|
15
|
+
*
|
|
16
|
+
* Свойства `length` / `isEmpty` — снимок на момент рендера. Реактивную длину
|
|
17
|
+
* для условного UI снаружи берите через `useFormControl(arrayNode).length`.
|
|
18
|
+
*
|
|
19
|
+
* @typeParam T - Тип одного элемента массива (как в `FormArray.Root control={...}`).
|
|
20
|
+
*
|
|
21
|
+
* @example Тулбар «Добавить / Очистить» поверх массива
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import { useRef } from 'react';
|
|
24
|
+
* import { FormArray, type FormArrayHandle } from '@reformer/cdk/form-array';
|
|
25
|
+
*
|
|
26
|
+
* function PropertiesEditor({ form }: Props) {
|
|
27
|
+
* const arrayRef = useRef<FormArrayHandle<Property>>(null);
|
|
28
|
+
*
|
|
29
|
+
* return (
|
|
30
|
+
* <>
|
|
31
|
+
* <div className="toolbar">
|
|
32
|
+
* <button onClick={() => arrayRef.current?.add({ type: 'apartment' })}>
|
|
33
|
+
* + Квартира
|
|
34
|
+
* </button>
|
|
35
|
+
* <button onClick={() => arrayRef.current?.add({ type: 'house' })}>
|
|
36
|
+
* + Дом
|
|
37
|
+
* </button>
|
|
38
|
+
* <button onClick={() => confirm('Удалить всё?') && arrayRef.current?.clear()}>
|
|
39
|
+
* Очистить
|
|
40
|
+
* </button>
|
|
41
|
+
* </div>
|
|
42
|
+
* <FormArray.Root ref={arrayRef} control={form.properties}>
|
|
43
|
+
* <FormArray.List>{({ control }) => <PropertyForm control={control} />}</FormArray.List>
|
|
44
|
+
* </FormArray.Root>
|
|
45
|
+
* </>
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example Импорт массива из API: insert + at для проверки дублей
|
|
51
|
+
* ```tsx
|
|
52
|
+
* const arrayRef = useRef<FormArrayHandle<Contact>>(null);
|
|
53
|
+
*
|
|
54
|
+
* async function importFromCSV(rows: Contact[]) {
|
|
55
|
+
* for (const row of rows) {
|
|
56
|
+
* // skip duplicates by email
|
|
57
|
+
* const existing = Array.from({ length: arrayRef.current?.length ?? 0 })
|
|
58
|
+
* .map((_, i) => arrayRef.current?.at(i)?.getValue());
|
|
59
|
+
* if (existing.some((c) => c?.email === row.email)) continue;
|
|
60
|
+
* arrayRef.current?.insert(0, row); // добавляем в начало
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export interface FormArrayHandle<T extends FormFields> {
|
|
66
|
+
/** Add a new item to the end of the array */
|
|
67
|
+
add: (value?: Partial<T>) => void;
|
|
68
|
+
/** Remove all items from the array */
|
|
69
|
+
clear: () => void;
|
|
70
|
+
/** Insert a new item at a specific index */
|
|
71
|
+
insert: (index: number, value?: Partial<T>) => void;
|
|
72
|
+
/** Remove item at specific index */
|
|
73
|
+
removeAt: (index: number) => void;
|
|
74
|
+
/** Current number of items */
|
|
75
|
+
length: number;
|
|
76
|
+
/** Whether the array is empty */
|
|
77
|
+
isEmpty: boolean;
|
|
78
|
+
/** Get item control at specific index */
|
|
79
|
+
at: (index: number) => FormProxy<T> | undefined;
|
|
80
|
+
}
|
|
81
|
+
declare const FormArrayRoot: <T extends FormFields>(props: FormArrayRootProps<T> & {
|
|
82
|
+
ref?: React.ForwardedRef<FormArrayHandle<T>>;
|
|
83
|
+
}) => React.ReactElement;
|
|
84
|
+
type FormArrayComponent = typeof FormArrayRoot & {
|
|
85
|
+
Root: typeof FormArrayRoot;
|
|
86
|
+
List: typeof FormArrayList;
|
|
87
|
+
AddButton: typeof FormArrayAddButton;
|
|
88
|
+
RemoveButton: typeof FormArrayRemoveButton;
|
|
89
|
+
Empty: typeof FormArrayEmpty;
|
|
90
|
+
Count: typeof FormArrayCount;
|
|
91
|
+
ItemIndex: typeof FormArrayItemIndex;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* FormArray - Headless compound component for managing form arrays
|
|
95
|
+
*
|
|
96
|
+
* Provides complete flexibility for building array UI while handling
|
|
97
|
+
* all the form array state and actions internally.
|
|
98
|
+
*
|
|
99
|
+
* ## Features
|
|
100
|
+
* - **Headless** - complete freedom in building UI
|
|
101
|
+
* - **Compound Components** - declarative API via nested components
|
|
102
|
+
* - **External Control** - control from outside via ref (useImperativeHandle)
|
|
103
|
+
* - **Type Safe** - full TypeScript support
|
|
104
|
+
*
|
|
105
|
+
* ## Sub-components
|
|
106
|
+
* - `FormArray.Root` - context provider, accepts ref for external control
|
|
107
|
+
* - `FormArray.List` - iterates over array items
|
|
108
|
+
* - `FormArray.AddButton` - button to add item
|
|
109
|
+
* - `FormArray.RemoveButton` - button to remove item (inside List)
|
|
110
|
+
* - `FormArray.Empty` - content for empty state
|
|
111
|
+
* - `FormArray.Count` - display item count
|
|
112
|
+
* - `FormArray.ItemIndex` - display item index (inside List)
|
|
113
|
+
*
|
|
114
|
+
* ## FormArrayHandle API (ref)
|
|
115
|
+
* - `add(value?)` - add item to the end
|
|
116
|
+
* - `insert(index, value?)` - insert item at position
|
|
117
|
+
* - `removeAt(index)` - remove item by index
|
|
118
|
+
* - `clear()` - clear array
|
|
119
|
+
* - `at(index)` - get item control by index
|
|
120
|
+
* - `length` - current item count
|
|
121
|
+
* - `isEmpty` - empty array flag
|
|
122
|
+
*
|
|
123
|
+
* @example Basic usage
|
|
124
|
+
* ```tsx
|
|
125
|
+
* <FormArray.Root control={form.properties}>
|
|
126
|
+
* <h3>Properties (<FormArray.Count />)</h3>
|
|
127
|
+
*
|
|
128
|
+
* <FormArray.Empty>
|
|
129
|
+
* <p className="text-gray-500">No properties added</p>
|
|
130
|
+
* </FormArray.Empty>
|
|
131
|
+
*
|
|
132
|
+
* <FormArray.List className="space-y-4">
|
|
133
|
+
* {({ control }) => (
|
|
134
|
+
* <div className="p-4 border rounded">
|
|
135
|
+
* <div className="flex justify-between mb-2">
|
|
136
|
+
* <h4>Property #<FormArray.ItemIndex /></h4>
|
|
137
|
+
* <FormArray.RemoveButton className="text-red-500">
|
|
138
|
+
* Remove
|
|
139
|
+
* </FormArray.RemoveButton>
|
|
140
|
+
* </div>
|
|
141
|
+
* <PropertyForm control={control} />
|
|
142
|
+
* </div>
|
|
143
|
+
* )}
|
|
144
|
+
* </FormArray.List>
|
|
145
|
+
*
|
|
146
|
+
* <FormArray.AddButton className="mt-4 btn-primary">
|
|
147
|
+
* + Add Property
|
|
148
|
+
* </FormArray.AddButton>
|
|
149
|
+
* </FormArray.Root>
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* @example External control via ref
|
|
153
|
+
* ```tsx
|
|
154
|
+
* import { useRef } from 'react';
|
|
155
|
+
* import { FormArray, FormArrayHandle } from '@reformer/cdk/form-array';
|
|
156
|
+
*
|
|
157
|
+
* function PropertiesManager() {
|
|
158
|
+
* const arrayRef = useRef<FormArrayHandle<Property>>(null);
|
|
159
|
+
*
|
|
160
|
+
* // Programmatic control from outside
|
|
161
|
+
* const handleAddApartment = () => {
|
|
162
|
+
* arrayRef.current?.add({ type: 'apartment', estimatedValue: 0 });
|
|
163
|
+
* };
|
|
164
|
+
*
|
|
165
|
+
* const handleClearAll = () => {
|
|
166
|
+
* if (confirm('Delete all items?')) {
|
|
167
|
+
* arrayRef.current?.clear();
|
|
168
|
+
* }
|
|
169
|
+
* };
|
|
170
|
+
*
|
|
171
|
+
* const handleRemoveFirst = () => {
|
|
172
|
+
* if (arrayRef.current && arrayRef.current.length > 0) {
|
|
173
|
+
* arrayRef.current.removeAt(0);
|
|
174
|
+
* }
|
|
175
|
+
* };
|
|
176
|
+
*
|
|
177
|
+
* const handleInsertAtStart = () => {
|
|
178
|
+
* arrayRef.current?.insert(0, { type: 'house' });
|
|
179
|
+
* };
|
|
180
|
+
*
|
|
181
|
+
* return (
|
|
182
|
+
* <div>
|
|
183
|
+
* <div className="toolbar">
|
|
184
|
+
* <button onClick={handleAddApartment}>+ Apartment</button>
|
|
185
|
+
* <button onClick={handleInsertAtStart}>Insert at start</button>
|
|
186
|
+
* <button onClick={handleRemoveFirst}>Remove first</button>
|
|
187
|
+
* <button onClick={handleClearAll}>Clear all</button>
|
|
188
|
+
* </div>
|
|
189
|
+
*
|
|
190
|
+
* <FormArray.Root ref={arrayRef} control={form.properties}>
|
|
191
|
+
* <FormArray.List>
|
|
192
|
+
* {({ control }) => <PropertyForm control={control} />}
|
|
193
|
+
* </FormArray.List>
|
|
194
|
+
* </FormArray.Root>
|
|
195
|
+
* </div>
|
|
196
|
+
* );
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @example Using useFormArray hook for full customization
|
|
201
|
+
* ```tsx
|
|
202
|
+
* import { useFormArray } from '@reformer/cdk/form-array';
|
|
203
|
+
*
|
|
204
|
+
* function CustomArrayUI() {
|
|
205
|
+
* const { items, add, isEmpty, length } = useFormArray(form.properties);
|
|
206
|
+
*
|
|
207
|
+
* return (
|
|
208
|
+
* <div>
|
|
209
|
+
* <span>Total: {length}</span>
|
|
210
|
+
* {items.map(({ control, id, remove }) => (
|
|
211
|
+
* <CustomCard key={id} onDelete={remove}>
|
|
212
|
+
* <PropertyForm control={control} />
|
|
213
|
+
* </CustomCard>
|
|
214
|
+
* ))}
|
|
215
|
+
* {isEmpty && <EmptyState />}
|
|
216
|
+
* <button onClick={() => add()}>Add</button>
|
|
217
|
+
* </div>
|
|
218
|
+
* );
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
export declare const FormArray: FormArrayComponent;
|
|
223
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { FormFields } from '@reformer/core';
|
|
3
|
+
import { FormArrayAddButtonProps } from './types';
|
|
4
|
+
export declare const FormArrayAddButton: <T extends FormFields = FormFields>(props: FormArrayAddButtonProps<T> & {
|
|
5
|
+
ref?: React.Ref<HTMLButtonElement>;
|
|
6
|
+
}) => ReactElement | null;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ArrayNode, FormFields, FormProxy } from '@reformer/core';
|
|
2
|
+
/**
|
|
3
|
+
* Представляет элемент массива с контролом, индексом и действиями
|
|
4
|
+
*/
|
|
5
|
+
export interface FormArrayItem<T extends FormFields> {
|
|
6
|
+
/** Контрол для данного элемента */
|
|
7
|
+
control: FormProxy<T>;
|
|
8
|
+
/** Индекс элемента (0-based) */
|
|
9
|
+
index: number;
|
|
10
|
+
/** Уникальный идентификатор для React key */
|
|
11
|
+
id: string | number;
|
|
12
|
+
/** Удалить этот элемент из массива */
|
|
13
|
+
remove: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Контекст уровня массива
|
|
17
|
+
*/
|
|
18
|
+
export interface FormArrayContextValue<T extends FormFields = FormFields> {
|
|
19
|
+
/** Массив элементов с контролами и действиями */
|
|
20
|
+
items: FormArrayItem<T>[];
|
|
21
|
+
/** Текущая длина массива */
|
|
22
|
+
length: number;
|
|
23
|
+
/** Пустой ли массив */
|
|
24
|
+
isEmpty: boolean;
|
|
25
|
+
/** Добавить новый элемент в конец */
|
|
26
|
+
add: (value?: Partial<T>) => void;
|
|
27
|
+
/** Удалить все элементы */
|
|
28
|
+
clear: () => void;
|
|
29
|
+
/** Вставить элемент на указанную позицию */
|
|
30
|
+
insert: (index: number, value?: Partial<T>) => void;
|
|
31
|
+
/** Оригинальный ArrayNode */
|
|
32
|
+
control: ArrayNode<T>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Контекст уровня элемента массива
|
|
36
|
+
*/
|
|
37
|
+
export interface FormArrayItemContextValue<T extends FormFields = FormFields> {
|
|
38
|
+
/** Контрол для данного элемента */
|
|
39
|
+
control: FormProxy<T>;
|
|
40
|
+
/** Индекс элемента (0-based) */
|
|
41
|
+
index: number;
|
|
42
|
+
/** Уникальный идентификатор для React key */
|
|
43
|
+
id: string | number;
|
|
44
|
+
/** Удалить этот элемент из массива */
|
|
45
|
+
remove: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* React context, который снабжает дочерние компоненты `FormArray` (List, AddButton, …)
|
|
49
|
+
* текущим `ArrayNode` и хелперами. Создаётся `FormArray.Root`. Читать через {@link useFormArrayContext}.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* import { FormArrayContext } from '@reformer/cdk/form-array';
|
|
54
|
+
*
|
|
55
|
+
* function MyConsumer() {
|
|
56
|
+
* const ctx = useContext(FormArrayContext);
|
|
57
|
+
* return <span>items: {ctx?.items.length}</span>;
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare const FormArrayContext: import('react').Context<FormArrayContextValue<any> | null>;
|
|
62
|
+
/**
|
|
63
|
+
* React context, видимый внутри `FormArray.List` для одного элемента массива.
|
|
64
|
+
* Содержит `index`, `path` и `remove()`. Читать через {@link useFormArrayItemContext}.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* import { FormArrayItemContext } from '@reformer/cdk/form-array';
|
|
69
|
+
*
|
|
70
|
+
* function CurrentIndex() {
|
|
71
|
+
* const item = useContext(FormArrayItemContext);
|
|
72
|
+
* return <small>#{item?.index}</small>;
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare const FormArrayItemContext: import('react').Context<FormArrayItemContextValue<any> | null>;
|
|
77
|
+
/**
|
|
78
|
+
* Хук для доступа к контексту `FormArray`. Бросает исключение, если вызван вне
|
|
79
|
+
* `FormArray.Root` или эквивалентного провайдера.
|
|
80
|
+
*
|
|
81
|
+
* @returns Текущий {@link FormArrayContextValue}.
|
|
82
|
+
* @throws Error если используется вне `FormArray.Root`.
|
|
83
|
+
*
|
|
84
|
+
* @example Кастомный AddButton с predefined значением
|
|
85
|
+
* ```tsx
|
|
86
|
+
* import { useFormArrayContext } from '@reformer/cdk/form-array';
|
|
87
|
+
*
|
|
88
|
+
* function AddDraftButton() {
|
|
89
|
+
* const { add } = useFormArrayContext<Item>();
|
|
90
|
+
* return (
|
|
91
|
+
* <button onClick={() => add({ status: 'draft', createdAt: Date.now() })}>
|
|
92
|
+
* + Add Draft
|
|
93
|
+
* </button>
|
|
94
|
+
* );
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* @example Счётчик и условный empty-state из произвольного места дерева
|
|
99
|
+
* ```tsx
|
|
100
|
+
* function ItemsBadge() {
|
|
101
|
+
* const { length, isEmpty } = useFormArrayContext();
|
|
102
|
+
* if (isEmpty) return <span className="text-gray-400">Нет элементов</span>;
|
|
103
|
+
* return <span className="badge">{length}</span>;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare function useFormArrayContext<T extends FormFields = FormFields>(): FormArrayContextValue<T>;
|
|
108
|
+
/**
|
|
109
|
+
* Хук для доступа к контексту текущего элемента внутри `FormArray.List`.
|
|
110
|
+
*
|
|
111
|
+
* @returns Текущий {@link FormArrayItemContextValue} (`index`, `path`, `remove`).
|
|
112
|
+
* @throws Error если используется вне `FormArray.List` или item-шаблона.
|
|
113
|
+
*
|
|
114
|
+
* @example Кнопка удаления текущего элемента
|
|
115
|
+
* ```tsx
|
|
116
|
+
* import { useFormArrayItemContext } from '@reformer/cdk/form-array';
|
|
117
|
+
*
|
|
118
|
+
* function ItemRemoveButton() {
|
|
119
|
+
* const { remove } = useFormArrayItemContext();
|
|
120
|
+
* return <button onClick={remove}>×</button>;
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @example Доступ к control + index для условной валидации
|
|
125
|
+
* ```tsx
|
|
126
|
+
* function ItemHeader() {
|
|
127
|
+
* const { control, index } = useFormArrayItemContext<Property>();
|
|
128
|
+
* const { value: type } = useFormControl(control.type);
|
|
129
|
+
* return (
|
|
130
|
+
* <h4>
|
|
131
|
+
* #{index + 1} — {type === 'house' ? 'Дом' : 'Квартира'}
|
|
132
|
+
* </h4>
|
|
133
|
+
* );
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare function useFormArrayItemContext<T extends FormFields = FormFields>(): FormArrayItemContextValue<T>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FormArrayCountProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* FormArray.Count - Displays the number of items in the array
|
|
4
|
+
*
|
|
5
|
+
* @example Basic usage
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <h3>Items (<FormArray.Count />)</h3>
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* @example With custom render
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <FormArray.Count render={(count) => (
|
|
13
|
+
* count === 0 ? 'No items' : `${count} item${count > 1 ? 's' : ''}`
|
|
14
|
+
* )} />
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function FormArrayCount({ render }: FormArrayCountProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FormArrayEmptyProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* FormArray.Empty - Renders children only when array is empty
|
|
4
|
+
*
|
|
5
|
+
* @example Basic usage
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <FormArray.Empty>
|
|
8
|
+
* <p className="text-gray-500">No items added yet</p>
|
|
9
|
+
* </FormArray.Empty>
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* @example With call to action
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <FormArray.Empty>
|
|
15
|
+
* <div className="text-center p-8">
|
|
16
|
+
* <p>No properties</p>
|
|
17
|
+
* <FormArray.AddButton>Add your first property</FormArray.AddButton>
|
|
18
|
+
* </div>
|
|
19
|
+
* </FormArray.Empty>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function FormArrayEmpty({ children }: FormArrayEmptyProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FormArrayItemIndexProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* FormArray.ItemIndex - Displays the index of current item (must be inside FormArray.List)
|
|
4
|
+
*
|
|
5
|
+
* @example Basic usage (1-based display)
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <FormArray.List>
|
|
8
|
+
* {() => (
|
|
9
|
+
* <h4>Item #<FormArray.ItemIndex render={(i) => i + 1} /></h4>
|
|
10
|
+
* )}
|
|
11
|
+
* </FormArray.List>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @example Zero-based index
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <FormArray.ItemIndex /> // Renders 0, 1, 2, ...
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example Custom render
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <FormArray.ItemIndex render={(index) => `Position: ${index + 1}`} />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function FormArrayItemIndex({ render }: FormArrayItemIndexProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FormFields } from '@reformer/core';
|
|
2
|
+
import { FormArrayListProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* FormArray.List - Iterates over array items and provides item context
|
|
5
|
+
*
|
|
6
|
+
* @example Basic usage
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <FormArray.List>
|
|
9
|
+
* {({ control, index, remove }) => (
|
|
10
|
+
* <div>
|
|
11
|
+
* <span>Item #{index + 1}</span>
|
|
12
|
+
* <button onClick={remove}>Remove</button>
|
|
13
|
+
* <ItemForm control={control} />
|
|
14
|
+
* </div>
|
|
15
|
+
* )}
|
|
16
|
+
* </FormArray.List>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example With custom container
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <FormArray.List className="space-y-4" as="ul">
|
|
22
|
+
* {(item) => <li><ItemForm control={item.control} /></li>}
|
|
23
|
+
* </FormArray.List>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function FormArrayList<T extends FormFields>({ children, className, as, }: FormArrayListProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FormArrayRemoveButtonProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* FormArray.RemoveButton - Button to remove current item (must be inside FormArray.List)
|
|
4
|
+
*
|
|
5
|
+
* @example Basic usage
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <FormArray.List>
|
|
8
|
+
* {({ control }) => (
|
|
9
|
+
* <div>
|
|
10
|
+
* <ItemForm control={control} />
|
|
11
|
+
* <FormArray.RemoveButton className="text-red-500">
|
|
12
|
+
* Remove
|
|
13
|
+
* </FormArray.RemoveButton>
|
|
14
|
+
* </div>
|
|
15
|
+
* )}
|
|
16
|
+
* </FormArray.List>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example With custom button (asChild)
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <FormArray.RemoveButton asChild>
|
|
22
|
+
* <IconButton icon="trash" aria-label="Remove" />
|
|
23
|
+
* </FormArray.RemoveButton>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const FormArrayRemoveButton: import('react').ForwardRefExoticComponent<FormArrayRemoveButtonProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { FormArray } from './FormArray';
|
|
2
|
+
export type { FormArrayHandle } from './FormArray';
|
|
3
|
+
export { FormArrayList } from './FormArrayList';
|
|
4
|
+
export { FormArrayAddButton } from './FormArrayAddButton';
|
|
5
|
+
export { FormArrayRemoveButton } from './FormArrayRemoveButton';
|
|
6
|
+
export { FormArrayEmpty } from './FormArrayEmpty';
|
|
7
|
+
export { FormArrayCount } from './FormArrayCount';
|
|
8
|
+
export { FormArrayItemIndex } from './FormArrayItemIndex';
|
|
9
|
+
export { useFormArray } from './useFormArray';
|
|
10
|
+
export type { FormArrayItem, UseFormArrayReturn } from './useFormArray';
|
|
11
|
+
export { FormArrayContext, FormArrayItemContext, useFormArrayContext, useFormArrayItemContext, } from './FormArrayContext';
|
|
12
|
+
export type { FormArrayRootProps, FormArrayListProps, FormArrayItemRenderProps, FormArrayAddButtonProps, FormArrayRemoveButtonProps, FormArrayEmptyProps, FormArrayCountProps, FormArrayItemIndexProps, } from './types';
|
|
13
|
+
export type { FormArrayContextValue, FormArrayItemContextValue } from './FormArrayContext';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ReactNode, ElementType } from 'react';
|
|
2
|
+
import { ArrayNode, FormFields, FormProxy } from '@reformer/core';
|
|
3
|
+
/**
|
|
4
|
+
* Props for FormArray.Root component
|
|
5
|
+
*/
|
|
6
|
+
export interface FormArrayRootProps<T extends FormFields> {
|
|
7
|
+
/** The ArrayNode control from the form */
|
|
8
|
+
control: ArrayNode<T>;
|
|
9
|
+
/** Child components */
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Props for FormArray.List component
|
|
14
|
+
*/
|
|
15
|
+
export interface FormArrayListProps<T extends FormFields> {
|
|
16
|
+
/** Render function for each item */
|
|
17
|
+
children: (item: FormArrayItemRenderProps<T>) => ReactNode;
|
|
18
|
+
/** Optional className for the list container */
|
|
19
|
+
className?: string;
|
|
20
|
+
/** Optional element type for the container (default: 'div') */
|
|
21
|
+
as?: ElementType;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props passed to the render function in FormArray.List
|
|
25
|
+
*/
|
|
26
|
+
export interface FormArrayItemRenderProps<T extends FormFields> {
|
|
27
|
+
/** The form control for this item */
|
|
28
|
+
control: FormProxy<T>;
|
|
29
|
+
/** Zero-based index of the item */
|
|
30
|
+
index: number;
|
|
31
|
+
/** Unique identifier for React key */
|
|
32
|
+
id: string | number;
|
|
33
|
+
/** Remove this item from the array */
|
|
34
|
+
remove: () => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Props for FormArray.AddButton component
|
|
38
|
+
*
|
|
39
|
+
* Generic `T` — тип элемента массива. По умолчанию `FormFields` (широкий) —
|
|
40
|
+
* для совместимости. Для type-safe initialValue передавайте generic явно
|
|
41
|
+
* (`<FormArray.AddButton<PropertyItem> ...>`) либо проксируйте через
|
|
42
|
+
* `FormArraySection<T>` из `@reformer/ui-kit`.
|
|
43
|
+
*/
|
|
44
|
+
export interface FormArrayAddButtonProps<T extends FormFields = FormFields> extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> {
|
|
45
|
+
/** Initial value for the new item */
|
|
46
|
+
initialValue?: Partial<T>;
|
|
47
|
+
/** Custom render function for the button */
|
|
48
|
+
asChild?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Props for FormArray.RemoveButton component
|
|
52
|
+
*/
|
|
53
|
+
export interface FormArrayRemoveButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> {
|
|
54
|
+
/** Custom render function for the button */
|
|
55
|
+
asChild?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Props for FormArray.Empty component
|
|
59
|
+
*/
|
|
60
|
+
export interface FormArrayEmptyProps {
|
|
61
|
+
/** Content to show when array is empty */
|
|
62
|
+
children: ReactNode;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Props for FormArray.Count component
|
|
66
|
+
*/
|
|
67
|
+
export interface FormArrayCountProps {
|
|
68
|
+
/** Custom render function for the count */
|
|
69
|
+
render?: (count: number) => ReactNode;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Props for FormArray.ItemIndex component
|
|
73
|
+
*/
|
|
74
|
+
export interface FormArrayItemIndexProps {
|
|
75
|
+
/** Custom render function for the index (receives 0-based index) */
|
|
76
|
+
render?: (index: number) => ReactNode;
|
|
77
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ArrayNode, FormFields, FormProxy } from '@reformer/core';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a single item in a form array with its control, index, and actions
|
|
4
|
+
*/
|
|
5
|
+
export interface FormArrayItem<T extends FormFields> {
|
|
6
|
+
/** The form control for this item */
|
|
7
|
+
control: FormProxy<T>;
|
|
8
|
+
/** Zero-based index of the item in the array */
|
|
9
|
+
index: number;
|
|
10
|
+
/** Unique identifier for React key (uses internal id or falls back to index) */
|
|
11
|
+
id: string | number;
|
|
12
|
+
/** Remove this item from the array */
|
|
13
|
+
remove: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Return type for useFormArray hook
|
|
17
|
+
*/
|
|
18
|
+
export interface UseFormArrayReturn<T extends FormFields> {
|
|
19
|
+
/** Array of items with their controls and actions */
|
|
20
|
+
items: FormArrayItem<T>[];
|
|
21
|
+
/** Current number of items in the array */
|
|
22
|
+
length: number;
|
|
23
|
+
/** Whether the array is empty */
|
|
24
|
+
isEmpty: boolean;
|
|
25
|
+
/** Add a new item to the end of the array */
|
|
26
|
+
add: (value?: Partial<T>) => void;
|
|
27
|
+
/** Remove all items from the array */
|
|
28
|
+
clear: () => void;
|
|
29
|
+
/** Insert a new item at a specific index */
|
|
30
|
+
insert: (index: number, value?: Partial<T>) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Headless hook for managing form arrays
|
|
34
|
+
*
|
|
35
|
+
* Provides reactive state and actions for form array manipulation
|
|
36
|
+
* without any UI - perfect for building custom array interfaces.
|
|
37
|
+
*
|
|
38
|
+
* @example Basic usage
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function PropertyList() {
|
|
41
|
+
* const { items, add, isEmpty } = useFormArray(form.properties);
|
|
42
|
+
*
|
|
43
|
+
* return (
|
|
44
|
+
* <div>
|
|
45
|
+
* {items.map(({ control, index, remove, id }) => (
|
|
46
|
+
* <div key={id}>
|
|
47
|
+
* <span>Property #{index + 1}</span>
|
|
48
|
+
* <button onClick={remove}>Remove</button>
|
|
49
|
+
* <PropertyForm control={control} />
|
|
50
|
+
* </div>
|
|
51
|
+
* ))}
|
|
52
|
+
* {isEmpty && <p>No properties added</p>}
|
|
53
|
+
* <button onClick={() => add()}>Add Property</button>
|
|
54
|
+
* </div>
|
|
55
|
+
* );
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example With initial values + clear / insert
|
|
60
|
+
* ```tsx
|
|
61
|
+
* function PropertyToolbar() {
|
|
62
|
+
* const { add, clear, insert, length } = useFormArray(form.properties);
|
|
63
|
+
*
|
|
64
|
+
* return (
|
|
65
|
+
* <div className="flex gap-2">
|
|
66
|
+
* <button onClick={() => add({ type: 'apartment', estimatedValue: 0 })}>
|
|
67
|
+
* + Квартира
|
|
68
|
+
* </button>
|
|
69
|
+
* <button onClick={() => insert(0, { type: 'house' })}>
|
|
70
|
+
* + Дом (в начало)
|
|
71
|
+
* </button>
|
|
72
|
+
* <button onClick={clear} disabled={length === 0}>Очистить</button>
|
|
73
|
+
* <span>{length} шт.</span>
|
|
74
|
+
* </div>
|
|
75
|
+
* );
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example Кастомный AddButton снаружи compound API (drop-down)
|
|
80
|
+
* ```tsx
|
|
81
|
+
* import { useFormArrayContext } from '@reformer/cdk/form-array';
|
|
82
|
+
*
|
|
83
|
+
* function AddPropertyMenu() {
|
|
84
|
+
* const { add } = useFormArrayContext<Property>();
|
|
85
|
+
* return (
|
|
86
|
+
* <Menu>
|
|
87
|
+
* <Menu.Trigger>+ Добавить ▾</Menu.Trigger>
|
|
88
|
+
* <Menu.Item onSelect={() => add({ type: 'apartment' })}>Квартира</Menu.Item>
|
|
89
|
+
* <Menu.Item onSelect={() => add({ type: 'house' })}>Дом</Menu.Item>
|
|
90
|
+
* </Menu>
|
|
91
|
+
* );
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function useFormArray<T extends FormFields>(control: ArrayNode<T>): UseFormArrayReturn<T>;
|