@dynamic-field-kit/react 1.3.0 → 1.5.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.js CHANGED
@@ -30,11 +30,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ DynamicFormDevTools: () => DynamicFormDevTools,
33
34
  DynamicInput: () => DynamicInput_default,
34
35
  FieldInput: () => FieldInput_default,
36
+ FieldRegistry: () => import_core6.FieldRegistry,
37
+ FieldRegistryProvider: () => FieldRegistryProvider,
35
38
  MultiFieldInput: () => MultiFieldInput_default,
39
+ defaultRenderersMap: () => defaultRenderersMap,
36
40
  fieldRegistry: () => fieldRegistry,
37
- layoutRegistry: () => layoutRegistry
41
+ getDefaultRenderer: () => getDefaultRenderer,
42
+ layoutRegistry: () => layoutRegistry,
43
+ resolveDisabled: () => import_core7.resolveDisabled,
44
+ resolveOptions: () => import_core7.resolveOptions,
45
+ resolveReadOnly: () => import_core7.resolveReadOnly,
46
+ useDynamicForm: () => useDynamicForm,
47
+ useFieldRegistry: () => useFieldRegistry,
48
+ validateField: () => import_core7.validateField,
49
+ validateFieldAsync: () => import_core7.validateFieldAsync,
50
+ validateFields: () => import_core7.validateFields,
51
+ validateFieldsAsync: () => import_core7.validateFieldsAsync,
52
+ validators: () => import_core7.validators
38
53
  });
39
54
  module.exports = __toCommonJS(index_exports);
40
55
 
@@ -101,17 +116,36 @@ function resolve(layout) {
101
116
  return { type: layout.type, config: layout };
102
117
  }
103
118
  function checkIsMobile(breakpoint) {
104
- return typeof window !== "undefined" && window.innerWidth < breakpoint;
119
+ if (typeof window === "undefined") {
120
+ return false;
121
+ }
122
+ if (typeof window.matchMedia === "function") {
123
+ return window.matchMedia(`(max-width: ${breakpoint - 1}px)`).matches;
124
+ }
125
+ return window.innerWidth < breakpoint;
105
126
  }
