@arqel-dev/ui 0.11.0 → 0.14.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.
Files changed (112) hide show
  1. package/SKILL.md +5 -2
  2. package/dist/AreaChart-NMQ2RQGA.js +33 -0
  3. package/dist/AreaChart-NMQ2RQGA.js.map +1 -0
  4. package/dist/ArqelCreatePage-O6KNXPAO.js +8 -0
  5. package/dist/ArqelCreatePage-O6KNXPAO.js.map +1 -0
  6. package/dist/ArqelDashboardPage-PX3Y75AA.js +6 -0
  7. package/dist/ArqelDashboardPage-PX3Y75AA.js.map +1 -0
  8. package/dist/ArqelEditPage-NAMWJ5MC.js +8 -0
  9. package/dist/ArqelEditPage-NAMWJ5MC.js.map +1 -0
  10. package/dist/ArqelIndexPage-GC33PNBL.js +12 -0
  11. package/dist/ArqelIndexPage-GC33PNBL.js.map +1 -0
  12. package/dist/ArqelShowPage-LGVTL6UZ.js +6 -0
  13. package/dist/ArqelShowPage-LGVTL6UZ.js.map +1 -0
  14. package/dist/BarChart-KLHKTLO2.js +19 -0
  15. package/dist/BarChart-KLHKTLO2.js.map +1 -0
  16. package/dist/DonutChart-L56R4QF2.js +11 -0
  17. package/dist/DonutChart-L56R4QF2.js.map +1 -0
  18. package/dist/LineChart-GHMPHFZZ.js +30 -0
  19. package/dist/LineChart-GHMPHFZZ.js.map +1 -0
  20. package/dist/PieChart-T2VQOFPP.js +4 -0
  21. package/dist/PieChart-T2VQOFPP.js.map +1 -0
  22. package/dist/RadarChart-CSWR5AO7.js +32 -0
  23. package/dist/RadarChart-CSWR5AO7.js.map +1 -0
  24. package/dist/Topbar-DffcNdcJ.d.ts +50 -0
  25. package/dist/action.d.ts +12 -2
  26. package/dist/action.js +6 -1090
  27. package/dist/action.js.map +1 -1
  28. package/dist/auth.js +1 -10
  29. package/dist/auth.js.map +1 -1
  30. package/dist/chunk-2LTZJ4R7.js +110 -0
  31. package/dist/chunk-2LTZJ4R7.js.map +1 -0
  32. package/dist/chunk-47HD7ALI.js +370 -0
  33. package/dist/chunk-47HD7ALI.js.map +1 -0
  34. package/dist/chunk-4JJS2SVS.js +628 -0
  35. package/dist/chunk-4JJS2SVS.js.map +1 -0
  36. package/dist/chunk-6UJQP7KU.js +737 -0
  37. package/dist/chunk-6UJQP7KU.js.map +1 -0
  38. package/dist/chunk-B7ULUJVO.js +11 -0
  39. package/dist/chunk-B7ULUJVO.js.map +1 -0
  40. package/dist/chunk-C5H44NSP.js +37 -0
  41. package/dist/chunk-C5H44NSP.js.map +1 -0
  42. package/dist/chunk-DSULVRB6.js +101 -0
  43. package/dist/chunk-DSULVRB6.js.map +1 -0
  44. package/dist/chunk-E2ORVQM5.js +50 -0
  45. package/dist/chunk-E2ORVQM5.js.map +1 -0
  46. package/dist/chunk-EYAUPJ6J.js +64 -0
  47. package/dist/chunk-EYAUPJ6J.js.map +1 -0
  48. package/dist/chunk-FHRQ7PWE.js +58 -0
  49. package/dist/chunk-FHRQ7PWE.js.map +1 -0
  50. package/dist/chunk-FV7RESKL.js +285 -0
  51. package/dist/chunk-FV7RESKL.js.map +1 -0
  52. package/dist/chunk-I6MWOIFR.js +204 -0
  53. package/dist/chunk-I6MWOIFR.js.map +1 -0
  54. package/dist/chunk-IKJYLSTK.js +440 -0
  55. package/dist/chunk-IKJYLSTK.js.map +1 -0
  56. package/dist/chunk-K7QY56XN.js +610 -0
  57. package/dist/chunk-K7QY56XN.js.map +1 -0
  58. package/dist/chunk-L3JBBN4H.js +27 -0
  59. package/dist/chunk-L3JBBN4H.js.map +1 -0
  60. package/dist/chunk-M6NI45DQ.js +49 -0
  61. package/dist/chunk-M6NI45DQ.js.map +1 -0
  62. package/dist/chunk-MTNFFSVA.js +92 -0
  63. package/dist/chunk-MTNFFSVA.js.map +1 -0
  64. package/dist/chunk-MTZJDFQ6.js +32 -0
  65. package/dist/chunk-MTZJDFQ6.js.map +1 -0
  66. package/dist/chunk-MXUXMHOK.js +16 -0
  67. package/dist/chunk-MXUXMHOK.js.map +1 -0
  68. package/dist/chunk-PXON56HL.js +35 -0
  69. package/dist/chunk-PXON56HL.js.map +1 -0
  70. package/dist/chunk-PZEVKMZX.js +598 -0
  71. package/dist/chunk-PZEVKMZX.js.map +1 -0
  72. package/dist/chunk-QTUBBZVT.js +57 -0
  73. package/dist/chunk-QTUBBZVT.js.map +1 -0
  74. package/dist/chunk-R4BOUU6Y.js +55 -0
  75. package/dist/chunk-R4BOUU6Y.js.map +1 -0
  76. package/dist/chunk-RMPOR2YZ.js +12 -0
  77. package/dist/chunk-RMPOR2YZ.js.map +1 -0
  78. package/dist/chunk-RNAMHQM6.js +12 -0
  79. package/dist/chunk-RNAMHQM6.js.map +1 -0
  80. package/dist/chunk-WKWV36PG.js +140 -0
  81. package/dist/chunk-WKWV36PG.js.map +1 -0
  82. package/dist/cn-By8XWiSA.d.ts +12 -0
  83. package/dist/flash.js +2 -104
  84. package/dist/flash.js.map +1 -1
  85. package/dist/form.d.ts +1 -1
  86. package/dist/form.js +4 -673
  87. package/dist/form.js.map +1 -1
  88. package/dist/index.d.ts +2 -2
  89. package/dist/index.js +18 -3706
  90. package/dist/index.js.map +1 -1
  91. package/dist/pages.js +23 -3025
  92. package/dist/pages.js.map +1 -1
  93. package/dist/palette.js +1 -283
  94. package/dist/palette.js.map +1 -1
  95. package/dist/primitives.js +3 -658
  96. package/dist/primitives.js.map +1 -1
  97. package/dist/resource.js +5 -720
  98. package/dist/resource.js.map +1 -1
  99. package/dist/shell.d.ts +26 -46
  100. package/dist/shell.js +46 -1039
  101. package/dist/shell.js.map +1 -1
  102. package/dist/table.js +4 -618
  103. package/dist/table.js.map +1 -1
  104. package/dist/utility.js +3 -104
  105. package/dist/utility.js.map +1 -1
  106. package/dist/utils.d.ts +12 -6
  107. package/dist/utils.js +2 -9
  108. package/dist/utils.js.map +1 -1
  109. package/dist/widgets.d.ts +7 -2
  110. package/dist/widgets.js +3 -675
  111. package/dist/widgets.js.map +1 -1
  112. package/package.json +15 -14
