@arqel-dev/ui 0.8.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/SKILL.md +116 -0
  4. package/dist/FormRenderer-BJBRIkfH.d.ts +70 -0
  5. package/dist/TableToolbar-POLdlSgC.d.ts +54 -0
  6. package/dist/WidgetWrapper-DUQy381N.d.ts +54 -0
  7. package/dist/action.d.ts +73 -0
  8. package/dist/action.js +1092 -0
  9. package/dist/action.js.map +1 -0
  10. package/dist/auth.d.ts +12 -0
  11. package/dist/auth.js +12 -0
  12. package/dist/auth.js.map +1 -0
  13. package/dist/flash.d.ts +37 -0
  14. package/dist/flash.js +106 -0
  15. package/dist/flash.js.map +1 -0
  16. package/dist/form.d.ts +61 -0
  17. package/dist/form.js +675 -0
  18. package/dist/form.js.map +1 -0
  19. package/dist/index.d.ts +24 -0
  20. package/dist/index.js +3708 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/pages.d.ts +73 -0
  23. package/dist/pages.js +1648 -0
  24. package/dist/pages.js.map +1 -0
  25. package/dist/palette.d.ts +28 -0
  26. package/dist/palette.js +285 -0
  27. package/dist/palette.js.map +1 -0
  28. package/dist/primitives.d.ts +76 -0
  29. package/dist/primitives.js +660 -0
  30. package/dist/primitives.js.map +1 -0
  31. package/dist/resource.d.ts +27 -0
  32. package/dist/resource.js +722 -0
  33. package/dist/resource.js.map +1 -0
  34. package/dist/shell.d.ts +50 -0
  35. package/dist/shell.js +1048 -0
  36. package/dist/shell.js.map +1 -0
  37. package/dist/table.d.ts +13 -0
  38. package/dist/table.js +620 -0
  39. package/dist/table.js.map +1 -0
  40. package/dist/utility.d.ts +62 -0
  41. package/dist/utility.js +106 -0
  42. package/dist/utility.js.map +1 -0
  43. package/dist/utils.d.ts +12 -0
  44. package/dist/utils.js +11 -0
  45. package/dist/utils.js.map +1 -0
  46. package/dist/widgets.d.ts +163 -0
  47. package/dist/widgets.js +677 -0
  48. package/dist/widgets.js.map +1 -0
  49. package/package.json +145 -0
  50. package/src/styles/globals.css +137 -0