106
127
  layoutRegistry.register("responsive", ({ children, config }) => {
107
128
  const responsiveConfig = config;
108
129
  const breakpoint = responsiveConfig.breakpoint ?? 768;
109
130
  const [isMobile, setIsMobile] = (0, import_react.useState)(() => checkIsMobile(breakpoint));
110
131
  (0, import_react.useEffect)(() => {
111
- const handleResize = () => setIsMobile(checkIsMobile(breakpoint));
112
- handleResize();
113
- window.addEventListener("resize", handleResize);
114
- return () => window.removeEventListener("resize", handleResize);
132
+ if (typeof window === "undefined") {
133
+ return;
134
+ }
135
+ if (typeof window.matchMedia !== "function") {
136
+ const handleResize = () => setIsMobile(checkIsMobile(breakpoint));
137
+ window.addEventListener("resize", handleResize);
138
+ return () => window.removeEventListener("resize", handleResize);
139
+ }
140
+ const mediaQuery = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
141
+ const handleChange = (e) => setIsMobile(e.matches);
142
+ setIsMobile(mediaQuery.matches);
143
+ if (mediaQuery.addEventListener) {
144
+ mediaQuery.addEventListener("change", handleChange);
145
+ return () => mediaQuery.removeEventListener("change", handleChange);
146
+ }
147
+ mediaQuery.addListener(handleChange);
148
+ return () => mediaQuery.removeListener(handleChange);
115
149
  }, [breakpoint]);
116
150
  const current = isMobile ? responsiveConfig.mobile : responsiveConfig.desktop;
117
151
  if (!current) {
@@ -132,58 +166,399 @@ layoutRegistry.register("responsive", ({ children, config }) => {
132
166
  });
133
167
 
134
168
  // src/components/DynamicInput.tsx
135
- var import_react2 = __toESM(require("react"));
169
+ var import_react3 = __toESM(require("react"));
170
+
171
+ // src/defaultRenderers.tsx
172
+ var import_jsx_runtime3 = require("react/jsx-runtime");
173
+ var DefaultTextRenderer = ({
174
+ value,
175
+ onValueChange,
176
+ onBlur,
177
+ disabled,
178
+ readOnly,
179
+ required,
180
+ placeholder,
181
+ id,
182
+ className,
183
+ ariaInvalid,
184
+ ariaDescribedBy,
185
+ ariaRequired,
186
+ inputType = "text"
187
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
188
+ "input",
189
+ {
190
+ type: inputType,
191
+ id,
192
+ className,
193
+ value: value ?? "",
194
+ onChange: (e) => onValueChange?.(e.target.value),
195
+ onBlur,
196
+ disabled,
197
+ readOnly,
198
+ required,
199
+ placeholder,
200
+ "aria-invalid": ariaInvalid,
201
+ "aria-describedby": ariaDescribedBy,
202
+ "aria-required": ariaRequired
203
+ }
204
+ );
205
+ var DefaultNumberRenderer = ({
206
+ value,
207
+ onValueChange,
208
+ onBlur,
209
+ disabled,
210
+ readOnly,
211
+ required,
212
+ placeholder,
213
+ id,
214
+ className,
215
+ ariaInvalid,
216
+ ariaDescribedBy,
217
+ ariaRequired
218
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
219
+ "input",
220
+ {
221
+ type: "number",
222
+ id,
223
+ className,
224
+ value: value ?? "",
225
+ onChange: (e) => onValueChange?.(
226
+ e.target.value === "" ? void 0 : Number(e.target.value)
227
+ ),
228
+ onBlur,
229
+ disabled,
230
+ readOnly,
231
+ required,
232
+ placeholder,
233
+ "aria-invalid": ariaInvalid,
234
+ "aria-describedby": ariaDescribedBy,
235
+ "aria-required": ariaRequired
236
+ }
237
+ );
238
+ var DefaultPasswordRenderer = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultTextRenderer, { ...props, inputType: "password" });
239
+ var DefaultEmailRenderer = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultTextRenderer, { ...props, inputType: "email" });
240
+ var DefaultTextareaRenderer = ({
241
+ value,
242
+ onValueChange,
243
+ onBlur,
244
+ disabled,
245
+ readOnly,
246
+ required,
247
+ placeholder,
248
+ id,
249
+ className,
250
+ ariaInvalid,
251
+ ariaDescribedBy,
252
+ ariaRequired
253
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
254
+ "textarea",
255
+ {
256
+ id,
257
+ className,
258
+ value: value ?? "",
259
+ onChange: (e) => onValueChange?.(e.target.value),
260
+ onBlur,
261
+ disabled,
262
+ readOnly,
263
+ required,
264
+ placeholder,
265
+ "aria-invalid": ariaInvalid,
266
+ "aria-describedby": ariaDescribedBy,
267
+ "aria-required": ariaRequired
268
+ }
269
+ );
270
+ var DefaultCheckboxRenderer = ({
271
+ value,
272
+ onValueChange,
273
+ onBlur,
274
+ disabled,
275
+ readOnly,
276
+ required,
277
+ id,
278
+ className,
279
+ ariaInvalid,
280
+ ariaDescribedBy,
281
+ ariaRequired
282
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
283
+ "input",
284
+ {
285
+ type: "checkbox",
286
+ id,
287
+ className,
288
+ checked: Boolean(value),
289
+ onChange: (e) => onValueChange?.(e.target.checked),
290
+ onBlur,
291
+ disabled: disabled || readOnly,
292
+ required,
293
+ "aria-invalid": ariaInvalid,
294
+ "aria-describedby": ariaDescribedBy,
295
+ "aria-required": ariaRequired
296
+ }
297
+ );
298
+ var DefaultSelectRenderer = ({
299
+ value,
300
+ onValueChange,
301
+ onBlur,
302
+ disabled,
303
+ readOnly,
304
+ required,
305
+ options = [],
306
+ id,
307
+ className,
308
+ ariaInvalid,
309
+ ariaDescribedBy,
310
+ ariaRequired
311
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
312
+ "select",
313
+ {
314
+ id,
315
+ className,
316
+ value: value ?? "",
317
+ onChange: (e) => onValueChange?.(e.target.value),
318
+ onBlur,
319
+ disabled: disabled || readOnly,
320
+ required,
321
+ "aria-invalid": ariaInvalid,
322
+ "aria-describedby": ariaDescribedBy,
323
+ "aria-required": ariaRequired,
324
+ children: [
325
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", disabled: true, children: "-- Select --" }),
326
+ options.map((opt, i) => {
327
+ const optVal = opt.value ?? opt.id ?? opt;
328
+ const optLabel = opt.label ?? opt.name ?? String(optVal);
329
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: String(optVal), children: String(optLabel) }, String(optVal) + i);
330
+ })
331
+ ]
332
+ }
333
+ );
334
+ var DefaultRadioRenderer = ({
335
+ value,
336
+ onValueChange,
337
+ onBlur,
338
+ disabled,
339
+ readOnly,
340
+ required,
341
+ options = [],
342
+ id,
343
+ className,
344
+ ariaInvalid,
345
+ ariaDescribedBy
346
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `dfk-radio-group ${className || ""}`, id, onBlur, children: options.map((opt, i) => {
347
+ const optVal = opt.value ?? opt.id ?? opt;
348
+ const optLabel = opt.label ?? opt.name ?? String(optVal);
349
+ const isChecked = String(value) === String(optVal);
350
+ const radioId = `${id || "radio"}-${i}`;
351
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
352
+ "label",
353
+ {
354
+ htmlFor: radioId,
355
+ style: {
356
+ marginRight: "12px",
357
+ display: "inline-flex",
358
+ alignItems: "center"
359
+ },
360
+ children: [
361
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
362
+ "input",
363
+ {
364
+ type: "radio",
365
+ id: radioId,
366
+ name: id,
367
+ value: String(optVal),
368
+ checked: isChecked,
369
+ onChange: () => onValueChange?.(optVal),
370
+ disabled: disabled || readOnly,
371
+ required,
372
+ "aria-invalid": ariaInvalid,
373
+ "aria-describedby": ariaDescribedBy
374
+ }
375
+ ),
376
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { marginLeft: "4px" }, children: String(optLabel) })
377
+ ]
378
+ },
379
+ String(optVal) + i
380
+ );
381
+ }) });
382
+ var DefaultRangeRenderer = ({
383
+ value,
384
+ onValueChange,
385
+ onBlur,
386
+ disabled,
387
+ readOnly,
388
+ required,
389
+ min,
390
+ max,
391
+ step,
392
+ id,
393
+ className,
394
+ ariaInvalid,
395
+ ariaDescribedBy
396
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
397
+ "input",
398
+ {
399
+ type: "range",
400
+ id,
401
+ className,
402
+ value: value ?? min ?? 0,
403
+ min,
404
+ max,
405
+ step,
406
+ onChange: (e) => onValueChange?.(Number(e.target.value)),
407
+ onBlur,
408
+ disabled: disabled || readOnly,
409
+ required,
410
+ "aria-invalid": ariaInvalid,
411
+ "aria-describedby": ariaDescribedBy
412
+ }
413
+ );
414
+ var DefaultFileRenderer = ({
415
+ onValueChange,
416
+ onBlur,
417
+ disabled,
418
+ readOnly,
419
+ required,
420
+ accept,
421
+ multiple,
422
+ id,
423
+ className,
424
+ ariaInvalid,
425
+ ariaDescribedBy
426
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
427
+ "input",
428
+ {
429
+ type: "file",
430
+ id,
431
+ className,
432
+ accept,
433
+ multiple,
434
+ onChange: (e) => {
435
+ const files = e.target.files;
436
+ if (!files) {
437
+ return;
438
+ }
439
+ onValueChange?.(multiple ? Array.from(files) : files[0] || null);
440
+ },
441
+ onBlur,
442
+ disabled: disabled || readOnly,
443
+ required,
444
+ "aria-invalid": ariaInvalid,
445
+ "aria-describedby": ariaDescribedBy
446
+ }
447
+ );
448
+ var DefaultDateRenderer = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultTextRenderer, { ...props, inputType: "date" });
449
+ var DefaultTimeRenderer = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultTextRenderer, { ...props, inputType: "time" });
450
+ var DefaultDateTimeLocalRenderer = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultTextRenderer, { ...props, inputType: "datetime-local" });
451
+ var DefaultSwitchRenderer = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultCheckboxRenderer, { ...props });
452
+ var defaultRenderersMap = {
453
+ text: DefaultTextRenderer,
454
+ number: DefaultNumberRenderer,
455
+ password: DefaultPasswordRenderer,
456
+ email: DefaultEmailRenderer,
457
+ textarea: DefaultTextareaRenderer,
458
+ checkbox: DefaultCheckboxRenderer,
459
+ select: DefaultSelectRenderer,
460
+ radio: DefaultRadioRenderer,
461
+ range: DefaultRangeRenderer,
462
+ file: DefaultFileRenderer,
463
+ date: DefaultDateRenderer,
464
+ time: DefaultTimeRenderer,
465
+ "datetime-local": DefaultDateTimeLocalRenderer,
466
+ switch: DefaultSwitchRenderer
467
+ };
468
+ function getDefaultRenderer(type) {
469
+ return defaultRenderersMap[type];
470
+ }
471
+
472
+ // src/FieldRegistryContext.tsx
473
+ var import_react2 = require("react");
136
474
 
