@arqel-dev/ui 0.11.0 → 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 (111) 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.js +23 -3025
  91. package/dist/pages.js.map +1 -1
  92. package/dist/palette.js +1 -283
  93. package/dist/palette.js.map +1 -1
  94. package/dist/primitives.js +3 -658
  95. package/dist/primitives.js.map +1 -1
  96. package/dist/resource.js +5 -720
  97. package/dist/resource.js.map +1 -1
  98. package/dist/shell.d.ts +26 -46
  99. package/dist/shell.js +46 -1039
  100. package/dist/shell.js.map +1 -1
  101. package/dist/table.js +4 -618
  102. package/dist/table.js.map +1 -1
  103. package/dist/utility.js +3 -104
  104. package/dist/utility.js.map +1 -1
  105. package/dist/utils.d.ts +12 -6
  106. package/dist/utils.js +2 -9
  107. package/dist/utils.js.map +1 -1
  108. package/dist/widgets.d.ts +7 -2
  109. package/dist/widgets.js +3 -675
  110. package/dist/widgets.js.map +1 -1
  111. 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