@arqel-dev/ui 0.10.1 → 0.13.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-P3G6TKIK.js +8 -0
  5. package/dist/ArqelCreatePage-P3G6TKIK.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-HKQSE6E5.js +8 -0
  9. package/dist/ArqelEditPage-HKQSE6E5.js.map +1 -0
  10. package/dist/ArqelIndexPage-MRH3572Z.js +12 -0
  11. package/dist/ArqelIndexPage-MRH3572Z.js.map +1 -0
  12. package/dist/ArqelShowPage-4FS5MTUN.js +6 -0
  13. package/dist/ArqelShowPage-4FS5MTUN.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-3JJOHYD7.js +57 -0
  33. package/dist/chunk-3JJOHYD7.js.map +1 -0
  34. package/dist/chunk-47HD7ALI.js +370 -0
  35. package/dist/chunk-47HD7ALI.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-7LSZADUC.js +50 -0
  39. package/dist/chunk-7LSZADUC.js.map +1 -0
  40. package/dist/chunk-B7ULUJVO.js +11 -0
  41. package/dist/chunk-B7ULUJVO.js.map +1 -0
  42. package/dist/chunk-C5H44NSP.js +37 -0
  43. package/dist/chunk-C5H44NSP.js.map +1 -0
  44. package/dist/chunk-DSULVRB6.js +101 -0
  45. package/dist/chunk-DSULVRB6.js.map +1 -0
  46. package/dist/chunk-ERT7LL6C.js +199 -0
  47. package/dist/chunk-ERT7LL6C.js.map +1 -0
  48. package/dist/chunk-EYAUPJ6J.js +64 -0
  49. package/dist/chunk-EYAUPJ6J.js.map +1 -0
  50. package/dist/chunk-FHRQ7PWE.js +58 -0
  51. package/dist/chunk-FHRQ7PWE.js.map +1 -0
  52. package/dist/chunk-FV7RESKL.js +285 -0
  53. package/dist/chunk-FV7RESKL.js.map +1 -0
  54. package/dist/chunk-K7QY56XN.js +610 -0
  55. package/dist/chunk-K7QY56XN.js.map +1 -0
  56. package/dist/chunk-L3JBBN4H.js +27 -0
  57. package/dist/chunk-L3JBBN4H.js.map +1 -0
  58. package/dist/chunk-MSRTGCTJ.js +597 -0
  59. package/dist/chunk-MSRTGCTJ.js.map +1 -0
  60. package/dist/chunk-MTNFFSVA.js +92 -0
  61. package/dist/chunk-MTNFFSVA.js.map +1 -0
  62. package/dist/chunk-MTZJDFQ6.js +32 -0
  63. package/dist/chunk-MTZJDFQ6.js.map +1 -0
  64. package/dist/chunk-MXUXMHOK.js +16 -0
  65. package/dist/chunk-MXUXMHOK.js.map +1 -0
  66. package/dist/chunk-PXON56HL.js +35 -0
  67. package/dist/chunk-PXON56HL.js.map +1 -0
  68. package/dist/chunk-PZEVKMZX.js +598 -0
  69. package/dist/chunk-PZEVKMZX.js.map +1 -0
  70. package/dist/chunk-R4BOUU6Y.js +55 -0
  71. package/dist/chunk-R4BOUU6Y.js.map +1 -0
  72. package/dist/chunk-RMPOR2YZ.js +12 -0
  73. package/dist/chunk-RMPOR2YZ.js.map +1 -0
  74. package/dist/chunk-RNAMHQM6.js +12 -0
  75. package/dist/chunk-RNAMHQM6.js.map +1 -0
  76. package/dist/chunk-TNJEHYRF.js +387 -0
  77. package/dist/chunk-TNJEHYRF.js.map +1 -0
  78. package/dist/chunk-VSTRVXXZ.js +49 -0
  79. package/dist/chunk-VSTRVXXZ.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.js +4 -673
  86. package/dist/form.js.map +1 -1
  87. package/dist/index.d.ts +2 -2
  88. package/dist/index.js +18 -3706
  89. package/dist/index.js.map +1 -1
  90. package/dist/pages.d.ts +19 -1
  91. package/dist/pages.js +23 -2278
  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,27 @@
