@axzydev/axzy_ui_system 1.2.16 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +3058 -1407
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +620 -54
- package/dist/index.d.ts +620 -54
- package/dist/index.js +2974 -1318
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +28 -0
- package/src/components/atoms/badget/badget.tsx +2 -4
- package/src/components/atoms/chip/chip.doc.mdx +47 -0
- package/src/components/atoms/chip/chip.props.ts +32 -0
- package/src/components/atoms/chip/chip.stories.tsx +64 -0
- package/src/components/atoms/chip/chip.tsx +112 -0
- package/src/components/atoms/grid/grid.tsx +1 -2
- package/src/components/molecules/accordion/accordion.doc.mdx +47 -0
- package/src/components/molecules/accordion/accordion.props.ts +35 -0
- package/src/components/molecules/accordion/accordion.stories.tsx +68 -0
- package/src/components/molecules/accordion/accordion.tsx +123 -0
- package/src/components/molecules/chip-input/chip-input.doc.mdx +44 -0
- package/src/components/molecules/chip-input/chip-input.props.ts +42 -0
- package/src/components/molecules/chip-input/chip-input.stories.tsx +52 -0
- package/src/components/molecules/chip-input/chip-input.tsx +205 -0
- package/src/components/molecules/date-picker/datePicker.tsx +45 -56
- package/src/components/molecules/dropdown-menu/dropdown-menu.doc.mdx +46 -0
- package/src/components/molecules/dropdown-menu/dropdown-menu.props.ts +41 -0
- package/src/components/molecules/dropdown-menu/dropdown-menu.stories.tsx +41 -0
- package/src/components/molecules/dropdown-menu/dropdown-menu.tsx +219 -0
- package/src/components/molecules/field/field.doc.mdx +43 -0
- package/src/components/molecules/field/field.props.ts +23 -0
- package/src/components/molecules/field/field.stories.tsx +42 -0
- package/src/components/molecules/field/field.tsx +54 -0
- package/src/components/molecules/multi-select/multi-select.doc.mdx +55 -0
- package/src/components/molecules/multi-select/multi-select.props.ts +65 -0
- package/src/components/molecules/multi-select/multi-select.stories.tsx +59 -0
- package/src/components/molecules/multi-select/multi-select.tsx +386 -0
- package/src/components/molecules/search-select/search-select.doc.mdx +5 -0
- package/src/components/molecules/search-select/search-select.props.ts +10 -1
- package/src/components/molecules/search-select/search-select.stories.tsx +18 -0
- package/src/components/molecules/search-select/search-select.tsx +102 -28
- package/src/components/molecules/time-picker/timePicker.tsx +14 -26
- package/src/components/molecules/wysiwyg/wysiwyg.props.ts +51 -0
- package/src/components/molecules/wysiwyg/wysiwyg.stories.tsx +95 -0
- package/src/components/molecules/wysiwyg/wysiwyg.tsx +609 -0
- package/src/components/organisms/form-builder/fieldRenderer.tsx +144 -27
- package/src/components/organisms/form-builder/formBuilder.context.tsx +77 -15
- package/src/components/organisms/form-builder/formBuilder.doc.mdx +213 -86
- package/src/components/organisms/form-builder/formBuilder.props.ts +118 -36
- package/src/components/organisms/form-builder/formBuilder.stories.tsx +694 -96
- package/src/components/organisms/form-builder/formBuilder.tsx +152 -69
- package/src/components/organisms/form-builder/useFormBuilder.ts +59 -21
- package/src/hooks/useFloatingPanel.ts +100 -0
- package/src/index.ts +24 -4
- package/src/showcases/DataShowcases.tsx +76 -0
- package/src/showcases/FormShowcases.tsx +396 -10
- package/src/showcases/HomeShowcase.tsx +6 -6
- package/src/showcases/NavigationShowcases.tsx +72 -2
- package/src/showcases/StructureShowcases.tsx +64 -0
- package/src/types/chip.types.ts +13 -0
|
@@ -12,41 +12,108 @@ import { getColSpanClass, getGridColsClass } from "@/utils/styles";
|
|
|
12
12
|
import ITText from "@/components/atoms/text/text";
|
|
13
13
|
import { FaSpinner } from "react-icons/fa";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Resolves `FieldConfigV2["options"]` to a stable `{ value, label }[]` array,
|
|
17
|
+
* calling the function form once per dependency change. Returns a loading flag
|
|
18
|
+
* while an async loader is in flight so the caller can render a placeholder.
|
|
19
|
+
*
|
|
20
|
+
* Sync arrays are returned synchronously; async functions are awaited and the
|
|
21
|
+
* resolved array is memoized until one of the supplied `deps` changes.
|
|
22
|
+
*
|
|
23
|
+
* The `deps` argument is intentionally a dynamic array — callers pass in the
|
|
24
|
+
* values of `dependsOn` siblings so option lists re-fetch when a parent field
|
|
25
|
+
* changes (e.g. cascading country -> city).
|
|
26
|
+
*/
|
|
27
|
+
function useAsyncOptions(
|
|
28
|
+
options: FieldConfigV2["options"],
|
|
29
|
+
deps: ReadonlyArray<unknown>,
|
|
30
|
+
): { options: { value: string; label: string }[]; loading: boolean } {
|
|
16
31
|
const [resolved, setResolved] = useState<{ value: string; label: string }[]>([]);
|
|
17
32
|
const [loading, setLoading] = useState(false);
|
|
33
|
+
|
|
34
|
+
// Keep a ref so the async loader closure always reads the freshest options
|
|
35
|
+
// function without re-running the effect on identity changes.
|
|
18
36
|
const optionsRef = useRef(options);
|
|
19
37
|
optionsRef.current = options;
|
|
20
38
|
|
|
21
39
|
useEffect(() => {
|
|
22
40
|
const opts = optionsRef.current;
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
|
|
23
43
|
if (typeof opts === "function") {
|
|
24
44
|
setLoading(true);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
Promise.resolve()
|
|
46
|
+
.then(() => opts())
|
|
47
|
+
.then((result) => {
|
|
48
|
+
if (cancelled) return;
|
|
49
|
+
setResolved(result.map((o) => ({ value: String(o.value), label: o.label })));
|
|
50
|
+
})
|
|
51
|
+
.catch((err) => {
|
|
52
|
+
if (cancelled) return;
|
|
53
|
+
if (typeof console !== "undefined") {
|
|
54
|
+
console.error("[ITFormBuilder] Failed to load async options:", err);
|
|
55
|
+
}
|
|
56
|
+
setResolved([]);
|
|
57
|
+
})
|
|
58
|
+
.finally(() => {
|
|
59
|
+
if (!cancelled) setLoading(false);
|
|
60
|
+
});
|
|
29
61
|
} else if (Array.isArray(opts)) {
|
|
30
|
-
setResolved(opts.map(o => ({ value: String(o.value), label: o.label })));
|
|
62
|
+
setResolved(opts.map((o) => ({ value: String(o.value), label: o.label })));
|
|
63
|
+
setLoading(false);
|
|
64
|
+
} else {
|
|
65
|
+
setResolved([]);
|
|
66
|
+
setLoading(false);
|
|
31
67
|
}
|
|
32
|
-
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
cancelled = true;
|
|
71
|
+
};
|
|
72
|
+
// `deps` is intentionally a dynamic array of dependent-values; the ESLint
|
|
73
|
+
// rule cannot statically verify the dependency surface here.
|
|
33
74
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
-
},
|
|
75
|
+
}, deps);
|
|
35
76
|
|
|
36
77
|
return { options: resolved, loading };
|
|
37
78
|
}
|
|
38
79
|
|
|
39
|
-
|
|
40
|
-
|
|
41
80
|
interface ITFieldRendererProps {
|
|
81
|
+
/** The V2 field definition driving the render. */
|
|
42
82
|
config: FieldConfigV2;
|
|
83
|
+
/** Grid column count used for `column` spans and nested `section` grids. */
|
|
43
84
|
columns?: number;
|
|
85
|
+
/** Current value of this field (controlled). */
|
|
44
86
|
value?: any;
|
|
87
|
+
/** Validation error message for this field. */
|
|
45
88
|
error?: string;
|
|
89
|
+
/** Whether this field has been touched. */
|
|
46
90
|
touched?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Pre-filtered `values` slice containing only the keys listed in
|
|
93
|
+
* `config.dependsOn`. Computed by {@link ITFormBuilder}.
|
|
94
|
+
*/
|
|
47
95
|
dependentValues?: Record<string, any>;
|
|
48
96
|
}
|
|
49
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Renders a single V2 {@link FieldConfigV2}. Mounts/unmounts the field in the
|
|
100
|
+
* shared registry, evaluates `renderWhen` / `dynamicProps` via
|
|
101
|
+
* {@link useFieldRules}, and dispatches to the correct input component for the
|
|
102
|
+
* field's `type`.
|
|
103
|
+
*
|
|
104
|
+
* Field kinds supported by `type`:
|
|
105
|
+
* - `"text" | "password" | "number" | "email"` -> `<ITInput>`
|
|
106
|
+
* - `"select"` (sync options) -> `<ITSelect>`
|
|
107
|
+
* - `"select"` (async options) -> `<ITSearchSelect>` with a spinner fallback
|
|
108
|
+
* - `"date"` -> `<ITDatePicker>`
|
|
109
|
+
* - `"time"` -> `<ITTimePicker>`
|
|
110
|
+
* - `"custom"` -> the `config.component` renderer
|
|
111
|
+
* - `"section"` -> nested grid of `config.fields`
|
|
112
|
+
* - `"array"` -> placeholder (reserved for future dynamic lists)
|
|
113
|
+
*
|
|
114
|
+
* Memoised with `React.memo`; equality is shallow on props so passing fresh
|
|
115
|
+
* `value`/`error`/`touched` per render is expected.
|
|
116
|
+
*/
|
|
50
117
|
const ITFieldRenderer = ({
|
|
51
118
|
config,
|
|
52
119
|
columns = 12,
|
|
@@ -66,7 +133,8 @@ const ITFieldRenderer = ({
|
|
|
66
133
|
return () => {
|
|
67
134
|
context.unregisterField(config.name);
|
|
68
135
|
};
|
|
69
|
-
// `config`/`context`
|
|
136
|
+
// `config`/`context` are stable in the provider; only `name` defines the
|
|
137
|
+
// registration key.
|
|
70
138
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
139
|
}, [config.name]);
|
|
72
140
|
|
|
@@ -94,17 +162,20 @@ const ITFieldRenderer = ({
|
|
|
94
162
|
rightIcon,
|
|
95
163
|
} = activeConfig;
|
|
96
164
|
|
|
97
|
-
const { options: resolvedOptions, loading: optionsLoading } = useAsyncOptions(
|
|
165
|
+
const { options: resolvedOptions, loading: optionsLoading } = useAsyncOptions(
|
|
166
|
+
options,
|
|
167
|
+
[options, dependentValues],
|
|
168
|
+
);
|
|
98
169
|
|
|
99
170
|
const handleChangeWrapper = useCallback(
|
|
100
171
|
async (val: any) => {
|
|
101
|
-
const finalValue = val
|
|
172
|
+
const finalValue = val && typeof val === "object" && "target" in val ? val.target.value : val;
|
|
102
173
|
await context.setFieldValue(name, finalValue);
|
|
103
174
|
if (activeConfig.onChangeAction) {
|
|
104
175
|
await activeConfig.onChangeAction(finalValue, context);
|
|
105
176
|
}
|
|
106
177
|
},
|
|
107
|
-
[name, context, activeConfig]
|
|
178
|
+
[name, context, activeConfig],
|
|
108
179
|
);
|
|
109
180
|
|
|
110
181
|
if (!isVisible) return null;
|
|
@@ -140,15 +211,20 @@ const ITFieldRenderer = ({
|
|
|
140
211
|
);
|
|
141
212
|
|
|
142
213
|
case "select":
|
|
143
|
-
if (optionsLoading) {
|
|
144
|
-
return (
|
|
145
|
-
<div className="flex items-center gap-2 py-3">
|
|
146
|
-
<FaSpinner className="animate-spin text-primary-500" />
|
|
147
|
-
<ITText as="span" className="text-sm text-slate-500">Cargando opciones...</ITText>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
214
|
if (typeof options === "function") {
|
|
215
|
+
if (optionsLoading) {
|
|
216
|
+
return (
|
|
217
|
+
<div
|
|
218
|
+
className="flex items-center gap-2 py-3"
|
|
219
|
+
data-testid={`field-loading-${name}`}
|
|
220
|
+
>
|
|
221
|
+
<FaSpinner className="animate-spin text-primary-500" />
|
|
222
|
+
<ITText as="span" className="text-sm text-slate-500">
|
|
223
|
+
Cargando opciones...
|
|
224
|
+
</ITText>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
152
228
|
return (
|
|
153
229
|
<ITSearchSelect
|
|
154
230
|
options={resolvedOptions}
|
|
@@ -159,7 +235,7 @@ const ITFieldRenderer = ({
|
|
|
159
235
|
value={value !== undefined ? value : activeConfig.defaultValue || ""}
|
|
160
236
|
valueField={valueField || "value"}
|
|
161
237
|
labelField={labelField || "label"}
|
|
162
|
-
onChange={
|
|
238
|
+
onChange={handleChangeWrapper}
|
|
163
239
|
onBlur={context.handleBlur}
|
|
164
240
|
touched={touched}
|
|
165
241
|
error={error}
|
|
@@ -220,9 +296,28 @@ const ITFieldRenderer = ({
|
|
|
220
296
|
case "custom":
|
|
221
297
|
if (activeConfig.component) {
|
|
222
298
|
const CustomComponent = activeConfig.component;
|
|
299
|
+
// Strip the builder-only keys before forwarding so the custom
|
|
300
|
+
// component receives a clean prop bag (no leaks of `component`,
|
|
301
|
+
// `fields`, `dynamicProps`, `renderWhen`, etc.).
|
|
302
|
+
const customProps: Record<string, unknown> = { ...activeConfig };
|
|
303
|
+
const BUILDER_ONLY_KEYS = [
|
|
304
|
+
"component",
|
|
305
|
+
"type",
|
|
306
|
+
"fields",
|
|
307
|
+
"renderWhen",
|
|
308
|
+
"dynamicProps",
|
|
309
|
+
"dependsOn",
|
|
310
|
+
"validation",
|
|
311
|
+
"asyncValidation",
|
|
312
|
+
"options",
|
|
313
|
+
"defaultValue",
|
|
314
|
+
"className",
|
|
315
|
+
];
|
|
316
|
+
for (const key of BUILDER_ONLY_KEYS) delete customProps[key];
|
|
223
317
|
return (
|
|
224
318
|
<CustomComponent
|
|
225
|
-
{...
|
|
319
|
+
{...(customProps as any)}
|
|
320
|
+
name={name}
|
|
226
321
|
value={value}
|
|
227
322
|
onChange={handleChangeWrapper}
|
|
228
323
|
onBlur={context.handleBlur}
|
|
@@ -232,17 +327,30 @@ const ITFieldRenderer = ({
|
|
|
232
327
|
/>
|
|
233
328
|
);
|
|
234
329
|
}
|
|
330
|
+
if (typeof console !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
331
|
+
console.warn(
|
|
332
|
+
`[ITFormBuilder] Field "${name}" has type "custom" but no \`component\` was provided.`,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
235
335
|
return null;
|
|
236
336
|
|
|
237
337
|
case "section":
|
|
238
338
|
return (
|
|
239
339
|
<div className={clsx("w-full col-span-full", activeConfig.className)}>
|
|
240
340
|
{label && (
|
|
241
|
-
<ITText
|
|
341
|
+
<ITText
|
|
342
|
+
as="h4"
|
|
343
|
+
className="text-lg font-semibold text-secondary-800 mb-4"
|
|
344
|
+
>
|
|
242
345
|
{label}
|
|
243
346
|
</ITText>
|
|
244
347
|
)}
|
|
245
|
-
<div
|
|
348
|
+
<div
|
|
349
|
+
className={clsx(
|
|
350
|
+
"grid gap-y-6 gap-x-5",
|
|
351
|
+
getGridColsClass(columns as any),
|
|
352
|
+
)}
|
|
353
|
+
>
|
|
246
354
|
{activeConfig.fields?.map((childConfig) => (
|
|
247
355
|
<ITFieldRenderer
|
|
248
356
|
key={childConfig.name}
|
|
@@ -264,12 +372,21 @@ const ITFieldRenderer = ({
|
|
|
264
372
|
);
|
|
265
373
|
|
|
266
374
|
default:
|
|
375
|
+
if (typeof console !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
376
|
+
console.warn(
|
|
377
|
+
`[ITFormBuilder] Unknown V2 field type "${type}" for field "${name}". ` +
|
|
378
|
+
`Supported: text, number, password, email, select, date, time, custom, section, array.`,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
267
381
|
return null;
|
|
268
382
|
}
|
|
269
383
|
};
|
|
270
384
|
|
|
271
385
|
return (
|
|
272
|
-
<div
|
|
386
|
+
<div
|
|
387
|
+
className={clsx(getColSpanClass(activeConfig.column, columns), activeConfig.className)}
|
|
388
|
+
data-field-name={name}
|
|
389
|
+
>
|
|
273
390
|
{renderField()}
|
|
274
391
|
</div>
|
|
275
392
|
);
|
|
@@ -1,30 +1,79 @@
|
|
|
1
1
|
import React, { createContext, useContext, useState } from 'react';
|
|
2
2
|
import { FieldConfigV2, FieldContextV2 } from '@/types/field.types';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Internal context shared between {@link ITFormBuilder} and any descendant
|
|
6
|
+
* (e.g. a custom submit button, a sidebar showing form progress). The shape
|
|
7
|
+
* extends the public {@link FieldContextV2} with bookkeeping for the V2
|
|
8
|
+
* architecture: a per-field config registry plus lifecycle flags mirroring
|
|
9
|
+
* Formik.
|
|
10
|
+
*/
|
|
11
|
+
export interface ITFormBuilderContextType extends FieldContextV2 {
|
|
12
|
+
/** The full V2 config array passed to `<ITFormBuilder config={...} />`. */
|
|
5
13
|
config: FieldConfigV2[];
|
|
14
|
+
/** True while a Formik `submitForm` is in flight. */
|
|
6
15
|
isSubmitting: boolean;
|
|
16
|
+
/** True while async validation is running for any field. */
|
|
7
17
|
isValidating: boolean;
|
|
18
|
+
/** Number of times the parent form has been submitted. */
|
|
8
19
|
submitCount: number;
|
|
20
|
+
/** Values present at `initialValues` time (reserved for future resets). */
|
|
9
21
|
initialValues: Record<string, any>;
|
|
22
|
+
/** Validation errors keyed by field `name`. */
|
|
10
23
|
errors: Record<string, string>;
|
|
24
|
+
/** Touch state keyed by field `name`. */
|
|
11
25
|
touched: Record<string, boolean>;
|
|
12
|
-
|
|
26
|
+
/** Generic change handler (Formik-style `(e) => void`). */
|
|
13
27
|
handleChange: (e: any) => void;
|
|
28
|
+
/** Generic blur handler (Formik-style `(e) => void`). */
|
|
29
|
+
handleBlur: (e: any) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Registers a field's config in the live registry. Called automatically by
|
|
32
|
+
* every `ITFieldRenderer` on mount; use `getFieldConfig` to read it back.
|
|
33
|
+
*/
|
|
14
34
|
registerField: (name: string, config: FieldConfigV2) => void;
|
|
35
|
+
/** Removes a field from the registry. Called on unmount. */
|
|
15
36
|
unregisterField: (name: string) => void;
|
|
37
|
+
/** Returns the registered config for a given field name, or `undefined`. */
|
|
16
38
|
getFieldConfig: (name: string) => FieldConfigV2 | undefined;
|
|
39
|
+
/** Returns the full list of registered field configs (snapshot). */
|
|
40
|
+
getAllFieldConfigs: () => FieldConfigV2[];
|
|
17
41
|
}
|
|
18
42
|
|
|
19
43
|
const ITFormBuilderContext = createContext<ITFormBuilderContextType | undefined>(undefined);
|
|
20
44
|
|
|
45
|
+
export interface ITFormBuilderProviderProps {
|
|
46
|
+
/** The children rendered inside the provider (typically the grid of fields). */
|
|
47
|
+
children: React.ReactNode;
|
|
48
|
+
/**
|
|
49
|
+
* Pre-built value object containing everything except the per-field registry
|
|
50
|
+
* helpers (`registerField` / `unregisterField` / `getFieldConfig` /
|
|
51
|
+
* `getAllFieldConfigs`), which the provider injects itself.
|
|
52
|
+
*/
|
|
53
|
+
value: Omit<
|
|
54
|
+
ITFormBuilderContextType,
|
|
55
|
+
'registerField' | 'unregisterField' | 'getFieldConfig' | 'getAllFieldConfigs'
|
|
56
|
+
>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wraps the V2 grid of fields with the shared context. You normally don't need
|
|
61
|
+
* to mount this manually — {@link ITFormBuilder} does it for you when the V2
|
|
62
|
+
* `config` prop is supplied. Mounting it yourself is useful for advanced
|
|
63
|
+
* compositions (e.g. embedding an `<ITFormBuilder />` inside another component
|
|
64
|
+
* that needs to read `useFormBuilder`).
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <ITFormBuilderProvider value={{ values, errors, touched, ... }}>
|
|
69
|
+
* <MyGrid />
|
|
70
|
+
* </ITFormBuilderProvider>
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
21
73
|
export const ITFormBuilderProvider = ({
|
|
22
74
|
children,
|
|
23
75
|
value,
|
|
24
|
-
}: {
|
|
25
|
-
children: React.ReactNode;
|
|
26
|
-
value: Omit<ITFormBuilderContextType, 'registerField' | 'unregisterField' | 'getFieldConfig'>;
|
|
27
|
-
}) => {
|
|
76
|
+
}: ITFormBuilderProviderProps) => {
|
|
28
77
|
const [fieldRegistry, setFieldRegistry] = useState<Record<string, FieldConfigV2>>({});
|
|
29
78
|
|
|
30
79
|
const registerField = React.useCallback((name: string, config: FieldConfigV2) => {
|
|
@@ -33,15 +82,21 @@ export const ITFormBuilderProvider = ({
|
|
|
33
82
|
|
|
34
83
|
const unregisterField = React.useCallback((name: string) => {
|
|
35
84
|
setFieldRegistry((prev) => {
|
|
36
|
-
const
|
|
37
|
-
delete
|
|
38
|
-
return
|
|
85
|
+
const next = { ...prev };
|
|
86
|
+
delete next[name];
|
|
87
|
+
return next;
|
|
39
88
|
});
|
|
40
89
|
}, []);
|
|
41
90
|
|
|
42
|
-
const getFieldConfig = React.useCallback(
|
|
43
|
-
|
|
44
|
-
|
|
91
|
+
const getFieldConfig = React.useCallback(
|
|
92
|
+
(name: string) => fieldRegistry[name],
|
|
93
|
+
[fieldRegistry]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const getAllFieldConfigs = React.useCallback(
|
|
97
|
+
() => Object.values(fieldRegistry),
|
|
98
|
+
[fieldRegistry]
|
|
99
|
+
);
|
|
45
100
|
|
|
46
101
|
const contextValue = React.useMemo(
|
|
47
102
|
() => ({
|
|
@@ -49,8 +104,9 @@ export const ITFormBuilderProvider = ({
|
|
|
49
104
|
registerField,
|
|
50
105
|
unregisterField,
|
|
51
106
|
getFieldConfig,
|
|
107
|
+
getAllFieldConfigs,
|
|
52
108
|
}),
|
|
53
|
-
[value, registerField, unregisterField, getFieldConfig]
|
|
109
|
+
[value, registerField, unregisterField, getFieldConfig, getAllFieldConfigs]
|
|
54
110
|
);
|
|
55
111
|
|
|
56
112
|
return (
|
|
@@ -60,7 +116,13 @@ export const ITFormBuilderProvider = ({
|
|
|
60
116
|
);
|
|
61
117
|
};
|
|
62
118
|
|
|
63
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Reads the live form context. Throws when called outside an
|
|
121
|
+
* `ITFormBuilderProvider` so misuse fails loudly in development.
|
|
122
|
+
*
|
|
123
|
+
* Prefer the higher-level {@link useFormBuilder} hook for typical use cases
|
|
124
|
+
* (progress, dirty state); this hook returns the un-augmented context shape.
|
|
125
|
+
*/
|
|
64
126
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
65
127
|
export const useITFormBuilderContext = () => {
|
|
66
128
|
const context = useContext(ITFormBuilderContext);
|
|
@@ -68,4 +130,4 @@ export const useITFormBuilderContext = () => {
|
|
|
68
130
|
throw new Error('useITFormBuilderContext must be used within an ITFormBuilderProvider');
|
|
69
131
|
}
|
|
70
132
|
return context;
|
|
71
|
-
};
|
|
133
|
+
};
|