@rsc-kit/press 0.30.51 → 0.30.52

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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/admin/panel.js +4 -1
  3. package/dist/admin/panel.js.map +1 -1
  4. package/dist/conditions.d.ts +32 -0
  5. package/dist/conditions.js +25 -0
  6. package/dist/conditions.js.map +1 -0
  7. package/dist/forms/actions.d.ts +6 -5
  8. package/dist/forms/actions.js +13 -8
  9. package/dist/forms/actions.js.map +1 -1
  10. package/dist/forms/blueprint.d.ts +99 -0
  11. package/dist/forms/blueprint.js +141 -0
  12. package/dist/forms/blueprint.js.map +1 -0
  13. package/dist/forms/brand.d.ts +26 -0
  14. package/dist/forms/brand.js +9 -0
  15. package/dist/forms/brand.js.map +1 -0
  16. package/dist/forms/host.d.ts +1 -1
  17. package/dist/forms/host.js.map +1 -1
  18. package/dist/forms/index.d.ts +3 -3
  19. package/dist/forms/index.js +1 -1
  20. package/dist/forms/index.js.map +1 -1
  21. package/dist/forms/registry.d.ts +6 -1
  22. package/dist/forms/registry.js +1 -1
  23. package/dist/forms/registry.js.map +1 -1
  24. package/dist/forms/theme.d.ts +10 -7
  25. package/dist/forms/theme.js.map +1 -1
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/resource.d.ts +23 -1
  30. package/dist/resource.js +80 -16
  31. package/dist/resource.js.map +1 -1
  32. package/dist/shadcn/blueprint-form.d.ts +8 -0
  33. package/dist/shadcn/{public-form.js → blueprint-form.js} +7 -6
  34. package/dist/shadcn/blueprint-form.js.map +1 -0
  35. package/dist/shadcn/fields.d.ts +11 -1
  36. package/dist/shadcn/fields.js +72 -10
  37. package/dist/shadcn/fields.js.map +1 -1
  38. package/dist/shadcn/index.d.ts +1 -1
  39. package/dist/shadcn/index.js +3 -3
  40. package/dist/shadcn/index.js.map +1 -1
  41. package/dist/shadcn/resource-form.js +3 -2
  42. package/dist/shadcn/resource-form.js.map +1 -1
  43. package/package.json +2 -2
  44. package/dist/forms/form.d.ts +0 -61
  45. package/dist/forms/form.js +0 -66
  46. package/dist/forms/form.js.map +0 -1
  47. package/dist/shadcn/public-form.d.ts +0 -8
  48. package/dist/shadcn/public-form.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useState } from 'react';
4
+ import { shown, slugify } from '../conditions.js';
4
5
  import { ArrowDown, ArrowUp, FileIcon, Plus, Trash2 } from 'lucide-react';
5
6
  import { sizeOf } from '../uploads.js';
6
7
  import { Button } from './components/ui/button.js';
@@ -16,19 +17,80 @@ import { Textarea } from './components/ui/textarea.js';
16
17
  * panel's forms and the public ones, so a field looks and checks the same in
17
18
  * both.
18
19
  */