package/dist/pages.js ADDED
@@ -0,0 +1,1648 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ import { useRef, useMemo, useCallback, useId, useState } from 'react';
4
+ import { cva } from 'class-variance-authority';
5
+ import { Slot } from 'radix-ui';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import { isFieldEntry, isLayoutEntry } from '@arqel-dev/types/forms';
8
+ import { useArqelForm } from '@arqel-dev/hooks';
9
+ import { usePage, router } from '@inertiajs/react';
10
+ import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table';
11
+
12
+ var __defProp = Object.defineProperty;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __esm = (fn, res) => function __init() {
15
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
+ };
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
21
+ function cn(...inputs) {
22
+ return twMerge(clsx(inputs));
23
+ }
24
+ var init_cn = __esm({
25
+ "src/utils/cn.ts"() {
26
+ }
27
+ });
28
+ function Button({
29
+ className,
30
+ variant = "default",
31
+ size = "default",
32
+ asChild = false,
33
+ ...props
34
+ }) {
35
+ const Comp = asChild ? Slot.Root : "button";
36
+ return /* @__PURE__ */ jsx(
37
+ Comp,
38
+ {
39
+ "data-slot": "button",
40
+ "data-variant": variant,
41
+ "data-size": size,
42
+ className: cn(buttonVariants({ variant, size, className })),
43
+ ...props
44
+ }
45
+ );
46
+ }
47
+ var buttonVariants;
48
+ var init_button = __esm({
49
+ "src/shadcn/ui/button.tsx"() {
50
+ init_cn();
51
+ buttonVariants = cva(
52
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
53
+ {
54
+ variants: {
55
+ variant: {
56
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
57
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
58
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
59
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
60
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
61
+ link: "text-primary underline-offset-4 hover:underline"
62
+ },
63
+ size: {
64
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
65
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
66
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
67
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
68
+ icon: "size-9",
69
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
70
+ "icon-sm": "size-8",
71
+ "icon-lg": "size-10"
72
+ }
73
+ },
74
+ defaultVariants: {
75
+ variant: "default",
76
+ size: "default"
77
+ }
78
+ }
79
+ );
80
+ }
81
+ });
82
+
83
+ // src/action/Button.tsx
84
+ var init_Button = __esm({
85
+ "src/action/Button.tsx"() {
86
+ init_button();
87
+ }
88
+ });
89
+ function FormActions({
90
+ submitLabel = "Save",
91
+ cancelLabel = "Cancel",
92
+ onCancel,
93
+ processing = false,
94
+ disabled = false,
95
+ extra,
96
+ className
97
+ }) {
98
+ return /* @__PURE__ */ jsxs(
99
+ "div",
100
+ {
101
+ className: cn(
102
+ "flex flex-wrap items-center justify-end gap-2 border-t border-border pt-4",
103
+ className
104
+ ),
105
+ children: [
106
+ extra && /* @__PURE__ */ jsx("div", { className: "mr-auto", children: extra }),
107
+ onCancel && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, disabled: processing, children: cancelLabel }),
108
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: processing || disabled, children: processing ? "Saving\u2026" : submitLabel })
109
+ ]
110
+ }
111
+ );
112
+ }
113
+ var init_FormActions = __esm({
114
+ "src/form/FormActions.tsx"() {
115
+ init_Button();
116
+ init_cn();
117
+ }
118
+ });
119
+
120
+ // src/form/FieldRegistry.tsx
121
+ function getFieldComponent(name) {
122
+ return registry.get(name);
123
+ }
124
+ var registry;
125
+ var init_FieldRegistry = __esm({
126
+ "src/form/FieldRegistry.tsx"() {
127
+ registry = /* @__PURE__ */ new Map();
128
+ }
129
+ });
130
+ function NativeFieldInput(props) {
131
+ const { field } = props;
132
+ switch (field.type) {
133
+ case "text":
134
+ case "email":
135
+ case "url":
136
+ case "password":
137
+ case "slug":
138
+ return /* @__PURE__ */ jsx(TextLike, { ...props, field });
139
+ case "textarea":
140
+ return /* @__PURE__ */ jsx(Textarea, { ...props, field });
141
+ case "number":
142
+ case "currency":
143
+ return /* @__PURE__ */ jsx(NumericInput, { ...props, field });
144
+ case "boolean":
145
+ case "toggle":
146
+ return /* @__PURE__ */ jsx(Checkbox, { ...props, field });
147
+ case "select":
148
+ return /* @__PURE__ */ jsx(SelectInput, { ...props, field });
149
+ case "multiSelect":
150
+ case "hasMany":
151
+ return /* @__PURE__ */ jsx(MultiSelectInput, { ...props, field });
152
+ case "radio":
153
+ return /* @__PURE__ */ jsx(RadioGroup, { ...props, field });
154
+ case "belongsTo":
155
+ return /* @__PURE__ */ jsx(BelongsToInput, { ...props, field });
156
+ case "date":
157
+ case "dateTime":
158
+ return /* @__PURE__ */ jsx(DateInput, { ...props, field });
159
+ case "file":
160
+ case "image":
161
+ return /* @__PURE__ */ jsx(FileInput, { ...props });
162
+ default:
163
+ return null;
164
+ }
165
+ }
166
+ function TextLike({
167
+ field,
168
+ value,
169
+ onChange,
170
+ disabled,
171
+ inputId,
172
+ describedBy
173
+ }) {
174
+ const htmlType = field.type === "email" ? "email" : field.type === "password" ? "password" : field.type === "url" ? "url" : "text";
175
+ const isDisabled = disabled || field.disabled || field.readonly;
176
+ return /* @__PURE__ */ jsx(
177
+ "input",
178
+ {
179
+ id: inputId,
180
+ type: htmlType,
181
+ className: inputClasses,
182
+ value: typeof value === "string" ? value : (value ?? "")?.toString() ?? "",
183
+ placeholder: field.placeholder ?? void 0,
184
+ disabled: isDisabled,
185
+ readOnly: field.readonly === true,
186
+ required: field.required === true,
187
+ "aria-describedby": describedBy,
188
+ onChange: (e) => onChange(e.target.value)
189
+ }
190
+ );
191
+ }
192
+ function Textarea({
193
+ field,
194
+ value,
195
+ onChange,
196
+ disabled,
197
+ inputId,
198
+ describedBy
199
+ }) {
200
+ return /* @__PURE__ */ jsx(
201
+ "textarea",
202
+ {
203
+ id: inputId,
204
+ className: cn(inputClasses, "h-auto min-h-[5rem] py-2"),
205
+ value: typeof value === "string" ? value : "",
206
+ placeholder: field.placeholder ?? void 0,
207
+ disabled: disabled || field.disabled || field.readonly,
208
+ readOnly: field.readonly === true,
209
+ required: field.required === true,
210
+ rows: 4,
211
+ "aria-describedby": describedBy,
212
+ onChange: (e) => onChange(e.target.value)
213
+ }
214
+ );
215
+ }
216
+ function NumericInput({
217
+ field,
218
+ value,
219
+ onChange,
220
+ disabled,
221
+ inputId,
222
+ describedBy
223
+ }) {
224
+ return /* @__PURE__ */ jsx(
225
+ "input",
226
+ {
227
+ id: inputId,
228
+ type: "number",
229
+ className: inputClasses,
230
+ value: value === null || value === void 0 ? "" : String(value),
231
+ placeholder: field.placeholder ?? void 0,
232
+ disabled: disabled || field.disabled || field.readonly,
233
+ readOnly: field.readonly === true,
234
+ required: field.required === true,
235
+ "aria-describedby": describedBy,
236
+ onChange: (e) => {
237
+ const raw = e.target.value;
238
+ if (raw === "") {
239
+ onChange(null);
240
+ return;
241
+ }
242
+ const num = Number(raw);
243
+ onChange(Number.isNaN(num) ? null : num);
244
+ }
245
+ }
246
+ );
247
+ }
248
+ function Checkbox({
249
+ field,
250
+ value,
251
+ onChange,
252
+ disabled,
253
+ inputId,
254
+ describedBy
255
+ }) {
256
+ return /* @__PURE__ */ jsx(
257
+ "input",
258
+ {
259
+ id: inputId,
260
+ type: "checkbox",
261
+ className: "h-4 w-4 rounded border-[var(--input)]",
262
+ checked: value === true,
263
+ disabled: disabled || field.disabled || field.readonly,
264
+ "aria-describedby": describedBy,
265
+ onChange: (e) => onChange(e.target.checked)
266
+ }
267
+ );
268
+ }
269
+ function normaliseOptions(options) {
270
+ if (!options) return [];
271
+ if (Array.isArray(options)) return options;
272
+ return Object.entries(options).map(([value, label]) => ({ value, label }));
273
+ }
274
+ function SelectInput({
275
+ field,
276
+ value,
277
+ onChange,
278
+ disabled,
279
+ inputId,
280
+ describedBy
281
+ }) {
282
+ const options = normaliseOptions(field.props.options);
283
+ return /* @__PURE__ */ jsxs(
284
+ "select",
285
+ {
286
+ id: inputId,
287
+ className: inputClasses,
288
+ value: value === null || value === void 0 ? "" : String(value),
289
+ disabled: disabled || field.disabled || field.readonly,
290
+ required: field.required === true,
291
+ "aria-describedby": describedBy,
292
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
293
+ children: [
294
+ field.placeholder && /* @__PURE__ */ jsx("option", { value: "", children: field.placeholder }),
295
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
296
+ ]
297
+ }
298
+ );
299
+ }
300
+ function MultiSelectInput({
301
+ field,
302
+ value,
303
+ onChange,
304
+ disabled,
305
+ inputId,
306
+ describedBy
307
+ }) {
308
+ const arr = Array.isArray(value) ? value : [];
309
+ const options = field.type === "multiSelect" ? normaliseOptions(field.props.options) : [];
310
+ return /* @__PURE__ */ jsx(
311
+ "select",
312
+ {
313
+ id: inputId,
314
+ multiple: true,
315
+ className: cn(inputClasses, "h-auto min-h-[6rem] py-1"),
316
+ value: arr.map(String),
317
+ disabled: disabled || field.disabled || field.readonly,
318
+ "aria-describedby": describedBy,
319
+ onChange: (e) => {
320
+ const next = Array.from(e.target.selectedOptions, (o) => o.value);
321
+ onChange(next);
322
+ },
323
+ children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
324
+ }
325
+ );
326
+ }
327
+ function RadioGroup({
328
+ field,
329
+ value,
330
+ onChange,
331
+ disabled,
332
+ inputId,
333
+ describedBy
334
+ }) {
335
+ return /* @__PURE__ */ jsx(
336
+ "div",
337
+ {
338
+ id: inputId,
339
+ className: "flex flex-col gap-1",
340
+ "aria-describedby": describedBy,
341
+ role: "radiogroup",
342
+ children: (field.props.options ?? []).map((opt) => /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [
343
+ /* @__PURE__ */ jsx(
344
+ "input",
345
+ {
346
+ type: "radio",
347
+ name: field.name,
348
+ value: String(opt.value),
349
+ checked: String(value ?? "") === String(opt.value),
350
+ disabled: disabled || field.disabled || field.readonly,
351
+ onChange: () => onChange(opt.value)
352
+ }
353
+ ),
354
+ opt.label
355
+ ] }, opt.value))
356
+ }
357
+ );
358
+ }
359
+ function BelongsToInput({
360
+ field,
361
+ value,
362
+ onChange,
363
+ disabled,
364
+ inputId,
365
+ describedBy
366
+ }) {
367
+ return /* @__PURE__ */ jsx(
368
+ "input",
369
+ {
370
+ id: inputId,
371
+ type: "text",
372
+ className: inputClasses,
373
+ value: value === null || value === void 0 ? "" : String(value),
374
+ placeholder: field.placeholder ?? `Search ${field.props.relatedResource}\u2026`,
375
+ disabled: disabled || field.disabled || field.readonly,
376
+ readOnly: field.readonly === true,
377
+ "aria-describedby": describedBy,
378
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
379
+ }
380
+ );
381
+ }
382
+ function DateInput({
383
+ field,
384
+ value,
385
+ onChange,
386
+ disabled,
387
+ inputId,
388
+ describedBy
389
+ }) {
390
+ return /* @__PURE__ */ jsx(
391
+ "input",
392
+ {
393
+ id: inputId,
394
+ type: field.type === "dateTime" ? "datetime-local" : "date",
395
+ className: inputClasses,
396
+ value: typeof value === "string" ? value : "",
397
+ disabled: disabled || field.disabled || field.readonly,
398
+ readOnly: field.readonly === true,
399
+ required: field.required === true,
400
+ "aria-describedby": describedBy,
401
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
402
+ }
403
+ );
404
+ }
405
+ function FileInput({
406
+ field,
407
+ onChange,
408
+ disabled,
409
+ inputId,
410
+ describedBy
411
+ }) {
412
+ return /* @__PURE__ */ jsx(
413
+ "input",
414
+ {
415
+ id: inputId,
416
+ type: "file",
417
+ className: "text-sm",
418
+ disabled: disabled || field.disabled || field.readonly,
419
+ "aria-describedby": describedBy,
420
+ onChange: (e) => onChange(e.target.files?.[0] ?? null)
421
+ }
422
+ );
423
+ }
424
+ var inputClasses;
425
+ var init_nativeFields = __esm({
426
+ "src/form/nativeFields.tsx"() {
427
+ init_cn();
428
+ inputClasses = cn(
429
+ "h-9 rounded-sm border border-[var(--input)]",
430
+ "bg-background px-3 text-sm",
431
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
432
+ "disabled:cursor-not-allowed disabled:opacity-50"
433
+ );
434
+ }
435
+ });
436
+ function FieldRenderer(props) {
437
+ const { field, errors, className } = props;
438
+ const id = useId();
439
+ const errorId = `${id}-error`;
440
+ const helperId = `${id}-helper`;
441
+ const hasError = errors && errors.length > 0;
442
+ const Custom = field.component ? getFieldComponent(field.component) : void 0;
443
+ const Input = Custom ?? NativeFieldInput;
444
+ const describedBy = [field.helperText ? helperId : null, hasError ? errorId : null].filter(Boolean).join(" ");
445
+ return /* @__PURE__ */ jsxs(
446
+ "div",
447
+ {
448
+ "data-arqel-field": field.name,
449
+ "data-field-type": field.type,
450
+ className: cn("flex flex-col gap-1", className),
451
+ children: [
452
+ field.label !== null && field.label !== "" && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-sm font-medium", children: [
453
+ field.label,
454
+ field.required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })
455
+ ] }),
456
+ /* @__PURE__ */ jsx(Input, { ...props, inputId: id, describedBy: describedBy || void 0 }),
457
+ field.helperText && !hasError && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-xs text-muted-foreground", children: field.helperText }),
458
+ hasError && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-xs text-destructive", children: errors[0] })
459
+ ]
460
+ }
461
+ );
462
+ }
463
+ var init_FieldRenderer = __esm({
464
+ "src/form/FieldRenderer.tsx"() {
465
+ init_cn();
466
+ init_FieldRegistry();
467
+ init_nativeFields();
468
+ }
469
+ });
470
+ function FormFieldset({ config, children, className }) {
471
+ return /* @__PURE__ */ jsxs("fieldset", { className: cn("rounded-md border border-border p-4", className), children: [
472
+ /* @__PURE__ */ jsx("legend", { className: "px-1 text-sm font-medium", children: config.legend }),
473
+ /* @__PURE__ */ jsx(
474
+ "div",
475
+ {
476
+ className: "grid gap-4",
477
+ style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
478
+ children
479
+ }
480
+ )
481
+ ] });
482
+ }
483
+ var init_FormFieldset = __esm({
484
+ "src/form/FormFieldset.tsx"() {
485
+ init_cn();
486
+ }
487
+ });
488
+ function FormGrid({ config, children, className }) {
489
+ const columns = typeof config.columns === "number" ? config.columns : config.columns["md"] ?? 1;
490
+ return /* @__PURE__ */ jsx(
491
+ "div",
492
+ {
493
+ className: cn("grid", className),
494
+ style: {
495
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
496
+ gap: config.gap ?? "1rem"
497
+ },
498
+ children
499
+ }
500
+ );
501
+ }
502
+ var init_FormGrid = __esm({
503
+ "src/form/FormGrid.tsx"() {
504
+ init_cn();
505
+ }
506
+ });
507
+ function FormSection({ config, children, className }) {
508
+ const [open, setOpen] = useState(!config.collapsed);
509
+ const collapsible = config.collapsible === true;
510
+ const heading = /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
511
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
512
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: config.heading }),
513
+ config.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: config.description })
514
+ ] }),
515
+ collapsible && /* @__PURE__ */ jsx(
516
+ "button",
517
+ {
518
+ type: "button",
519
+ "aria-expanded": open,
520
+ onClick: () => setOpen((v) => !v),
521
+ className: "text-sm text-muted-foreground hover:underline",
522
+ children: open ? "Hide" : "Show"
523
+ }
524
+ )
525
+ ] });
526
+ const body = open && /* @__PURE__ */ jsx(
527
+ "div",
528
+ {
529
+ className: "grid gap-4",
530
+ style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
531
+ children
532
+ }
533
+ );
534
+ return /* @__PURE__ */ jsx(
535
+ "section",
536
+ {
537
+ "data-arqel-section": "",
538
+ className: cn(
539
+ "rounded-md border border-border bg-background",
540
+ config.compact ? "p-3" : "p-4",
541
+ className
542
+ ),
543
+ children: config.aside ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[16rem_1fr]", children: [
544
+ /* @__PURE__ */ jsx("div", { children: heading }),
545
+ /* @__PURE__ */ jsx("div", { children: body })
546
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
547
+ /* @__PURE__ */ jsx("div", { className: "mb-3", children: heading }),
548
+ body
549
+ ] })
550
+ }
551
+ );
552
+ }
553
+ var init_FormSection = __esm({
554
+ "src/form/FormSection.tsx"() {
555
+ init_cn();
556
+ }
557
+ });
558
+ function FormTabs({ config, tabs, className }) {
559
+ const [active, setActive] = useState(config.defaultTab ?? tabs[0]?.id ?? null);
560
+ const baseId = useId();
561
+ const refs = useRef([]);
562
+ if (tabs.length === 0 || active === null) return null;
563
+ const activeIndex = Math.max(
564
+ 0,
565
+ tabs.findIndex((t) => t.id === active)
566
+ );
567
+ const activeTab = tabs[activeIndex];
568
+ if (!activeTab) return null;
569
+ const focus = (i) => {
570
+ const target = tabs[i];
571
+ if (!target) return;
572
+ setActive(target.id);
573
+ refs.current[i]?.focus();
574
+ };
575
+ const onKeyDown = (e) => {
576
+ if (e.key === "ArrowRight") {
577
+ e.preventDefault();
578
+ focus((activeIndex + 1) % tabs.length);
579
+ } else if (e.key === "ArrowLeft") {
580
+ e.preventDefault();
581
+ focus((activeIndex - 1 + tabs.length) % tabs.length);
582
+ } else if (e.key === "Home") {
583
+ e.preventDefault();
584
+ focus(0);
585
+ } else if (e.key === "End") {
586
+ e.preventDefault();
587
+ focus(tabs.length - 1);
588
+ }
589
+ };
590
+ return /* @__PURE__ */ jsxs("div", { className, children: [
591
+ /* @__PURE__ */ jsx(
592
+ "div",
593
+ {
594
+ role: "tablist",
595
+ "aria-orientation": config.orientation,
596
+ className: "flex gap-1 border-b border-border",
597
+ children: tabs.map((tab, i) => {
598
+ const selected = tab.id === active;
599
+ return /* @__PURE__ */ jsxs(
600
+ "button",
601
+ {
602
+ ref: (el) => {
603
+ refs.current[i] = el;
604
+ },
605
+ type: "button",
606
+ role: "tab",
607
+ id: `${baseId}-tab-${tab.id}`,
608
+ "aria-controls": `${baseId}-panel-${tab.id}`,
609
+ "aria-selected": selected,
610
+ tabIndex: selected ? 0 : -1,
611
+ onClick: () => setActive(tab.id),
612
+ onKeyDown,
613
+ className: cn(
614
+ "-mb-px border-b-2 px-3 py-2 text-sm transition-colors",
615
+ selected ? "border-primary font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
616
+ ),
617
+ children: [
618
+ tab.label,
619
+ 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 })
620
+ ]
621
+ },
622
+ tab.id
623
+ );
624
+ })
625
+ }
626
+ ),
627
+ /* @__PURE__ */ jsx(
628
+ "div",
629
+ {
630
+ role: "tabpanel",
631
+ id: `${baseId}-panel-${activeTab.id}`,
632
+ "aria-labelledby": `${baseId}-tab-${activeTab.id}`,
633
+ className: "pt-4",
634
+ children: activeTab.content
635
+ }
636
+ )
637
+ ] });
638
+ }
639
+ var init_FormTabs = __esm({
640
+ "src/form/FormTabs.tsx"() {
641
+ init_cn();
642
+ }
643
+ });
644
+ function FormRenderer({
645
+ schema,
646
+ fields,
647
+ values,
648
+ errors = {},
649
+ onChange,
650
+ disabled = false,
651
+ className
652
+ }) {
653
+ const fieldsByName = new Map(fields.map((f) => [f.name, f]));
654
+ const renderEntry = (entry, key) => {
655
+ if (isFieldEntry(entry)) {
656
+ return renderFieldEntry(entry, key);
657
+ }
658
+ if (isLayoutEntry(entry)) {
659
+ return renderLayoutEntry(entry, key);
660
+ }
661
+ return null;
662
+ };
663
+ const renderFieldEntry = (entry, key) => {
664
+ const field = fieldsByName.get(entry.name);
665
+ if (!field) return null;
666
+ return /* @__PURE__ */ jsx(
667
+ FieldRenderer,
668
+ {
669
+ field,
670
+ value: values[entry.name],
671
+ errors: errors[entry.name],
672
+ disabled: disabled || schema.disabled === true,
673
+ onChange: (value) => onChange(entry.name, value)
674
+ },
675
+ key
676
+ );
677
+ };
678
+ const renderChildren = (entries) => {
679
+ if (!entries) return null;
680
+ return entries.map((child, i) => renderEntry(child, `${i}`));
681
+ };
682
+ const renderLayoutEntry = (entry, key) => {
683
+ switch (entry.type) {
684
+ case "section":
685
+ return /* @__PURE__ */ jsx(FormSection, { config: entry.props, children: renderChildren(entry.schema) }, key);
686
+ case "fieldset":
687
+ return /* @__PURE__ */ jsx(FormFieldset, { config: entry.props, children: renderChildren(entry.schema) }, key);
688
+ case "grid":
689
+ case "columns":
690
+ return /* @__PURE__ */ jsx(
691
+ FormGrid,
692
+ {
693
+ config: entry.type === "columns" ? { columns: entry.props.columns } : entry.props,
694
+ children: renderChildren(entry.schema)
695
+ },
696
+ key
697
+ );
698
+ case "group":
699
+ return /* @__PURE__ */ jsx(
700
+ "div",
701
+ {
702
+ className: cn(
703
+ "flex gap-4",
704
+ entry.props.orientation === "vertical" ? "flex-col" : "flex-row"
705
+ ),
706
+ children: renderChildren(entry.schema)
707
+ },
708
+ key
709
+ );
710
+ case "tabs": {
711
+ const tabs = (entry.schema ?? []).filter((c) => isLayoutEntry(c) && c.type === "tab").map((tab) => ({
712
+ id: tab.props.id,
713
+ label: tab.props.label,
714
+ badge: tab.props.badge,
715
+ content: renderChildren(tab.schema) ?? /* @__PURE__ */ jsx("span", {}, `tab-${tab.props.id}-empty`)
716
+ }));
717
+ return /* @__PURE__ */ jsx(FormTabs, { config: entry.props, tabs }, key);
718
+ }
719
+ case "tab":
720
+ return null;
721
+ }
722
+ };
723
+ const columns = schema.columns ?? 1;
724
+ return /* @__PURE__ */ jsx(
725
+ "div",
726
+ {
727
+ "data-arqel-form": "",
728
+ className: cn("grid gap-4", className),
729
+ style: columns > 1 ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : void 0,
730
+ children: schema.schema.map((entry, i) => renderEntry(entry, `${i}`))
731
+ }
732
+ );
733
+ }
734
+ var init_FormRenderer = __esm({
735
+ "src/form/FormRenderer.tsx"() {
736
+ init_cn();
737
+ init_FieldRenderer();
738
+ init_FormFieldset();
739
+ init_FormGrid();
740
+ init_FormSection();
741
+ init_FormTabs();
742
+ }
743
+ });
744
+ function PageHeader({ title, description, actions, className }) {
745
+ return /* @__PURE__ */ jsxs("header", { className: cn("flex flex-wrap items-start gap-4", className), children: [
746
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
747
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: title }),
748
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })
749
+ ] }),
750
+ actions && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: actions })
751
+ ] });
752
+ }
753
+ var init_PageHeader = __esm({
754
+ "src/utility/PageHeader.tsx"() {
755
+ init_cn();
756
+ }
757
+ });
758
+
759
+ // src/pages/ArqelCreatePage.tsx
760
+ var ArqelCreatePage_exports = {};
761
+ __export(ArqelCreatePage_exports, {
762
+ default: () => ArqelCreatePage
763
+ });
764
+ function ArqelCreatePage() {
765
+ const page = usePage();
766
+ const props = page.props;
767
+ const fields = props.fields ?? [];
768
+ const schema = props.form ?? FALLBACK_SCHEMA;
769
+ const form = useArqelForm({ fields });
770
+ const submit = (e) => {
771
+ e.preventDefault();
772
+ const slug = props.resource?.slug ?? "";
773
+ router.post(`/${slug}`, form.data, {
774
+ preserveScroll: true
775
+ });
776
+ };
777
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
778
+ /* @__PURE__ */ jsx(PageHeader, { title: `Create ${props.resource?.label ?? "record"}`, description: null }),
779
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-6", children: [
780
+ /* @__PURE__ */ jsx(
781
+ FormRenderer,
782
+ {
783
+ schema,
784
+ fields,
785
+ values: form.data,
786
+ onChange: (name, value) => form.setData(name, value),
787
+ errors: form.errors
788
+ }
789
+ ),
790
+ /* @__PURE__ */ jsx(FormActions, { processing: form.processing, onCancel: () => window.history.back() })
791
+ ] })
792
+ ] });
793
+ }
794
+ var FALLBACK_SCHEMA;
795
+ var init_ArqelCreatePage = __esm({
796
+ "src/pages/ArqelCreatePage.tsx"() {
797
+ init_FormActions();
798
+ init_FormRenderer();
799
+ init_PageHeader();
800
+ FALLBACK_SCHEMA = {
801
+ schema: [],
802
+ columns: 1,
803
+ model: null,
804
+ inline: false,
805
+ disabled: false
806
+ };
807
+ }
808
+ });
809
+
810
+ // src/pages/ArqelEditPage.tsx
811
+ var ArqelEditPage_exports = {};
812
+ __export(ArqelEditPage_exports, {
813
+ default: () => ArqelEditPage
814
+ });
815
+ function ArqelEditPage() {
816
+ const page = usePage();
817
+ const props = page.props;
818
+ const fields = props.fields ?? [];
819
+ const record = props.record;
820
+ const schema = props.form ?? FALLBACK_SCHEMA2;
821
+ const form = useArqelForm({ fields, record });
822
+ const submit = (e) => {
823
+ e.preventDefault();
824
+ const id = String(record?.id ?? "");
825
+ const slug = props.resource?.slug ?? "";
826
+ router.put(`/${slug}/${id}`, form.data, {
827
+ preserveScroll: true
828
+ });
829
+ };
830
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
831
+ /* @__PURE__ */ jsx(
832
+ PageHeader,
833
+ {
834
+ title: props.recordTitle ?? `Edit ${props.resource?.label ?? "record"}`,
835
+ description: props.recordSubtitle ?? null
836
+ }
837
+ ),
838
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-6", children: [
839
+ /* @__PURE__ */ jsx(
840
+ FormRenderer,
841
+ {
842
+ schema,
843
+ fields,
844
+ values: form.data,
845
+ onChange: (name, value) => form.setData(name, value),
846
+ errors: form.errors
847
+ }
848
+ ),
849
+ /* @__PURE__ */ jsx(FormActions, { processing: form.processing, onCancel: () => window.history.back() })
850
+ ] })
851
+ ] });
852
+ }
853
+ var FALLBACK_SCHEMA2;
854
+ var init_ArqelEditPage = __esm({
855
+ "src/pages/ArqelEditPage.tsx"() {
856
+ init_FormActions();
857
+ init_FormRenderer();
858
+ init_PageHeader();
859
+ FALLBACK_SCHEMA2 = {
860
+ schema: [],
861
+ columns: 1,
862
+ model: null,
863
+ inline: false,
864
+ disabled: false
865
+ };
866
+ }
867
+ });
868
+ function TableCell({ column, value }) {
869
+ switch (column.type) {
870
+ case "text":
871
+ return /* @__PURE__ */ jsx(TextCell, { column, value });
872
+ case "badge":
873
+ return /* @__PURE__ */ jsx(BadgeCell, { column, value });
874
+ case "boolean":
875
+ return /* @__PURE__ */ jsx(BooleanCell, { column, value });
876
+ case "date":
877
+ return /* @__PURE__ */ jsx(DateCell, { column, value });
878
+ case "number":
879
+ return /* @__PURE__ */ jsx(NumberCell, { column, value });
880
+ case "icon":
881
+ return /* @__PURE__ */ jsx(IconCell, { column });
882
+ case "image":
883
+ return /* @__PURE__ */ jsx(ImageCell, { column, value });
884
+ case "relationship":
885
+ return /* @__PURE__ */ jsx(RelationshipCell, { column, value });
886
+ case "computed":
887
+ return /* @__PURE__ */ jsx(ComputedCell, { column, value });
888
+ }
889
+ }
890
+ function asString(value) {
891
+ if (value === null || value === void 0) return "";
892
+ if (typeof value === "string") return value;
893
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
894
+ return JSON.stringify(value);
895
+ }
896
+ function TextCell({ column, value }) {
897
+ const str = asString(value);
898
+ const truncated = column.props.truncate && str.length > column.props.truncate ? `${str.slice(0, column.props.truncate)}\u2026` : str;
899
+ return /* @__PURE__ */ jsx(
900
+ "span",
901
+ {
902
+ className: cn(
903
+ column.props.weight === "bold" && "font-bold",
904
+ column.props.weight === "medium" && "font-medium"
905
+ ),
906
+ children: truncated
907
+ }
908
+ );
909
+ }
910
+ function BadgeCell({ column, value }) {
911
+ const option = column.props.options?.find((o) => o.value === value);
912
+ const label = option?.label ?? asString(value);
913
+ return /* @__PURE__ */ jsx(
914
+ "span",
915
+ {
916
+ className: cn(
917
+ "inline-flex items-center px-2 py-0.5 text-xs",
918
+ column.props.pill ? "rounded-full" : "rounded-sm",
919
+ "bg-muted text-foreground"
920
+ ),
921
+ children: label
922
+ }
923
+ );
924
+ }
925
+ function BooleanCell({ column, value }) {
926
+ const truthy = Boolean(value);
927
+ return /* @__PURE__ */ jsx("span", { role: "img", "aria-label": truthy ? "true" : "false", children: truthy ? column.props.trueIcon ?? "\u2713" : column.props.falseIcon ?? "\u2014" });
928
+ }
929
+ function DateCell({ column, value }) {
930
+ if (!value) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
931
+ const raw = String(value);
932
+ const date = new Date(raw);
933
+ if (Number.isNaN(date.getTime())) return /* @__PURE__ */ jsx("span", { children: raw });
934
+ if (column.props.mode === "since") {
935
+ return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: formatRelative(date) });
936
+ }
937
+ if (column.props.mode === "datetime") {
938
+ return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: date.toLocaleString() });
939
+ }
940
+ return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: date.toLocaleDateString() });
941
+ }
942
+ function formatRelative(date) {
943
+ const seconds = Math.round((Date.now() - date.getTime()) / 1e3);
944
+ if (seconds < 60) return `${seconds}s ago`;
945
+ const minutes = Math.round(seconds / 60);
946
+ if (minutes < 60) return `${minutes}m ago`;
947
+ const hours = Math.round(minutes / 60);
948
+ if (hours < 24) return `${hours}h ago`;
949
+ const days = Math.round(hours / 24);
950
+ return `${days}d ago`;
951
+ }
952
+ function NumberCell({ column, value }) {
953
+ if (value === null || value === void 0) return /* @__PURE__ */ jsx("span", { children: "\u2014" });
954
+ const num = typeof value === "number" ? value : Number(value);
955
+ if (Number.isNaN(num)) return /* @__PURE__ */ jsx("span", { children: asString(value) });
956
+ const formatted = num.toFixed(column.props.decimals ?? 0);
957
+ const [whole, dec] = formatted.split(".");
958
+ const safeWhole = whole ?? "0";
959
+ const ts = column.props.thousandsSeparator;
960
+ const finalWhole = ts ? safeWhole.replace(/\B(?=(\d{3})+(?!\d))/g, ts) : safeWhole;
961
+ const ds = column.props.decimalSeparator ?? ".";
962
+ return /* @__PURE__ */ jsxs("span", { children: [
963
+ column.props.prefix ?? "",
964
+ finalWhole,
965
+ dec ? `${ds}${dec}` : "",
966
+ column.props.suffix ?? ""
967
+ ] });
968
+ }
969
+ function IconCell({ column }) {
970
+ return /* @__PURE__ */ jsx("span", { role: "img", "aria-label": column.props.icon, title: column.props.icon, children: column.props.icon });
971
+ }
972
+ function ImageCell({ column, value }) {
973
+ const src = asString(value);
974
+ if (!src) return null;
975
+ const size = column.props.size ?? 32;
976
+ return /* @__PURE__ */ jsx(
977
+ "img",
978
+ {
979
+ src,
980
+ alt: "",
981
+ style: { width: size, height: size },
982
+ className: cn(column.props.shape === "circular" ? "rounded-full" : "rounded")
983
+ }
984
+ );
985
+ }
986
+ function RelationshipCell({ column, value }) {
987
+ if (value === null || value === void 0) return /* @__PURE__ */ jsx("span", { children: "\u2014" });
988
+ if (typeof value === "object") {
989
+ const attr = value[column.props.attribute];
990
+ return /* @__PURE__ */ jsx("span", { children: asString(attr) });
991
+ }
992
+ return /* @__PURE__ */ jsx("span", { children: asString(value) });
993
+ }
994
+ function ComputedCell({ column, value }) {
995
+ if (value === null || value === void 0) {
996
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: column.props.placeholder ?? "\u2014" });
997
+ }
998
+ return /* @__PURE__ */ jsx("span", { children: asString(value) });
999
+ }
1000
+ var init_cells = __esm({
1001
+ "src/table/cells.tsx"() {
1002
+ init_cn();
1003
+ }
1004
+ });
1005
+ function DataTable({
1006
+ columns,
1007
+ records,
1008
+ enableSelection = false,
1009
+ selectedIds = [],
1010
+ onSelectionChange,
1011
+ sort = null,
1012
+ onSortChange,
1013
+ rowActions,
1014
+ loading = false,
1015
+ emptyState,
1016
+ className
1017
+ }) {
1018
+ const lastClickedIndexRef = useRef(null);
1019
+ const visibleColumns = useMemo(() => columns.filter((col) => !col.hidden), [columns]);
1020
+ const tableColumns = useMemo(
1021
+ () => visibleColumns.map((col) => ({
1022
+ id: col.name,
1023
+ accessorFn: (row) => row[col.name],
1024
+ header: () => col.label ?? col.name,
1025
+ cell: (info) => /* @__PURE__ */ jsx(TableCell, { column: col, value: info.getValue() }),
1026
+ enableSorting: col.sortable
1027
+ })),
1028
+ [visibleColumns]
1029
+ );
1030
+ const table = useReactTable({
1031
+ data: records,
1032
+ columns: tableColumns,
1033
+ getCoreRowModel: getCoreRowModel()
1034
+ });
1035
+ const allSelected = enableSelection && records.length > 0 && selectedIds.length === records.length;
1036
+ const someSelected = enableSelection && selectedIds.length > 0 && selectedIds.length < records.length;
1037
+ const toggleAll = useCallback(() => {
1038
+ if (!onSelectionChange) return;
1039
+ if (allSelected) onSelectionChange([]);
1040
+ else onSelectionChange(records.map((r) => r.id));
1041
+ }, [allSelected, records, onSelectionChange]);
1042
+ const toggleRow = useCallback(
1043
+ (id, index, shiftKey) => {
1044
+ if (!onSelectionChange) return;
1045
+ const set = new Set(selectedIds);
1046
+ const lastIndex = lastClickedIndexRef.current;
1047
+ if (shiftKey && lastIndex !== null) {
1048
+ const [from, to] = lastIndex < index ? [lastIndex, index] : [index, lastIndex];
1049
+ const shouldSelect = !set.has(id);
1050
+ for (let i = from; i <= to; i++) {
1051
+ const rowId = records[i]?.id;
1052
+ if (rowId === void 0) continue;
1053
+ if (shouldSelect) set.add(rowId);
1054
+ else set.delete(rowId);
1055
+ }
1056
+ } else if (set.has(id)) {
1057
+ set.delete(id);
1058
+ } else {
1059
+ set.add(id);
1060
+ }
1061
+ lastClickedIndexRef.current = index;
1062
+ onSelectionChange(Array.from(set));
1063
+ },
1064
+ [records, selectedIds, onSelectionChange]
1065
+ );
1066
+ const headerSortDirection = (columnName) => {
1067
+ if (!sort || sort.column !== columnName) return null;
1068
+ return sort.direction === "desc" ? "desc" : "asc";
1069
+ };
1070
+ return /* @__PURE__ */ jsx("div", { className: cn("w-full overflow-x-auto", className), children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
1071
+ /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-background", children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-border", children: [
1072
+ enableSelection && /* @__PURE__ */ jsx("th", { scope: "col", className: "w-10 px-3 py-2 text-left", children: /* @__PURE__ */ jsx(
1073
+ "input",
1074
+ {
1075
+ type: "checkbox",
1076
+ "aria-label": "Select all rows",
1077
+ checked: allSelected,
1078
+ ref: (el) => {
1079
+ if (el) el.indeterminate = someSelected;
1080
+ },
1081
+ onChange: toggleAll
1082
+ }
1083
+ ) }),
1084
+ group.headers.map((header) => {
1085
+ const col = visibleColumns.find((c) => c.name === header.column.id);
1086
+ const sortable = col?.sortable && onSortChange;
1087
+ const direction = headerSortDirection(header.column.id);
1088
+ return /* @__PURE__ */ jsx(
1089
+ "th",
1090
+ {
1091
+ scope: "col",
1092
+ className: cn(
1093
+ "px-3 py-2 text-left font-medium text-muted-foreground",
1094
+ col?.align === "center" && "text-center",
1095
+ col?.align === "end" && "text-right"
1096
+ ),
1097
+ "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none",
1098
+ children: sortable ? /* @__PURE__ */ jsxs(
1099
+ "button",
1100
+ {
1101
+ type: "button",
1102
+ className: "inline-flex items-center gap-1 hover:text-foreground",
1103
+ onClick: () => onSortChange(header.column.id, direction === "asc" ? "desc" : "asc"),
1104
+ children: [
1105
+ flexRender(header.column.columnDef.header, header.getContext()),
1106
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: direction === "asc" ? "\u25B2" : direction === "desc" ? "\u25BC" : "\u2195" })
1107
+ ]
1108
+ }
1109
+ ) : flexRender(header.column.columnDef.header, header.getContext())
1110
+ },
1111
+ header.id
1112
+ );
1113
+ }),
1114
+ rowActions && /* @__PURE__ */ jsx("th", { scope: "col", className: "w-24 px-3 py-2 text-right", "aria-label": "Actions" })
1115
+ ] }, group.id)) }),
1116
+ /* @__PURE__ */ jsxs("tbody", { children: [
1117
+ loading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
1118
+ "td",
1119
+ {
1120
+ colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
1121
+ className: "px-3 py-4 text-center text-muted-foreground",
1122
+ children: "Loading\u2026"
1123
+ }
1124
+ ) }),
1125
+ !loading && records.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
1126
+ "td",
1127
+ {
1128
+ colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
1129
+ className: "px-3 py-8 text-center text-muted-foreground",
1130
+ children: emptyState ?? "No records found."
1131
+ }
1132
+ ) }),
1133
+ !loading && table.getRowModel().rows.map((row, index) => {
1134
+ const record = row.original;
1135
+ const checked = selectedIds.includes(record.id);
1136
+ return /* @__PURE__ */ jsxs(
1137
+ "tr",
1138
+ {
1139
+ "data-selected": checked || void 0,
1140
+ className: cn("border-b border-border", checked && "bg-muted"),
1141
+ children: [
1142
+ enableSelection && /* @__PURE__ */ jsx("td", { className: "w-10 px-3 py-2", children: /* @__PURE__ */ jsx(
1143
+ "input",
1144
+ {
1145
+ type: "checkbox",
1146
+ "aria-label": `Select row ${record.id}`,
1147
+ checked,
1148
+ onChange: (event) => {
1149
+ const native = event.nativeEvent;
1150
+ toggleRow(record.id, index, native.shiftKey === true);
1151
+ }
1152
+ }
1153
+ ) }),
1154
+ row.getVisibleCells().map((cell) => {
1155
+ const col = visibleColumns.find((c) => c.name === cell.column.id);
1156
+ return /* @__PURE__ */ jsx(
1157
+ "td",
1158
+ {
1159
+ className: cn(
1160
+ "px-3 py-2",
1161
+ col?.align === "center" && "text-center",
1162
+ col?.align === "end" && "text-right",
1163
+ col?.hiddenOnMobile && "hidden md:table-cell"
1164
+ ),
1165
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
1166
+ },
1167
+ cell.id
1168
+ );
1169
+ }),
1170
+ rowActions && /* @__PURE__ */ jsx("td", { className: "w-24 px-3 py-2 text-right", children: rowActions(record) })
1171
+ ]
1172
+ },
1173
+ row.id
1174
+ );
1175
+ })
1176
+ ] })
1177
+ ] }) });
1178
+ }
1179
+ var init_DataTable = __esm({
1180
+ "src/table/DataTable.tsx"() {
1181
+ init_cn();
1182
+ init_cells();
1183
+ }
1184
+ });
1185
+ function TableFilters({
1186
+ filters,
1187
+ values,
1188
+ onChange,
1189
+ onClearAll,
1190
+ className
1191
+ }) {
1192
+ if (filters.length === 0) return null;
1193
+ const activeCount = filters.reduce((acc, f) => {
1194
+ const v = values[f.name];
1195
+ return v !== void 0 && v !== null && v !== "" ? acc + 1 : acc;
1196
+ }, 0);
1197
+ return /* @__PURE__ */ jsxs("fieldset", { className: cn("flex flex-wrap items-end gap-2 border-0 p-0", className), children: [
1198
+ /* @__PURE__ */ jsx("legend", { className: "sr-only", children: "Filters" }),
1199
+ filters.map((filter) => /* @__PURE__ */ jsx(
1200
+ FilterControl,
1201
+ {
1202
+ filter,
1203
+ value: values[filter.name],
1204
+ onChange: (v) => onChange(filter.name, v)
1205
+ },
1206
+ filter.name
1207
+ )),
1208
+ activeCount > 0 && onClearAll && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: onClearAll, children: [
1209
+ "Clear filters (",
1210
+ activeCount,
1211
+ ")"
1212
+ ] })
1213
+ ] });
1214
+ }
1215
+ function FilterControl({
1216
+ filter,
1217
+ value,
1218
+ onChange
1219
+ }) {
1220
+ switch (filter.type) {
1221
+ case "select":
1222
+ return /* @__PURE__ */ jsx(SelectFilter, { filter, value, onChange });
1223
+ case "multiSelect":
1224
+ return /* @__PURE__ */ jsx(MultiSelectFilter, { filter, value, onChange });
1225
+ case "text":
1226
+ return /* @__PURE__ */ jsx(TextFilter, { filter, value, onChange });
1227
+ case "ternary":
1228
+ return /* @__PURE__ */ jsx(TernaryFilter, { filter, value, onChange });
1229
+ case "dateRange":
1230
+ case "scope":
1231
+ return null;
1232
+ }
1233
+ }
1234
+ function controlClasses() {
1235
+ return cn(
1236
+ "h-9 rounded-sm border border-[var(--input)]",
1237
+ "bg-background px-3 text-sm",
1238
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1239
+ );
1240
+ }
1241
+ function SelectFilter({
1242
+ filter,
1243
+ value,
1244
+ onChange
1245
+ }) {
1246
+ return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
1247
+ filter.label ?? filter.name,
1248
+ /* @__PURE__ */ jsxs(
1249
+ "select",
1250
+ {
1251
+ className: controlClasses(),
1252
+ value: value === void 0 || value === null ? "" : String(value),
1253
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
1254
+ children: [
1255
+ /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
1256
+ filter.props.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
1257
+ ]
1258
+ }
1259
+ )
1260
+ ] });
1261
+ }
1262
+ function MultiSelectFilter({
1263
+ filter,
1264
+ value,
1265
+ onChange
1266
+ }) {
1267
+ const arr = Array.isArray(value) ? value : [];
1268
+ return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
1269
+ filter.label ?? filter.name,
1270
+ /* @__PURE__ */ jsx(
1271
+ "select",
1272
+ {
1273
+ multiple: true,
1274
+ className: cn(controlClasses(), "h-auto min-h-[2.25rem] py-1"),
1275
+ value: arr.map(String),
1276
+ onChange: (e) => {
1277
+ const next = Array.from(e.target.selectedOptions, (o) => o.value);
1278
+ onChange(next.length === 0 ? null : next);
1279
+ },
1280
+ children: filter.props.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
1281
+ }
1282
+ )
1283
+ ] });
1284
+ }
1285
+ function TextFilter({
1286
+ filter,
1287
+ value,
1288
+ onChange
1289
+ }) {
1290
+ return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
1291
+ filter.label ?? filter.name,
1292
+ /* @__PURE__ */ jsx(
1293
+ "input",
1294
+ {
1295
+ type: "text",
1296
+ className: controlClasses(),
1297
+ value: typeof value === "string" ? value : "",
1298
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
1299
+ }
1300
+ )
1301
+ ] });
1302
+ }
1303
+ function TernaryFilter({
1304
+ filter,
1305
+ value,
1306
+ onChange
1307
+ }) {
1308
+ const current = value === void 0 || value === null ? "all" : String(value);
1309
+ return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
1310
+ filter.label ?? filter.name,
1311
+ /* @__PURE__ */ jsxs(
1312
+ "select",
1313
+ {
1314
+ className: controlClasses(),
1315
+ value: current,
1316
+ onChange: (e) => {
1317
+ const v = e.target.value;
1318
+ onChange(v === "all" ? null : v);
1319
+ },
1320
+ children: [
1321
+ /* @__PURE__ */ jsx("option", { value: "all", children: filter.props.allLabel ?? "All" }),
1322
+ /* @__PURE__ */ jsx("option", { value: "true", children: filter.props.trueLabel ?? "Yes" }),
1323
+ /* @__PURE__ */ jsx("option", { value: "false", children: filter.props.falseLabel ?? "No" })
1324
+ ]
1325
+ }
1326
+ )
1327
+ ] });
1328
+ }
1329
+ var init_TableFilters = __esm({
1330
+ "src/table/TableFilters.tsx"() {
1331
+ init_Button();
1332
+ init_cn();
1333
+ }
1334
+ });
1335
+ function TablePagination({
1336
+ meta,
1337
+ onPageChange,
1338
+ onPerPageChange,
1339
+ perPageOptions = [10, 25, 50, 100],
1340
+ className
1341
+ }) {
1342
+ const from = (meta.currentPage - 1) * meta.perPage + 1;
1343
+ const to = Math.min(meta.currentPage * meta.perPage, meta.total);
1344
+ const isFirst = meta.currentPage <= 1;
1345
+ const isLast = meta.currentPage >= meta.lastPage;
1346
+ return /* @__PURE__ */ jsxs(
1347
+ "nav",
1348
+ {
1349
+ className: cn(
1350
+ "flex flex-wrap items-center justify-between gap-2 border-t border-border px-3 py-2 text-sm",
1351
+ className
1352
+ ),
1353
+ "aria-label": "Pagination",
1354
+ children: [
1355
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: meta.total === 0 ? "No results" : `${from}\u2013${to} of ${meta.total}` }),
1356
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1357
+ onPerPageChange && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
1358
+ "Per page",
1359
+ /* @__PURE__ */ jsx(
1360
+ "select",
1361
+ {
1362
+ className: "h-8 rounded-sm border border-[var(--input)] bg-background px-2",
1363
+ value: meta.perPage,
1364
+ onChange: (e) => onPerPageChange(Number(e.target.value)),
1365
+ children: perPageOptions.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
1366
+ }
1367
+ )
1368
+ ] }),
1369
+ /* @__PURE__ */ jsx(
1370
+ Button,
1371
+ {
1372
+ variant: "outline",
1373
+ size: "sm",
1374
+ disabled: isFirst,
1375
+ onClick: () => onPageChange(meta.currentPage - 1),
1376
+ "aria-label": "Previous page",
1377
+ children: "Prev"
1378
+ }
1379
+ ),
1380
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
1381
+ meta.currentPage,
1382
+ " / ",
1383
+ meta.lastPage
1384
+ ] }),
1385
+ /* @__PURE__ */ jsx(
1386
+ Button,
1387
+ {
1388
+ variant: "outline",
1389
+ size: "sm",
1390
+ disabled: isLast,
1391
+ onClick: () => onPageChange(meta.currentPage + 1),
1392
+ "aria-label": "Next page",
1393
+ children: "Next"
1394
+ }
1395
+ )
1396
+ ] })
1397
+ ]
1398
+ }
1399
+ );
1400
+ }
1401
+ var init_TablePagination = __esm({
1402
+ "src/table/TablePagination.tsx"() {
1403
+ init_Button();
1404
+ init_cn();
1405
+ }
1406
+ });
1407
+ function TableToolbar({
1408
+ search,
1409
+ filters,
1410
+ selectedCount = 0,
1411
+ bulkActions,
1412
+ onClearSelection,
1413
+ className
1414
+ }) {
1415
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3 px-3 py-3", className), children: [
1416
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [
1417
+ search,
1418
+ filters
1419
+ ] }),
1420
+ selectedCount > 0 && /* @__PURE__ */ jsxs(
1421
+ "section",
1422
+ {
1423
+ className: "flex flex-wrap items-center gap-2 rounded-sm border border-border bg-muted px-3 py-2",
1424
+ "aria-label": "Bulk actions",
1425
+ children: [
1426
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
1427
+ selectedCount,
1428
+ " selected"
1429
+ ] }),
1430
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [
1431
+ bulkActions,
1432
+ onClearSelection && /* @__PURE__ */ jsx(
1433
+ "button",
1434
+ {
1435
+ type: "button",
1436
+ className: "text-sm text-muted-foreground hover:underline",
1437
+ onClick: onClearSelection,
1438
+ children: "Clear"
1439
+ }
1440
+ )
1441
+ ] })
1442
+ ]
1443
+ }
1444
+ )
1445
+ ] });
1446
+ }
1447
+ var init_TableToolbar = __esm({
1448
+ "src/table/TableToolbar.tsx"() {
1449
+ init_cn();
1450
+ }
1451
+ });
1452
+ function ResourceIndex({
1453
+ resource,
1454
+ records,
1455
+ pagination,
1456
+ columns,
1457
+ filters,
1458
+ search,
1459
+ sort,
1460
+ selectedIds = [],
1461
+ filterValues = {},
1462
+ onSelectionChange,
1463
+ onSortChange,
1464
+ onPageChange,
1465
+ onPerPageChange,
1466
+ onSearchChange,
1467
+ onFilterChange,
1468
+ onClearFilters,
1469
+ searchSlot,
1470
+ toolbarActions,
1471
+ rowActions,
1472
+ bulkActions,
1473
+ emptyState,
1474
+ loading = false,
1475
+ className
1476
+ }) {
1477
+ const enableSelection = Boolean(onSelectionChange);
1478
+ const renderSearch = () => {
1479
+ if (searchSlot) return searchSlot;
1480
+ if (!onSearchChange) return null;
1481
+ return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
1482
+ "Search",
1483
+ /* @__PURE__ */ jsx(
1484
+ "input",
1485
+ {
1486
+ type: "search",
1487
+ placeholder: `Search ${resource.pluralLabel.toLowerCase()}\u2026`,
1488
+ className: "h-9 w-64 rounded-sm border border-[var(--input)] bg-background px-3 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1489
+ value: search ?? "",
1490
+ onChange: (e) => onSearchChange(e.target.value)
1491
+ }
1492
+ )
1493
+ ] });
1494
+ };
1495
+ const normalisedSort = sort?.column ? {
1496
+ column: sort.column,
1497
+ direction: sort.direction === "desc" ? "desc" : "asc"
1498
+ } : null;
1499
+ return /* @__PURE__ */ jsxs(
1500
+ "section",
1501
+ {
1502
+ className: cn("flex flex-col rounded-md border border-border bg-background", className),
1503
+ "aria-label": resource.pluralLabel,
1504
+ children: [
1505
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-center gap-3 border-b border-border px-4 py-3", children: [
1506
+ /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: resource.pluralLabel }),
1507
+ /* @__PURE__ */ jsx("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: toolbarActions })
1508
+ ] }),
1509
+ /* @__PURE__ */ jsx(
1510
+ TableToolbar,
1511
+ {
1512
+ search: renderSearch(),
1513
+ filters: filters.length > 0 ? /* @__PURE__ */ jsx(
1514
+ TableFilters,
1515
+ {
1516
+ filters,
1517
+ values: filterValues,
1518
+ onChange: (name, value) => onFilterChange?.(name, value),
1519
+ onClearAll: onClearFilters
1520
+ }
1521
+ ) : null,
1522
+ selectedCount: selectedIds.length,
1523
+ bulkActions,
1524
+ onClearSelection: onSelectionChange ? () => onSelectionChange([]) : void 0
1525
+ }
1526
+ ),
1527
+ /* @__PURE__ */ jsx(
1528
+ DataTable,
1529
+ {
1530
+ columns,
1531
+ records,
1532
+ enableSelection,
1533
+ selectedIds,
1534
+ onSelectionChange,
1535
+ sort: normalisedSort,
1536
+ onSortChange,
1537
+ rowActions: rowActions ? (record) => rowActions(record) : void 0,
1538
+ loading,
1539
+ emptyState
1540
+ }
1541
+ ),
1542
+ pagination && /* @__PURE__ */ jsx(
1543
+ TablePagination,
1544
+ {
1545
+ meta: pagination,
1546
+ onPageChange: (page) => onPageChange?.(page),
1547
+ onPerPageChange
1548
+ }
1549
+ )
1550
+ ]
1551
+ }
1552
+ );
1553
+ }
1554
+ var init_ResourceIndex = __esm({
1555
+ "src/resource/ResourceIndex.tsx"() {
1556
+ init_DataTable();
1557
+ init_TableFilters();
1558
+ init_TablePagination();
1559
+ init_TableToolbar();
1560
+ init_cn();
1561
+ }
1562
+ });
1563
+
1564
+ // src/pages/ArqelIndexPage.tsx
1565
+ var ArqelIndexPage_exports = {};
1566
+ __export(ArqelIndexPage_exports, {
1567
+ default: () => ArqelIndexPage
1568
+ });
1569
+ function ArqelIndexPage() {
1570
+ const page = usePage();
1571
+ const props = page.props;
1572
+ return /* @__PURE__ */ jsx(ResourceIndex, { ...props });
1573
+ }
1574
+ var init_ArqelIndexPage = __esm({
1575
+ "src/pages/ArqelIndexPage.tsx"() {
1576
+ init_ResourceIndex();
1577
+ }
1578
+ });
1579
+
1580
+ // src/pages/ArqelShowPage.tsx
1581
+ var ArqelShowPage_exports = {};
1582
+ __export(ArqelShowPage_exports, {
1583
+ default: () => ArqelShowPage
1584
+ });
1585
+ function ArqelShowPage() {
1586
+ const page = usePage();
1587
+ const props = page.props;
1588
+ const fields = props.fields ?? [];
1589
+ const values = {};
1590
+ for (const field of fields) {
1591
+ const name = field.name;
1592
+ if (typeof name === "string" && props.record !== null && typeof props.record === "object") {
1593
+ values[name] = props.record[name];
1594
+ }
1595
+ }
1596
+ const declaredForm = props.form;
1597
+ const schema = declaredForm ? { ...declaredForm, disabled: true } : FALLBACK_SCHEMA3;
1598
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
1599
+ /* @__PURE__ */ jsx(
1600
+ PageHeader,
1601
+ {
1602
+ title: props.recordTitle ?? props.resource?.label ?? "Record",
1603
+ description: props.recordSubtitle ?? null
1604
+ }
1605
+ ),
1606
+ /* @__PURE__ */ jsx(
1607
+ FormRenderer,
1608
+ {
1609
+ schema,
1610
+ fields,
1611
+ values,
1612
+ onChange: () => {
1613
+ },
1614
+ errors: {}
1615
+ }
1616
+ )
1617
+ ] });
1618
+ }
1619
+ var FALLBACK_SCHEMA3;
1620
+ var init_ArqelShowPage = __esm({
1621
+ "src/pages/ArqelShowPage.tsx"() {
1622
+ init_FormRenderer();
1623
+ init_PageHeader();
1624
+ FALLBACK_SCHEMA3 = {
1625
+ schema: [],
1626
+ columns: 1,
1627
+ model: null,
1628
+ inline: false,
1629
+ disabled: true
1630
+ };
1631
+ }
1632
+ });
1633
+
1634
+ // src/pages/index.ts
1635
+ init_ArqelCreatePage();
1636
+ init_ArqelEditPage();
1637
+ init_ArqelIndexPage();
1638
+ init_ArqelShowPage();
1639
+ var arqelPages = {
1640
+ "arqel::index": () => Promise.resolve().then(() => (init_ArqelIndexPage(), ArqelIndexPage_exports)),
1641
+ "arqel::create": () => Promise.resolve().then(() => (init_ArqelCreatePage(), ArqelCreatePage_exports)),
1642
+ "arqel::edit": () => Promise.resolve().then(() => (init_ArqelEditPage(), ArqelEditPage_exports)),
1643
+ "arqel::show": () => Promise.resolve().then(() => (init_ArqelShowPage(), ArqelShowPage_exports))
1644
+ };
1645
+
1646
+ export { ArqelCreatePage, ArqelEditPage, ArqelIndexPage, ArqelShowPage, arqelPages };
1647
+ //# sourceMappingURL=pages.js.map
1648
+ //# sourceMappingURL=pages.js.map