137
475
  // src/fieldRegistry.ts
138
476
  var import_core = require("@dynamic-field-kit/core");
139
477
  var fieldRegistry = import_core.fieldRegistry;
140
478
 
479
+ // src/FieldRegistryContext.tsx
480
+ var import_jsx_runtime4 = require("react/jsx-runtime");
481
+ var FieldRegistryContext = (0, import_react2.createContext)(fieldRegistry);
482
+ var FieldRegistryProvider = ({
483
+ registry,
484
+ children
485
+ }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldRegistryContext.Provider, { value: registry, children });
486
+ function useFieldRegistry() {
487
+ return (0, import_react2.useContext)(FieldRegistryContext);
488
+ }
489
+
141
490
  // src/components/DynamicInput.tsx
142
- var import_jsx_runtime3 = require("react/jsx-runtime");
491
+ var import_jsx_runtime5 = require("react/jsx-runtime");
143
492
  var DynamicInputInner = ({
144
493
  type,
145
494
  value,
146
495
  onChange,
496
+ onBlur,
147
497
  label,
148
498
  options,
149
499
  className,
150
- description
500
+ description,
501
+ disabled,
502
+ readOnly,
503
+ required,
504
+ touched,
505
+ dirty,
506
+ error,
507
+ id,
508
+ ariaInvalid,
509
+ ariaDescribedBy,
510
+ ariaRequired,
511
+ extraProps
151
512
  }) => {
152
- const Renderer = (0, import_react2.useMemo)(
153
- () => fieldRegistry.get(type),
154
- [type]
513
+ const registry = useFieldRegistry();
514
+ const Renderer = (0, import_react3.useMemo)(
515
+ () => registry.get(type) || getDefaultRenderer(type),
516
+ [registry, type]
155
517
  );
156
518
  if (!Renderer) {
157
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
519
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
158
520
  "Unknown field type: ",
159
521
  type
160
522
  ] });
161
523
  }