1
+ // src/widgets/types.ts
2
+ function toRowShape(data) {
3
+ return data.labels.map((label, i) => {
4
+ const row = { name: label };
5
+ data.datasets.forEach((ds, j) => {
6
+ row[`dataset${j}`] = ds.data[i] ?? 0;
7
+ });
8
+ return row;
9
+ });
10
+ }
11
+ var PALETTE = [
12
+ "#2563eb",
13
+ "#10b981",
14
+ "#f59e0b",
15
+ "#ef4444",
16
+ "#8b5cf6",
17
+ "#ec4899",
18
+ "#14b8a6",
19
+ "#f97316"
20
+ ];
21
+ function colorFor(dataset, index) {
22
+ return dataset.color ?? PALETTE[index % PALETTE.length] ?? "#2563eb";
23
+ }
24
+
25
+ export { colorFor, toRowShape };
26
+ //# sourceMappingURL=chunk-L3JBBN4H.js.map
27
+ //# sourceMappingURL=chunk-L3JBBN4H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/widgets/types.ts"],"names":[],"mappings":";AA4CO,SAAS,WAAW,IAAA,EAAyD;AAClF,EAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,CAAC,OAAO,CAAA,KAAM;AACnC,IAAA,MAAM,GAAA,GAAuC,EAAE,IAAA,EAAM,KAAA,EAAM;AAC3D,IAAA,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,CAAC,EAAA,EAAI,CAAA,KAAM;AAC/B,MAAA,GAAA,CAAI,UAAU,CAAC,CAAA,CAAE,IAAI,EAAA,CAAG,IAAA,CAAK,CAAC,CAAA,IAAK,CAAA;AAAA,IACrC,CAAC,CAAA;AACD,IAAA,OAAO,GAAA;AAAA,EACT,CAAC,CAAA;AACH;AAEA,IAAM,OAAA,GAAU;AAAA,EACd,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA;AAEO,SAAS,QAAA,CAAS,SAAuB,KAAA,EAAuB;AACrE,EAAA,OAAO,QAAQ,KAAA,IAAS,OAAA,CAAQ,KAAA,GAAQ,OAAA,CAAQ,MAAM,CAAA,IAAK,SAAA;AAC7D","file":"chunk-L3JBBN4H.js","sourcesContent":["export type ChartType = 'line' | 'bar' | 'area' | 'pie' | 'donut' | 'radar';\n\nexport interface ChartDataset {\n label: string;\n data: number[];\n color?: string;\n}\n\nexport interface ChartData {\n labels: string[];\n datasets: ChartDataset[];\n}\n\nexport interface ChartWidgetProps {\n widget: {\n name: string;\n type: 'chart';\n heading?: string | undefined;\n description?: string | undefined;\n chartType: ChartType;\n chartData: ChartData;\n chartOptions?: Record<string, unknown> | undefined;\n height: number;\n showLegend: boolean;\n showGrid: boolean;\n };\n}\n\nexport interface ChartSubProps {\n chartData: ChartData;\n chartOptions?: Record<string, unknown> | undefined;\n height: number;\n showLegend: boolean;\n showGrid: boolean;\n}\n\n/**\n * Translates the PHP-side `{labels, datasets}` shape into Recharts' expected\n * row shape: `[{ name: label, dataset1: value, dataset2: value, ... }]`.\n *\n * Dataset keys are stable indices (`dataset0`, `dataset1`, ...) so chart\n * components can pair them with their human label/color via the datasets\n * array passed to `<Line dataKey=\"dataset0\" name={datasets[0].label} />`.\n */\nexport function toRowShape(data: ChartData): Array<Record<string, number | string>> {\n return data.labels.map((label, i) => {\n const row: Record<string, number | string> = { name: label };\n data.datasets.forEach((ds, j) => {\n row[`dataset${j}`] = ds.data[i] ?? 0;\n });\n return row;\n });\n}\n\nconst PALETTE = [\n '#2563eb',\n '#10b981',\n '#f59e0b',\n '#ef4444',\n '#8b5cf6',\n '#ec4899',\n '#14b8a6',\n '#f97316',\n];\n\nexport function colorFor(dataset: ChartDataset, index: number): string {\n return dataset.color ?? PALETTE[index % PALETTE.length] ?? '#2563eb';\n}\n"]}
@@ -0,0 +1,597 @@
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 null;
63
+ }
64
+ }
65
+ function TextLike({
66
+ field,
67
+ value,
68
+ onChange,
69
+ disabled,
70
+ inputId,
71
+ describedBy
72
+ }) {
73
+ const htmlType = field.type === "email" ? "email" : field.type === "password" ? "password" : field.type === "url" ? "url" : "text";
74
+ const isDisabled = disabled || field.disabled || field.readonly;
75
+ return /* @__PURE__ */ jsx(
76
+ "input",
77
+ {
78
+ id: inputId,
79
+ type: htmlType,
80
+ className: inputClasses,
81
+ value: typeof value === "string" ? value : (value ?? "")?.toString() ?? "",
82
+ placeholder: field.placeholder ?? void 0,
83
+ disabled: isDisabled,
84
+ readOnly: field.readonly === true,
85
+ required: field.required === true,
86
+ "aria-describedby": describedBy,
87
+ onChange: (e) => onChange(e.target.value)
88
+ }
89
+ );
90
+ }
91
+ function Textarea({
92
+ field,
93
+ value,
94
+ onChange,
95
+ disabled,
96
+ inputId,
97
+ describedBy
98
+ }) {
99
+ return /* @__PURE__ */ jsx(
100
+ "textarea",
101
+ {
102
+ id: inputId,
103
+ className: cn(inputClasses, "h-auto min-h-[5rem] py-2"),
104
+ value: typeof value === "string" ? value : "",
105
+ placeholder: field.placeholder ?? void 0,
106
+ disabled: disabled || field.disabled || field.readonly,
107
+ readOnly: field.readonly === true,
108
+ required: field.required === true,
109
+ rows: 4,
110
+ "aria-describedby": describedBy,
111
+ onChange: (e) => onChange(e.target.value)
112
+ }
113
+ );
114
+ }
115
+ function NumericInput({
116
+ field,
117
+ value,
118
+ onChange,
119
+ disabled,
120
+ inputId,
121
+ describedBy
122
+ }) {
123
+ return /* @__PURE__ */ jsx(
124
+ "input",
125
+ {
126
+ id: inputId,
127
+ type: "number",
128
+ className: inputClasses,
129
+ value: value === null || value === void 0 ? "" : 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
+ "aria-describedby": describedBy,
135
+ onChange: (e) => {
136
+ const raw = e.target.value;
137
+ if (raw === "") {
138
+ onChange(null);
139
+ return;
140
+ }
141
+ const num = Number(raw);
142
+ onChange(Number.isNaN(num) ? null : num);
143
+ }
144
+ }
145
+ );
146
+ }
147
+ function Checkbox({
148
+ field,
149
+ value,
150
+ onChange,
151
+ disabled,
152
+ inputId,
153
+ describedBy
154
+ }) {
155
+ return /* @__PURE__ */ jsx(
156
+ "input",
157
+ {
158
+ id: inputId,
159
+ type: "checkbox",
160
+ className: "h-4 w-4 rounded border-[var(--input)]",
161
+ checked: value === true,
162
+ disabled: disabled || field.disabled || field.readonly,
163
+ "aria-describedby": describedBy,
164
+ onChange: (e) => onChange(e.target.checked)
165
+ }
166
+ );
167
+ }
168
+ function normaliseOptions(options) {
169
+ if (!options) return [];
170
+ if (Array.isArray(options)) return options;
171
+ return Object.entries(options).map(([value, label]) => ({ value, label }));
172
+ }
173
+ function SelectInput({
174
+ field,
175
+ value,
176
+ onChange,
177
+ disabled,
178
+ inputId,
179
+ describedBy
180
+ }) {
181
+ const options = normaliseOptions(field.props.options);
182
+ return /* @__PURE__ */ jsxs(
183
+ "select",
184
+ {
185
+ id: inputId,
186
+ className: inputClasses,
187
+ value: value === null || value === void 0 ? "" : String(value),
188
+ disabled: disabled || field.disabled || field.readonly,
189
+ required: field.required === true,
190
+ "aria-describedby": describedBy,
191
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
192
+ children: [
193
+ field.placeholder && /* @__PURE__ */ jsx("option", { value: "", children: field.placeholder }),
194
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
195
+ ]
196
+ }
197
+ );
198
+ }
199
+ function MultiSelectInput({
200
+ field,
201
+ value,
202
+ onChange,
203
+ disabled,
204
+ inputId,
205
+ describedBy
206
+ }) {
207
+ const arr = Array.isArray(value) ? value : [];
208
+ const options = field.type === "multiSelect" ? normaliseOptions(field.props.options) : [];
209
+ return /* @__PURE__ */ jsx(
210
+ "select",
211
+ {
212
+ id: inputId,
213
+ multiple: true,
214
+ className: cn(inputClasses, "h-auto min-h-[6rem] py-1"),
215
+ value: arr.map(String),
216
+ disabled: disabled || field.disabled || field.readonly,
217
+ "aria-describedby": describedBy,
218
+ onChange: (e) => {
219
+ const next = Array.from(e.target.selectedOptions, (o) => o.value);
220
+ onChange(next);
221
+ },
222
+ children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
223
+ }
224
+ );
225
+ }
226
+ function RadioGroup({
227
+ field,
228
+ value,
229
+ onChange,
230
+ disabled,
231
+ inputId,
232
+ describedBy
233
+ }) {
234
+ return /* @__PURE__ */ jsx(
235
+ "div",
236
+ {
237
+ id: inputId,
238
+ className: "flex flex-col gap-1",
239
+ "aria-describedby": describedBy,
240
+ role: "radiogroup",
241
+ children: (field.props.options ?? []).map((opt) => /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [
242
+ /* @__PURE__ */ jsx(
243
+ "input",
244
+ {
245
+ type: "radio",
246
+ name: field.name,
247
+ value: String(opt.value),
248
+ checked: String(value ?? "") === String(opt.value),
249
+ disabled: disabled || field.disabled || field.readonly,
250
+ onChange: () => onChange(opt.value)
251
+ }
252
+ ),
253
+ opt.label
254
+ ] }, opt.value))
255
+ }
256
+ );
257
+ }
258
+ function BelongsToInput({
259
+ field,
260
+ value,
261
+ onChange,
262
+ disabled,
263
+ inputId,
264
+ describedBy
265
+ }) {
266
+ return /* @__PURE__ */ jsx(
267
+ "input",
268
+ {
269
+ id: inputId,
270
+ type: "text",
271
+ className: inputClasses,
272
+ value: value === null || value === void 0 ? "" : String(value),
273
+ placeholder: field.placeholder ?? `Search ${field.props.relatedResource}\u2026`,
274
+ disabled: disabled || field.disabled || field.readonly,
275
+ readOnly: field.readonly === true,
276
+ "aria-describedby": describedBy,
277
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
278
+ }
279
+ );
280
+ }
281
+ function DateInput({
282
+ field,
283
+ value,
284
+ onChange,
285
+ disabled,
286
+ inputId,
287
+ describedBy
288
+ }) {
289
+ return /* @__PURE__ */ jsx(
290
+ "input",
291
+ {
292
+ id: inputId,
293
+ type: field.type === "dateTime" ? "datetime-local" : "date",
294
+ className: inputClasses,
295
+ value: typeof value === "string" ? value : "",
296
+ disabled: disabled || field.disabled || field.readonly,
297
+ readOnly: field.readonly === true,
298
+ required: field.required === true,
299
+ "aria-describedby": describedBy,
300
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
301
+ }
302
+ );
303
+ }
304
+ function FileInput({
305
+ field,
306
+ onChange,
307
+ disabled,
308
+ inputId,
309
+ describedBy
310
+ }) {
311
+ return /* @__PURE__ */ jsx(
312
+ "input",
313
+ {
314
+ id: inputId,
315
+ type: "file",
316
+ className: "text-sm",
317
+ disabled: disabled || field.disabled || field.readonly,
318
+ "aria-describedby": describedBy,
319
+ onChange: (e) => onChange(e.target.files?.[0] ?? null)
320
+ }
321
+ );
322
+ }
323
+ function FieldRenderer(props) {
324
+ const { field, errors, className } = props;
325
+ const id = useId();
326
+ const errorId = `${id}-error`;
327
+ const helperId = `${id}-helper`;
328
+ const hasError = errors && errors.length > 0;
329
+ const Custom = field.component ? getFieldComponent(field.component) : void 0;
330
+ const Input = Custom ?? NativeFieldInput;
331
+ const describedBy = [field.helperText ? helperId : null, hasError ? errorId : null].filter(Boolean).join(" ");
332
+ return /* @__PURE__ */ jsxs(
333
+ "div",
334
+ {
335
+ "data-arqel-field": field.name,
336
+ "data-field-type": field.type,
337
+ className: cn("flex flex-col gap-1", className),
338
+ children: [
339
+ field.label !== null && field.label !== "" && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-sm font-medium", children: [
340
+ field.label,
341
+ field.required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })
342
+ ] }),
343
+ /* @__PURE__ */ jsx(Input, { ...props, inputId: id, describedBy: describedBy || void 0 }),
344
+ field.helperText && !hasError && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-xs text-muted-foreground", children: field.helperText }),
345
+ hasError && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-xs text-destructive", children: errors[0] })
346
+ ]
347
+ }
348
+ );
349
+ }
350
+ function FormFieldset({ config, children, className }) {
351
+ return /* @__PURE__ */ jsxs("fieldset", { className: cn("rounded-md border border-border p-4", className), children: [
352
+ /* @__PURE__ */ jsx("legend", { className: "px-1 text-sm font-medium", children: config.legend }),
353
+ /* @__PURE__ */ jsx(
354
+ "div",
355
+ {
356
+ className: "grid gap-4",
357
+ style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
358
+ children
359
+ }
360
+ )
361
+ ] });
362
+ }
363
+ function FormGrid({ config, children, className }) {
364
+ const columns = typeof config.columns === "number" ? config.columns : config.columns["md"] ?? 1;
365
+ return /* @__PURE__ */ jsx(
366
+ "div",
367
+ {
368
+ className: cn("grid", className),
369
+ style: {
370
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
371
+ gap: config.gap ?? "1rem"
372
+ },
373
+ children
374
+ }
375
+ );
376
+ }
377
+ function FormSection({ config, children, className }) {
378
+ const [open, setOpen] = useState(!config.collapsed);
379
+ const collapsible = config.collapsible === true;
380
+ const heading = /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
381
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
382
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: config.heading }),
383
+ config.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: config.description })
384
+ ] }),
385
+ collapsible && /* @__PURE__ */ jsx(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ "aria-expanded": open,
390
+ onClick: () => setOpen((v) => !v),
391
+ className: "text-sm text-muted-foreground hover:underline",
392
+ children: open ? "Hide" : "Show"
393
+ }
394
+ )
395
+ ] });
396
+ const body = open && /* @__PURE__ */ jsx(
397
+ "div",
398
+ {
399
+ className: "grid gap-4",
400
+ style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
401
+ children
402
+ }
403
+ );
404
+ return /* @__PURE__ */ jsx(
405
+ "section",
406
+ {
407
+ "data-arqel-section": "",
408
+ className: cn(
409
+ "rounded-md border border-border bg-background",
410
+ config.compact ? "p-3" : "p-4",
411
+ className
412
+ ),
413
+ children: config.aside ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[16rem_1fr]", children: [
414
+ /* @__PURE__ */ jsx("div", { children: heading }),
415
+ /* @__PURE__ */ jsx("div", { children: body })
416
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
417
+ /* @__PURE__ */ jsx("div", { className: "mb-3", children: heading }),
418
+ body
419
+ ] })
420
+ }
421
+ );
422
+ }
423
+ function FormTabs({ config, tabs, className }) {
424
+ const [active, setActive] = useState(config.defaultTab ?? tabs[0]?.id ?? null);
425
+ const baseId = useId();
426
+ const refs = useRef([]);
427
+ if (tabs.length === 0 || active === null) return null;
428
+ const activeIndex = Math.max(
429
+ 0,
430
+ tabs.findIndex((t) => t.id === active)
431
+ );
432
+ const activeTab = tabs[activeIndex];
433
+ if (!activeTab) return null;
434
+ const focus = (i) => {
435
+ const target = tabs[i];
436
+ if (!target) return;
437
+ setActive(target.id);
438
+ refs.current[i]?.focus();
439
+ };
440
+ const onKeyDown = (e) => {
441
+ if (e.key === "ArrowRight") {
442
+ e.preventDefault();
443
+ focus((activeIndex + 1) % tabs.length);
444
+ } else if (e.key === "ArrowLeft") {
445
+ e.preventDefault();
446
+ focus((activeIndex - 1 + tabs.length) % tabs.length);
447
+ } else if (e.key === "Home") {
448
+ e.preventDefault();
449
+ focus(0);
450
+ } else if (e.key === "End") {
451
+ e.preventDefault();
452
+ focus(tabs.length - 1);
453
+ }
454
+ };
455
+ return /* @__PURE__ */ jsxs("div", { className, children: [
456
+ /* @__PURE__ */ jsx(
457
+ "div",
458
+ {
459
+ role: "tablist",
460
+ "aria-orientation": config.orientation,
461
+ className: "flex gap-1 border-b border-border",
462
+ children: tabs.map((tab, i) => {
463
+ const selected = tab.id === active;
464
+ return /* @__PURE__ */ jsxs(
465
+ "button",
466
+ {
467
+ ref: (el) => {
468
+ refs.current[i] = el;
469
+ },
470
+ type: "button",
471
+ role: "tab",
472
+ id: `${baseId}-tab-${tab.id}`,
473
+ "aria-controls": `${baseId}-panel-${tab.id}`,
474
+ "aria-selected": selected,
475
+ tabIndex: selected ? 0 : -1,
476
+ onClick: () => setActive(tab.id),
477
+ onKeyDown,
478
+ className: cn(
479
+ "-mb-px border-b-2 px-3 py-2 text-sm transition-colors",
480
+ selected ? "border-primary font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
481
+ ),
482
+ children: [
483
+ tab.label,
484
+ 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 })
485
+ ]
486
+ },
487
+ tab.id
488
+ );
489
+ })
490
+ }
491
+ ),
492
+ /* @__PURE__ */ jsx(
493
+ "div",
494
+ {
495
+ role: "tabpanel",
496
+ id: `${baseId}-panel-${activeTab.id}`,
497
+ "aria-labelledby": `${baseId}-tab-${activeTab.id}`,
498
+ className: "pt-4",
499
+ children: activeTab.content
500
+ }
501
+ )
502
+ ] });
503
+ }
504
+ function FormRenderer({
505
+ schema,
506
+ fields,
507
+ values,
508
+ errors = {},
509
+ onChange,
510
+ disabled = false,
511
+ className
512
+ }) {
513
+ const fieldsByName = new Map(fields.map((f) => [f.name, f]));
514
+ const renderEntry = (entry, key) => {
515
+ if (isFieldEntry(entry)) {
516
+ return renderFieldEntry(entry, key);
517
+ }
518
+ if (isLayoutEntry(entry)) {
519
+ return renderLayoutEntry(entry, key);
520
+ }
521
+ return null;
522
+ };
523
+ const renderFieldEntry = (entry, key) => {
524
+ const field = fieldsByName.get(entry.name);
525
+ if (!field) return null;
526
+ return /* @__PURE__ */ jsx(
527
+ FieldRenderer,
528
+ {
529
+ field,
530
+ value: values[entry.name],
531
+ errors: errors[entry.name],
532
+ disabled: disabled || schema.disabled === true,
533
+ onChange: (value) => onChange(entry.name, value)
534
+ },
535
+ key
536
+ );
537
+ };
538
+ const renderChildren = (entries) => {
539
+ if (!entries) return null;
540
+ return entries.map((child, i) => renderEntry(child, `${i}`));
541
+ };
542
+ const renderLayoutEntry = (entry, key) => {
543
+ switch (entry.type) {
544
+ case "section":
545
+ return /* @__PURE__ */ jsx(FormSection, { config: entry.props, children: renderChildren(entry.schema) }, key);
546
+ case "fieldset":
547
+ return /* @__PURE__ */ jsx(FormFieldset, { config: entry.props, children: renderChildren(entry.schema) }, key);
548
+ case "grid":
549
+ case "columns":
550
+ return /* @__PURE__ */ jsx(
551
+ FormGrid,
552
+ {
553
+ config: entry.type === "columns" ? { columns: entry.props.columns } : entry.props,
554
+ children: renderChildren(entry.schema)
555
+ },
556
+ key
557
+ );
558
+ case "group":
559
+ return /* @__PURE__ */ jsx(
560
+ "div",
561
+ {
562
+ className: cn(
563
+ "flex gap-4",
564
+ entry.props.orientation === "vertical" ? "flex-col" : "flex-row"
565
+ ),
566
+ children: renderChildren(entry.schema)
567
+ },
568
+ key
569
+ );
570
+ case "tabs": {
571
+ const tabs = (entry.schema ?? []).filter((c) => isLayoutEntry(c) && c.type === "tab").map((tab) => ({
572
+ id: tab.props.id,
573
+ label: tab.props.label,
574
+ badge: tab.props.badge,
575
+ content: renderChildren(tab.schema) ?? /* @__PURE__ */ jsx("span", {}, `tab-${tab.props.id}-empty`)
576
+ }));
577
+ return /* @__PURE__ */ jsx(FormTabs, { config: entry.props, tabs }, key);
578
+ }
579
+ case "tab":
580
+ return null;
581
+ }
582
+ };
583
+ const columns = schema.columns ?? 1;
584
+ return /* @__PURE__ */ jsx(
585
+ "div",
586
+ {
587
+ "data-arqel-form": "",
588
+ className: cn("grid gap-4", className),
589
+ style: columns > 1 ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : void 0,
590
+ children: schema.schema.map((entry, i) => renderEntry(entry, `${i}`))
591
+ }
592
+ );
593
+ }
594
+
595
+ export { FieldRenderer, FormFieldset, FormGrid, FormRenderer, FormSection, FormTabs, NativeFieldInput, clearFieldRegistry, getFieldComponent, getRegisteredFields, registerField, unregisterField };
596
+ //# sourceMappingURL=chunk-MSRTGCTJ.js.map
597
+ //# sourceMappingURL=chunk-MSRTGCTJ.js.map