@@ -0,0 +1,628 @@
1
+ import { cn } from './chunk-B7ULUJVO.js';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { useId, useState, useRef } from 'react';
4
+ import { isFieldEntry, isLayoutEntry } from '@arqel-dev/types/forms';
5
+
6
+ // src/form/FieldRegistry.tsx
7
+ var registry = /* @__PURE__ */ new Map();
8
+ function registerField(name, component) {
9
+ registry.set(name, component);
10
+ }
11
+ function getFieldComponent(name) {
12
+ return registry.get(name);
13
+ }
14
+ function unregisterField(name) {
15
+ registry.delete(name);
16
+ }
17
+ function getRegisteredFields() {
18
+ return Array.from(registry.keys()).sort();
19
+ }
20
+ function clearFieldRegistry() {
21
+ registry.clear();
22
+ }
23
+ var inputClasses = cn(
24
+ "h-9 rounded-sm border border-[var(--input)]",
25
+ "bg-background px-3 text-sm",
26
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
27
+ "disabled:cursor-not-allowed disabled:opacity-50"
28
+ );
29
+ function NativeFieldInput(props) {
30
+ const { field } = props;
31
+ switch (field.type) {
32
+ case "text":
33
+ case "email":
34
+ case "url":
35
+ case "password":
36
+ case "slug":
37
+ return /* @__PURE__ */ jsx(TextLike, { ...props, field });
38
+ case "textarea":
39
+ return /* @__PURE__ */ jsx(Textarea, { ...props, field });
40
+ case "number":
41
+ case "currency":
42
+ return /* @__PURE__ */ jsx(NumericInput, { ...props, field });
43
+ case "boolean":
44
+ case "toggle":
45
+ return /* @__PURE__ */ jsx(Checkbox, { ...props, field });
46
+ case "select":
47
+ return /* @__PURE__ */ jsx(SelectInput, { ...props, field });
48
+ case "multiSelect":
49
+ case "hasMany":
50
+ return /* @__PURE__ */ jsx(MultiSelectInput, { ...props, field });
51
+ case "radio":
52
+ return /* @__PURE__ */ jsx(RadioGroup, { ...props, field });
53
+ case "belongsTo":
54
+ return /* @__PURE__ */ jsx(BelongsToInput, { ...props, field });
55
+ case "date":
56
+ case "dateTime":
57
+ return /* @__PURE__ */ jsx(DateInput, { ...props, field });
58
+ case "file":
59
+ case "image":
60
+ return /* @__PURE__ */ jsx(FileInput, { ...props });
61
+ default:
62
+ return /* @__PURE__ */ jsx(UnregisteredField, { field });
63
+ }
64
+ }
65
+ function UnregisteredField({ field }) {
66
+ if (field.component) {
67
+ console.warn(
68
+ `[arqel] field "${field.name}" uses unregistered component "${field.component}" \u2014 did you import its /register module?`
69
+ );
70
+ } else {
71
+ console.warn(`[arqel] field "${field.name}" has no native input for type "${field.type}".`);
72
+ }
73
+ const detail = field.component ? `unregistered component "${field.component}"` : `unsupported type "${field.type}"`;
74
+ return /* @__PURE__ */ jsxs(
75
+ "p",
76
+ {
77
+ "data-testid": "arqel-unregistered-field",
78
+ role: "alert",
79
+ className: "rounded-sm border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
80
+ children: [
81
+ 'Field "',
82
+ field.name,
83
+ '" could not render: ',
84
+ detail,
85
+ "."
86
+ ]
87
+ }
88
+ );
89
+ }
90
+ function TextLike({
91
+ field,
92
+ value,
93
+ onChange,
94
+ disabled,
95
+ inputId,
96
+ describedBy
97
+ }) {
98
+ const htmlType = field.type === "email" ? "email" : field.type === "password" ? "password" : field.type === "url" ? "url" : "text";
99
+ const isDisabled = disabled || field.disabled || field.readonly;
100
+ return /* @__PURE__ */ jsx(
101
+ "input",
102
+ {
103
+ id: inputId,
104
+ type: htmlType,
105
+ className: inputClasses,
106
+ value: typeof value === "string" ? value : (value ?? "")?.toString() ?? "",
107
+ placeholder: field.placeholder ?? void 0,
108
+ disabled: isDisabled,
109
+ readOnly: field.readonly === true,
110
+ required: field.required === true,
111
+ "aria-describedby": describedBy,
112
+ onChange: (e) => onChange(e.target.value)
113
+ }
114
+ );
115
+ }
116
+ function Textarea({
117
+ field,
118
+ value,
119
+ onChange,
120
+ disabled,
121
+ inputId,
122
+ describedBy
123
+ }) {
124
+ return /* @__PURE__ */ jsx(
125
+ "textarea",
126
+ {
127
+ id: inputId,
128
+ className: cn(inputClasses, "h-auto min-h-[5rem] py-2"),
129
+ value: typeof value === "string" ? value : "",
130
+ placeholder: field.placeholder ?? void 0,
131
+ disabled: disabled || field.disabled || field.readonly,
132
+ readOnly: field.readonly === true,
133
+ required: field.required === true,
134
+ rows: 4,
135
+ "aria-describedby": describedBy,
136
+ onChange: (e) => onChange(e.target.value)
137
+ }
138
+ );
139
+ }
140
+ function NumericInput({
141
+ field,
142
+ value,
143
+ onChange,
144
+ disabled,
145
+ inputId,
146
+ describedBy
147
+ }) {
148
+ return /* @__PURE__ */ jsx(
149
+ "input",
150
+ {
151
+ id: inputId,
152
+ type: "number",
153
+ className: inputClasses,
154
+ value: value === null || value === void 0 ? "" : String(value),
155
+ placeholder: field.placeholder ?? void 0,
156
+ disabled: disabled || field.disabled || field.readonly,
157
+ readOnly: field.readonly === true,
158
+ required: field.required === true,
159
+ "aria-describedby": describedBy,
160
+ onChange: (e) => {
161
+ const raw = e.target.value;
162
+ if (raw === "") {
163
+ onChange(null);
164
+ return;
165
+ }
166
+ const num = Number(raw);
167
+ onChange(Number.isNaN(num) ? null : num);
168
+ }
169
+ }
170
+ );
171
+ }
172
+ function Checkbox({
173
+ field,
174
+ value,
175
+ onChange,
176
+ disabled,
177
+ inputId,
178
+ describedBy
179
+ }) {
180
+ return /* @__PURE__ */ jsx(
181
+ "input",
182
+ {
183
+ id: inputId,
184
+ type: "checkbox",
185
+ className: "h-4 w-4 rounded border-[var(--input)]",
186
+ checked: value === true,
187
+ disabled: disabled || field.disabled || field.readonly,
188
+ "aria-describedby": describedBy,
189
+ onChange: (e) => onChange(e.target.checked)
190
+ }
191
+ );
192
+ }
193
+ function normaliseOptions(options) {
194
+ if (!options) return [];
195
+ if (Array.isArray(options)) return options;
196
+ return Object.entries(options).map(([value, label]) => ({ value, label }));
197
+ }
198
+ function SelectInput({
199
+ field,
200
+ value,
201
+ onChange,
202
+ disabled,
203
+ inputId,
204
+ describedBy
205
+ }) {
206
+ const options = normaliseOptions(field.props.options);
207
+ return /* @__PURE__ */ jsxs(
208
+ "select",
209
+ {
210
+ id: inputId,
211
+ className: inputClasses,
212
+ value: value === null || value === void 0 ? "" : String(value),
213
+ disabled: disabled || field.disabled || field.readonly,
214
+ required: field.required === true,
215
+ "aria-describedby": describedBy,
216
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
217
+ children: [
218
+ field.placeholder && /* @__PURE__ */ jsx("option", { value: "", children: field.placeholder }),
219
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
220
+ ]
221
+ }
222
+ );
223
+ }
224
+ function MultiSelectInput({
225
+ field,
226
+ value,
227
+ onChange,
228
+ disabled,
229
+ inputId,
230
+ describedBy
231
+ }) {
232
+ const arr = Array.isArray(value) ? value : [];
233
+ const options = field.type === "multiSelect" ? normaliseOptions(field.props.options) : [];
234
+ return /* @__PURE__ */ jsx(
235
+ "select",
236
+ {
237
+ id: inputId,
238
+ multiple: true,
239
+ className: cn(inputClasses, "h-auto min-h-[6rem] py-1"),
240
+ value: arr.map(String),
241
+ disabled: disabled || field.disabled || field.readonly,
242
+ "aria-describedby": describedBy,
243
+ onChange: (e) => {
244
+ const next = Array.from(e.target.selectedOptions, (o) => o.value);
245
+ onChange(next);
246
+ },
247
+ children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
248
+ }
249
+ );
250
+ }
251
+ function RadioGroup({
252
+ field,
253
+ value,
254
+ onChange,
255
+ disabled,
256
+ inputId,
257
+ describedBy
258
+ }) {
259
+ return /* @__PURE__ */ jsx(
260
+ "div",
261
+ {
262
+ id: inputId,
263
+ className: "flex flex-col gap-1",
264
+ "aria-describedby": describedBy,
265
+ role: "radiogroup",
266
+ children: (field.props.options ?? []).map((opt) => /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [
267
+ /* @__PURE__ */ jsx(
268
+ "input",
269
+ {
270
+ type: "radio",
271
+ name: field.name,
272
+ value: String(opt.value),
273
+ checked: String(value ?? "") === String(opt.value),
274
+ disabled: disabled || field.disabled || field.readonly,
275
+ onChange: () => onChange(opt.value)
276
+ }
277
+ ),
278
+ opt.label
279
+ ] }, opt.value))
280
+ }
281
+ );
282
+ }
283
+ function BelongsToInput({
284
+ field,
285
+ value,
286
+ onChange,
287
+ disabled,
288
+ inputId,
289
+ describedBy
290
+ }) {
291
+ return /* @__PURE__ */ jsx(
292
+ "input",
293
+ {
294
+ id: inputId,
295
+ type: "text",
296
+ className: inputClasses,
297
+ value: value === null || value === void 0 ? "" : String(value),
298
+ placeholder: field.placeholder ?? `Search ${field.props.relatedResource}\u2026`,
299
+ disabled: disabled || field.disabled || field.readonly,
300
+ readOnly: field.readonly === true,
301
+ "aria-describedby": describedBy,
302
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
303
+ }
304
+ );
305
+ }
306
+ function DateInput({
307
+ field,
308
+ value,
309
+ onChange,
310
+ disabled,
311
+ inputId,
312
+ describedBy
313
+ }) {
314
+ return /* @__PURE__ */ jsx(
315
+ "input",
316
+ {
317
+ id: inputId,
318
+ type: field.type === "dateTime" ? "datetime-local" : "date",
319
+ className: inputClasses,
320
+ value: typeof value === "string" ? value : "",
321
+ disabled: disabled || field.disabled || field.readonly,
322
+ readOnly: field.readonly === true,
323
+ required: field.required === true,
324
+ "aria-describedby": describedBy,
325
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
326
+ }
327
+ );
328
+ }
329
+ function FileInput({
330
+ field,
331
+ onChange,
332
+ disabled,
333
+ inputId,
334
+ describedBy
335
+ }) {
336
+ return /* @__PURE__ */ jsx(
337
+ "input",
338
+ {
339
+ id: inputId,
340
+ type: "file",
341
+ className: "text-sm",
342
+ disabled: disabled || field.disabled || field.readonly,
343
+ "aria-describedby": describedBy,
344
+ onChange: (e) => onChange(e.target.files?.[0] ?? null)
345
+ }
346
+ );
347
+ }
348
+ function FieldRenderer(props) {
349
+ const { field, errors, className } = props;
350
+ const id = useId();
351
+ const errorId = `${id}-error`;
352
+ const helperId = `${id}-helper`;
353
+ const hasError = errors && errors.length > 0;
354
+ const Custom = field.component ? getFieldComponent(field.component) : void 0;
355
+ const Input = Custom ?? NativeFieldInput;
356
+ const describedBy = [field.helperText ? helperId : null, hasError ? errorId : null].filter(Boolean).join(" ");
357
+ return /* @__PURE__ */ jsxs(
358
+ "div",
359
+ {
360
+ "data-arqel-field": field.name,
361
+ "data-field-type": field.type,
362
+ className: cn("flex flex-col gap-1", className),
363
+ children: [
364
+ field.label !== null && field.label !== "" && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-sm font-medium", children: [
365
+ field.label,
366
+ field.required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })
367
+ ] }),
368
+ /* @__PURE__ */ jsx(Input, { ...props, inputId: id, describedBy: describedBy || void 0 }),
369
+ field.helperText && !hasError && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-xs text-muted-foreground", children: field.helperText }),
370
+ hasError && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-xs text-destructive", children: errors[0] })
371
+ ]
372
+ }
373
+ );
374
+ }
375
+ function FormFieldset({ config, children, className }) {
376
+ return /* @__PURE__ */ jsxs("fieldset", { className: cn("rounded-md border border-border p-4", className), children: [
377
+ /* @__PURE__ */ jsx("legend", { className: "px-1 text-sm font-medium", children: config.legend }),
378
+ /* @__PURE__ */ jsx(
379
+ "div",
380
+ {
381
+ className: "grid gap-4",
382
+ style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
383
+ children
384
+ }
385
+ )
386
+ ] });
387
+ }
388
+ function FormGrid({ config, children, className }) {
389
+ const columns = typeof config.columns === "number" ? config.columns : config.columns["md"] ?? 1;
390
+ return /* @__PURE__ */ jsx(
391
+ "div",
392
+ {
393
+ className: cn("grid", className),
394
+ style: {
395
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
396
+ gap: config.gap ?? "1rem"
397
+ },
398
+ children
399
+ }
400
+ );
401
+ }
402
+ function FormSection({ config, children, className }) {
403
+ const [open, setOpen] = useState(!config.collapsed);
404
+ const collapsible = config.collapsible === true;
405
+ const heading = /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
406
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
407
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: config.heading }),
408
+ config.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: config.description })
409
+ ] }),
410
+ collapsible && /* @__PURE__ */ jsx(
411
+ "button",
412
+ {
413
+ type: "button",
414
+ "aria-expanded": open,
415
+ onClick: () => setOpen((v) => !v),
416
+ className: "text-sm text-muted-foreground hover:underline",
417
+ children: open ? "Hide" : "Show"
418
+ }
419
+ )
420
+ ] });
421
+ const body = open && /* @__PURE__ */ jsx(
422
+ "div",
423
+ {
424
+ className: "grid gap-4",
425
+ style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
426
+ children
427
+ }
428
+ );
429
+ return /* @__PURE__ */ jsx(
430
+ "section",
431
+ {
432
+ "data-arqel-section": "",
433
+ className: cn(
434
+ "rounded-md border border-border bg-background",
435
+ config.compact ? "p-3" : "p-4",
436
+ className
437
+ ),
438
+ children: config.aside ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[16rem_1fr]", children: [
439
+ /* @__PURE__ */ jsx("div", { children: heading }),
440
+ /* @__PURE__ */ jsx("div", { children: body })
441
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
442
+ /* @__PURE__ */ jsx("div", { className: "mb-3", children: heading }),
443
+ body
444
+ ] })
445
+ }
446
+ );
447
+ }
448
+ function FormTabs({ config, tabs, className }) {
449
+ const [active, setActive] = useState(config.defaultTab ?? tabs[0]?.id ?? null);
450
+ const baseId = useId();
451
+ const refs = useRef([]);
452
+ if (tabs.length === 0 || active === null) return null;
453
+ const activeIndex = Math.max(
454
+ 0,
455
+ tabs.findIndex((t) => t.id === active)
456
+ );
457
+ const activeTab = tabs[activeIndex];
458
+ if (!activeTab) return null;
459
+ const focus = (i) => {
460
+ const target = tabs[i];
461
+ if (!target) return;
462
+ setActive(target.id);
463
+ refs.current[i]?.focus();
464
+ };
465
+ const onKeyDown = (e) => {
466
+ if (e.key === "ArrowRight") {
467
+ e.preventDefault();
468
+ focus((activeIndex + 1) % tabs.length);
469
+ } else if (e.key === "ArrowLeft") {
470
+ e.preventDefault();
471
+ focus((activeIndex - 1 + tabs.length) % tabs.length);
472
+ } else if (e.key === "Home") {
473
+ e.preventDefault();
474
+ focus(0);
475
+ } else if (e.key === "End") {
476
+ e.preventDefault();
477
+ focus(tabs.length - 1);
478
+ }
479
+ };
480
+ return /* @__PURE__ */ jsxs("div", { className, children: [
481
+ /* @__PURE__ */ jsx(
482
+ "div",
483
+ {
484
+ role: "tablist",
485
+ "aria-orientation": config.orientation,
486
+ className: "flex gap-1 border-b border-border",
487
+ children: tabs.map((tab, i) => {
488
+ const selected = tab.id === active;
489
+ return /* @__PURE__ */ jsxs(
490
+ "button",
491
+ {
492
+ ref: (el) => {
493
+ refs.current[i] = el;
494
+ },
495
+ type: "button",
496
+ role: "tab",
497
+ id: `${baseId}-tab-${tab.id}`,
498
+ "aria-controls": `${baseId}-panel-${tab.id}`,
499
+ "aria-selected": selected,
500
+ tabIndex: selected ? 0 : -1,
501
+ onClick: () => setActive(tab.id),
502
+ onKeyDown,
503
+ className: cn(
504
+ "-mb-px border-b-2 px-3 py-2 text-sm transition-colors",
505
+ selected ? "border-primary font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
506
+ ),
507
+ children: [
508
+ tab.label,
509
+ tab.badge !== void 0 && tab.badge !== null && /* @__PURE__ */ jsx("span", { className: "ml-1 rounded-full bg-muted px-1.5 text-xs", children: tab.badge })
510
+ ]
511
+ },
512
+ tab.id
513
+ );
514
+ })
515
+ }
516
+ ),
517
+ /* @__PURE__ */ jsx(
518
+ "div",
519
+ {
520
+ role: "tabpanel",
521
+ id: `${baseId}-panel-${activeTab.id}`,
522
+ "aria-labelledby": `${baseId}-tab-${activeTab.id}`,
523
+ className: "pt-4",
524
+ children: activeTab.content
525
+ }
526
+ )
527
+ ] });
528
+ }
529
+ function FormRenderer({
530
+ schema,
531
+ fields,
532
+ values,
533
+ errors = {},
534
+ onChange,
535
+ disabled = false,
536
+ className
537
+ }) {
538
+ const remaining = [...fields];
539
+ const claimField = (entry) => {
540
+ let idx = remaining.findIndex((f) => f.name === entry.name && f.type === entry.type);
541
+ if (idx === -1) idx = remaining.findIndex((f) => f.name === entry.name);
542
+ if (idx === -1) return void 0;
543
+ return remaining.splice(idx, 1)[0];
544
+ };
545
+ const renderEntry = (entry, key) => {
546
+ if (isFieldEntry(entry)) {
547
+ return renderFieldEntry(entry, key);
548
+ }
549
+ if (isLayoutEntry(entry)) {
550
+ return renderLayoutEntry(entry, key);
551
+ }
552
+ return null;
553
+ };
554
+ const renderFieldEntry = (entry, key) => {
555
+ const field = claimField(entry);
556
+ if (!field) return null;
557
+ return /* @__PURE__ */ jsx(
558
+ FieldRenderer,
559
+ {
560
+ field,
561
+ value: values[entry.name],
562
+ errors: errors[entry.name],
563
+ disabled: disabled || schema.disabled === true,
564
+ onChange: (value) => onChange(entry.name, value)
565
+ },
566
+ key
567
+ );
568
+ };
569
+ const renderChildren = (entries) => {
570
+ if (!entries) return null;
571
+ return entries.map((child, i) => renderEntry(child, `${i}`));
572
+ };
573
+ const renderLayoutEntry = (entry, key) => {
574
+ switch (entry.type) {
575
+ case "section":
576
+ return /* @__PURE__ */ jsx(FormSection, { config: entry.props, children: renderChildren(entry.schema) }, key);
577
+ case "fieldset":
578
+ return /* @__PURE__ */ jsx(FormFieldset, { config: entry.props, children: renderChildren(entry.schema) }, key);
579
+ case "grid":
580
+ case "columns":
581
+ return /* @__PURE__ */ jsx(
582
+ FormGrid,
583
+ {
584
+ config: entry.type === "columns" ? { columns: entry.props.columns } : entry.props,
585
+ children: renderChildren(entry.schema)
586
+ },
587
+ key
588
+ );
589
+ case "group":
590
+ return /* @__PURE__ */ jsx(
591
+ "div",
592
+ {
593
+ className: cn(
594
+ "flex gap-4",
595
+ entry.props.orientation === "vertical" ? "flex-col" : "flex-row"
596
+ ),
597
+ children: renderChildren(entry.schema)
598
+ },
599
+ key
600
+ );
601
+ case "tabs": {
602
+ const tabs = (entry.schema ?? []).filter((c) => isLayoutEntry(c) && c.type === "tab").map((tab) => ({
603
+ id: tab.props.id,
604
+ label: tab.props.label,
605
+ badge: tab.props.badge,
606
+ content: renderChildren(tab.schema) ?? /* @__PURE__ */ jsx("span", {}, `tab-${tab.props.id}-empty`)
607
+ }));
608
+ return /* @__PURE__ */ jsx(FormTabs, { config: entry.props, tabs }, key);
609
+ }
610
+ case "tab":
611
+ return null;
612
+ }
613
+ };
614
+ const columns = schema.columns ?? 1;
615
+ return /* @__PURE__ */ jsx(
616
+ "div",
617
+ {
618
+ "data-arqel-form": "",
619
+ className: cn("grid gap-4", className),
620
+ style: columns > 1 ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : void 0,
621
+ children: schema.schema.map((entry, i) => renderEntry(entry, `${i}`))
622
+ }
623
+ );
624
+ }
625
+
626
+ export { FieldRenderer, FormFieldset, FormGrid, FormRenderer, FormSection, FormTabs, NativeFieldInput, clearFieldRegistry, getFieldComponent, getRegisteredFields, registerField, unregisterField };
627
+ //# sourceMappingURL=chunk-4JJS2SVS.js.map
628
+ //# sourceMappingURL=chunk-4JJS2SVS.js.map