162
- return import_react2.default.createElement(Renderer, {
524
+ return import_react3.default.createElement(Renderer, {
525
+ ...extraProps,
163
526
  value,
164
527
  onValueChange: onChange,
528
+ onBlur,
165
529
  label,
166
530
  options,
167
531
  className,
168
- description
532
+ description,
533
+ disabled,
534
+ readOnly,
535
+ required,
536
+ touched,
537
+ dirty,
538
+ error,
539
+ id,
540
+ ariaInvalid,
541
+ ariaDescribedBy,
542
+ ariaRequired
169
543
  });
170
544
  };
171
- var DynamicInput = import_react2.default.memo(
545
+ var DynamicInput = /* @__PURE__ */ import_react3.default.memo(
172
546
  DynamicInputInner
173
547
  );
174
548
  var DynamicInput_default = DynamicInput;
175
549
 
176
550
  // src/components/FieldInput.tsx
177
- var import_react5 = __toESM(require("react"));
551
+ var import_core4 = require("@dynamic-field-kit/core");
552
+ var import_react6 = __toESM(require("react"));
178
553
 
179
554
  // src/components/FieldGroupInput.tsx
180
555
  var import_core3 = require("@dynamic-field-kit/core");
181
- var import_react4 = require("react");
556
+ var import_react5 = require("react");
182
557
 
183
558
  // src/components/MultiFieldInput.tsx
184
559
  var import_core2 = require("@dynamic-field-kit/core");
185
- var import_react3 = require("react");
186
- var import_jsx_runtime4 = require("react/jsx-runtime");
560
+ var import_react4 = require("react");
561
+ var import_jsx_runtime6 = require("react/jsx-runtime");
187
562
  function resolveLayout(layout) {
188
563
  if (!layout) {
189
564
  return { type: "column", config: {} };
@@ -197,45 +572,74 @@ var MultiFieldInput = ({
197
572
  fieldDescriptions,
198
573
  properties,
199
574
  onChange,
200
- layout
575
+ layout,
576
+ rootData,
577
+ onValidityChange,
578
+ onBlurField
201
579
  }) => {
202
- const [data, setData] = (0, import_react3.useState)({});
203
- (0, import_react3.useEffect)(() => {
580
+ const [data, setData] = (0, import_react4.useState)({});
581
+ const [touchedFields, setTouchedFields] = (0, import_react4.useState)(
582
+ {}
583
+ );
584
+ const initialPropertiesRef = (0, import_react4.useRef)(properties ?? {});
585
+ (0, import_react4.useEffect)(() => {
204
586
  if (properties) {
205
- setData((0, import_core2.applyComputedValues)(fieldDescriptions, properties));
587
+ setData((0, import_core2.applyComputedValues)(fieldDescriptions, properties, rootData));
206
588
  }
207
589
  }, [properties]);
208
- const visibleFields = (0, import_react3.useMemo)(
590
+ const effectiveRoot = rootData ?? data;
591
+ const visibleFields = (0, import_react4.useMemo)(
209
592
  () => fieldDescriptions.filter(
210
- (f) => !f.appearCondition || f.appearCondition(data)
593
+ (f) => !f.appearCondition || f.appearCondition(data, effectiveRoot)
211
594
  ),
212
- [fieldDescriptions, data]
595
+ [fieldDescriptions, data, effectiveRoot]
213
596
  );
214
- const dataRef = (0, import_react3.useRef)(data);
597
+ const dataRef = (0, import_react4.useRef)(data);
215
598
  dataRef.current = data;
216
- const onChangeRef = (0, import_react3.useRef)(onChange);
599
+ const onChangeRef = (0, import_react4.useRef)(onChange);
217
600
  onChangeRef.current = onChange;
218
- const fieldDescriptionsRef = (0, import_react3.useRef)(fieldDescriptions);
601
+ const fieldDescriptionsRef = (0, import_react4.useRef)(fieldDescriptions);
219
602
  fieldDescriptionsRef.current = fieldDescriptions;
220
- const handleValueChangeField = (0, import_react3.useCallback)((value, key) => {
221
- const next = (0, import_core2.applyComputedValues)(fieldDescriptionsRef.current, {
222
- ...dataRef.current,
223
- [key]: value
224
- });
603
+ const rootDataRef = (0, import_react4.useRef)(rootData);
604
+ rootDataRef.current = rootData;
605
+ const onValidityChangeRef = (0, import_react4.useRef)(onValidityChange);
606
+ onValidityChangeRef.current = onValidityChange;
607
+ const onBlurFieldRef = (0, import_react4.useRef)(onBlurField);
608
+ onBlurFieldRef.current = onBlurField;
609
+ (0, import_react4.useEffect)(() => {
610
+ onValidityChangeRef.current?.(
611
+ (0, import_core2.validateFields)(fieldDescriptions, data, rootData)
612
+ );
613
+ }, [data, fieldDescriptions, rootData]);
614
+ const handleValueChangeField = (0, import_react4.useCallback)((value, key) => {
615
+ const merged = { ...dataRef.current, [key]: value };
616
+ const next = (0, import_core2.applyComputedValues)(
617
+ fieldDescriptionsRef.current,
618
+ merged,
619
+ rootDataRef.current
620
+ );
225
621
  dataRef.current = next;
226
622
  setData(next);
227
623
  onChangeRef.current?.(next);
228
624
  }, []);
625
+ const handleBlurField = (0, import_react4.useCallback)((key) => {
626
+ setTouchedFields((prev) => prev[key] ? prev : { ...prev, [key]: true });
627
+ onBlurFieldRef.current?.(key);
628
+ }, []);
229
629
  const { type, config } = resolveLayout(layout);
230
630
  const Layout = layoutRegistry.get(type);
231
631
  if (!Layout) {
232
632
  throw new Error(`Unknown layout: ${type}`);
233
633
  }
234
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Layout, { config, children: visibleFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
634
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Layout, { config, children: visibleFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
235
635
  FieldInput_default,
236
636
  {
237
637
  fieldDescription: f,
238
638
  renderInfos: data,
639
+ rootData: effectiveRoot,
640
+ touched: Boolean(touchedFields[f.name]),
641
+ dirty: data[f.name] !== initialPropertiesRef.current[f.name],
642
+ onBlurField: handleBlurField,
239
643
  onValueChangeField: handleValueChangeField
240
644
  },
241
645
  f.name
@@ -244,10 +648,25 @@ var MultiFieldInput = ({
244
648
  var MultiFieldInput_default = MultiFieldInput;
245
649
 
246
650
  // src/components/FieldGroupInput.tsx
247
- var import_jsx_runtime5 = require("react/jsx-runtime");
248
- var FieldGroupInput = ({ fieldDescription, items, onChange }) => {
249
- const { fields = [], label, addLabel, removeLabel } = fieldDescription;
250
- const handleItemChange = (0, import_react4.useCallback)(
651
+ var import_jsx_runtime7 = require("react/jsx-runtime");
652
+ var FieldGroupInput = ({
653
+ fieldDescription,
654
+ items,
655
+ rootData,
656
+ onChange
657
+ }) => {
658
+ const {
659
+ fields = [],
660
+ label,
661
+ addLabel,
662
+ removeLabel,
663
+ keyField
664
+ } = fieldDescription;
665
+ const itemKey = (item, index) => keyField ? item[keyField] ?? index : index;
666
+ const addText = addLabel ?? "Add";
667
+ const removeText = removeLabel ?? "Remove";
668
+ const groupName = label ?? fieldDescription.name;
669
+ const handleItemChange = (0, import_react5.useCallback)(
251
670
  (index, next) => {
252
671
  const nextItems = items.slice();
253
672
  nextItems[index] = next;
@@ -255,13 +674,13 @@ var FieldGroupInput = ({ fieldDescription, items, onChange }) => {
255
674
  },
256
675
  [items, onChange]
257
676
  );
258
- const handleAdd = (0, import_react4.useCallback)(() => {
677
+ const handleAdd = (0, import_react5.useCallback)(() => {
259
678
  if (!(0, import_core3.canAddGroupItem)(fieldDescription, items)) {
260
679
  return;
261
680
  }
262
681
  onChange([...items, (0, import_core3.createGroupItem)(fieldDescription)]);
263
682
  }, [fieldDescription, items, onChange]);
264
- const handleRemove = (0, import_react4.useCallback)(
683
+ const handleRemove = (0, import_react5.useCallback)(
265
684
  (index) => {
266
685
  if (!(0, import_core3.canRemoveGroupItem)(fieldDescription, items)) {
267
686
  return;
@@ -270,41 +689,44 @@ var FieldGroupInput = ({ fieldDescription, items, onChange }) => {
270
689
  },
271
690
  [fieldDescription, items, onChange]
272
691
  );
273
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: fieldDescription.className, children: [
274
- label && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: label }),
275
- items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
692
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: fieldDescription.className, children: [
693
+ label && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: label }),
694
+ items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
276
695
  "div",
277
696
  {
278
697
  style: { display: "flex", alignItems: "flex-start", gap: 8 },
279
698
  children: [
280
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
699
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
281
700
  MultiFieldInput_default,
282
701
  {
283
702
  fieldDescriptions: fields,
284
703
  properties: item,
704
+ rootData,
285
705
  onChange: (next) => handleItemChange(index, next)
286
706
  }
287
707
  ) }),
288
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
708
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
289
709
  "button",
290
710
  {
291
711
  type: "button",
712
+ "aria-label": `${removeText} ${groupName} ${index + 1}`,
292
713
  onClick: () => handleRemove(index),
293
714
  disabled: !(0, import_core3.canRemoveGroupItem)(fieldDescription, items),
294
- children: removeLabel ?? "Remove"
715
+ children: removeText
295
716
  }
296
717
  )
297
718
  ]
298
719
  },
299
- index
720
+ itemKey(item, index)
300
721
  )),
301
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
722
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
302
723
  "button",
303
724
  {
304
725
  type: "button",
726
+ "aria-label": `${addText} ${groupName}`,
305
727
  onClick: handleAdd,
306
728
  disabled: !(0, import_core3.canAddGroupItem)(fieldDescription, items),
307
- children: addLabel ?? "Add"
729
+ children: addText
308
730
  }
309
731
  )
310
732
  ] });
@@ -312,52 +734,411 @@ var FieldGroupInput = ({ fieldDescription, items, onChange }) => {
312
734
  var FieldGroupInput_default = FieldGroupInput;
313
735
 
314
736
  // src/components/FieldInput.tsx
315
- var import_jsx_runtime6 = require("react/jsx-runtime");
737
+ var import_jsx_runtime8 = require("react/jsx-runtime");
316
738
  var FieldInputInner = ({
317
739
  fieldDescription,
318
740
  renderInfos,
741
+ rootData,
742
+ touched,
743
+ dirty,
744
+ onBlurField,
319
745
  onValueChangeField
320
746
  }) => {
321
- const { name, type, label, options, className, description, fields } = fieldDescription;
322
- const handleChange = (0, import_react5.useCallback)(
747
+ const { name, type, label, className, description, props, fields, required } = fieldDescription;
748
+ const handleChange = (0, import_react6.useCallback)(
323
749
  (v) => onValueChangeField(v, name),
324
750
  [onValueChangeField, name]
325
751
  );
752
+ const handleBlur = (0, import_react6.useCallback)(
753
+ () => onBlurField?.(name),
754
+ [onBlurField, name]
755
+ );
326
756
  if (fields) {
327
757
  const items = Array.isArray(renderInfos[name]) ? renderInfos[name] : [];
328
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
758
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
329
759
  FieldGroupInput_default,
330
760
  {
331
761
  fieldDescription,
332
762
  items,
763
+ rootData,
333
764
  onChange: handleChange
334
765
  }
335
766
  );
336
767
  }
337
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
768
+ const effectiveDisabled = (0, import_core4.resolveDisabled)(
769
+ fieldDescription,
770
+ renderInfos,
771
+ rootData
772
+ );
773
+ const readOnly = (0, import_core4.resolveReadOnly)(fieldDescription, renderInfos, rootData);
774
+ const resolvedOptionsList = (0, import_core4.resolveOptions)(
775
+ fieldDescription,
776
+ renderInfos,
777
+ rootData
778
+ );
779
+ const errors = effectiveDisabled ? [] : (0, import_core4.validateField)(fieldDescription, renderInfos[name], renderInfos, rootData);
780
+ const error = errors.length > 0 ? errors : void 0;
781
+ const fieldId = `dfk-field-${name}`;
782
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
338
783
  DynamicInput_default,
339
784
  {
785
+ id: fieldId,
340
786
  type,
341
787
  label,
342
788
  value: renderInfos[name],
343
- options,
789
+ options: resolvedOptionsList,
344
790
  className,
345
791
  description,
346
- onChange: handleChange
792
+ disabled: effectiveDisabled,
793
+ readOnly,
794
+ required,
795
+ touched,
796
+ dirty,
797
+ error,
798
+ ariaInvalid: Boolean(error),
799
+ ariaRequired: Boolean(required),
800
+ extraProps: props,
801
+ onChange: handleChange,
802
+ onBlur: handleBlur
347
803
  }
348
804
  );
349
805
  };
350
- var FieldInput = import_react5.default.memo(FieldInputInner, (prev, next) => {
806
+ var FieldInput = /* @__PURE__ */ import_react6.default.memo(FieldInputInner, (prev, next) => {
351
807
  const name = prev.fieldDescription.name;
352
- return prev.fieldDescription === next.fieldDescription && prev.onValueChangeField === next.onValueChangeField && prev.renderInfos[name] === next.renderInfos[name];
808
+ return prev.fieldDescription === next.fieldDescription && prev.onValueChangeField === next.onValueChangeField && prev.onBlurField === next.onBlurField && prev.rootData === next.rootData && prev.touched === next.touched && prev.dirty === next.dirty && prev.renderInfos[name] === next.renderInfos[name];
353
809
  });
354
810
  var FieldInput_default = FieldInput;
811
+
812
+ // src/components/DynamicFormDevTools.tsx
813
+ var import_react7 = require("react");
814
+ var import_jsx_runtime9 = require("react/jsx-runtime");
815
+ var DynamicFormDevTools = ({
816
+ data,
817
+ errors = {},
818
+ touched = {},
819
+ isDirty = false,
820
+ fields = [],
821
+ position = "bottom-right"
822
+ }) => {
823
+ const [isOpen, setIsOpen] = (0, import_react7.useState)(false);
824
+ const [activeTab, setActiveTab] = (0, import_react7.useState)("data");
825
+ const errorCount = Object.keys(errors).length;
826
+ const posStyle = position === "bottom-left" ? { left: "16px", bottom: "16px" } : { right: "16px", bottom: "16px" };
827
+ if (!isOpen) {
828
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
829
+ "button",
830
+ {
831
+ type: "button",
832
+ onClick: () => setIsOpen(true),
833
+ style: {
834
+ position: "fixed",
835
+ ...posStyle,
836
+ zIndex: 99999,
837
+ background: "#1e293b",
838
+ color: "#f8fafc",
839
+ border: "1px solid #334155",
840
+ borderRadius: "20px",
841
+ padding: "8px 14px",
842
+ fontSize: "12px",
843
+ fontWeight: 600,
844
+ cursor: "pointer",
845
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
846
+ display: "flex",
847
+ alignItems: "center",
848
+ gap: "6px"
849
+ },
850
+ children: [
851
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "\u{1F50D} DevTools" }),
852
+ errorCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
853
+ "span",
854
+ {
855
+ style: {
856
+ background: "#ef4444",
857
+ color: "#fff",
858
+ borderRadius: "10px",
859
+ padding: "2px 6px",
860
+ fontSize: "10px"
861
+ },
862
+ children: errorCount
863
+ }
864
+ )
865
+ ]
866
+ }
867
+ );
868
+ }
869
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
870
+ "div",
871
+ {
872
+ style: {
873
+ position: "fixed",
874
+ ...posStyle,
875
+ zIndex: 99999,
876
+ width: "360px",
877
+ maxHeight: "420px",
878
+ background: "#0f172a",
879
+ color: "#f8fafc",
880
+ border: "1px solid #334155",
881
+ borderRadius: "12px",
882
+ boxShadow: "0 10px 25px rgba(0,0,0,0.3)",
883
+ display: "flex",
884
+ flexDirection: "column",
885
+ fontFamily: "monospace, sans-serif",
886
+ fontSize: "12px",
887
+ overflow: "hidden"
888
+ },
889
+ children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
891
+ "div",
892
+ {
893
+ style: {
894
+ padding: "10px 14px",
895
+ background: "#1e293b",
896
+ display: "flex",
897
+ justifyContent: "space-between",
898
+ alignItems: "center",
899
+ borderBottom: "1px solid #334155"
900
+ },
901
+ children: [
902
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontWeight: "bold", color: "#38bdf8" }, children: "\u{1F6E0}\uFE0F Form DevTools" }),
903
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
904
+ "button",
905
+ {
906
+ type: "button",
907
+ onClick: () => setIsOpen(false),
908
+ style: {
909
+ background: "transparent",
910
+ border: "none",
911
+ color: "#94a3b8",
912
+ fontSize: "14px",
913
+ cursor: "pointer"
914
+ },
915
+ children: "\u2715"
916
+ }
917
+ )
918
+ ]
919
+ }
920
+ ),
921
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
922
+ "div",
923
+ {
924
+ style: {
925
+ display: "flex",
926
+ background: "#1e293b",
927
+ borderBottom: "1px solid #334155"
928
+ },
929
+ children: ["data", "errors", "meta", "fields"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
930
+ "button",
931
+ {
932
+ type: "button",
933
+ onClick: () => setActiveTab(tab),
934
+ style: {
935
+ flex: 1,
936
+ padding: "6px 0",
937
+ background: activeTab === tab ? "#0f172a" : "transparent",
938
+ color: activeTab === tab ? "#38bdf8" : "#94a3b8",
939
+ border: "none",
940
+ cursor: "pointer",
941
+ textTransform: "capitalize",
942
+ fontSize: "11px",
943
+ fontWeight: activeTab === tab ? "bold" : "normal"
944
+ },
945
+ children: [
946
+ tab,
947
+ " ",
948
+ tab === "errors" && errorCount > 0 ? `(${errorCount})` : ""
949
+ ]
950
+ },
951
+ tab
952
+ ))
953
+ }
954
+ ),
955
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: "12px", overflowY: "auto", flex: 1 }, children: [
956
+ activeTab === "data" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
957
+ "pre",
958
+ {
959
+ style: {
960
+ margin: 0,
961
+ whiteSpace: "pre-wrap",
962
+ wordBreak: "break-all",
963
+ color: "#a7f3d0"
964
+ },
965
+ children: JSON.stringify(data, null, 2)
966
+ }
967
+ ),
968
+ activeTab === "errors" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { children: Object.keys(errors).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: "#4ade80" }, children: "\u2713 No validation errors" }) : Object.entries(errors).map(([field, msgs]) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { marginBottom: "8px" }, children: [
969
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { color: "#f87171", fontWeight: "bold" }, children: [
970
+ field,
971
+ ":"
972
+ ] }),
973
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: msgs.map((m, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { style: { color: "#fca5a5" }, children: m }, i)) })
974
+ ] }, field)) }),
975
+ activeTab === "meta" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { marginBottom: "6px" }, children: [
977
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: "#94a3b8" }, children: "isDirty: " }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: isDirty ? "#facc15" : "#4ade80" }, children: String(isDirty) })
979
+ ] }),
980
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
981
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: "#94a3b8" }, children: "Touched Fields:" }),
982
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { style: { margin: "4px 0 0 0", color: "#cbd5e1" }, children: JSON.stringify(touched, null, 2) })
983
+ ] })
984
+ ] }),
985
+ activeTab === "fields" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { children: fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: "#94a3b8" }, children: "No field descriptions passed" }) : fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
986
+ "div",
987
+ {
988
+ style: {
989
+ padding: "6px",
990
+ marginBottom: "6px",
991
+ background: "#1e293b",
992
+ borderRadius: "4px"
993
+ },
994
+ children: [
995
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { color: "#38bdf8", fontWeight: "bold" }, children: f.name }),
996
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { color: "#94a3b8", fontSize: "10px" }, children: [
997
+ "type: ",
998
+ f.type,
999
+ " | required: ",
1000
+ String(Boolean(f.required))
1001
+ ] })
1002
+ ]
1003
+ },
1004
+ f.name
1005
+ )) })
1006
+ ] })
1007
+ ]
1008
+ }
1009
+ );
1010
+ };
1011
+
1012
+ // src/useDynamicForm.ts
1013
+ var import_core5 = require("@dynamic-field-kit/core");
1014
+ var import_react8 = require("react");
1015
+ function useDynamicForm({
1016
+ fields,
1017
+ initialValues = {},
1018
+ validateOnBlur = true,
1019
+ validateOnChange = false
1020
+ }) {
1021
+ const [data, setData] = (0, import_react8.useState)(
1022
+ () => (0, import_core5.applyComputedValues)(fields, initialValues)
1023
+ );
1024
+ const [errors, setErrors] = (0, import_react8.useState)({});
1025
+ const [isDirty, setIsDirty] = (0, import_react8.useState)(false);
1026
+ const [touched, setTouched] = (0, import_react8.useState)({});
1027
+ const [isSubmitting, setIsSubmitting] = (0, import_react8.useState)(false);
1028
+ const [isSubmitted, setIsSubmitted] = (0, import_react8.useState)(false);
1029
+ const validate = (0, import_react8.useCallback)(() => {
1030
+ const res = (0, import_core5.validateFields)(fields, data);
1031
+ setErrors(res.errors);
1032
+ return res.valid;
1033
+ }, [fields, data]);
1034
+ const handleChange = (0, import_react8.useCallback)(
1035
+ (newData) => {
1036
+ const next = (0, import_core5.applyComputedValues)(fields, newData);
1037
+ setData(next);
1038
+ setIsDirty(true);
1039
+ if (validateOnChange) {
1040
+ const res = (0, import_core5.validateFields)(fields, next);
1041
+ setErrors(res.errors);
1042
+ }
1043
+ },
1044
+ [fields, validateOnChange]
1045
+ );
1046
+ const setFieldValue = (0, import_react8.useCallback)(
1047
+ (name, value) => {
1048
+ handleChange({ ...data, [name]: value });
1049
+ },
1050
+ [data, handleChange]
1051
+ );
1052
+ const setFieldTouched = (0, import_react8.useCallback)((name, isTouched = true) => {
1053
+ setTouched((prev) => ({ ...prev, [name]: isTouched }));
1054
+ }, []);
1055
+ const handleBlur = (0, import_react8.useCallback)(
1056
+ (fieldName) => {
1057
+ setFieldTouched(fieldName, true);
1058
+ if (validateOnBlur) {
1059
+ const res = (0, import_core5.validateFields)(fields, data);
1060
+ setErrors(res.errors);
1061
+ }
1062
+ },
1063
+ [fields, data, validateOnBlur, setFieldTouched]
1064
+ );
1065
+ const reset = (0, import_react8.useCallback)(
1066
+ (newValues) => {
1067
+ const seed = newValues ?? initialValues;
1068
+ const next = (0, import_core5.applyComputedValues)(fields, seed);
1069
+ setData(next);
1070
+ setErrors({});
1071
+ setIsDirty(false);
1072
+ setTouched({});
1073
+ setIsSubmitting(false);
1074
+ setIsSubmitted(false);
1075
+ },
1076
+ [fields, initialValues]
1077
+ );
1078
+ const handleSubmit = (0, import_react8.useCallback)(
1079
+ (onValid, onInvalid) => async (e) => {
1080
+ if (e && typeof e.preventDefault === "function") {
1081
+ e.preventDefault();
1082
+ }
1083
+ setIsSubmitting(true);
1084
+ try {
1085
+ const res = (0, import_core5.validateFields)(fields, data);
1086
+ setErrors(res.errors);
1087
+ setIsSubmitted(true);
1088
+ if (res.valid) {
1089
+ await onValid(data);
1090
+ } else if (onInvalid) {
1091
+ onInvalid(res.errors);
1092
+ }
1093
+ } finally {
1094
+ setIsSubmitting(false);
1095
+ }
1096
+ },
1097
+ [fields, data]
1098
+ );
1099
+ const isValid = Object.keys(errors).length === 0;
1100
+ return {
1101
+ data,
1102
+ errors,
1103
+ isValid,
1104
+ isDirty,
1105
+ isSubmitting,
1106
+ isSubmitted,
1107
+ touched,
1108
+ setData,
1109
+ setFieldValue,
1110
+ setFieldTouched,
1111
+ handleChange,
1112
+ handleBlur,
1113
+ reset,
1114
+ validate,
1115
+ handleSubmit
1116
+ };
1117
+ }
1118
+
1119
+ // src/index.ts
1120
+ var import_core6 = require("@dynamic-field-kit/core");
1121
+ var import_core7 = require("@dynamic-field-kit/core");
355
1122
  // Annotate the CommonJS export names for ESM import in node:
356
1123
  0 && (module.exports = {
1124
+ DynamicFormDevTools,
357
1125
  DynamicInput,
358
1126
  FieldInput,
1127
+ FieldRegistry,
1128
+ FieldRegistryProvider,
359
1129
  MultiFieldInput,
1130
+ defaultRenderersMap,
360
1131
  fieldRegistry,
361
- layoutRegistry
1132
+ getDefaultRenderer,
1133
+ layoutRegistry,
1134
+ resolveDisabled,
1135
+ resolveOptions,
1136
+ resolveReadOnly,
1137
+ useDynamicForm,
1138
+ useFieldRegistry,
1139
+ validateField,
1140
+ validateFieldAsync,
1141
+ validateFields,
1142
+ validateFieldsAsync,
1143
+ validators
362
1144
  });
363
- //# sourceMappingURL=index.js.map