19
- export function FieldList({ fields, values, error }) {
20
+ export function FieldList({ fields, values, error, hidden, }) {
20
21
  return fields.map((field) => {
21
- const id = `field-${field.name}`;
22
- const invalid = !!error(field.name);
23
- if (field.type === 'blocks') {
24
- return _jsx(BlocksField, { field: field, value: values[field.name], error: error }, field.name);
25
- }
26
- if (field.type === 'toggle') {
27
- return (_jsxs(Field, { orientation: "horizontal", "data-invalid": invalid || undefined, children: [_jsxs(FieldContent, { children: [_jsx(FieldLabel, { htmlFor: id, children: field.label }), field.description && _jsx(FieldDescription, { children: field.description }), invalid && _jsx(FieldError, { children: error(field.name) })] }), _jsx(Switch, { id: id, name: field.name, defaultChecked: Boolean(values[field.name]), "aria-invalid": invalid || undefined })] }, field.name));
28
- }
29
- return (_jsxs(Field, { "data-invalid": invalid || undefined, children: [_jsxs(FieldLabel, { htmlFor: id, children: [field.label, field.required && _jsx("span", { className: "text-destructive", children: "*" })] }), field.type === 'file' || field.type === 'image' ? (_jsx(FileControl, { id: id, field: field, value: values[field.name], invalid: invalid }, String(values[field.name] ?? ''))) : (_jsx(Control, { id: id, field: field, value: values[field.name], invalid: invalid })), (field.description ?? limitOf(field)) && _jsx(FieldDescription, { children: field.description ?? limitOf(field) }), invalid && _jsx(FieldError, { children: error(field.name) })] }, field.name));
22
+ const control = fieldOf(field, values, error);
23
+ if (!field.visible)
24
+ return control;
25
+ // Disabled, a hidden field's controls are neither sent nor checked by the browser.
26
+ const off = hidden?.has(field.name) ?? false;
27
+ return (_jsx("fieldset", { disabled: off, hidden: off, className: off ? 'hidden' : 'contents', children: control }, field.name));
30
28
  });
31
29
  }
30
+ /**
31
+ * The fields' conditions and slugs, applied while the visitor types: which
32
+ * fields are hidden now, read from the form's own controls - the form stays
33
+ * uncontrolled, so nothing typed is lost to a render. A slug follows its
34
+ * source until it is edited by hand. The server applies both again.
35
+ */
36
+ export function useConditions(form, fields, values) {
37
+ const initial = (name) => {
38
+ const value = values[name];
39
+ return typeof value === 'boolean' ? value : value === undefined || value === null ? '' : String(value);
40
+ };
41
+ const [hidden, setHidden] = useState(() => new Set(fields.filter((field) => field.visible && !shown(field.visible, initial)).map((field) => field.name)));
42
+ useEffect(() => {
43
+ const element = form.current;
44
+ const conditional = fields.filter((field) => field.visible);
45
+ const derived = fields.filter((field) => field.from);
46
+ if (!element || (conditional.length === 0 && derived.length === 0))
47
+ return;
48
+ const types = new Map(fields.map((field) => [field.name, field.type]));
49
+ // What each slug was last made as: it follows while it still says that.
50
+ const last = new Map(derived.map((field) => [field.name, slugify(String(initial(field.from.field)))]));
51
+ const update = () => {
52
+ const data = new FormData(element);
53
+ const read = (name) => (types.get(name) === 'toggle' ? data.has(name) : String(data.get(name) ?? ''));
54
+ for (const field of derived) {
55
+ const target = element.elements.namedItem(field.name);
56
+ const made = slugify(String(read(field.from.field)));
57
+ if (target instanceof HTMLInputElement && (target.value === '' || target.value === last.get(field.name)))
58
+ target.value = made;
59
+ last.set(field.name, made);
60
+ }
61
+ const now = new Set(conditional.filter((field) => !shown(field.visible, read)).map((field) => field.name));
62
+ setHidden((before) => (before.size === now.size && [...now].every((name) => before.has(name)) ? before : now));
63
+ };
64
+ // A select sets its hidden input after the click or key that chose it,
65
+ // with no event of its own, from a list drawn outside the form - so
66
+ // clicks and keys anywhere are read a frame later.
67
+ const soon = () => requestAnimationFrame(update);
68
+ const typed = [['input', update], ['change', update]];
69
+ const chosen = [['click', soon], ['keyup', soon]];
70
+ for (const [name, listener] of typed)
71
+ element.addEventListener(name, listener);
72
+ for (const [name, listener] of chosen)
73
+ document.addEventListener(name, listener);
74
+ return () => {
75
+ for (const [name, listener] of typed)
76
+ element.removeEventListener(name, listener);
77
+ for (const [name, listener] of chosen)
78
+ document.removeEventListener(name, listener);
79
+ };
80
+ }, [form, fields]);
81
+ return hidden;
82
+ }
83
+ function fieldOf(field, values, error) {
84
+ const id = `field-${field.name}`;
85
+ const invalid = !!error(field.name);
86
+ if (field.type === 'blocks') {
87
+ return _jsx(BlocksField, { field: field, value: values[field.name], error: error }, field.name);
88
+ }
89
+ if (field.type === 'toggle') {
90
+ return (_jsxs(Field, { orientation: "horizontal", "data-invalid": invalid || undefined, children: [_jsxs(FieldContent, { children: [_jsx(FieldLabel, { htmlFor: id, children: field.label }), field.description && _jsx(FieldDescription, { children: field.description }), invalid && _jsx(FieldError, { children: error(field.name) })] }), _jsx(Switch, { id: id, name: field.name, defaultChecked: Boolean(values[field.name]), "aria-invalid": invalid || undefined })] }, field.name));
91
+ }
92
+ return (_jsxs(Field, { "data-invalid": invalid || undefined, children: [_jsxs(FieldLabel, { htmlFor: id, children: [field.label, field.required && _jsx("span", { className: "text-destructive", children: "*" })] }), field.type === 'file' || field.type === 'image' ? (_jsx(FileControl, { id: id, field: field, value: values[field.name], invalid: invalid }, String(values[field.name] ?? ''))) : (_jsx(Control, { id: id, field: field, value: values[field.name], invalid: invalid })), (field.description ?? limitOf(field)) && _jsx(FieldDescription, { children: field.description ?? limitOf(field) }), invalid && _jsx(FieldError, { children: error(field.name) })] }, field.name));
93
+ }
32
94
  /**
33
95
  * A list of blocks an editor adds, orders and removes, each with its own
34
96
  * fields. Every control stays an ordinary named input - block i's heading is
@@ -1 +1 @@
1
- {"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/shadcn/fields.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAC3H,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAC3I,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACzG,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAyG;IACxJ,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC1B,MAAM,EAAE,GAAG,SAAS,KAAK,CAAC,IAAI,EAAE,CAAA;QAChC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAEnC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,KAAC,WAAW,IAAkB,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,IAAjE,KAAK,CAAC,IAAI,CAA2D,CAAA;QAChG,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,CACL,MAAC,KAAK,IAAkB,WAAW,EAAC,YAAY,kBAAe,OAAO,IAAI,SAAS,aACjF,MAAC,YAAY,eACX,KAAC,UAAU,IAAC,OAAO,EAAE,EAAE,YAAG,KAAK,CAAC,KAAK,GAAc,EAClD,KAAK,CAAC,WAAW,IAAI,KAAC,gBAAgB,cAAE,KAAK,CAAC,WAAW,GAAoB,EAC7E,OAAO,IAAI,KAAC,UAAU,cAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAc,IAC3C,EACf,KAAC,MAAM,IAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,kBAAgB,OAAO,IAAI,SAAS,GAAI,KAN3G,KAAK,CAAC,IAAI,CAOd,CACT,CAAA;QACH,CAAC;QAED,OAAO,CACL,MAAC,KAAK,oBAAgC,OAAO,IAAI,SAAS,aACxD,MAAC,UAAU,IAAC,OAAO,EAAE,EAAE,aACpB,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,QAAQ,IAAI,eAAM,SAAS,EAAC,kBAAkB,kBAAS,IACnD,EACZ,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CACjD,KAAC,WAAW,IAAwC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,IAAnG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAuE,CAC1H,CAAC,CAAC,CAAC,CACF,KAAC,OAAO,IAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAI,CAC/E,EACA,CAAC,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,KAAC,gBAAgB,cAAE,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,GAAoB,EACnH,OAAO,IAAI,KAAC,UAAU,cAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAc,KAX9C,KAAK,CAAC,IAAI,CAYd,CACT,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAQD;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAqF;IAC7H,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAA;IAChC,MAAM,MAAM,GAAG,KAAK,EAAE,CAAA;IACtB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAA4D,CAAC,CAAC,CAAC,EAAE,CAAA;IACvG,yEAAyE;IACzE,uBAAuB;IACvB,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAA;IAC7F,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAS,GAAG,EAAE,CAC9C,KAAK;SACF,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;SAC/D,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CACjG,CAAA;IACD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAEnC,MAAM,GAAG,GAAG,CAAC,IAAe,EAAE,EAAE;QAC9B,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,MAAM,QAAQ,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAChG,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IAC/B,CAAC,CAAA;IAED,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE,CACxC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;QACnB,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;QACzB,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAEnC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,IAAK,CAAC,CAAA;QAEzB,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;IAEJ,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAA;IAEhG,OAAO,CACL,MAAC,QAAQ,eACP,KAAC,WAAW,IAAC,OAAO,EAAC,OAAO,YAAE,KAAK,CAAC,KAAK,GAAe,EACvD,KAAK,CAAC,WAAW,IAAI,KAAC,gBAAgB,cAAE,KAAK,CAAC,WAAW,GAAoB,EAC7E,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,YAAG,SAAS,EAAC,+BAA+B,+BAAmB,EACrF,OAAO,GAAG,CAAC,IAAI,CACd,aAAG,SAAS,EAAC,+BAA+B,aACzC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,aAAa,uDAAmD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,SACvI,CACL,EACA,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBACzB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAE,CAAA;gBACrE,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,GAAG,CAAA;gBAEtC,OAAO,CACL,eAAoB,SAAS,EAAC,mBAAmB,aAC/C,eAAK,SAAS,EAAC,4CAA4C,aACzD,eAAM,SAAS,EAAC,qBAAqB,YAAE,IAAI,CAAC,KAAK,GAAQ,EACzD,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,GAAG,IAAI,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,GAAI,EAC9D,eAAK,SAAS,EAAC,oBAAoB,aACjC,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,SAAS,gBAAY,SAAS,EAAC,QAAQ,EAAE,KAAK,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,YACpI,KAAC,OAAO,KAAG,GACJ,EACT,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACH,WAAW,EACtB,QAAQ,EAAE,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EACpC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,YAErC,KAAC,SAAS,KAAG,GACN,EACT,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,SAAS,gBAAY,QAAQ,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YACnG,KAAC,MAAM,KAAG,GACH,IACL,IACF,EACN,cAAK,SAAS,EAAC,KAAK,YAClB,KAAC,UAAU,cACT,KAAC,SAAS,IACR,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,EAC3E,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAChG,KAAK,EAAE,KAAK,GACZ,GACS,GACT,EACL,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,IAAI,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,GAAc,KAhC5G,IAAI,CAAC,GAAG,CAiCZ,CACP,CAAA;YACH,CAAC,CAAC,EACF,wBACE,MAAC,YAAY,eACX,MAAC,mBAAmB,IAAC,MAAM,EAAE,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,SAAS,GAAG,aACrE,KAAC,IAAI,KAAG,iBAEY,EACtB,KAAC,mBAAmB,IAAC,KAAK,EAAC,OAAO,YAC/B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,KAAC,gBAAgB,IAAiB,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,YACvD,IAAI,CAAC,KAAK,IADU,IAAI,CAAC,IAAI,CAEb,CACpB,CAAC,GACkB,IACT,GACX,IACG,CACZ,CAAA;AACH,CAAC;AAID,MAAM,SAAS,GAAG,CAAC,MAA0B,EAAqB,EAAE,CAClE,MAAM,YAAY,gBAAgB,IAAI,MAAM,YAAY,mBAAmB,IAAI,MAAM,YAAY,iBAAiB,CAAA;AAEpH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA6C;IAC5E,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAyB,EAAE,CAAC,CAAA;IAEhE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAE5B,IAAI,CAAC,OAAO;YAAE,OAAM;QAEpB,IAAI,QAAQ,GAAG,KAAK,CAAA;QAEpB,MAAM,SAAS,GAAG,CAAC,KAAY,EAAE,EAAE;YACjC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI;gBAAE,OAAM;YAE1D,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;YAEhD,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAA;YAEnH,sEAAsE;YACtE,wEAAwE;YACxE,+DAA+D;YAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAA;gBACf,UAAU,CAAC,GAAG,EAAE;oBACd,QAAQ,GAAG,KAAK,CAAA;oBAChB,OAAO,CAAC,aAAa,CAAU,UAAU,CAAC,EAAE,KAAK,EAAE,CAAA;gBACrD,CAAC,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAA;QAED,MAAM,MAAM,GAAG,CAAC,KAAY,EAAE,EAAE;YAC9B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI;gBAAE,OAAM;YAE1D,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;YAE1D,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE;gBACpB,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC;oBAAE,OAAO,OAAO,CAAA;gBACtC,IAAI,CAAC,QAAQ,CAAC,KAAK;oBAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,CAAA;gBAErE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAA;gBAEtC,OAAO,IAAI,CAAA;YACb,CAAC,CAAC,CAAA;QACJ,CAAC,CAAA;QAED,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;QACpD,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QACzC,OAAO,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAE1C,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;YACvD,OAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YAC5C,OAAO,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC/C,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAsE;IAChH,MAAM,MAAM,GAAG;QACb,EAAE;QACF,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,cAAc,EAAE,OAAO,IAAI,SAAS;KACrC,CAAA;IACD,MAAM,OAAO,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAA;IAE1E,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,KAAC,QAAQ,OAAK,MAAM,KAAM,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,GAAI,CAAA;QAC1F,KAAK,QAAQ,EAAE,CAAC;YACd,oEAAoE;YACpE,kEAAkE;YAClE,qEAAqE;YACrE,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;YAEjH,OAAO,CACL,MAAC,MAAM,IAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,aACvI,KAAC,aAAa,IAAC,EAAE,EAAE,EAAE,kBAAgB,OAAO,IAAI,SAAS,EAAE,SAAS,EAAC,QAAQ,YAC3E,KAAC,WAAW,KAAG,GACD,EAChB,KAAC,aAAa,cACX,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACvB,KAAC,UAAU,IAAoB,KAAK,EAAE,MAAM,CAAC,KAAK,YAC/C,MAAM,CAAC,KAAK,IADE,MAAM,CAAC,KAAK,CAEhB,CACd,CAAC,GACY,IACT,CACV,CAAA;QACH,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,CACL,KAAC,KAAK,OACA,MAAM,EACV,IAAI,EAAC,QAAQ,EACb,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,EACzB,YAAY,EAAE,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GACxE,CACH,CAAA;QACH,KAAK,OAAO;YACV,OAAO,KAAC,KAAK,OAAK,MAAM,KAAM,OAAO,EAAE,IAAI,EAAC,OAAO,EAAC,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,GAAI,CAAA;QAC3F,KAAK,MAAM;YACT,OAAO,KAAC,KAAK,OAAK,MAAM,EAAE,IAAI,EAAC,MAAM,EAAC,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAI,CAAA;QAC1F;YACE,OAAO,KAAC,KAAK,OAAK,MAAM,KAAM,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,GAAI,CAAA;IACxG,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAS,OAAO,CAAC,KAAgB;IAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AACzI,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAsE;IACpH,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACxE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAE3D,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;QACnB,IAAI,OAAO;YAAE,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAC3C,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,MAAM,MAAM,GAAG,CAAC,KAA0C,EAAE,EAAE;QAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAA;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QAE7B,KAAK,CAAC,iBAAiB,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACrJ,UAAU,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACvG,CAAC,CAAA;IAED,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAA;IAEvC,OAAO,CACL,eAAK,SAAS,EAAC,qBAAqB,aACjC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,cAAK,GAAG,EAAE,KAAK,EAAE,GAAG,EAAC,EAAE,EAAC,SAAS,EAAC,sDAAsD,GAAG,EAC9H,IAAI,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,CAC5C,aAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,EAAC,SAAS,EAAC,0EAA0E,aAClI,KAAC,QAAQ,IAAC,SAAS,EAAC,QAAQ,GAAG,EAC9B,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,IACpE,CACL,EACD,eAAK,SAAS,EAAC,yBAAyB,aACtC,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,KAAK,CAAC,IAAI,EAChB,IAAI,EAAC,MAAM,EACX,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,CAAC,IAAI,kBACnB,OAAO,IAAI,SAAS,EAClC,QAAQ,EAAE,MAAM,GAChB,EACD,IAAI,IAAI,KAAK,CAAC,QAAQ,IAAI,CACzB,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,uBAEzD,CACV,IACG,EACN,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,GAAI,IAClD,CACP,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { useEffect, useId, useState } from 'react'\nimport { ArrowDown, ArrowUp, FileIcon, Plus, Trash2 } from 'lucide-react'\nimport type { BlockSpec, Field as FieldSpec } from '../resource.js'\nimport { sizeOf } from '../uploads.js'\nimport { Button } from './components/ui/button.js'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './components/ui/dropdown-menu.js'\nimport { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSet } from './components/ui/field.js'\nimport { Input } from './components/ui/input.js'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './components/ui/select.js'\nimport { Switch } from './components/ui/switch.js'\nimport { Textarea } from './components/ui/textarea.js'\n\n/**\n * Each field as its shadcn control, labelled, with its message under it: the\n * browser's while the value is still wrong, else the server's. Shared by the\n * panel's forms and the public ones, so a field looks and checks the same in\n * both.\n */\nexport function FieldList({ fields, values, error }: { fields: FieldSpec[]; values: Record<string, unknown>; error: (name: string) => string | undefined }) {\n return fields.map((field) => {\n const id = `field-${field.name}`\n const invalid = !!error(field.name)\n\n if (field.type === 'blocks') {\n return <BlocksField key={field.name} field={field} value={values[field.name]} error={error} />\n }\n\n if (field.type === 'toggle') {\n return (\n <Field key={field.name} orientation=\"horizontal\" data-invalid={invalid || undefined}>\n <FieldContent>\n <FieldLabel htmlFor={id}>{field.label}</FieldLabel>\n {field.description && <FieldDescription>{field.description}</FieldDescription>}\n {invalid && <FieldError>{error(field.name)}</FieldError>}\n </FieldContent>\n <Switch id={id} name={field.name} defaultChecked={Boolean(values[field.name])} aria-invalid={invalid || undefined} />\n </Field>\n )\n }\n\n return (\n <Field key={field.name} data-invalid={invalid || undefined}>\n <FieldLabel htmlFor={id}>\n {field.label}\n {field.required && <span className=\"text-destructive\">*</span>}\n </FieldLabel>\n {field.type === 'file' || field.type === 'image' ? (\n <FileControl key={String(values[field.name] ?? '')} id={id} field={field} value={values[field.name]} invalid={invalid} />\n ) : (\n <Control id={id} field={field} value={values[field.name]} invalid={invalid} />\n )}\n {(field.description ?? limitOf(field)) && <FieldDescription>{field.description ?? limitOf(field)}</FieldDescription>}\n {invalid && <FieldError>{error(field.name)}</FieldError>}\n </Field>\n )\n })\n}\n\ninterface Item {\n key: string\n type: string\n data: Record<string, unknown>\n}\n\n/**\n * A list of blocks an editor adds, orders and removes, each with its own\n * fields. Every control stays an ordinary named input - block i's heading is\n * `content.i.heading` - so it submits with the form, is checked by the\n * browser like any other, and shows the server's refusal under itself. Each\n * block keeps a key of its own, so moving one moves what was typed into it.\n */\nfunction BlocksField({ field, value, error }: { field: FieldSpec; value: unknown; error: (name: string) => string | undefined }) {\n const kinds = field.blocks ?? []\n const prefix = useId()\n const saved = Array.isArray(value) ? (value as { type: string; data?: Record<string, unknown> }[]) : []\n // A kind the app no longer offers has no fields to draw; it is left out,\n // and saving drops it.\n const retired = saved.filter((item) => !kinds.some((kind) => kind.name === item.type)).length\n const [items, setItems] = useState<Item[]>(() =>\n saved\n .filter((item) => kinds.some((kind) => kind.name === item.type))\n .map((item, index) => ({ key: `${prefix}-${index}`, type: item.type, data: item.data ?? {} })),\n )\n const [made, setMade] = useState(0)\n\n const add = (kind: BlockSpec) => {\n setItems((current) => [...current, { key: `${prefix}-new-${made}`, type: kind.name, data: {} }])\n setMade((count) => count + 1)\n }\n\n const move = (from: number, to: number) =>\n setItems((current) => {\n const next = [...current]\n const [item] = next.splice(from, 1)\n\n next.splice(to, 0, item!)\n\n return next\n })\n\n const remove = (at: number) => setItems((current) => current.filter((_, index) => index !== at))\n\n return (\n <FieldSet>\n <FieldLegend variant=\"label\">{field.label}</FieldLegend>\n {field.description && <FieldDescription>{field.description}</FieldDescription>}\n {items.length === 0 && <p className=\"text-sm text-muted-foreground\">No blocks yet.</p>}\n {retired > 0 && (\n <p className=\"text-sm text-muted-foreground\">\n {retired === 1 ? 'One block is' : `${retired} blocks are`} of a kind no longer offered, and saving removes {retired === 1 ? 'it' : 'them'}.\n </p>\n )}\n {items.map((item, index) => {\n const kind = kinds.find((candidate) => candidate.name === item.type)!\n const base = `${field.name}.${index}.`\n\n return (\n <div key={item.key} className=\"rounded-lg border\">\n <div className=\"flex items-center gap-1 border-b px-3 py-2\">\n <span className=\"text-sm font-medium\">{kind.label}</span>\n <input type=\"hidden\" name={`${base}type`} value={item.type} />\n <div className=\"ml-auto flex gap-1\">\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" aria-label=\"Move up\" disabled={index === 0} onClick={() => move(index, index - 1)}>\n <ArrowUp />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Move down\"\n disabled={index === items.length - 1}\n onClick={() => move(index, index + 1)}\n >\n <ArrowDown />\n </Button>\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" aria-label=\"Remove\" onClick={() => remove(index)}>\n <Trash2 />\n </Button>\n </div>\n </div>\n <div className=\"p-3\">\n <FieldGroup>\n <FieldList\n fields={kind.fields.map((inner) => ({ ...inner, name: base + inner.name }))}\n values={Object.fromEntries(Object.entries(item.data).map(([name, data]) => [base + name, data]))}\n error={error}\n />\n </FieldGroup>\n </div>\n {error(`${field.name}.${index}`) && <FieldError className=\"px-3 pb-3\">{error(`${field.name}.${index}`)}</FieldError>}\n </div>\n )\n })}\n <div>\n <DropdownMenu>\n <DropdownMenuTrigger render={<Button type=\"button\" variant=\"outline\" />}>\n <Plus />\n Add block\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {kinds.map((kind) => (\n <DropdownMenuItem key={kind.name} onClick={() => add(kind)}>\n {kind.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </FieldSet>\n )\n}\n\ntype Control = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n\nconst isControl = (target: EventTarget | null): target is Control =>\n target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement\n\n/**\n * The browser's own validation, shown the shadcn way. A submit fires\n * 'invalid' at each control that fails its constraints; that is cancelled -\n * no bubble - and its validationMessage, in the visitor's language, is kept\n * under its name. Fixing the value clears it. 'invalid' does not bubble, so\n * the form listens in the capture phase.\n */\nexport function useBrowserErrors(form: React.RefObject<HTMLFormElement | null>): Record<string, string> {\n const [errors, setErrors] = useState<Record<string, string>>({})\n\n useEffect(() => {\n const element = form.current\n\n if (!element) return\n\n let focusing = false\n\n const onInvalid = (event: Event) => {\n if (!isControl(event.target) || !event.target.name) return\n\n const { name, validationMessage } = event.target\n\n event.preventDefault()\n setErrors((current) => (current[name] === validationMessage ? current : { ...current, [name]: validationMessage }))\n\n // Cancelling the bubble also cancels the browser's focus on the first\n // wrong field. Every control's 'invalid' fires before the next task, so\n // give focus back once, to the first one, after they all have.\n if (!focusing) {\n focusing = true\n setTimeout(() => {\n focusing = false\n element.querySelector<Control>(':invalid')?.focus()\n })\n }\n }\n\n const onEdit = (event: Event) => {\n if (!isControl(event.target) || !event.target.name) return\n\n const { name, validity, validationMessage } = event.target\n\n setErrors((current) => {\n if (!(name in current)) return current\n if (!validity.valid) return { ...current, [name]: validationMessage }\n\n const { [name]: _, ...rest } = current\n\n return rest\n })\n }\n\n element.addEventListener('invalid', onInvalid, true)\n element.addEventListener('input', onEdit)\n element.addEventListener('change', onEdit)\n\n return () => {\n element.removeEventListener('invalid', onInvalid, true)\n element.removeEventListener('input', onEdit)\n element.removeEventListener('change', onEdit)\n }\n }, [form])\n\n return errors\n}\n\nfunction Control({ id, field, value, invalid }: { id: string; field: FieldSpec; value: unknown; invalid: boolean }) {\n const shared = {\n id,\n name: field.name,\n required: field.required,\n 'aria-invalid': invalid || undefined,\n }\n const lengths = { minLength: field.minLength, maxLength: field.maxLength }\n\n switch (field.type) {\n case 'textarea':\n return <Textarea {...shared} {...lengths} rows={4} defaultValue={String(value ?? '')} />\n case 'select': {\n // An optional select can be left empty: its first choice is \"None\",\n // which arrives as \"\" and is left out of the values, as any empty\n // optional field is. A required one starts on its first real choice.\n const options = field.required ? (field.options ?? []) : [{ value: '', label: 'None' }, ...(field.options ?? [])]\n\n return (\n <Select name={field.name} items={options} defaultValue={value === null || value === undefined ? (options[0]?.value ?? '') : String(value)}>\n <SelectTrigger id={id} aria-invalid={invalid || undefined} className=\"w-full\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n case 'number':\n return (\n <Input\n {...shared}\n type=\"number\"\n min={field.min}\n max={field.max}\n step={field.step ?? 'any'}\n defaultValue={value === undefined || value === null ? '' : String(value)}\n />\n )\n case 'email':\n return <Input {...shared} {...lengths} type=\"email\" defaultValue={String(value ?? '')} />\n case 'date':\n return <Input {...shared} type=\"date\" defaultValue={String(value ?? '').slice(0, 10)} />\n default:\n return <Input {...shared} {...lengths} pattern={field.pattern} defaultValue={String(value ?? '')} />\n }\n}\n\n/** A file field's size limit, said under it when the app said nothing else. */\nfunction limitOf(field: FieldSpec): string | undefined {\n return (field.type === 'file' || field.type === 'image') && field.maxSize !== undefined ? `Up to ${sizeOf(field.maxSize)}.` : undefined\n}\n\n/**\n * A file or image field: what it holds now, and a file input to replace it.\n * The value it holds goes back with the form in a hidden input under the\n * same name, so a save that chooses nothing keeps it; the server keeps it\n * only if the record already had it. A file over the size limit is refused\n * as it is chosen, through the input's own validity, so it shows as any\n * other field's message and never makes the trip.\n */\nfunction FileControl({ id, field, value, invalid }: { id: string; field: FieldSpec; value: unknown; invalid: boolean }) {\n const [kept, setKept] = useState(typeof value === 'string' ? value : '')\n const [preview, setPreview] = useState<string | null>(null)\n\n useEffect(() => () => {\n if (preview) URL.revokeObjectURL(preview)\n }, [preview])\n\n const choose = (event: React.ChangeEvent<HTMLInputElement>) => {\n const input = event.currentTarget\n const file = input.files?.[0]\n\n input.setCustomValidity(file && field.maxSize !== undefined && file.size > field.maxSize ? `Choose a file of ${sizeOf(field.maxSize)} or less.` : '')\n setPreview(file && field.type === 'image' && input.validity.valid ? URL.createObjectURL(file) : null)\n }\n\n const shown = preview ?? (kept || null)\n\n return (\n <div className=\"flex flex-col gap-3\">\n {shown && field.type === 'image' && <img src={shown} alt=\"\" className=\"h-32 w-fit max-w-full rounded-md border object-cover\" />}\n {kept && !preview && field.type === 'file' && (\n <a href={kept} target=\"_blank\" rel=\"noreferrer\" className=\"flex w-fit items-center gap-2 text-sm underline-offset-4 hover:underline\">\n <FileIcon className=\"size-4\" />\n {decodeURIComponent(kept.split(/[/?#]/).filter(Boolean).pop() ?? kept)}\n </a>\n )}\n <div className=\"flex items-center gap-2\">\n <Input\n id={id}\n name={field.name}\n type=\"file\"\n accept={field.accept}\n required={field.required && !kept}\n aria-invalid={invalid || undefined}\n onChange={choose}\n />\n {kept && field.nullable && (\n <Button type=\"button\" variant=\"outline\" onClick={() => setKept('')}>\n Remove\n </Button>\n )}\n </div>\n <input type=\"hidden\" name={field.name} value={kept} />\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/shadcn/fields.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAElD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAEjD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAC3H,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAC3I,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACzG,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,MAAM,EACN,MAAM,EACN,KAAK,EACL,MAAM,GAOP;IACC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;QAE7C,IAAI,CAAC,KAAK,CAAC,OAAO;YAAE,OAAO,OAAO,CAAA;QAElC,mFAAmF;QACnF,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAA;QAE5C,OAAO,CACL,mBAA2B,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,YAC1F,OAAO,IADK,KAAK,CAAC,IAAI,CAEd,CACZ,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAuC,EAAE,MAAmB,EAAE,MAA+B;IACzH,MAAM,OAAO,GAAS,CAAC,IAAI,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;QAE1B,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACxG,CAAC,CAAA;IACD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAsB,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAE9K,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAC5B,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC3D,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAEpD,IAAI,CAAC,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;YAAE,OAAM;QAE1E,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACtE,wEAAwE;QACxE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAEvG,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAA;YAClC,MAAM,IAAI,GAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;YAE3G,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACrD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBAErD,IAAI,MAAM,YAAY,gBAAgB,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAAE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAA;gBAE7H,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAC5B,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;YAE3G,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAChH,CAAC,CAAA;QACD,uEAAuE;QACvE,oEAAoE;QACpE,mDAAmD;QACnD,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAA;QAChD,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAU,CAAA;QAC9D,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAU,CAAA;QAE1D,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,KAAK;YAAE,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAC9E,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM;YAAE,QAAQ,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAEhF,OAAO,GAAG,EAAE;YACV,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,KAAK;gBAAE,OAAO,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;YACjF,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM;gBAAE,QAAQ,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QACrF,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAElB,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,OAAO,CAAC,KAAgB,EAAE,MAA+B,EAAE,KAA2C;IAC7G,MAAM,EAAE,GAAG,SAAS,KAAK,CAAC,IAAI,EAAE,CAAA;IAChC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAEnC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,KAAC,WAAW,IAAkB,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,IAAjE,KAAK,CAAC,IAAI,CAA2D,CAAA;IAChG,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,CACL,MAAC,KAAK,IAAkB,WAAW,EAAC,YAAY,kBAAe,OAAO,IAAI,SAAS,aACjF,MAAC,YAAY,eACX,KAAC,UAAU,IAAC,OAAO,EAAE,EAAE,YAAG,KAAK,CAAC,KAAK,GAAc,EAClD,KAAK,CAAC,WAAW,IAAI,KAAC,gBAAgB,cAAE,KAAK,CAAC,WAAW,GAAoB,EAC7E,OAAO,IAAI,KAAC,UAAU,cAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAc,IAC3C,EACf,KAAC,MAAM,IAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,kBAAgB,OAAO,IAAI,SAAS,GAAI,KAN3G,KAAK,CAAC,IAAI,CAOd,CACT,CAAA;IACH,CAAC;IAED,OAAO,CACL,MAAC,KAAK,oBAAgC,OAAO,IAAI,SAAS,aACxD,MAAC,UAAU,IAAC,OAAO,EAAE,EAAE,aACpB,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,QAAQ,IAAI,eAAM,SAAS,EAAC,kBAAkB,kBAAS,IACnD,EACZ,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CACjD,KAAC,WAAW,IAAwC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,IAAnG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAuE,CAC1H,CAAC,CAAC,CAAC,CACF,KAAC,OAAO,IAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAI,CAC/E,EACA,CAAC,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,KAAC,gBAAgB,cAAE,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,GAAoB,EACnH,OAAO,IAAI,KAAC,UAAU,cAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAc,KAX9C,KAAK,CAAC,IAAI,CAYd,CACT,CAAA;AACH,CAAC;AAQD;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAqF;IAC7H,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAA;IAChC,MAAM,MAAM,GAAG,KAAK,EAAE,CAAA;IACtB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAA4D,CAAC,CAAC,CAAC,EAAE,CAAA;IACvG,yEAAyE;IACzE,uBAAuB;IACvB,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAA;IAC7F,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAS,GAAG,EAAE,CAC9C,KAAK;SACF,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;SAC/D,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CACjG,CAAA;IACD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAEnC,MAAM,GAAG,GAAG,CAAC,IAAe,EAAE,EAAE;QAC9B,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,MAAM,QAAQ,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAChG,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IAC/B,CAAC,CAAA;IAED,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE,CACxC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE;QACnB,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;QACzB,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAEnC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,IAAK,CAAC,CAAA;QAEzB,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;IAEJ,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAA;IAEhG,OAAO,CACL,MAAC,QAAQ,eACP,KAAC,WAAW,IAAC,OAAO,EAAC,OAAO,YAAE,KAAK,CAAC,KAAK,GAAe,EACvD,KAAK,CAAC,WAAW,IAAI,KAAC,gBAAgB,cAAE,KAAK,CAAC,WAAW,GAAoB,EAC7E,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,YAAG,SAAS,EAAC,+BAA+B,+BAAmB,EACrF,OAAO,GAAG,CAAC,IAAI,CACd,aAAG,SAAS,EAAC,+BAA+B,aACzC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,aAAa,uDAAmD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,SACvI,CACL,EACA,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBACzB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAE,CAAA;gBACrE,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,GAAG,CAAA;gBAEtC,OAAO,CACL,eAAoB,SAAS,EAAC,mBAAmB,aAC/C,eAAK,SAAS,EAAC,4CAA4C,aACzD,eAAM,SAAS,EAAC,qBAAqB,YAAE,IAAI,CAAC,KAAK,GAAQ,EACzD,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,GAAG,IAAI,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,GAAI,EAC9D,eAAK,SAAS,EAAC,oBAAoB,aACjC,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,SAAS,gBAAY,SAAS,EAAC,QAAQ,EAAE,KAAK,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,YACpI,KAAC,OAAO,KAAG,GACJ,EACT,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACH,WAAW,EACtB,QAAQ,EAAE,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EACpC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,YAErC,KAAC,SAAS,KAAG,GACN,EACT,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,SAAS,gBAAY,QAAQ,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YACnG,KAAC,MAAM,KAAG,GACH,IACL,IACF,EACN,cAAK,SAAS,EAAC,KAAK,YAClB,KAAC,UAAU,cACT,KAAC,SAAS,IACR,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,EAC3E,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAChG,KAAK,EAAE,KAAK,GACZ,GACS,GACT,EACL,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,IAAI,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,GAAc,KAhC5G,IAAI,CAAC,GAAG,CAiCZ,CACP,CAAA;YACH,CAAC,CAAC,EACF,wBACE,MAAC,YAAY,eACX,MAAC,mBAAmB,IAAC,MAAM,EAAE,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,SAAS,GAAG,aACrE,KAAC,IAAI,KAAG,iBAEY,EACtB,KAAC,mBAAmB,IAAC,KAAK,EAAC,OAAO,YAC/B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,KAAC,gBAAgB,IAAiB,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,YACvD,IAAI,CAAC,KAAK,IADU,IAAI,CAAC,IAAI,CAEb,CACpB,CAAC,GACkB,IACT,GACX,IACG,CACZ,CAAA;AACH,CAAC;AAID,MAAM,SAAS,GAAG,CAAC,MAA0B,EAAqB,EAAE,CAClE,MAAM,YAAY,gBAAgB,IAAI,MAAM,YAAY,mBAAmB,IAAI,MAAM,YAAY,iBAAiB,CAAA;AAEpH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA6C;IAC5E,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAyB,EAAE,CAAC,CAAA;IAEhE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAE5B,IAAI,CAAC,OAAO;YAAE,OAAM;QAEpB,IAAI,QAAQ,GAAG,KAAK,CAAA;QAEpB,MAAM,SAAS,GAAG,CAAC,KAAY,EAAE,EAAE;YACjC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI;gBAAE,OAAM;YAE1D,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;YAEhD,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAA;YAEnH,sEAAsE;YACtE,wEAAwE;YACxE,+DAA+D;YAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAA;gBACf,UAAU,CAAC,GAAG,EAAE;oBACd,QAAQ,GAAG,KAAK,CAAA;oBAChB,OAAO,CAAC,aAAa,CAAU,UAAU,CAAC,EAAE,KAAK,EAAE,CAAA;gBACrD,CAAC,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAA;QAED,MAAM,MAAM,GAAG,CAAC,KAAY,EAAE,EAAE;YAC9B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI;gBAAE,OAAM;YAE1D,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;YAE1D,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE;gBACpB,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC;oBAAE,OAAO,OAAO,CAAA;gBACtC,IAAI,CAAC,QAAQ,CAAC,KAAK;oBAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,CAAA;gBAErE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAA;gBAEtC,OAAO,IAAI,CAAA;YACb,CAAC,CAAC,CAAA;QACJ,CAAC,CAAA;QAED,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;QACpD,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QACzC,OAAO,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAE1C,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;YACvD,OAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YAC5C,OAAO,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC/C,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAsE;IAChH,MAAM,MAAM,GAAG;QACb,EAAE;QACF,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,cAAc,EAAE,OAAO,IAAI,SAAS;KACrC,CAAA;IACD,MAAM,OAAO,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAA;IAE1E,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,KAAC,QAAQ,OAAK,MAAM,KAAM,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,GAAI,CAAA;QAC1F,KAAK,QAAQ,EAAE,CAAC;YACd,oEAAoE;YACpE,kEAAkE;YAClE,qEAAqE;YACrE,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;YAEjH,OAAO,CACL,MAAC,MAAM,IAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,aACvI,KAAC,aAAa,IAAC,EAAE,EAAE,EAAE,kBAAgB,OAAO,IAAI,SAAS,EAAE,SAAS,EAAC,QAAQ,YAC3E,KAAC,WAAW,KAAG,GACD,EAChB,KAAC,aAAa,cACX,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACvB,KAAC,UAAU,IAAoB,KAAK,EAAE,MAAM,CAAC,KAAK,YAC/C,MAAM,CAAC,KAAK,IADE,MAAM,CAAC,KAAK,CAEhB,CACd,CAAC,GACY,IACT,CACV,CAAA;QACH,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,CACL,KAAC,KAAK,OACA,MAAM,EACV,IAAI,EAAC,QAAQ,EACb,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,EACzB,YAAY,EAAE,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GACxE,CACH,CAAA;QACH,KAAK,OAAO;YACV,OAAO,KAAC,KAAK,OAAK,MAAM,KAAM,OAAO,EAAE,IAAI,EAAC,OAAO,EAAC,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,GAAI,CAAA;QAC3F,KAAK,MAAM;YACT,OAAO,KAAC,KAAK,OAAK,MAAM,EAAE,IAAI,EAAC,MAAM,EAAC,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAI,CAAA;QAC1F;YACE,OAAO,KAAC,KAAK,OAAK,MAAM,KAAM,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,GAAI,CAAA;IACxG,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAS,OAAO,CAAC,KAAgB;IAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AACzI,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAsE;IACpH,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACxE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAE3D,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;QACnB,IAAI,OAAO;YAAE,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAC3C,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,MAAM,MAAM,GAAG,CAAC,KAA0C,EAAE,EAAE;QAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAA;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QAE7B,KAAK,CAAC,iBAAiB,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACrJ,UAAU,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACvG,CAAC,CAAA;IAED,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAA;IAEvC,OAAO,CACL,eAAK,SAAS,EAAC,qBAAqB,aACjC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,cAAK,GAAG,EAAE,KAAK,EAAE,GAAG,EAAC,EAAE,EAAC,SAAS,EAAC,sDAAsD,GAAG,EAC9H,IAAI,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,CAC5C,aAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,EAAC,SAAS,EAAC,0EAA0E,aAClI,KAAC,QAAQ,IAAC,SAAS,EAAC,QAAQ,GAAG,EAC9B,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,IACpE,CACL,EACD,eAAK,SAAS,EAAC,yBAAyB,aACtC,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,KAAK,CAAC,IAAI,EAChB,IAAI,EAAC,MAAM,EACX,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,CAAC,IAAI,kBACnB,OAAO,IAAI,SAAS,EAClC,QAAQ,EAAE,MAAM,GAChB,EACD,IAAI,IAAI,KAAK,CAAC,QAAQ,IAAI,CACzB,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,uBAEzD,CACV,IACG,EACN,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,GAAI,IAClD,CACP,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { useEffect, useId, useState } from 'react'\nimport type { RefObject } from 'react'\nimport { shown, slugify } from '../conditions.js'\nimport type { Read } from '../conditions.js'\nimport { ArrowDown, ArrowUp, FileIcon, Plus, Trash2 } from 'lucide-react'\nimport type { BlockSpec, Field as FieldSpec } from '../resource.js'\nimport { sizeOf } from '../uploads.js'\nimport { Button } from './components/ui/button.js'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './components/ui/dropdown-menu.js'\nimport { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSet } from './components/ui/field.js'\nimport { Input } from './components/ui/input.js'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './components/ui/select.js'\nimport { Switch } from './components/ui/switch.js'\nimport { Textarea } from './components/ui/textarea.js'\n\n/**\n * Each field as its shadcn control, labelled, with its message under it: the\n * browser's while the value is still wrong, else the server's. Shared by the\n * panel's forms and the public ones, so a field looks and checks the same in\n * both.\n */\nexport function FieldList({\n fields,\n values,\n error,\n hidden,\n}: {\n fields: FieldSpec[]\n values: Record<string, unknown>\n error: (name: string) => string | undefined\n /** The fields whose condition hides them now, from useConditions. */\n hidden?: ReadonlySet<string>\n}) {\n return fields.map((field) => {\n const control = fieldOf(field, values, error)\n\n if (!field.visible) return control\n\n // Disabled, a hidden field's controls are neither sent nor checked by the browser.\n const off = hidden?.has(field.name) ?? false\n\n return (\n <fieldset key={field.name} disabled={off} hidden={off} className={off ? 'hidden' : 'contents'}>\n {control}\n </fieldset>\n )\n })\n}\n\n/**\n * The fields' conditions and slugs, applied while the visitor types: which\n * fields are hidden now, read from the form's own controls - the form stays\n * uncontrolled, so nothing typed is lost to a render. A slug follows its\n * source until it is edited by hand. The server applies both again.\n */\nexport function useConditions(form: RefObject<HTMLFormElement | null>, fields: FieldSpec[], values: Record<string, unknown>): ReadonlySet<string> {\n const initial: Read = (name) => {\n const value = values[name]\n\n return typeof value === 'boolean' ? value : value === undefined || value === null ? '' : String(value)\n }\n const [hidden, setHidden] = useState<ReadonlySet<string>>(() => new Set(fields.filter((field) => field.visible && !shown(field.visible, initial)).map((field) => field.name)))\n\n useEffect(() => {\n const element = form.current\n const conditional = fields.filter((field) => field.visible)\n const derived = fields.filter((field) => field.from)\n\n if (!element || (conditional.length === 0 && derived.length === 0)) return\n\n const types = new Map(fields.map((field) => [field.name, field.type]))\n // What each slug was last made as: it follows while it still says that.\n const last = new Map(derived.map((field) => [field.name, slugify(String(initial(field.from!.field)))]))\n\n const update = () => {\n const data = new FormData(element)\n const read: Read = (name) => (types.get(name) === 'toggle' ? data.has(name) : String(data.get(name) ?? ''))\n\n for (const field of derived) {\n const target = element.elements.namedItem(field.name)\n const made = slugify(String(read(field.from!.field)))\n\n if (target instanceof HTMLInputElement && (target.value === '' || target.value === last.get(field.name))) target.value = made\n\n last.set(field.name, made)\n }\n\n const now = new Set(conditional.filter((field) => !shown(field.visible!, read)).map((field) => field.name))\n\n setHidden((before) => (before.size === now.size && [...now].every((name) => before.has(name)) ? before : now))\n }\n // A select sets its hidden input after the click or key that chose it,\n // with no event of its own, from a list drawn outside the form - so\n // clicks and keys anywhere are read a frame later.\n const soon = () => requestAnimationFrame(update)\n const typed = [['input', update], ['change', update]] as const\n const chosen = [['click', soon], ['keyup', soon]] as const\n\n for (const [name, listener] of typed) element.addEventListener(name, listener)\n for (const [name, listener] of chosen) document.addEventListener(name, listener)\n\n return () => {\n for (const [name, listener] of typed) element.removeEventListener(name, listener)\n for (const [name, listener] of chosen) document.removeEventListener(name, listener)\n }\n }, [form, fields])\n\n return hidden\n}\n\nfunction fieldOf(field: FieldSpec, values: Record<string, unknown>, error: (name: string) => string | undefined) {\n const id = `field-${field.name}`\n const invalid = !!error(field.name)\n\n if (field.type === 'blocks') {\n return <BlocksField key={field.name} field={field} value={values[field.name]} error={error} />\n }\n\n if (field.type === 'toggle') {\n return (\n <Field key={field.name} orientation=\"horizontal\" data-invalid={invalid || undefined}>\n <FieldContent>\n <FieldLabel htmlFor={id}>{field.label}</FieldLabel>\n {field.description && <FieldDescription>{field.description}</FieldDescription>}\n {invalid && <FieldError>{error(field.name)}</FieldError>}\n </FieldContent>\n <Switch id={id} name={field.name} defaultChecked={Boolean(values[field.name])} aria-invalid={invalid || undefined} />\n </Field>\n )\n }\n\n return (\n <Field key={field.name} data-invalid={invalid || undefined}>\n <FieldLabel htmlFor={id}>\n {field.label}\n {field.required && <span className=\"text-destructive\">*</span>}\n </FieldLabel>\n {field.type === 'file' || field.type === 'image' ? (\n <FileControl key={String(values[field.name] ?? '')} id={id} field={field} value={values[field.name]} invalid={invalid} />\n ) : (\n <Control id={id} field={field} value={values[field.name]} invalid={invalid} />\n )}\n {(field.description ?? limitOf(field)) && <FieldDescription>{field.description ?? limitOf(field)}</FieldDescription>}\n {invalid && <FieldError>{error(field.name)}</FieldError>}\n </Field>\n )\n}\n\ninterface Item {\n key: string\n type: string\n data: Record<string, unknown>\n}\n\n/**\n * A list of blocks an editor adds, orders and removes, each with its own\n * fields. Every control stays an ordinary named input - block i's heading is\n * `content.i.heading` - so it submits with the form, is checked by the\n * browser like any other, and shows the server's refusal under itself. Each\n * block keeps a key of its own, so moving one moves what was typed into it.\n */\nfunction BlocksField({ field, value, error }: { field: FieldSpec; value: unknown; error: (name: string) => string | undefined }) {\n const kinds = field.blocks ?? []\n const prefix = useId()\n const saved = Array.isArray(value) ? (value as { type: string; data?: Record<string, unknown> }[]) : []\n // A kind the app no longer offers has no fields to draw; it is left out,\n // and saving drops it.\n const retired = saved.filter((item) => !kinds.some((kind) => kind.name === item.type)).length\n const [items, setItems] = useState<Item[]>(() =>\n saved\n .filter((item) => kinds.some((kind) => kind.name === item.type))\n .map((item, index) => ({ key: `${prefix}-${index}`, type: item.type, data: item.data ?? {} })),\n )\n const [made, setMade] = useState(0)\n\n const add = (kind: BlockSpec) => {\n setItems((current) => [...current, { key: `${prefix}-new-${made}`, type: kind.name, data: {} }])\n setMade((count) => count + 1)\n }\n\n const move = (from: number, to: number) =>\n setItems((current) => {\n const next = [...current]\n const [item] = next.splice(from, 1)\n\n next.splice(to, 0, item!)\n\n return next\n })\n\n const remove = (at: number) => setItems((current) => current.filter((_, index) => index !== at))\n\n return (\n <FieldSet>\n <FieldLegend variant=\"label\">{field.label}</FieldLegend>\n {field.description && <FieldDescription>{field.description}</FieldDescription>}\n {items.length === 0 && <p className=\"text-sm text-muted-foreground\">No blocks yet.</p>}\n {retired > 0 && (\n <p className=\"text-sm text-muted-foreground\">\n {retired === 1 ? 'One block is' : `${retired} blocks are`} of a kind no longer offered, and saving removes {retired === 1 ? 'it' : 'them'}.\n </p>\n )}\n {items.map((item, index) => {\n const kind = kinds.find((candidate) => candidate.name === item.type)!\n const base = `${field.name}.${index}.`\n\n return (\n <div key={item.key} className=\"rounded-lg border\">\n <div className=\"flex items-center gap-1 border-b px-3 py-2\">\n <span className=\"text-sm font-medium\">{kind.label}</span>\n <input type=\"hidden\" name={`${base}type`} value={item.type} />\n <div className=\"ml-auto flex gap-1\">\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" aria-label=\"Move up\" disabled={index === 0} onClick={() => move(index, index - 1)}>\n <ArrowUp />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Move down\"\n disabled={index === items.length - 1}\n onClick={() => move(index, index + 1)}\n >\n <ArrowDown />\n </Button>\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" aria-label=\"Remove\" onClick={() => remove(index)}>\n <Trash2 />\n </Button>\n </div>\n </div>\n <div className=\"p-3\">\n <FieldGroup>\n <FieldList\n fields={kind.fields.map((inner) => ({ ...inner, name: base + inner.name }))}\n values={Object.fromEntries(Object.entries(item.data).map(([name, data]) => [base + name, data]))}\n error={error}\n />\n </FieldGroup>\n </div>\n {error(`${field.name}.${index}`) && <FieldError className=\"px-3 pb-3\">{error(`${field.name}.${index}`)}</FieldError>}\n </div>\n )\n })}\n <div>\n <DropdownMenu>\n <DropdownMenuTrigger render={<Button type=\"button\" variant=\"outline\" />}>\n <Plus />\n Add block\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {kinds.map((kind) => (\n <DropdownMenuItem key={kind.name} onClick={() => add(kind)}>\n {kind.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </FieldSet>\n )\n}\n\ntype Control = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n\nconst isControl = (target: EventTarget | null): target is Control =>\n target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement\n\n/**\n * The browser's own validation, shown the shadcn way. A submit fires\n * 'invalid' at each control that fails its constraints; that is cancelled -\n * no bubble - and its validationMessage, in the visitor's language, is kept\n * under its name. Fixing the value clears it. 'invalid' does not bubble, so\n * the form listens in the capture phase.\n */\nexport function useBrowserErrors(form: React.RefObject<HTMLFormElement | null>): Record<string, string> {\n const [errors, setErrors] = useState<Record<string, string>>({})\n\n useEffect(() => {\n const element = form.current\n\n if (!element) return\n\n let focusing = false\n\n const onInvalid = (event: Event) => {\n if (!isControl(event.target) || !event.target.name) return\n\n const { name, validationMessage } = event.target\n\n event.preventDefault()\n setErrors((current) => (current[name] === validationMessage ? current : { ...current, [name]: validationMessage }))\n\n // Cancelling the bubble also cancels the browser's focus on the first\n // wrong field. Every control's 'invalid' fires before the next task, so\n // give focus back once, to the first one, after they all have.\n if (!focusing) {\n focusing = true\n setTimeout(() => {\n focusing = false\n element.querySelector<Control>(':invalid')?.focus()\n })\n }\n }\n\n const onEdit = (event: Event) => {\n if (!isControl(event.target) || !event.target.name) return\n\n const { name, validity, validationMessage } = event.target\n\n setErrors((current) => {\n if (!(name in current)) return current\n if (!validity.valid) return { ...current, [name]: validationMessage }\n\n const { [name]: _, ...rest } = current\n\n return rest\n })\n }\n\n element.addEventListener('invalid', onInvalid, true)\n element.addEventListener('input', onEdit)\n element.addEventListener('change', onEdit)\n\n return () => {\n element.removeEventListener('invalid', onInvalid, true)\n element.removeEventListener('input', onEdit)\n element.removeEventListener('change', onEdit)\n }\n }, [form])\n\n return errors\n}\n\nfunction Control({ id, field, value, invalid }: { id: string; field: FieldSpec; value: unknown; invalid: boolean }) {\n const shared = {\n id,\n name: field.name,\n required: field.required,\n 'aria-invalid': invalid || undefined,\n }\n const lengths = { minLength: field.minLength, maxLength: field.maxLength }\n\n switch (field.type) {\n case 'textarea':\n return <Textarea {...shared} {...lengths} rows={4} defaultValue={String(value ?? '')} />\n case 'select': {\n // An optional select can be left empty: its first choice is \"None\",\n // which arrives as \"\" and is left out of the values, as any empty\n // optional field is. A required one starts on its first real choice.\n const options = field.required ? (field.options ?? []) : [{ value: '', label: 'None' }, ...(field.options ?? [])]\n\n return (\n <Select name={field.name} items={options} defaultValue={value === null || value === undefined ? (options[0]?.value ?? '') : String(value)}>\n <SelectTrigger id={id} aria-invalid={invalid || undefined} className=\"w-full\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n case 'number':\n return (\n <Input\n {...shared}\n type=\"number\"\n min={field.min}\n max={field.max}\n step={field.step ?? 'any'}\n defaultValue={value === undefined || value === null ? '' : String(value)}\n />\n )\n case 'email':\n return <Input {...shared} {...lengths} type=\"email\" defaultValue={String(value ?? '')} />\n case 'date':\n return <Input {...shared} type=\"date\" defaultValue={String(value ?? '').slice(0, 10)} />\n default:\n return <Input {...shared} {...lengths} pattern={field.pattern} defaultValue={String(value ?? '')} />\n }\n}\n\n/** A file field's size limit, said under it when the app said nothing else. */\nfunction limitOf(field: FieldSpec): string | undefined {\n return (field.type === 'file' || field.type === 'image') && field.maxSize !== undefined ? `Up to ${sizeOf(field.maxSize)}.` : undefined\n}\n\n/**\n * A file or image field: what it holds now, and a file input to replace it.\n * The value it holds goes back with the form in a hidden input under the\n * same name, so a save that chooses nothing keeps it; the server keeps it\n * only if the record already had it. A file over the size limit is refused\n * as it is chosen, through the input's own validity, so it shows as any\n * other field's message and never makes the trip.\n */\nfunction FileControl({ id, field, value, invalid }: { id: string; field: FieldSpec; value: unknown; invalid: boolean }) {\n const [kept, setKept] = useState(typeof value === 'string' ? value : '')\n const [preview, setPreview] = useState<string | null>(null)\n\n useEffect(() => () => {\n if (preview) URL.revokeObjectURL(preview)\n }, [preview])\n\n const choose = (event: React.ChangeEvent<HTMLInputElement>) => {\n const input = event.currentTarget\n const file = input.files?.[0]\n\n input.setCustomValidity(file && field.maxSize !== undefined && file.size > field.maxSize ? `Choose a file of ${sizeOf(field.maxSize)} or less.` : '')\n setPreview(file && field.type === 'image' && input.validity.valid ? URL.createObjectURL(file) : null)\n }\n\n const shown = preview ?? (kept || null)\n\n return (\n <div className=\"flex flex-col gap-3\">\n {shown && field.type === 'image' && <img src={shown} alt=\"\" className=\"h-32 w-fit max-w-full rounded-md border object-cover\" />}\n {kept && !preview && field.type === 'file' && (\n <a href={kept} target=\"_blank\" rel=\"noreferrer\" className=\"flex w-fit items-center gap-2 text-sm underline-offset-4 hover:underline\">\n <FileIcon className=\"size-4\" />\n {decodeURIComponent(kept.split(/[/?#]/).filter(Boolean).pop() ?? kept)}\n </a>\n )}\n <div className=\"flex items-center gap-2\">\n <Input\n id={id}\n name={field.name}\n type=\"file\"\n accept={field.accept}\n required={field.required && !kept}\n aria-invalid={invalid || undefined}\n onChange={choose}\n />\n {kept && field.nullable && (\n <Button type=\"button\" variant=\"outline\" onClick={() => setKept('')}>\n Remove\n </Button>\n )}\n </div>\n <input type=\"hidden\" name={field.name} value={kept} />\n </div>\n )\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { AdminTheme } from '../admin/theme.js';
2
2
  import type { FormsTheme } from '../forms/theme.js';
3
3
  /**
4
- * Panels and public forms drawn with shadcn/ui: its sidebar layout, data table, fields and
4
+ * Panels and blueprints' forms drawn with shadcn/ui: its sidebar layout, data table, fields and
5
5
  * cards, in the app's own theme variables, so it follows the app's colours,
6
6
  * radius, fonts and dark mode. Its classes reach the app's stylesheet through
7
7
  *
@@ -1,12 +1,12 @@
1
1
  import { PanelBreadcrumbs } from './breadcrumbs.js';
2
2
  import { DataTable } from './data-table.js';
3
3
  import { PanelNav } from './nav.js';
4
- import { PublicForm } from './public-form.js';
4
+ import { BlueprintForm } from './blueprint-form.js';
5
5
  import { Brand, Cell, Dashboard, Form, Index, Loading, Plugins, Shell } from './pages.js';
6
6
  import { TenantSwitcher } from './tenant-switcher.js';
7
7
  import { UserMenu } from './user-menu.js';
8
8
  /**
9
- * Panels and public forms drawn with shadcn/ui: its sidebar layout, data table, fields and
9
+ * Panels and blueprints' forms drawn with shadcn/ui: its sidebar layout, data table, fields and
10
10
  * cards, in the app's own theme variables, so it follows the app's colours,
11
11
  * radius, fonts and dark mode. Its classes reach the app's stylesheet through
12
12
  *
@@ -26,6 +26,6 @@ export const shadcn = {
26
26
  Plugins,
27
27
  Cell,
28
28
  Loading,
29
- PublicForm,
29
+ BlueprintForm,
30
30
  };
31
31
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shadcn/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAA4B;IAC7C,KAAK;IACL,KAAK;IACL,cAAc;IACd,GAAG,EAAE,QAAQ;IACb,WAAW,EAAE,gBAAgB;IAC7B,QAAQ;IACR,SAAS;IACT,KAAK;IACL,KAAK,EAAE,SAAS;IAChB,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,OAAO;IACP,UAAU;CACX,CAAA","sourcesContent":["import type { AdminTheme } from '../admin/theme.js'\nimport type { FormsTheme } from '../forms/theme.js'\nimport { PanelBreadcrumbs } from './breadcrumbs.js'\nimport { DataTable } from './data-table.js'\nimport { PanelNav } from './nav.js'\nimport { PublicForm } from './public-form.js'\nimport { Brand, Cell, Dashboard, Form, Index, Loading, Plugins, Shell } from './pages.js'\nimport { TenantSwitcher } from './tenant-switcher.js'\nimport { UserMenu } from './user-menu.js'\n\n/**\n * Panels and public forms drawn with shadcn/ui: its sidebar layout, data table, fields and\n * cards, in the app's own theme variables, so it follows the app's colours,\n * radius, fonts and dark mode. Its classes reach the app's stylesheet through\n *\n * @import \"@rsc-kit/press/shadcn.css\";\n */\nexport const shadcn: AdminTheme & FormsTheme = {\n Shell,\n Brand,\n TenantSwitcher,\n Nav: PanelNav,\n Breadcrumbs: PanelBreadcrumbs,\n UserMenu,\n Dashboard,\n Index,\n Table: DataTable,\n Form,\n Plugins,\n Cell,\n Loading,\n PublicForm,\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shadcn/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAA4B;IAC7C,KAAK;IACL,KAAK;IACL,cAAc;IACd,GAAG,EAAE,QAAQ;IACb,WAAW,EAAE,gBAAgB;IAC7B,QAAQ;IACR,SAAS;IACT,KAAK;IACL,KAAK,EAAE,SAAS;IAChB,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,OAAO;IACP,aAAa;CACd,CAAA","sourcesContent":["import type { AdminTheme } from '../admin/theme.js'\nimport type { FormsTheme } from '../forms/theme.js'\nimport { PanelBreadcrumbs } from './breadcrumbs.js'\nimport { DataTable } from './data-table.js'\nimport { PanelNav } from './nav.js'\nimport { BlueprintForm } from './blueprint-form.js'\nimport { Brand, Cell, Dashboard, Form, Index, Loading, Plugins, Shell } from './pages.js'\nimport { TenantSwitcher } from './tenant-switcher.js'\nimport { UserMenu } from './user-menu.js'\n\n/**\n * Panels and blueprints' forms drawn with shadcn/ui: its sidebar layout, data table, fields and\n * cards, in the app's own theme variables, so it follows the app's colours,\n * radius, fonts and dark mode. Its classes reach the app's stylesheet through\n *\n * @import \"@rsc-kit/press/shadcn.css\";\n */\nexport const shadcn: AdminTheme & FormsTheme = {\n Shell,\n Brand,\n TenantSwitcher,\n Nav: PanelNav,\n Breadcrumbs: PanelBreadcrumbs,\n UserMenu,\n Dashboard,\n Index,\n Table: DataTable,\n Form,\n Plugins,\n Cell,\n Loading,\n BlueprintForm,\n}\n"]}
@@ -6,7 +6,7 @@ import Link from '@rsc-kit/core/Link';
6
6
  import { Button } from './components/ui/button.js';
7
7
  import { Card, CardContent, CardFooter } from './components/ui/card.js';
8
8
  import { FieldGroup } from './components/ui/field.js';
9
- import { FieldList, useBrowserErrors } from './fields.js';
9
+ import { FieldList, useBrowserErrors, useConditions } from './fields.js';
10
10
  /**
11
11
  * Every resource's create and edit form, drawn from its fields. Uncontrolled:
12
12
  * a value starts as its default, and a refused save keeps what was typed.
@@ -21,10 +21,11 @@ import { FieldList, useBrowserErrors } from './fields.js';
21
21
  export function ResourceForm({ action, fields, values, submit, cancel, }) {
22
22
  const form = useRef(null);
23
23
  const browser = useBrowserErrors(form);
24
+ const hidden = useConditions(form, fields, values);
24
25
  return (_jsx(Form, { action: action, ref: form, children: ({ pending, error: server, formError }) => {
25
26
  // The browser's message while the field is still wrong, else the server's.
26
27
  const error = (name) => browser[name] ?? server(name);
27
- return (_jsxs(Card, { className: "max-w-2xl", children: [_jsx(CardContent, { children: _jsxs(FieldGroup, { children: [_jsx(FieldList, { fields: fields, values: values, error: error }), formError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: formError }))] }) }), _jsxs(CardFooter, { className: "justify-end gap-2 border-t", children: [_jsx(Button, { variant: "outline", nativeButton: false, render: _jsx(Link, { href: cancel }), children: "Cancel" }), _jsx(Button, { type: "submit", disabled: pending, children: pending ? 'Saving…' : submit })] })] }));
28
+ return (_jsxs(Card, { className: "max-w-2xl", children: [_jsx(CardContent, { children: _jsxs(FieldGroup, { children: [_jsx(FieldList, { fields: fields, values: values, error: error, hidden: hidden }), formError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: formError }))] }) }), _jsxs(CardFooter, { className: "justify-end gap-2 border-t", children: [_jsx(Button, { variant: "outline", nativeButton: false, render: _jsx(Link, { href: cancel }), children: "Cancel" }), _jsx(Button, { type: "submit", disabled: pending, children: pending ? 'Saving…' : submit })] })] }));
28
29
  } }));
29
30
  }
30
31
  //# sourceMappingURL=resource-form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource-form.js","sourceRoot":"","sources":["../../src/shadcn/resource-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,IAAI,MAAM,oBAAoB,CAAA;AACrC,OAAO,IAAI,MAAM,oBAAoB,CAAA;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEzD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GAOP;IACC,MAAM,IAAI,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAEtC,OAAO,CACL,KAAC,IAAI,IAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,YAC5B,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE;YACzC,2EAA2E;YAC3E,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;YAE7D,OAAO,CACL,MAAC,IAAI,IAAC,SAAS,EAAC,WAAW,aACzB,KAAC,WAAW,cACV,MAAC,UAAU,eACT,KAAC,SAAS,IAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,EAC1D,SAAS,IAAI,CACZ,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACjD,SAAS,GACR,CACL,IACU,GACD,EACd,MAAC,UAAU,IAAC,SAAS,EAAC,4BAA4B,aAChD,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,KAAC,IAAI,IAAC,IAAI,EAAE,MAAe,GAAI,uBAE7E,EACT,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,YACpC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,GACtB,IACE,IACR,CACR,CAAA;QACH,CAAC,GACI,CACR,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport Form from '@rsc-kit/core/form'\nimport Link from '@rsc-kit/core/Link'\nimport type { Field as FieldSpec } from '../resource.js'\nimport { Button } from './components/ui/button.js'\nimport { Card, CardContent, CardFooter } from './components/ui/card.js'\nimport { FieldGroup } from './components/ui/field.js'\nimport { FieldList, useBrowserErrors } from './fields.js'\n\n/**\n * Every resource's create and edit form, drawn from its fields. Uncontrolled:\n * a value starts as its default, and a refused save keeps what was typed.\n * The controls are shadcn's, and each submits under its field's name - a\n * Select and a Switch through the hidden input base-ui renders for them.\n *\n * The browser checks each control first, against the constraints read from\n * the schema (required, min, maxlength, pattern, type=\"email\"). Its own\n * bubble is replaced by its message in the field's FieldError, where the\n * server's refusals show too; with JavaScript off the bubble is what shows.\n */\nexport function ResourceForm({\n action,\n fields,\n values,\n submit,\n cancel,\n}: {\n action: (form: FormData) => Promise<unknown>\n fields: FieldSpec[]\n values: Record<string, unknown>\n submit: string\n cancel: string\n}) {\n const form = useRef<HTMLFormElement>(null)\n const browser = useBrowserErrors(form)\n\n return (\n <Form action={action} ref={form}>\n {({ pending, error: server, formError }) => {\n // The browser's message while the field is still wrong, else the server's.\n const error = (name: string) => browser[name] ?? server(name)\n\n return (\n <Card className=\"max-w-2xl\">\n <CardContent>\n <FieldGroup>\n <FieldList fields={fields} values={values} error={error} />\n {formError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {formError}\n </p>\n )}\n </FieldGroup>\n </CardContent>\n <CardFooter className=\"justify-end gap-2 border-t\">\n <Button variant=\"outline\" nativeButton={false} render={<Link href={cancel as never} />}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={pending}>\n {pending ? 'Saving…' : submit}\n </Button>\n </CardFooter>\n </Card>\n )\n }}\n </Form>\n )\n}\n"]}
1
+ {"version":3,"file":"resource-form.js","sourceRoot":"","sources":["../../src/shadcn/resource-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,IAAI,MAAM,oBAAoB,CAAA;AACrC,OAAO,IAAI,MAAM,oBAAoB,CAAA;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAExE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GAOP;IACC,MAAM,IAAI,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAElD,OAAO,CACL,KAAC,IAAI,IAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,YAC5B,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE;YACzC,2EAA2E;YAC3E,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;YAE7D,OAAO,CACL,MAAC,IAAI,IAAC,SAAS,EAAC,WAAW,aACzB,KAAC,WAAW,cACV,MAAC,UAAU,eACT,KAAC,SAAS,IAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAI,EAC1E,SAAS,IAAI,CACZ,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACjD,SAAS,GACR,CACL,IACU,GACD,EACd,MAAC,UAAU,IAAC,SAAS,EAAC,4BAA4B,aAChD,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,KAAC,IAAI,IAAC,IAAI,EAAE,MAAe,GAAI,uBAE7E,EACT,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,YACpC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,GACtB,IACE,IACR,CACR,CAAA;QACH,CAAC,GACI,CACR,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport Form from '@rsc-kit/core/form'\nimport Link from '@rsc-kit/core/Link'\nimport type { Field as FieldSpec } from '../resource.js'\nimport { Button } from './components/ui/button.js'\nimport { Card, CardContent, CardFooter } from './components/ui/card.js'\nimport { FieldGroup } from './components/ui/field.js'\nimport { FieldList, useBrowserErrors, useConditions } from './fields.js'\n\n/**\n * Every resource's create and edit form, drawn from its fields. Uncontrolled:\n * a value starts as its default, and a refused save keeps what was typed.\n * The controls are shadcn's, and each submits under its field's name - a\n * Select and a Switch through the hidden input base-ui renders for them.\n *\n * The browser checks each control first, against the constraints read from\n * the schema (required, min, maxlength, pattern, type=\"email\"). Its own\n * bubble is replaced by its message in the field's FieldError, where the\n * server's refusals show too; with JavaScript off the bubble is what shows.\n */\nexport function ResourceForm({\n action,\n fields,\n values,\n submit,\n cancel,\n}: {\n action: (form: FormData) => Promise<unknown>\n fields: FieldSpec[]\n values: Record<string, unknown>\n submit: string\n cancel: string\n}) {\n const form = useRef<HTMLFormElement>(null)\n const browser = useBrowserErrors(form)\n const hidden = useConditions(form, fields, values)\n\n return (\n <Form action={action} ref={form}>\n {({ pending, error: server, formError }) => {\n // The browser's message while the field is still wrong, else the server's.\n const error = (name: string) => browser[name] ?? server(name)\n\n return (\n <Card className=\"max-w-2xl\">\n <CardContent>\n <FieldGroup>\n <FieldList fields={fields} values={values} error={error} hidden={hidden} />\n {formError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {formError}\n </p>\n )}\n </FieldGroup>\n </CardContent>\n <CardFooter className=\"justify-end gap-2 border-t\">\n <Button variant=\"outline\" nativeButton={false} render={<Link href={cancel as never} />}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={pending}>\n {pending ? 'Saving…' : submit}\n </Button>\n </CardFooter>\n </Card>\n )\n }}\n </Form>\n )\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsc-kit/press",
3
- "version": "0.30.51",
3
+ "version": "0.30.52",
4
4
  "description": "Admin panels, public forms and pages built from blocks for rsc-kit: resources, data sources, tenants and plugins, with the drawing in a theme - shadcn/ui is the one that ships.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  "prepack": "bun run build"
51
51
  },
52
52
  "peerDependencies": {
53
- "@rsc-kit/core": "^0.30.51",
53
+ "@rsc-kit/core": "^0.30.52",
54
54
  "react": "^19.2.0",
55
55
  "react-dom": "^19.2.0",
56
56
  "tailwindcss": "^4.0.0"
@@ -1,61 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js';
3
- import type { HostAction, HostFields } from './host.js';
4
- import type { FormsTheme } from './theme.js';
5
- interface FormOptions {
6
- /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */
7
- name: string;
8
- /** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */
9
- theme: FormsTheme;
10
- /** The submit button's words. Send by default. */
11
- submit?: string;
12
- /** What the visitor reads in place of the form once it is sent. */
13
- done?: string;
14
- /** Where the visitor goes once it is sent, instead of `done`. */
15
- redirect?: string;
16
- }
17
- export interface FormDefinition<Schema extends FormSchema> extends FormOptions {
18
- /** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */
19
- form: Schema;
20
- /** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */
21
- fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>;
22
- /**
23
- * What a submission that passed the schema does: store it, send it, call a
24
- * backend. Throw an ActionValidationError - or let a backend's 422 through -
25
- * and its messages land under the fields.
26
- */
27
- handle: (values: ValuesOf<Schema>) => void | Promise<void>;
28
- }
29
- export interface HostFormDefinition<Action extends HostAction> extends FormOptions {
30
- /**
31
- * The backend action the form calls, as the build generated it:
32
- * `import { contactSend } from '../server-actions.generated'`. Its fields are
33
- * drawn from what the backend says it takes, and it does all the checking:
34
- * a Laravel FormRequest, a Go struct. Its refusals land under the fields.
35
- */
36
- action: Action;
37
- /** Labels, descriptions and controls the backend's type cannot say. */
38
- fields?: Partial<Record<HostFields<Action>, FieldOverride>>;
39
- }
40
- /**
41
- * A form anyone may fill in - a contact form, a sign-up, a survey. Either from
42
- * a Standard Schema, checked by the browser first and by the schema on the
43
- * server always, or from a backend function, checked by the browser first and
44
- * by the backend always. Place it in any page:
45
- *
46
- * export default function Contact() {
47
- * return <main><h1>Contact us</h1><contact.Form /></main>
48
- * }
49
- *
50
- * The form reads nothing from the request, so the page around it can still
51
- * be stored by the build.
52
- */
53
- export declare function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>): PublicFormHandle;
54
- export declare function defineForm<Action extends HostAction>(definition: HostFormDefinition<Action>): PublicFormHandle;
55
- export interface PublicFormHandle {
56
- name: string;
57
- fields: Field[];
58
- Form: () => ReactNode;
59
- }
60
- export type PublicForm = PublicFormHandle;
61
- export {};
@@ -1,66 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { fieldsOf } from '../resource.js';
3
- import { submitForm } from './actions.js';
4
- import { hostSchema, requireDrawable } from './host.js';
5
- import { HONEYPOT } from './honeypot.js';
6
- import { registerForm } from './registry.js';
7
- export function defineForm(definition) {
8
- if (!/^[a-z0-9-]+$/.test(definition.name)) {
9
- throw new Error(`[forms] a form's name is lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`);
10
- }
11
- if (definition.done !== undefined && definition.redirect !== undefined) {
12
- throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`);
13
- }
14
- const { action } = definition;
15
- if (typeof action === 'string') {
16
- throw new Error(`[forms] the form ${definition.name}'s action is the generated action now, not its name: ` +
17
- `import { contactSend } from '../server-actions.generated', then action: contactSend, for ${JSON.stringify(action)}`);
18
- }
19
- if ((action === undefined) === (definition.form === undefined)) {
20
- throw new Error(`[forms] the form ${definition.name} needs a form schema with handle, or an action, and not both`);
21
- }
22
- const overrides = (definition.fields ?? {});
23
- const schema = action === undefined ? definition.form : hostSchema(action);
24
- let drawn = null;
25
- function fields() {
26
- if (drawn)
27
- return drawn;
28
- if (action !== undefined)
29
- requireDrawable(definition.name, action, overrides);
30
- const fields = fieldsOf(schema, overrides);
31
- if (fields.some((field) => field.name === HONEYPOT)) {
32
- throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`);
33
- }
34
- return (drawn = fields);
35
- }
36
- // A backend's types arrive with the server entry, which runs after this
37
- // module: those fields wait for the first render. A schema's fail here.
38
- if (action === undefined)
39
- fields();
40
- const handle = action === undefined
41
- ? definition.handle
42
- : async (values) => void (await action(values));
43
- registerForm({
44
- name: definition.name,
45
- schema,
46
- get fields() {
47
- return fields();
48
- },
49
- handle,
50
- redirect: definition.redirect ?? null,
51
- });
52
- const theme = definition.theme;
53
- const submit = definition.submit ?? 'Send';
54
- const done = definition.redirect === undefined ? (definition.done ?? 'Thank you. It was sent.') : undefined;
55
- function Form() {
56
- return (_jsx(theme.PublicForm, { action: submitForm.bind(null, definition.name), fields: fields(), submit: submit, ...(done === undefined ? {} : { done }), honeypot: HONEYPOT }));
57
- }
58
- return {
59
- name: definition.name,
60
- get fields() {
61
- return fields();
62
- },
63
- Form,
64
- };
65
- }
66
- //# sourceMappingURL=form.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/forms/form.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport { fieldsOf } from '../resource.js'\nimport type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js'\nimport { submitForm } from './actions.js'\nimport { hostSchema, requireDrawable } from './host.js'\nimport type { HostAction, HostFields } from './host.js'\nimport { HONEYPOT } from './honeypot.js'\nimport { registerForm } from './registry.js'\nimport type { FormsTheme } from './theme.js'\n\ninterface FormOptions {\n /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */\n name: string\n /** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */\n theme: FormsTheme\n /** The submit button's words. Send by default. */\n submit?: string\n /** What the visitor reads in place of the form once it is sent. */\n done?: string\n /** Where the visitor goes once it is sent, instead of `done`. */\n redirect?: string\n}\n\nexport interface FormDefinition<Schema extends FormSchema> extends FormOptions {\n /** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */\n form: Schema\n /** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */\n fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>\n /**\n * What a submission that passed the schema does: store it, send it, call a\n * backend. Throw an ActionValidationError - or let a backend's 422 through -\n * and its messages land under the fields.\n */\n handle: (values: ValuesOf<Schema>) => void | Promise<void>\n}\n\nexport interface HostFormDefinition<Action extends HostAction> extends FormOptions {\n /**\n * The backend action the form calls, as the build generated it:\n * `import { contactSend } from '../server-actions.generated'`. Its fields are\n * drawn from what the backend says it takes, and it does all the checking:\n * a Laravel FormRequest, a Go struct. Its refusals land under the fields.\n */\n action: Action\n /** Labels, descriptions and controls the backend's type cannot say. */\n fields?: Partial<Record<HostFields<Action>, FieldOverride>>\n}\n\n/**\n * A form anyone may fill in - a contact form, a sign-up, a survey. Either from\n * a Standard Schema, checked by the browser first and by the schema on the\n * server always, or from a backend function, checked by the browser first and\n * by the backend always. Place it in any page:\n *\n * export default function Contact() {\n * return <main><h1>Contact us</h1><contact.Form /></main>\n * }\n *\n * The form reads nothing from the request, so the page around it can still\n * be stored by the build.\n */\nexport function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>): PublicFormHandle\nexport function defineForm<Action extends HostAction>(definition: HostFormDefinition<Action>): PublicFormHandle\nexport function defineForm(definition: (FormDefinition<FormSchema> | HostFormDefinition<HostAction>) & { form?: FormSchema; action?: HostAction }): PublicFormHandle {\n if (!/^[a-z0-9-]+$/.test(definition.name)) {\n throw new Error(`[forms] a form's name is lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`)\n }\n\n if (definition.done !== undefined && definition.redirect !== undefined) {\n throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`)\n }\n\n const { action } = definition\n\n if (typeof action === 'string') {\n throw new Error(\n `[forms] the form ${definition.name}'s action is the generated action now, not its name: ` +\n `import { contactSend } from '../server-actions.generated', then action: contactSend, for ${JSON.stringify(action)}`,\n )\n }\n\n if ((action === undefined) === (definition.form === undefined)) {\n throw new Error(`[forms] the form ${definition.name} needs a form schema with handle, or an action, and not both`)\n }\n\n const overrides = (definition.fields ?? {}) as Record<string, FieldOverride>\n const schema = action === undefined ? definition.form! : hostSchema(action)\n let drawn: Field[] | null = null\n\n function fields(): Field[] {\n if (drawn) return drawn\n\n if (action !== undefined) requireDrawable(definition.name, action, overrides)\n\n const fields = fieldsOf(schema, overrides)\n\n if (fields.some((field) => field.name === HONEYPOT)) {\n throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`)\n }\n\n return (drawn = fields)\n }\n\n // A backend's types arrive with the server entry, which runs after this\n // module: those fields wait for the first render. A schema's fail here.\n if (action === undefined) fields()\n\n const handle = action === undefined\n ? ((definition as FormDefinition<FormSchema>).handle as (values: Record<string, unknown>) => void | Promise<void>)\n : async (values: Record<string, unknown>) => void (await (action as (values: Record<string, unknown>) => Promise<unknown>)(values))\n\n registerForm({\n name: definition.name,\n schema,\n get fields() {\n return fields()\n },\n handle,\n redirect: definition.redirect ?? null,\n })\n\n const theme = definition.theme\n const submit = definition.submit ?? 'Send'\n const done = definition.redirect === undefined ? (definition.done ?? 'Thank you. It was sent.') : undefined\n\n function Form() {\n return (\n <theme.PublicForm\n action={submitForm.bind(null, definition.name)}\n fields={fields()}\n submit={submit}\n {...(done === undefined ? {} : { done })}\n honeypot={HONEYPOT}\n />\n )\n }\n\n return {\n name: definition.name,\n get fields() {\n return fields()\n },\n Form,\n }\n}\n\nexport interface PublicFormHandle {\n name: string\n fields: Field[]\n Form: () => ReactNode\n}\n\nexport type PublicForm = PublicFormHandle\n"]}
@@ -1,8 +0,0 @@
1
- import type { PublicFormProps } from '../forms/theme.js';
2
- /**
3
- * A form anyone may fill in, drawn with the same fields as the panel's: the
4
- * browser checks each one first, and the server's refusals land under the
5
- * same fields. Once sent it says so in its place - or the action redirects.
6
- * No card around it: it sits in the app's own page, which decides that.
7
- */
8
- export declare function PublicForm({ action, fields, submit, done, honeypot }: PublicFormProps): import("react").JSX.Element;
@@ -1 +0,0 @@
1
- {"version":3,"file":"public-form.js","sourceRoot":"","sources":["../../src/shadcn/public-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,IAAI,MAAM,oBAAoB,CAAA;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEzD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAmB;IACpF,MAAM,IAAI,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAEtC,OAAO,CACL,KAAC,IAAI,IAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,YAC5B,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE;YACpD,IAAI,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACpC,OAAO,CACL,aAAG,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,iCAAiC,aAC1D,KAAC,WAAW,IAAC,SAAS,EAAC,8BAA8B,GAAG,EACvD,IAAI,IACH,CACL,CAAA;YACH,CAAC;YAED,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;YAE7D,OAAO,CACL,MAAC,UAAU,eACT,KAAC,SAAS,IAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,GAAI,EAEvD,6BAAiB,MAAM,EAAC,SAAS,EAAC,iDAAiD,YACjF,gBAAO,IAAI,EAAC,MAAM,EAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,YAAY,EAAC,KAAK,EAAC,YAAY,EAAC,EAAE,GAAG,GAClF,EACL,SAAS,IAAI,CACZ,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACjD,SAAS,GACR,CACL,EACD,wBACE,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,YACpC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,GACvB,GACL,IACK,CACd,CAAA;QACH,CAAC,GACI,CACR,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { useRef } from 'react'\nimport Form from '@rsc-kit/core/form'\nimport { CircleCheck } from 'lucide-react'\nimport type { PublicFormProps } from '../forms/theme.js'\nimport { Button } from './components/ui/button.js'\nimport { FieldGroup } from './components/ui/field.js'\nimport { FieldList, useBrowserErrors } from './fields.js'\n\n/**\n * A form anyone may fill in, drawn with the same fields as the panel's: the\n * browser checks each one first, and the server's refusals land under the\n * same fields. Once sent it says so in its place - or the action redirects.\n * No card around it: it sits in the app's own page, which decides that.\n */\nexport function PublicForm({ action, fields, submit, done, honeypot }: PublicFormProps) {\n const form = useRef<HTMLFormElement>(null)\n const browser = useBrowserErrors(form)\n\n return (\n <Form action={action} ref={form}>\n {({ pending, error: server, formError, succeeded }) => {\n if (succeeded && done !== undefined) {\n return (\n <p role=\"status\" className=\"flex items-center gap-2 text-sm\">\n <CircleCheck className=\"size-4 text-muted-foreground\" />\n {done}\n </p>\n )\n }\n\n const error = (name: string) => browser[name] ?? server(name)\n\n return (\n <FieldGroup>\n <FieldList fields={fields} values={{}} error={error} />\n {/* For bots: out of sight, out of the tab order, and not read out. */}\n <div aria-hidden=\"true\" className=\"absolute -left-[9999px] size-px overflow-hidden\">\n <input type=\"text\" name={honeypot} tabIndex={-1} autoComplete=\"off\" defaultValue=\"\" />\n </div>\n {formError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {formError}\n </p>\n )}\n <div>\n <Button type=\"submit\" disabled={pending}>\n {pending ? 'Sending…' : submit}\n </Button>\n </div>\n </FieldGroup>\n )\n }}\n </Form>\n )\n}\n"]}