@10x-media/form-builder 0.1.0-beta.6 → 0.1.0-beta.7

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 (167) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/actions/buildActionBlocks.js +12 -9
  3. package/dist/actions/buildActionBlocks.js.map +1 -1
  4. package/dist/actions/builtin/confirmation.js +13 -68
  5. package/dist/actions/builtin/confirmation.js.map +1 -1
  6. package/dist/actions/builtin/emailAction.d.ts +17 -0
  7. package/dist/actions/builtin/emailAction.js +83 -0
  8. package/dist/actions/builtin/emailAction.js.map +1 -0
  9. package/dist/actions/builtin/emailTeam.js +14 -74
  10. package/dist/actions/builtin/emailTeam.js.map +1 -1
  11. package/dist/actions/builtin/index.d.ts +9 -12
  12. package/dist/actions/builtin/index.js +11 -11
  13. package/dist/actions/builtin/index.js.map +1 -1
  14. package/dist/actions/defineAction.d.ts +2 -2
  15. package/dist/actions/defineAction.js.map +1 -1
  16. package/dist/actions/emailRecipients.js +75 -31
  17. package/dist/actions/emailRecipients.js.map +1 -1
  18. package/dist/actions/registry.d.ts +2 -1
  19. package/dist/actions/registry.js +2 -6
  20. package/dist/actions/registry.js.map +1 -1
  21. package/dist/actions/task.js +5 -3
  22. package/dist/actions/task.js.map +1 -1
  23. package/dist/calc/evaluate.js +1 -2
  24. package/dist/calc/evaluate.js.map +1 -1
  25. package/dist/calc/normalizeCalc.js +1 -2
  26. package/dist/calc/normalizeCalc.js.map +1 -1
  27. package/dist/calc/types.js.map +1 -1
  28. package/dist/collections/buttonFields.js +1 -1
  29. package/dist/collections/formSubmissions.js +2 -1
  30. package/dist/collections/formSubmissions.js.map +1 -1
  31. package/dist/collections/forms.js +30 -99
  32. package/dist/collections/forms.js.map +1 -1
  33. package/dist/collections/formsEndpoints.js +101 -0
  34. package/dist/collections/formsEndpoints.js.map +1 -0
  35. package/dist/conditions/evaluate.js +1 -1
  36. package/dist/conditions/evaluate.js.map +1 -1
  37. package/dist/conditions/fieldTypes.d.ts +3 -1
  38. package/dist/conditions/fieldTypes.js +4 -1
  39. package/dist/conditions/fieldTypes.js.map +1 -1
  40. package/dist/conditions/normalizeConditions.js +18 -5
  41. package/dist/conditions/normalizeConditions.js.map +1 -1
  42. package/dist/consent/captureConsent.d.ts +7 -0
  43. package/dist/consent/captureConsent.js +7 -0
  44. package/dist/consent/captureConsent.js.map +1 -1
  45. package/dist/consent/consentSourcesField.d.ts +4 -0
  46. package/dist/consent/consentSourcesField.js +5 -1
  47. package/dist/consent/consentSourcesField.js.map +1 -1
  48. package/dist/consent/resolveConsentEntries.d.ts +1 -1
  49. package/dist/consent/resolveConsentEntries.js +1 -4
  50. package/dist/consent/resolveConsentEntries.js.map +1 -1
  51. package/dist/consent/resolveConsentSourcesRequest.js +0 -1
  52. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
  53. package/dist/consent/resolveConsentStatements.d.ts +1 -1
  54. package/dist/consent/resolveConsentStatements.js.map +1 -1
  55. package/dist/consent/types.d.ts +9 -1
  56. package/dist/email/departmentsField.js +1 -1
  57. package/dist/exports/react.d.ts +3 -2
  58. package/dist/exports/react.js +3 -2
  59. package/dist/exports/rsc.js +1 -1
  60. package/dist/exports/types.d.ts +1 -1
  61. package/dist/fields/builtin/email.js +1 -1
  62. package/dist/fields/builtin/email.js.map +1 -1
  63. package/dist/fields/builtin/message.js +1 -1
  64. package/dist/fields/builtin/repeater.js +1 -1
  65. package/dist/fields/builtin/select.js +1 -1
  66. package/dist/fields/registry.d.ts +2 -1
  67. package/dist/fields/registry.js +2 -6
  68. package/dist/fields/registry.js.map +1 -1
  69. package/dist/fields/sharedConfig.js +1 -1
  70. package/dist/flow/normalizeFlow.js +7 -3
  71. package/dist/flow/normalizeFlow.js.map +1 -1
  72. package/dist/index.d.ts +4 -201
  73. package/dist/index.js +11 -4
  74. package/dist/index.js.map +1 -1
  75. package/dist/options.d.ts +228 -0
  76. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  77. package/dist/plugin/applyRegistryConfig.js +20 -0
  78. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  79. package/dist/plugin/registerCollections.js +2 -1
  80. package/dist/plugin/registerCollections.js.map +1 -1
  81. package/dist/poll/buildPollOptionSourceFields.js +2 -2
  82. package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
  83. package/dist/poll/definePollOptionSource.d.ts +2 -2
  84. package/dist/poll/definePollOptionSource.js.map +1 -1
  85. package/dist/poll/pollTypeRegistry.js +2 -4
  86. package/dist/poll/pollTypeRegistry.js.map +1 -1
  87. package/dist/poll/registry.d.ts +2 -1
  88. package/dist/poll/registry.js +2 -6
  89. package/dist/poll/registry.js.map +1 -1
  90. package/dist/poll/resolvePollOptionsRequest.js +0 -1
  91. package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
  92. package/dist/prefill/valuesFromSearchParams.js +2 -7
  93. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  94. package/dist/react/Form.d.ts +3 -1
  95. package/dist/react/Form.js +93 -155
  96. package/dist/react/Form.js.map +1 -1
  97. package/dist/react/FormContext.d.ts +5 -1
  98. package/dist/react/FormContext.js.map +1 -1
  99. package/dist/react/FormFields.d.ts +18 -0
  100. package/dist/react/FormFields.js +114 -0
  101. package/dist/react/FormFields.js.map +1 -0
  102. package/dist/react/FormLayout.d.ts +4 -2
  103. package/dist/react/FormLayout.js +3 -2
  104. package/dist/react/FormLayout.js.map +1 -1
  105. package/dist/react/Poll.js +13 -3
  106. package/dist/react/Poll.js.map +1 -1
  107. package/dist/react/presentation/DialogSurface.js +1 -4
  108. package/dist/react/presentation/DialogSurface.js.map +1 -1
  109. package/dist/react/renderers/calculation.js +1 -3
  110. package/dist/react/renderers/calculation.js.map +1 -1
  111. package/dist/react/renderers/checkbox.js +1 -3
  112. package/dist/react/renderers/checkbox.js.map +1 -1
  113. package/dist/react/renderers/consent.js +2 -3
  114. package/dist/react/renderers/consent.js.map +1 -1
  115. package/dist/react/renderers/date.js +1 -3
  116. package/dist/react/renderers/date.js.map +1 -1
  117. package/dist/react/renderers/email.js +1 -3
  118. package/dist/react/renderers/email.js.map +1 -1
  119. package/dist/react/renderers/file.js +6 -7
  120. package/dist/react/renderers/file.js.map +1 -1
  121. package/dist/react/renderers/number.js +1 -3
  122. package/dist/react/renderers/number.js.map +1 -1
  123. package/dist/react/renderers/region.js +1 -3
  124. package/dist/react/renderers/region.js.map +1 -1
  125. package/dist/react/renderers/repeater.js +20 -4
  126. package/dist/react/renderers/repeater.js.map +1 -1
  127. package/dist/react/renderers/select.js +1 -3
  128. package/dist/react/renderers/select.js.map +1 -1
  129. package/dist/react/renderers/text.js +1 -3
  130. package/dist/react/renderers/text.js.map +1 -1
  131. package/dist/react/renderers/textarea.js +1 -3
  132. package/dist/react/renderers/textarea.js.map +1 -1
  133. package/dist/spam/constants.js +7 -1
  134. package/dist/spam/constants.js.map +1 -1
  135. package/dist/spam/resolveSpam.js +2 -1
  136. package/dist/spam/resolveSpam.js.map +1 -1
  137. package/dist/spam/spamGuard.js +22 -6
  138. package/dist/spam/spamGuard.js.map +1 -1
  139. package/dist/spam/types.d.ts +17 -2
  140. package/dist/submissions/coerceBoolean.js +14 -0
  141. package/dist/submissions/coerceBoolean.js.map +1 -0
  142. package/dist/submissions/runSubmission.js +7 -7
  143. package/dist/submissions/runSubmission.js.map +1 -1
  144. package/dist/submissions/validateSubmission.js +3 -2
  145. package/dist/submissions/validateSubmission.js.map +1 -1
  146. package/dist/translations/de.js +10 -0
  147. package/dist/translations/de.js.map +1 -1
  148. package/dist/translations/en.js +10 -0
  149. package/dist/translations/en.js.map +1 -1
  150. package/dist/translations/keys.d.ts +10 -0
  151. package/dist/translations/keys.js +10 -0
  152. package/dist/translations/keys.js.map +1 -1
  153. package/dist/translations/server.js +8 -1
  154. package/dist/translations/server.js.map +1 -1
  155. package/dist/uploads/captureFileRef.d.ts +10 -3
  156. package/dist/uploads/captureFileRef.js +18 -9
  157. package/dist/uploads/captureFileRef.js.map +1 -1
  158. package/dist/validation/builtin/email.js +1 -1
  159. package/dist/validation/builtin/email.js.map +1 -1
  160. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  161. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  162. package/dist/validation/emailPattern.js +10 -0
  163. package/dist/validation/emailPattern.js.map +1 -0
  164. package/dist/validation/registry.d.ts +2 -1
  165. package/dist/validation/registry.js +2 -9
  166. package/dist/validation/registry.js.map +1 -1
  167. package/package.json +4 -4
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ import { fieldKey, isNamedField } from "../fields/fieldKey.js";
3
+ import { calcExpressionOf } from "../calc/computeCalcFields.js";
4
+ import { useFormContext } from "./FormContext.js";
5
+ import { FormLayout, widthProps } from "./FormLayout.js";
6
+ import { applyRecall } from "./recall.js";
7
+ import { useField } from "./useField.js";
8
+ import { jsx } from "react/jsx-runtime";
9
+ import { createElement, useId } from "react";
10
+ //#region src/react/FormFields.tsx
11
+ const FIELD_WIDTHS = new Set([
12
+ "full",
13
+ "half",
14
+ "third",
15
+ "twoThirds"
16
+ ]);
17
+ /** Fallback when no recall resolver is on the context (a hand-built provider); returns the token name unchanged. */
18
+ const identityRecall = (name) => name;
19
+ const FieldHost = ({ field, renderer, locale, t }) => {
20
+ const id = useId();
21
+ const { value, errors, setValue, onBlur } = useField(field.name);
22
+ return createElement(renderer, {
23
+ field,
24
+ id,
25
+ name: field.name,
26
+ value,
27
+ onChange: setValue,
28
+ onBlur,
29
+ errors,
30
+ required: Boolean(field.required),
31
+ locale,
32
+ t
33
+ });
34
+ };
35
+ /** Hosts a derived (calc) field: read-only, value supplied from `effectiveValues`, never bound via `useField`. */
36
+ const CalcFieldHost = ({ field, renderer, value, locale, t }) => {
37
+ return createElement(renderer, {
38
+ field,
39
+ id: useId(),
40
+ name: field.name,
41
+ value,
42
+ onChange: () => {},
43
+ onBlur: () => {},
44
+ errors: [],
45
+ required: false,
46
+ disabled: true,
47
+ locale,
48
+ t
49
+ });
50
+ };
51
+ /**
52
+ * Hosts a nameless (bare) display block, e.g. a message: no `useField` binding at all. The
53
+ * renderer reads only the instance and the form context; `name` is the row key for consistency.
54
+ */
55
+ const StaticFieldHost = ({ field, renderer, locale, t }) => {
56
+ return createElement(renderer, {
57
+ field,
58
+ id: useId(),
59
+ name: fieldKey(field),
60
+ value: void 0,
61
+ onChange: () => {},
62
+ onBlur: () => {},
63
+ errors: [],
64
+ required: false,
65
+ locale,
66
+ t
67
+ });
68
+ };
69
+ /**
70
+ * The standard field loop, driven entirely by `useFormContext()`. Rendered by `<Form>` in default
71
+ * mode and exported so a custom `children` layout can drop the same loop in without reimplementing
72
+ * visibility, calc, recall, and step filtering. In `children` mode the host owns the success-state
73
+ * swap (via `useFormState`); this component only ever renders the fields.
74
+ */
75
+ const FormFields = ({ layout, className }) => {
76
+ const ctx = useFormContext();
77
+ const { rendererRegistry, effectiveValues, locale, t } = ctx;
78
+ const recall = ctx.recall ?? identityRecall;
79
+ const renderedFields = ctx.renderedFields ?? [];
80
+ return /* @__PURE__ */ jsx(FormLayout, {
81
+ enabled: layout !== false,
82
+ className,
83
+ children: renderedFields.map((field) => {
84
+ const renderer = rendererRegistry.get(field.blockType);
85
+ if (!renderer) return null;
86
+ const width = typeof field.width === "string" && FIELD_WIDTHS.has(field.width) ? field.width : void 0;
87
+ const recalledField = applyRecall(field, recall);
88
+ return /* @__PURE__ */ jsx("div", {
89
+ ...widthProps(width),
90
+ children: !isNamedField(recalledField) ? /* @__PURE__ */ jsx(StaticFieldHost, {
91
+ field: recalledField,
92
+ renderer,
93
+ locale,
94
+ t
95
+ }) : calcExpressionOf(recalledField) ? /* @__PURE__ */ jsx(CalcFieldHost, {
96
+ field: recalledField,
97
+ renderer,
98
+ value: effectiveValues?.[recalledField.name],
99
+ locale,
100
+ t
101
+ }) : /* @__PURE__ */ jsx(FieldHost, {
102
+ field: recalledField,
103
+ renderer,
104
+ locale,
105
+ t
106
+ })
107
+ }, fieldKey(field));
108
+ })
109
+ });
110
+ };
111
+ //#endregion
112
+ export { FormFields };
113
+
114
+ //# sourceMappingURL=FormFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormFields.js","names":[],"sources":["../../src/react/FormFields.tsx"],"sourcesContent":["'use client'\n\nimport { createElement, useId } from 'react'\nimport { calcExpressionOf } from '../calc/computeCalcFields'\nimport { fieldKey, isNamedField, type NamedFormFieldInstance } from '../fields/fieldKey'\nimport type { RecallResolver } from '../recall/resolver'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { FieldRenderer, RendererTranslate } from './contract'\nimport { useFormContext } from './FormContext'\nimport { type FieldWidth, FormLayout, widthProps } from './FormLayout'\nimport { applyRecall } from './recall'\nimport { useField } from './useField'\n\nconst FIELD_WIDTHS = new Set<string>(['full', 'half', 'third', 'twoThirds'])\n\n/** Fallback when no recall resolver is on the context (a hand-built provider); returns the token name unchanged. */\nconst identityRecall: RecallResolver = (name) => name\n\ntype FieldHostProps = {\n\tfield: NamedFormFieldInstance\n\trenderer: FieldRenderer\n\tlocale: string\n\tt: RendererTranslate\n}\n\nconst FieldHost = ({ field, renderer, locale, t }: FieldHostProps) => {\n\tconst id = useId()\n\tconst { value, errors, setValue, onBlur } = useField(field.name)\n\treturn createElement(renderer, {\n\t\tfield,\n\t\tid,\n\t\tname: field.name,\n\t\tvalue,\n\t\tonChange: setValue,\n\t\tonBlur,\n\t\terrors,\n\t\trequired: Boolean(field.required),\n\t\tlocale,\n\t\tt,\n\t})\n}\n\ntype CalcFieldHostProps = FieldHostProps & { value: unknown }\n\n/** Hosts a derived (calc) field: read-only, value supplied from `effectiveValues`, never bound via `useField`. */\nconst CalcFieldHost = ({ field, renderer, value, locale, t }: CalcFieldHostProps) => {\n\tconst id = useId()\n\treturn createElement(renderer, {\n\t\tfield,\n\t\tid,\n\t\tname: field.name,\n\t\tvalue,\n\t\tonChange: () => {},\n\t\tonBlur: () => {},\n\t\terrors: [],\n\t\trequired: false,\n\t\tdisabled: true,\n\t\tlocale,\n\t\tt,\n\t})\n}\n\ntype StaticFieldHostProps = Omit<FieldHostProps, 'field'> & { field: FormFieldInstance }\n\n/**\n * Hosts a nameless (bare) display block, e.g. a message: no `useField` binding at all. The\n * renderer reads only the instance and the form context; `name` is the row key for consistency.\n */\nconst StaticFieldHost = ({ field, renderer, locale, t }: StaticFieldHostProps) => {\n\tconst id = useId()\n\treturn createElement(renderer, {\n\t\tfield,\n\t\tid,\n\t\tname: fieldKey(field),\n\t\tvalue: undefined,\n\t\tonChange: () => {},\n\t\tonBlur: () => {},\n\t\terrors: [],\n\t\trequired: false,\n\t\tlocale,\n\t\tt,\n\t})\n}\n\nexport type FormFieldsProps = {\n\t/** When false, render the fields in document order with no grid class. Defaults to the grid. */\n\tlayout?: boolean\n\t/** Extra class names merged onto the layout container. */\n\tclassName?: string\n}\n\n/**\n * The standard field loop, driven entirely by `useFormContext()`. Rendered by `<Form>` in default\n * mode and exported so a custom `children` layout can drop the same loop in without reimplementing\n * visibility, calc, recall, and step filtering. In `children` mode the host owns the success-state\n * swap (via `useFormState`); this component only ever renders the fields.\n */\nexport const FormFields = ({ layout, className }: FormFieldsProps) => {\n\tconst ctx = useFormContext()\n\tconst { rendererRegistry, effectiveValues, locale, t } = ctx\n\tconst recall = ctx.recall ?? identityRecall\n\tconst renderedFields = ctx.renderedFields ?? []\n\treturn (\n\t\t<FormLayout enabled={layout !== false} className={className}>\n\t\t\t{renderedFields.map((field) => {\n\t\t\t\tconst renderer = rendererRegistry.get(field.blockType)\n\t\t\t\tif (!renderer) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tconst width: FieldWidth | undefined =\n\t\t\t\t\ttypeof field.width === 'string' && FIELD_WIDTHS.has(field.width)\n\t\t\t\t\t\t? (field.width as FieldWidth)\n\t\t\t\t\t\t: undefined\n\t\t\t\tconst recalledField = applyRecall(field, recall)\n\t\t\t\treturn (\n\t\t\t\t\t<div key={fieldKey(field)} {...widthProps(width)}>\n\t\t\t\t\t\t{!isNamedField(recalledField) ? (\n\t\t\t\t\t\t\t<StaticFieldHost field={recalledField} renderer={renderer} locale={locale} t={t} />\n\t\t\t\t\t\t) : calcExpressionOf(recalledField) ? (\n\t\t\t\t\t\t\t<CalcFieldHost\n\t\t\t\t\t\t\t\tfield={recalledField}\n\t\t\t\t\t\t\t\trenderer={renderer}\n\t\t\t\t\t\t\t\tvalue={effectiveValues?.[recalledField.name]}\n\t\t\t\t\t\t\t\tlocale={locale}\n\t\t\t\t\t\t\t\tt={t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<FieldHost field={recalledField} renderer={renderer} locale={locale} t={t} />\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)\n\t\t\t})}\n\t\t</FormLayout>\n\t)\n}\n"],"mappings":";;;;;;;;;;AAaA,MAAM,eAAe,IAAI,IAAY;CAAC;CAAQ;CAAQ;CAAS;AAAW,CAAC;;AAG3E,MAAM,kBAAkC,SAAS;AASjD,MAAM,aAAa,EAAE,OAAO,UAAU,QAAQ,QAAwB;CACrE,MAAM,KAAK,MAAM;CACjB,MAAM,EAAE,OAAO,QAAQ,UAAU,WAAW,SAAS,MAAM,IAAI;CAC/D,OAAO,cAAc,UAAU;EAC9B;EACA;EACA,MAAM,MAAM;EACZ;EACA,UAAU;EACV;EACA;EACA,UAAU,QAAQ,MAAM,QAAQ;EAChC;EACA;CACD,CAAC;AACF;;AAKA,MAAM,iBAAiB,EAAE,OAAO,UAAU,OAAO,QAAQ,QAA4B;CAEpF,OAAO,cAAc,UAAU;EAC9B;EACA,IAHU,MAGT;EACD,MAAM,MAAM;EACZ;EACA,gBAAgB,CAAC;EACjB,cAAc,CAAC;EACf,QAAQ,CAAC;EACT,UAAU;EACV,UAAU;EACV;EACA;CACD,CAAC;AACF;;;;;AAQA,MAAM,mBAAmB,EAAE,OAAO,UAAU,QAAQ,QAA8B;CAEjF,OAAO,cAAc,UAAU;EAC9B;EACA,IAHU,MAGT;EACD,MAAM,SAAS,KAAK;EACpB,OAAO,KAAA;EACP,gBAAgB,CAAC;EACjB,cAAc,CAAC;EACf,QAAQ,CAAC;EACT,UAAU;EACV;EACA;CACD,CAAC;AACF;;;;;;;AAeA,MAAa,cAAc,EAAE,QAAQ,gBAAiC;CACrE,MAAM,MAAM,eAAe;CAC3B,MAAM,EAAE,kBAAkB,iBAAiB,QAAQ,MAAM;CACzD,MAAM,SAAS,IAAI,UAAU;CAC7B,MAAM,iBAAiB,IAAI,kBAAkB,CAAC;CAC9C,OACC,oBAAC,YAAD;EAAY,SAAS,WAAW;EAAkB;YAChD,eAAe,KAAK,UAAU;GAC9B,MAAM,WAAW,iBAAiB,IAAI,MAAM,SAAS;GACrD,IAAI,CAAC,UACJ,OAAO;GAER,MAAM,QACL,OAAO,MAAM,UAAU,YAAY,aAAa,IAAI,MAAM,KAAK,IAC3D,MAAM,QACP,KAAA;GACJ,MAAM,gBAAgB,YAAY,OAAO,MAAM;GAC/C,OACC,oBAAC,OAAD;IAA2B,GAAI,WAAW,KAAK;cAC7C,CAAC,aAAa,aAAa,IAC3B,oBAAC,iBAAD;KAAiB,OAAO;KAAyB;KAAkB;KAAW;IAAI,CAAA,IAC/E,iBAAiB,aAAa,IACjC,oBAAC,eAAD;KACC,OAAO;KACG;KACV,OAAO,kBAAkB,cAAc;KAC/B;KACL;IACH,CAAA,IAED,oBAAC,WAAD;KAAW,OAAO;KAAyB;KAAkB;KAAW;IAAI,CAAA;GAEzE,GAdK,SAAS,KAAK,CAcnB;EAEP,CAAC;CACU,CAAA;AAEd"}
@@ -4,12 +4,14 @@ import { ReactNode } from "react";
4
4
  type FieldWidth = 'full' | 'half' | 'third' | 'twoThirds';
5
5
  type FormLayoutProps = {
6
6
  children: ReactNode; /** When false, render a plain container with no grid class (document-order layout). */
7
- enabled?: boolean;
7
+ enabled?: boolean; /** Extra class names merged onto the layout container. */
8
+ className?: string;
8
9
  };
9
10
  /** Establishes the layout container. With the stylesheet imported, children with `data-width` flow into a grid. */
10
11
  declare const FormLayout: ({
11
12
  children,
12
- enabled
13
+ enabled,
14
+ className
13
15
  }: FormLayoutProps) => import("react/jsx-runtime").JSX.Element;
14
16
  /** Props to spread onto a field wrapper to declare its grid width. */
15
17
  declare const widthProps: (width: FieldWidth | undefined) => {
@@ -1,9 +1,10 @@
1
1
  "use client";
2
+ import { cn } from "./cn.js";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  //#region src/react/FormLayout.tsx
4
5
  /** Establishes the layout container. With the stylesheet imported, children with `data-width` flow into a grid. */
5
- const FormLayout = ({ children, enabled = true }) => /* @__PURE__ */ jsx("div", {
6
- className: enabled ? "fb-form fb-form--grid" : "fb-form",
6
+ const FormLayout = ({ children, enabled = true, className }) => /* @__PURE__ */ jsx("div", {
7
+ className: cn(enabled ? "fb-form fb-form--grid" : "fb-form", className),
7
8
  children
8
9
  });
9
10
  /** Props to spread onto a field wrapper to declare its grid width. */
@@ -1 +1 @@
1
- {"version":3,"file":"FormLayout.js","names":[],"sources":["../../src/react/FormLayout.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\n\nexport type FieldWidth = 'full' | 'half' | 'third' | 'twoThirds'\n\nexport type FormLayoutProps = {\n\tchildren: ReactNode\n\t/** When false, render a plain container with no grid class (document-order layout). */\n\tenabled?: boolean\n}\n\n/** Establishes the layout container. With the stylesheet imported, children with `data-width` flow into a grid. */\nexport const FormLayout = ({ children, enabled = true }: FormLayoutProps) => (\n\t<div className={enabled ? 'fb-form fb-form--grid' : 'fb-form'}>{children}</div>\n)\n\n/** Props to spread onto a field wrapper to declare its grid width. */\nexport const widthProps = (width: FieldWidth | undefined) => ({ 'data-width': width ?? 'full' })\n"],"mappings":";;;;AAaA,MAAa,cAAc,EAAE,UAAU,UAAU,WAChD,oBAAC,OAAD;CAAK,WAAW,UAAU,0BAA0B;CAAY;AAAc,CAAA;;AAI/E,MAAa,cAAc,WAAmC,EAAE,cAAc,SAAS,OAAO"}
1
+ {"version":3,"file":"FormLayout.js","names":[],"sources":["../../src/react/FormLayout.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\nimport { cn } from './cn'\n\nexport type FieldWidth = 'full' | 'half' | 'third' | 'twoThirds'\n\nexport type FormLayoutProps = {\n\tchildren: ReactNode\n\t/** When false, render a plain container with no grid class (document-order layout). */\n\tenabled?: boolean\n\t/** Extra class names merged onto the layout container. */\n\tclassName?: string\n}\n\n/** Establishes the layout container. With the stylesheet imported, children with `data-width` flow into a grid. */\nexport const FormLayout = ({ children, enabled = true, className }: FormLayoutProps) => (\n\t<div className={cn(enabled ? 'fb-form fb-form--grid' : 'fb-form', className)}>{children}</div>\n)\n\n/** Props to spread onto a field wrapper to declare its grid width. */\nexport const widthProps = (width: FieldWidth | undefined) => ({ 'data-width': width ?? 'full' })\n"],"mappings":";;;;;AAgBA,MAAa,cAAc,EAAE,UAAU,UAAU,MAAM,gBACtD,oBAAC,OAAD;CAAK,WAAW,GAAG,UAAU,0BAA0B,WAAW,SAAS;CAAI;AAAc,CAAA;;AAI9F,MAAa,cAAc,WAAmC,EAAE,cAAc,SAAS,OAAO"}
@@ -42,6 +42,7 @@ const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchForm
42
42
  const resultsAwaitClose = !closed && !finalized && poll?.resultsVisibility === "afterClose";
43
43
  const [voted, setVoted] = useState(false);
44
44
  const [results, setResults] = useState(null);
45
+ const [loadFailed, setLoadFailed] = useState(false);
45
46
  const translate = useMemo(() => formProps.t ?? makeTranslate(en), [formProps.t]);
46
47
  const loadResults = useCallback(async () => {
47
48
  const result = await fetchResultsImpl({
@@ -49,13 +50,21 @@ const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchForm
49
50
  field: resultsField,
50
51
  apiRoute
51
52
  });
52
- setResults(result.ok ? result.results : []);
53
+ if (result.ok) {
54
+ setResults(result.results);
55
+ setLoadFailed(false);
56
+ } else setLoadFailed(true);
53
57
  }, [
54
58
  fetchResultsImpl,
55
59
  formProps.form.id,
56
60
  resultsField,
57
61
  apiRoute
58
62
  ]);
63
+ const resultsError = /* @__PURE__ */ jsx("p", {
64
+ className: "fb-poll__error",
65
+ role: "alert",
66
+ children: translate(keys.pollResultsError)
67
+ });
59
68
  useEffect(() => {
60
69
  const already = hasVoted === true || readVoted(key);
61
70
  if (already) setVoted(true);
@@ -84,7 +93,7 @@ const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchForm
84
93
  children: [/* @__PURE__ */ jsx("p", {
85
94
  className: "fb-poll__final",
86
95
  children: translate(keys.pollFinalResult)
87
- }), results ? /* @__PURE__ */ jsx(FormResults, {
96
+ }), loadFailed ? resultsError : results ? /* @__PURE__ */ jsx(FormResults, {
88
97
  results,
89
98
  winningValues,
90
99
  t: formProps.t,
@@ -96,7 +105,7 @@ const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchForm
96
105
  children: [/* @__PURE__ */ jsx("p", {
97
106
  className: "fb-poll__closed",
98
107
  children: translate(keys.pollClosed)
99
- }), results ? /* @__PURE__ */ jsx(FormResults, {
108
+ }), loadFailed ? resultsError : results ? /* @__PURE__ */ jsx(FormResults, {
100
109
  results,
101
110
  t: formProps.t,
102
111
  locale: formProps.locale
@@ -107,6 +116,7 @@ const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchForm
107
116
  className: "fb-poll__await-close",
108
117
  children: translate(keys.pollResultsAfterClose)
109
118
  });
119
+ if (loadFailed) return resultsError;
110
120
  return results ? /* @__PURE__ */ jsx(FormResults, {
111
121
  results,
112
122
  t: formProps.t,
@@ -1 +1 @@
1
- {"version":3,"file":"Poll.js","names":[],"sources":["../../src/react/Poll.tsx"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { FieldAggregation } from '../aggregation/types'\nimport { isPollClosed } from '../form/pollState'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport { Form, type FormProps } from './Form'\nimport { FormResults } from './FormResults'\nimport { type FetchResultsResult, fetchFormResults } from './fetchResults'\n\nexport type PollProps = FormProps & {\n\t/** The choice field whose results are shown after voting (should match the form's public `poll.resultsField`). */\n\tresultsField: string\n\t/** localStorage key for the per-browser voted guard. Default `fb-poll-{form.id}`. */\n\tstorageKey?: string\n\t/**\n\t * Server-known voted state, ORed with the localStorage guard: `true` marks the visitor as voted;\n\t * `false`/omitted falls back to localStorage. SSR hosts using the plugin's `poll.votedCookie`\n\t * option pass `hasVotedCookie(cookieHeader, form.id)` here.\n\t */\n\thasVoted?: boolean\n\t/**\n\t * Injectable results fetch (testing); defaults to `fetchFormResults`. Pass a stable reference (module\n\t * scope, `useCallback`, or `useMemo`): an inline function re-runs the load effect and double-fetches.\n\t */\n\tfetchResultsImpl?: typeof fetchFormResults\n}\n\nconst readVoted = (key: string): boolean => {\n\ttry {\n\t\treturn window.localStorage.getItem(key) != null\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst writeVoted = (key: string): void => {\n\ttry {\n\t\twindow.localStorage.setItem(key, '1')\n\t} catch {\n\t\t// Private mode / storage disabled: the guard is best-effort UX, never integrity.\n\t}\n}\n\n/**\n * A poll: renders `<Form>` while open and not yet voted, then fetches the aggregate results and shows\n * `<FormResults>`. Lifecycle comes from `form.poll`: past `closesAt` the poll is closed (a translated\n * notice plus results, which the endpoint serves for any visibility once closed); a voted-but-open\n * `afterClose` poll shows a translated wait notice instead of fetching (the endpoint would refuse). A\n * recorded `outcome.winningValues` (via `resolvePollOutcome`) supersedes everything: a translated final\n * notice plus results with every winning bucket highlighted (a tie highlights more than one). A\n * per-browser localStorage flag (`storageKey`) skips straight to results on revisit; `hasVoted: true`\n * (e.g. from the server-set voted cookie) marks voted regardless of localStorage. The guard is UX, not\n * integrity (bypassable): server-enforced\n * one-per-identity dedup composes via `req.user` (authed forms) or a `notAlreadySubmitted` rule.\n */\nexport const Poll = ({\n\tresultsField,\n\tstorageKey,\n\thasVoted,\n\tfetchResultsImpl = fetchFormResults,\n\tapiRoute,\n\tonSuccess,\n\t...formProps\n}: PollProps) => {\n\tconst key = storageKey ?? `fb-poll-${formProps.form.id}`\n\tconst poll = formProps.form.poll\n\tconst closed = isPollClosed(poll)\n\tconst winningValues = poll?.outcome?.winningValues\n\tconst finalized = Array.isArray(winningValues) && winningValues.length > 0\n\tconst resultsAwaitClose = !closed && !finalized && poll?.resultsVisibility === 'afterClose'\n\tconst [voted, setVoted] = useState(false)\n\tconst [results, setResults] = useState<FieldAggregation[] | null>(null)\n\tconst translate = useMemo(() => formProps.t ?? makeTranslate(en), [formProps.t])\n\n\tconst loadResults = useCallback(async () => {\n\t\tconst result: FetchResultsResult = await fetchResultsImpl({\n\t\t\tformId: formProps.form.id,\n\t\t\tfield: resultsField,\n\t\t\tapiRoute,\n\t\t})\n\t\tsetResults(result.ok ? result.results : [])\n\t}, [fetchResultsImpl, formProps.form.id, resultsField, apiRoute])\n\n\tuseEffect(() => {\n\t\tconst already = hasVoted === true || readVoted(key)\n\t\tif (already) {\n\t\t\tsetVoted(true)\n\t\t}\n\t\tif ((already && !resultsAwaitClose) || closed || finalized) {\n\t\t\tvoid loadResults()\n\t\t}\n\t}, [hasVoted, key, loadResults, closed, resultsAwaitClose, finalized])\n\n\tconst handleSuccess = useCallback(\n\t\t(submissionId?: string) => {\n\t\t\twriteVoted(key)\n\t\t\tsetVoted(true)\n\t\t\tif (!resultsAwaitClose) {\n\t\t\t\tvoid loadResults()\n\t\t\t}\n\t\t\tonSuccess?.(submissionId)\n\t\t},\n\t\t[key, loadResults, onSuccess, resultsAwaitClose]\n\t)\n\n\tif (finalized) {\n\t\treturn (\n\t\t\t<div className=\"fb-poll fb-poll--final\">\n\t\t\t\t<p className=\"fb-poll__final\">{translate(keys.pollFinalResult)}</p>\n\t\t\t\t{results ? (\n\t\t\t\t\t<FormResults\n\t\t\t\t\t\tresults={results}\n\t\t\t\t\t\twinningValues={winningValues}\n\t\t\t\t\t\tt={formProps.t}\n\t\t\t\t\t\tlocale={formProps.locale}\n\t\t\t\t\t/>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (closed) {\n\t\treturn (\n\t\t\t<div className=\"fb-poll fb-poll--closed\">\n\t\t\t\t<p className=\"fb-poll__closed\">{translate(keys.pollClosed)}</p>\n\t\t\t\t{results ? (\n\t\t\t\t\t<FormResults results={results} t={formProps.t} locale={formProps.locale} />\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (voted) {\n\t\tif (resultsAwaitClose) {\n\t\t\treturn <p className=\"fb-poll__await-close\">{translate(keys.pollResultsAfterClose)}</p>\n\t\t}\n\t\treturn results ? (\n\t\t\t<FormResults results={results} t={formProps.t} locale={formProps.locale} />\n\t\t) : null\n\t}\n\n\treturn <Form {...formProps} apiRoute={apiRoute} onSuccess={handleSuccess} />\n}\n"],"mappings":";;;;;;;;;;;AA8BA,MAAM,aAAa,QAAyB;CAC3C,IAAI;EACH,OAAO,OAAO,aAAa,QAAQ,GAAG,KAAK;CAC5C,QAAQ;EACP,OAAO;CACR;AACD;AAEA,MAAM,cAAc,QAAsB;CACzC,IAAI;EACH,OAAO,aAAa,QAAQ,KAAK,GAAG;CACrC,QAAQ,CAER;AACD;;;;;;;;;;;;;AAcA,MAAa,QAAQ,EACpB,cACA,YACA,UACA,mBAAmB,kBACnB,UACA,WACA,GAAG,gBACa;CAChB,MAAM,MAAM,cAAc,WAAW,UAAU,KAAK;CACpD,MAAM,OAAO,UAAU,KAAK;CAC5B,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,gBAAgB,MAAM,SAAS;CACrC,MAAM,YAAY,MAAM,QAAQ,aAAa,KAAK,cAAc,SAAS;CACzE,MAAM,oBAAoB,CAAC,UAAU,CAAC,aAAa,MAAM,sBAAsB;CAC/E,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CACxC,MAAM,CAAC,SAAS,cAAc,SAAoC,IAAI;CACtE,MAAM,YAAY,cAAc,UAAU,KAAK,cAAc,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;CAE/E,MAAM,cAAc,YAAY,YAAY;EAC3C,MAAM,SAA6B,MAAM,iBAAiB;GACzD,QAAQ,UAAU,KAAK;GACvB,OAAO;GACP;EACD,CAAC;EACD,WAAW,OAAO,KAAK,OAAO,UAAU,CAAC,CAAC;CAC3C,GAAG;EAAC;EAAkB,UAAU,KAAK;EAAI;EAAc;CAAQ,CAAC;CAEhE,gBAAgB;EACf,MAAM,UAAU,aAAa,QAAQ,UAAU,GAAG;EAClD,IAAI,SACH,SAAS,IAAI;EAEd,IAAK,WAAW,CAAC,qBAAsB,UAAU,WAChD,YAAiB;CAEnB,GAAG;EAAC;EAAU;EAAK;EAAa;EAAQ;EAAmB;CAAS,CAAC;CAErE,MAAM,gBAAgB,aACpB,iBAA0B;EAC1B,WAAW,GAAG;EACd,SAAS,IAAI;EACb,IAAI,CAAC,mBACJ,YAAiB;EAElB,YAAY,YAAY;CACzB,GACA;EAAC;EAAK;EAAa;EAAW;CAAiB,CAChD;CAEA,IAAI,WACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAkB,UAAU,KAAK,eAAe;EAAK,CAAA,GACjE,UACA,oBAAC,aAAD;GACU;GACM;GACf,GAAG,UAAU;GACb,QAAQ,UAAU;EAClB,CAAA,IACE,IACA;;CAIP,IAAI,QACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAmB,UAAU,KAAK,UAAU;EAAK,CAAA,GAC7D,UACA,oBAAC,aAAD;GAAsB;GAAS,GAAG,UAAU;GAAG,QAAQ,UAAU;EAAS,CAAA,IACvE,IACA;;CAIP,IAAI,OAAO;EACV,IAAI,mBACH,OAAO,oBAAC,KAAD;GAAG,WAAU;aAAwB,UAAU,KAAK,qBAAqB;EAAK,CAAA;EAEtF,OAAO,UACN,oBAAC,aAAD;GAAsB;GAAS,GAAG,UAAU;GAAG,QAAQ,UAAU;EAAS,CAAA,IACvE;CACL;CAEA,OAAO,oBAAC,MAAD;EAAM,GAAI;EAAqB;EAAU,WAAW;CAAgB,CAAA;AAC5E"}
1
+ {"version":3,"file":"Poll.js","names":[],"sources":["../../src/react/Poll.tsx"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { FieldAggregation } from '../aggregation/types'\nimport { isPollClosed } from '../form/pollState'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport { Form, type FormProps } from './Form'\nimport { FormResults } from './FormResults'\nimport { type FetchResultsResult, fetchFormResults } from './fetchResults'\n\nexport type PollProps = FormProps & {\n\t/** The choice field whose results are shown after voting (should match the form's public `poll.resultsField`). */\n\tresultsField: string\n\t/** localStorage key for the per-browser voted guard. Default `fb-poll-{form.id}`. */\n\tstorageKey?: string\n\t/**\n\t * Server-known voted state, ORed with the localStorage guard: `true` marks the visitor as voted;\n\t * `false`/omitted falls back to localStorage. SSR hosts using the plugin's `poll.votedCookie`\n\t * option pass `hasVotedCookie(cookieHeader, form.id)` here.\n\t */\n\thasVoted?: boolean\n\t/**\n\t * Injectable results fetch (testing); defaults to `fetchFormResults`. Pass a stable reference (module\n\t * scope, `useCallback`, or `useMemo`): an inline function re-runs the load effect and double-fetches.\n\t */\n\tfetchResultsImpl?: typeof fetchFormResults\n}\n\nconst readVoted = (key: string): boolean => {\n\ttry {\n\t\treturn window.localStorage.getItem(key) != null\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst writeVoted = (key: string): void => {\n\ttry {\n\t\twindow.localStorage.setItem(key, '1')\n\t} catch {\n\t\t// Private mode / storage disabled: the guard is best-effort UX, never integrity.\n\t}\n}\n\n/**\n * A poll: renders `<Form>` while open and not yet voted, then fetches the aggregate results and shows\n * `<FormResults>`. Lifecycle comes from `form.poll`: past `closesAt` the poll is closed (a translated\n * notice plus results, which the endpoint serves for any visibility once closed); a voted-but-open\n * `afterClose` poll shows a translated wait notice instead of fetching (the endpoint would refuse). A\n * recorded `outcome.winningValues` (via `resolvePollOutcome`) supersedes everything: a translated final\n * notice plus results with every winning bucket highlighted (a tie highlights more than one). A\n * per-browser localStorage flag (`storageKey`) skips straight to results on revisit; `hasVoted: true`\n * (e.g. from the server-set voted cookie) marks voted regardless of localStorage. The guard is UX, not\n * integrity (bypassable): server-enforced\n * one-per-identity dedup composes via `req.user` (authed forms) or a `notAlreadySubmitted` rule.\n */\nexport const Poll = ({\n\tresultsField,\n\tstorageKey,\n\thasVoted,\n\tfetchResultsImpl = fetchFormResults,\n\tapiRoute,\n\tonSuccess,\n\t...formProps\n}: PollProps) => {\n\tconst key = storageKey ?? `fb-poll-${formProps.form.id}`\n\tconst poll = formProps.form.poll\n\tconst closed = isPollClosed(poll)\n\tconst winningValues = poll?.outcome?.winningValues\n\tconst finalized = Array.isArray(winningValues) && winningValues.length > 0\n\tconst resultsAwaitClose = !closed && !finalized && poll?.resultsVisibility === 'afterClose'\n\tconst [voted, setVoted] = useState(false)\n\tconst [results, setResults] = useState<FieldAggregation[] | null>(null)\n\tconst [loadFailed, setLoadFailed] = useState(false)\n\tconst translate = useMemo(() => formProps.t ?? makeTranslate(en), [formProps.t])\n\n\tconst loadResults = useCallback(async () => {\n\t\tconst result: FetchResultsResult = await fetchResultsImpl({\n\t\t\tformId: formProps.form.id,\n\t\t\tfield: resultsField,\n\t\t\tapiRoute,\n\t\t})\n\t\t// A failed load surfaces as an error, not an empty result set: `[]` would read as \"no votes yet\".\n\t\tif (result.ok) {\n\t\t\tsetResults(result.results)\n\t\t\tsetLoadFailed(false)\n\t\t} else {\n\t\t\tsetLoadFailed(true)\n\t\t}\n\t}, [fetchResultsImpl, formProps.form.id, resultsField, apiRoute])\n\n\tconst resultsError = (\n\t\t<p className=\"fb-poll__error\" role=\"alert\">\n\t\t\t{translate(keys.pollResultsError)}\n\t\t</p>\n\t)\n\n\tuseEffect(() => {\n\t\tconst already = hasVoted === true || readVoted(key)\n\t\tif (already) {\n\t\t\tsetVoted(true)\n\t\t}\n\t\tif ((already && !resultsAwaitClose) || closed || finalized) {\n\t\t\tvoid loadResults()\n\t\t}\n\t}, [hasVoted, key, loadResults, closed, resultsAwaitClose, finalized])\n\n\tconst handleSuccess = useCallback(\n\t\t(submissionId?: string) => {\n\t\t\twriteVoted(key)\n\t\t\tsetVoted(true)\n\t\t\tif (!resultsAwaitClose) {\n\t\t\t\tvoid loadResults()\n\t\t\t}\n\t\t\tonSuccess?.(submissionId)\n\t\t},\n\t\t[key, loadResults, onSuccess, resultsAwaitClose]\n\t)\n\n\tif (finalized) {\n\t\treturn (\n\t\t\t<div className=\"fb-poll fb-poll--final\">\n\t\t\t\t<p className=\"fb-poll__final\">{translate(keys.pollFinalResult)}</p>\n\t\t\t\t{loadFailed ? (\n\t\t\t\t\tresultsError\n\t\t\t\t) : results ? (\n\t\t\t\t\t<FormResults\n\t\t\t\t\t\tresults={results}\n\t\t\t\t\t\twinningValues={winningValues}\n\t\t\t\t\t\tt={formProps.t}\n\t\t\t\t\t\tlocale={formProps.locale}\n\t\t\t\t\t/>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (closed) {\n\t\treturn (\n\t\t\t<div className=\"fb-poll fb-poll--closed\">\n\t\t\t\t<p className=\"fb-poll__closed\">{translate(keys.pollClosed)}</p>\n\t\t\t\t{loadFailed ? (\n\t\t\t\t\tresultsError\n\t\t\t\t) : results ? (\n\t\t\t\t\t<FormResults results={results} t={formProps.t} locale={formProps.locale} />\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (voted) {\n\t\tif (resultsAwaitClose) {\n\t\t\treturn <p className=\"fb-poll__await-close\">{translate(keys.pollResultsAfterClose)}</p>\n\t\t}\n\t\tif (loadFailed) {\n\t\t\treturn resultsError\n\t\t}\n\t\treturn results ? (\n\t\t\t<FormResults results={results} t={formProps.t} locale={formProps.locale} />\n\t\t) : null\n\t}\n\n\treturn <Form {...formProps} apiRoute={apiRoute} onSuccess={handleSuccess} />\n}\n"],"mappings":";;;;;;;;;;;AA8BA,MAAM,aAAa,QAAyB;CAC3C,IAAI;EACH,OAAO,OAAO,aAAa,QAAQ,GAAG,KAAK;CAC5C,QAAQ;EACP,OAAO;CACR;AACD;AAEA,MAAM,cAAc,QAAsB;CACzC,IAAI;EACH,OAAO,aAAa,QAAQ,KAAK,GAAG;CACrC,QAAQ,CAER;AACD;;;;;;;;;;;;;AAcA,MAAa,QAAQ,EACpB,cACA,YACA,UACA,mBAAmB,kBACnB,UACA,WACA,GAAG,gBACa;CAChB,MAAM,MAAM,cAAc,WAAW,UAAU,KAAK;CACpD,MAAM,OAAO,UAAU,KAAK;CAC5B,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,gBAAgB,MAAM,SAAS;CACrC,MAAM,YAAY,MAAM,QAAQ,aAAa,KAAK,cAAc,SAAS;CACzE,MAAM,oBAAoB,CAAC,UAAU,CAAC,aAAa,MAAM,sBAAsB;CAC/E,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CACxC,MAAM,CAAC,SAAS,cAAc,SAAoC,IAAI;CACtE,MAAM,CAAC,YAAY,iBAAiB,SAAS,KAAK;CAClD,MAAM,YAAY,cAAc,UAAU,KAAK,cAAc,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;CAE/E,MAAM,cAAc,YAAY,YAAY;EAC3C,MAAM,SAA6B,MAAM,iBAAiB;GACzD,QAAQ,UAAU,KAAK;GACvB,OAAO;GACP;EACD,CAAC;EAED,IAAI,OAAO,IAAI;GACd,WAAW,OAAO,OAAO;GACzB,cAAc,KAAK;EACpB,OACC,cAAc,IAAI;CAEpB,GAAG;EAAC;EAAkB,UAAU,KAAK;EAAI;EAAc;CAAQ,CAAC;CAEhE,MAAM,eACL,oBAAC,KAAD;EAAG,WAAU;EAAiB,MAAK;YACjC,UAAU,KAAK,gBAAgB;CAC9B,CAAA;CAGJ,gBAAgB;EACf,MAAM,UAAU,aAAa,QAAQ,UAAU,GAAG;EAClD,IAAI,SACH,SAAS,IAAI;EAEd,IAAK,WAAW,CAAC,qBAAsB,UAAU,WAChD,YAAiB;CAEnB,GAAG;EAAC;EAAU;EAAK;EAAa;EAAQ;EAAmB;CAAS,CAAC;CAErE,MAAM,gBAAgB,aACpB,iBAA0B;EAC1B,WAAW,GAAG;EACd,SAAS,IAAI;EACb,IAAI,CAAC,mBACJ,YAAiB;EAElB,YAAY,YAAY;CACzB,GACA;EAAC;EAAK;EAAa;EAAW;CAAiB,CAChD;CAEA,IAAI,WACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAkB,UAAU,KAAK,eAAe;EAAK,CAAA,GACjE,aACA,eACG,UACH,oBAAC,aAAD;GACU;GACM;GACf,GAAG,UAAU;GACb,QAAQ,UAAU;EAClB,CAAA,IACE,IACA;;CAIP,IAAI,QACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAmB,UAAU,KAAK,UAAU;EAAK,CAAA,GAC7D,aACA,eACG,UACH,oBAAC,aAAD;GAAsB;GAAS,GAAG,UAAU;GAAG,QAAQ,UAAU;EAAS,CAAA,IACvE,IACA;;CAIP,IAAI,OAAO;EACV,IAAI,mBACH,OAAO,oBAAC,KAAD;GAAG,WAAU;aAAwB,UAAU,KAAK,qBAAqB;EAAK,CAAA;EAEtF,IAAI,YACH,OAAO;EAER,OAAO,UACN,oBAAC,aAAD;GAAsB;GAAS,GAAG,UAAU;GAAG,QAAQ,UAAU;EAAS,CAAA,IACvE;CACL;CAEA,OAAO,oBAAC,MAAD;EAAM,GAAI;EAAqB;EAAU,WAAW;CAAgB,CAAA;AAC5E"}
@@ -30,10 +30,7 @@ const DialogSurface = ({ open, onClose, label, labelledBy, surface, closeLabel =
30
30
  };
31
31
  }, [open]);
32
32
  if (!open) return null;
33
- return createElement("div", { "data-fb-overlay": surface ?? "" }, [createElement(Backdrop, {
34
- key: "backdrop",
35
- onClick: onClose
36
- }), createElement("div", {
33
+ return createElement("div", { "data-fb-overlay": surface ?? "" }, [createElement(Backdrop, { key: "backdrop" }), createElement("div", {
37
34
  key: "surface",
38
35
  ref,
39
36
  role: "dialog",
@@ -1 +1 @@
1
- {"version":3,"file":"DialogSurface.js","names":[],"sources":["../../../src/react/presentation/DialogSurface.tsx"],"sourcesContent":["'use client'\n\nimport { createElement, type ReactNode, useEffect, useRef } from 'react'\nimport { Backdrop } from './Backdrop'\nimport { useDismiss } from './useDismiss'\nimport { useFocusTrap } from './useFocusTrap'\nimport { useScrollLock } from './useScrollLock'\n\nexport type DialogSurfaceProps = {\n\topen: boolean\n\tonClose: () => void\n\t/** Accessible name; pass either label or labelledBy. */\n\tlabel?: string\n\tlabelledBy?: string\n\t/** CSS data hook, e.g. 'modal' | 'drawer'. */\n\tsurface?: string\n\tcloseLabel?: string\n\tcloseOnEscape?: boolean\n\tcloseOnOutsideClick?: boolean\n\tchildren?: ReactNode\n}\n\n/**\n * Accessible, dependency-free overlay surface: backdrop + role=dialog/aria-modal, focus-trap,\n * scroll-lock, Escape/outside-click dismiss, initial focus, and focus restore on close. Composes the\n * exported primitives so a consumer can rebuild it from the same parts.\n */\nexport const DialogSurface = ({\n\topen,\n\tonClose,\n\tlabel,\n\tlabelledBy,\n\tsurface,\n\tcloseLabel = 'Close',\n\tcloseOnEscape,\n\tcloseOnOutsideClick,\n\tchildren,\n}: DialogSurfaceProps) => {\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst restoreRef = useRef<HTMLElement | null>(null)\n\n\tuseScrollLock(open)\n\tuseFocusTrap(ref, open)\n\tuseDismiss({ active: open, onDismiss: onClose, closeOnEscape, closeOnOutsideClick }, ref)\n\n\tuseEffect(() => {\n\t\tif (!open) {\n\t\t\treturn\n\t\t}\n\t\trestoreRef.current =\n\t\t\tdocument.activeElement instanceof HTMLElement ? document.activeElement : null\n\t\tref.current?.focus()\n\t\treturn () => {\n\t\t\trestoreRef.current?.focus()\n\t\t}\n\t}, [open])\n\n\tif (!open) {\n\t\treturn null\n\t}\n\n\treturn createElement('div', { 'data-fb-overlay': surface ?? '' }, [\n\t\tcreateElement(Backdrop, { key: 'backdrop', onClick: onClose }),\n\t\tcreateElement(\n\t\t\t'div',\n\t\t\t{\n\t\t\t\tkey: 'surface',\n\t\t\t\tref,\n\t\t\t\trole: 'dialog',\n\t\t\t\t'aria-modal': true,\n\t\t\t\t'aria-label': labelledBy ? undefined : label,\n\t\t\t\t'aria-labelledby': labelledBy,\n\t\t\t\t'data-fb-dialog': surface ?? '',\n\t\t\t\ttabIndex: -1,\n\t\t\t},\n\t\t\t[\n\t\t\t\tcreateElement(\n\t\t\t\t\t'button',\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: 'close',\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t'data-fb-dialog-close': '',\n\t\t\t\t\t\t'aria-label': closeLabel,\n\t\t\t\t\t\tonClick: onClose,\n\t\t\t\t\t},\n\t\t\t\t\t'\\xd7'\n\t\t\t\t),\n\t\t\t\tcreateElement('div', { key: 'body' }, children),\n\t\t\t]\n\t\t),\n\t])\n}\n"],"mappings":";;;;;;;;;;;;AA2BA,MAAa,iBAAiB,EAC7B,MACA,SACA,OACA,YACA,SACA,aAAa,SACb,eACA,qBACA,eACyB;CACzB,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,aAAa,OAA2B,IAAI;CAElD,cAAc,IAAI;CAClB,aAAa,KAAK,IAAI;CACtB,WAAW;EAAE,QAAQ;EAAM,WAAW;EAAS;EAAe;CAAoB,GAAG,GAAG;CAExF,gBAAgB;EACf,IAAI,CAAC,MACJ;EAED,WAAW,UACV,SAAS,yBAAyB,cAAc,SAAS,gBAAgB;EAC1E,IAAI,SAAS,MAAM;EACnB,aAAa;GACZ,WAAW,SAAS,MAAM;EAC3B;CACD,GAAG,CAAC,IAAI,CAAC;CAET,IAAI,CAAC,MACJ,OAAO;CAGR,OAAO,cAAc,OAAO,EAAE,mBAAmB,WAAW,GAAG,GAAG,CACjE,cAAc,UAAU;EAAE,KAAK;EAAY,SAAS;CAAQ,CAAC,GAC7D,cACC,OACA;EACC,KAAK;EACL;EACA,MAAM;EACN,cAAc;EACd,cAAc,aAAa,KAAA,IAAY;EACvC,mBAAmB;EACnB,kBAAkB,WAAW;EAC7B,UAAU;CACX,GACA,CACC,cACC,UACA;EACC,KAAK;EACL,MAAM;EACN,wBAAwB;EACxB,cAAc;EACd,SAAS;CACV,GACA,GACD,GACA,cAAc,OAAO,EAAE,KAAK,OAAO,GAAG,QAAQ,CAC/C,CACD,CACD,CAAC;AACF"}
1
+ {"version":3,"file":"DialogSurface.js","names":[],"sources":["../../../src/react/presentation/DialogSurface.tsx"],"sourcesContent":["'use client'\n\nimport { createElement, type ReactNode, useEffect, useRef } from 'react'\nimport { Backdrop } from './Backdrop'\nimport { useDismiss } from './useDismiss'\nimport { useFocusTrap } from './useFocusTrap'\nimport { useScrollLock } from './useScrollLock'\n\nexport type DialogSurfaceProps = {\n\topen: boolean\n\tonClose: () => void\n\t/** Accessible name; pass either label or labelledBy. */\n\tlabel?: string\n\tlabelledBy?: string\n\t/** CSS data hook, e.g. 'modal' | 'drawer'. */\n\tsurface?: string\n\tcloseLabel?: string\n\tcloseOnEscape?: boolean\n\tcloseOnOutsideClick?: boolean\n\tchildren?: ReactNode\n}\n\n/**\n * Accessible, dependency-free overlay surface: backdrop + role=dialog/aria-modal, focus-trap,\n * scroll-lock, Escape/outside-click dismiss, initial focus, and focus restore on close. Composes the\n * exported primitives so a consumer can rebuild it from the same parts.\n */\nexport const DialogSurface = ({\n\topen,\n\tonClose,\n\tlabel,\n\tlabelledBy,\n\tsurface,\n\tcloseLabel = 'Close',\n\tcloseOnEscape,\n\tcloseOnOutsideClick,\n\tchildren,\n}: DialogSurfaceProps) => {\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst restoreRef = useRef<HTMLElement | null>(null)\n\n\tuseScrollLock(open)\n\tuseFocusTrap(ref, open)\n\tuseDismiss({ active: open, onDismiss: onClose, closeOnEscape, closeOnOutsideClick }, ref)\n\n\tuseEffect(() => {\n\t\tif (!open) {\n\t\t\treturn\n\t\t}\n\t\trestoreRef.current =\n\t\t\tdocument.activeElement instanceof HTMLElement ? document.activeElement : null\n\t\tref.current?.focus()\n\t\treturn () => {\n\t\t\trestoreRef.current?.focus()\n\t\t}\n\t}, [open])\n\n\tif (!open) {\n\t\treturn null\n\t}\n\n\treturn createElement('div', { 'data-fb-overlay': surface ?? '' }, [\n\t\t// No onClick here: useDismiss already closes on an outside pointerdown (the backdrop is outside\n\t\t// the dialog ref), so wiring onClose here too would fire it twice per click and would ignore\n\t\t// closeOnOutsideClick. The backdrop stays purely presentational.\n\t\tcreateElement(Backdrop, { key: 'backdrop' }),\n\t\tcreateElement(\n\t\t\t'div',\n\t\t\t{\n\t\t\t\tkey: 'surface',\n\t\t\t\tref,\n\t\t\t\trole: 'dialog',\n\t\t\t\t'aria-modal': true,\n\t\t\t\t'aria-label': labelledBy ? undefined : label,\n\t\t\t\t'aria-labelledby': labelledBy,\n\t\t\t\t'data-fb-dialog': surface ?? '',\n\t\t\t\ttabIndex: -1,\n\t\t\t},\n\t\t\t[\n\t\t\t\tcreateElement(\n\t\t\t\t\t'button',\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: 'close',\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t'data-fb-dialog-close': '',\n\t\t\t\t\t\t'aria-label': closeLabel,\n\t\t\t\t\t\tonClick: onClose,\n\t\t\t\t\t},\n\t\t\t\t\t'\\xd7'\n\t\t\t\t),\n\t\t\t\tcreateElement('div', { key: 'body' }, children),\n\t\t\t]\n\t\t),\n\t])\n}\n"],"mappings":";;;;;;;;;;;;AA2BA,MAAa,iBAAiB,EAC7B,MACA,SACA,OACA,YACA,SACA,aAAa,SACb,eACA,qBACA,eACyB;CACzB,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,aAAa,OAA2B,IAAI;CAElD,cAAc,IAAI;CAClB,aAAa,KAAK,IAAI;CACtB,WAAW;EAAE,QAAQ;EAAM,WAAW;EAAS;EAAe;CAAoB,GAAG,GAAG;CAExF,gBAAgB;EACf,IAAI,CAAC,MACJ;EAED,WAAW,UACV,SAAS,yBAAyB,cAAc,SAAS,gBAAgB;EAC1E,IAAI,SAAS,MAAM;EACnB,aAAa;GACZ,WAAW,SAAS,MAAM;EAC3B;CACD,GAAG,CAAC,IAAI,CAAC;CAET,IAAI,CAAC,MACJ,OAAO;CAGR,OAAO,cAAc,OAAO,EAAE,mBAAmB,WAAW,GAAG,GAAG,CAIjE,cAAc,UAAU,EAAE,KAAK,WAAW,CAAC,GAC3C,cACC,OACA;EACC,KAAK;EACL;EACA,MAAM;EACN,cAAc;EACd,cAAc,aAAa,KAAA,IAAY;EACvC,mBAAmB;EACnB,kBAAkB,WAAW;EAC7B,UAAU;CACX,GACA,CACC,cACC,UACA;EACC,KAAK;EACL,MAAM;EACN,wBAAwB;EACxB,cAAc;EACd,SAAS;CACV,GACA,GACD,GACA,cAAc,OAAO,EAAE,KAAK,OAAO,GAAG,QAAQ,CAC/C,CACD,CACD,CAAC;AACF"}
@@ -2,11 +2,9 @@
2
2
  import { defineFieldRenderer } from "../contract.js";
3
3
  import { FieldShell } from "../primitives/FieldShell.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
- import { useId } from "react";
6
5
  //#region src/react/renderers/calculation.tsx
7
6
  /** Read-only renderer for a calculation field: the value is derived, never user-editable, so it shows in an `<output>` rather than an input. */
8
- const calculationRenderer = defineFieldRenderer(({ field, value, errors, required }) => {
9
- const id = useId();
7
+ const calculationRenderer = defineFieldRenderer(({ field, id, value, errors, required }) => {
10
8
  const describedById = `${id}-desc`;
11
9
  return /* @__PURE__ */ jsx(FieldShell, {
12
10
  id,
@@ -1 +1 @@
1
- {"version":3,"file":"calculation.js","names":[],"sources":["../../../src/react/renderers/calculation.tsx"],"sourcesContent":["'use client'\n\nimport { useId } from 'react'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\n\n/** Read-only renderer for a calculation field: the value is derived, never user-editable, so it shows in an `<output>` rather than an input. */\nexport const calculationRenderer = defineFieldRenderer<number | undefined>(\n\t({ field, value, errors, required }) => {\n\t\tconst id = useId()\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<output id={id} className=\"fb-field__calc\" aria-describedby={describedById}>\n\t\t\t\t\t{value == null ? '' : String(value)}\n\t\t\t\t</output>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;AAOA,MAAa,sBAAsB,qBACjC,EAAE,OAAO,OAAO,QAAQ,eAAe;CACvC,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,UAAD;GAAY;GAAI,WAAU;GAAiB,oBAAkB;aAC3D,SAAS,OAAO,KAAK,OAAO,KAAK;EAC3B,CAAA;CACG,CAAA;AAEd,CACD"}
1
+ {"version":3,"file":"calculation.js","names":[],"sources":["../../../src/react/renderers/calculation.tsx"],"sourcesContent":["'use client'\n\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\n\n/** Read-only renderer for a calculation field: the value is derived, never user-editable, so it shows in an `<output>` rather than an input. */\nexport const calculationRenderer = defineFieldRenderer<number | undefined>(\n\t({ field, id, value, errors, required }) => {\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<output id={id} className=\"fb-field__calc\" aria-describedby={describedById}>\n\t\t\t\t\t{value == null ? '' : String(value)}\n\t\t\t\t</output>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;AAMA,MAAa,sBAAsB,qBACjC,EAAE,OAAO,IAAI,OAAO,QAAQ,eAAe;CAC3C,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,UAAD;GAAY;GAAI,WAAU;GAAiB,oBAAkB;aAC3D,SAAS,OAAO,KAAK,OAAO,KAAK;EAC3B,CAAA;CACG,CAAA;AAEd,CACD"}
@@ -3,10 +3,8 @@ import { defineFieldRenderer } from "../contract.js";
3
3
  import { FieldShell } from "../primitives/FieldShell.js";
4
4
  import { Checkbox } from "../primitives/Checkbox.js";
5
5
  import { jsx } from "react/jsx-runtime";
6
- import { useId } from "react";
7
6
  //#region src/react/renderers/checkbox.tsx
8
- const checkboxRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, required, disabled }) => {
9
- const id = useId();
7
+ const checkboxRenderer = defineFieldRenderer(({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {
10
8
  const describedById = `${id}-desc`;
11
9
  return /* @__PURE__ */ jsx(FieldShell, {
12
10
  id,
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.js","names":[],"sources":["../../../src/react/renderers/checkbox.tsx"],"sourcesContent":["'use client'\n\nimport { useId } from 'react'\nimport { defineFieldRenderer } from '../contract'\nimport { Checkbox } from '../primitives/Checkbox'\nimport { FieldShell } from '../primitives/FieldShell'\n\nexport const checkboxRenderer = defineFieldRenderer<boolean>(\n\t({ field, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst id = useId()\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Checkbox\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\tchecked={value ?? false}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;AAOA,MAAa,mBAAmB,qBAC9B,EAAE,OAAO,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CACzE,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,UAAD;GACK;GACE;GACN,SAAS,SAAS;GACR;GACF;GACE;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
1
+ {"version":3,"file":"checkbox.js","names":[],"sources":["../../../src/react/renderers/checkbox.tsx"],"sourcesContent":["'use client'\n\nimport { defineFieldRenderer } from '../contract'\nimport { Checkbox } from '../primitives/Checkbox'\nimport { FieldShell } from '../primitives/FieldShell'\n\nexport const checkboxRenderer = defineFieldRenderer<boolean>(\n\t({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Checkbox\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\tchecked={value ?? false}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;AAMA,MAAa,mBAAmB,qBAC9B,EAAE,OAAO,IAAI,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CAC7E,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,UAAD;GACK;GACE;GACN,SAAS,SAAS;GACR;GACF;GACE;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
@@ -6,7 +6,7 @@ import { defineFieldRenderer } from "../contract.js";
6
6
  import { FieldShell } from "../primitives/FieldShell.js";
7
7
  import { Checkbox } from "../primitives/Checkbox.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
- import { useId, useMemo } from "react";
9
+ import { useMemo } from "react";
10
10
  //#region src/react/renderers/consent.tsx
11
11
  const text = (raw) => typeof raw === "string" && raw.trim() !== "" ? raw : void 0;
12
12
  /**
@@ -42,8 +42,7 @@ const linkOf = (raw) => {
42
42
  * injected by `toFormDocument(doc, { consentStatements })`; the form document itself carries only
43
43
  * the source key, so a form rendered without that step shows no statement rather than a stale one.
44
44
  */
45
- const consentRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, required, disabled }) => {
46
- const id = useId();
45
+ const consentRenderer = defineFieldRenderer(({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {
47
46
  const describedById = `${id}-desc`;
48
47
  const plainStatement = text(textOfBody(field.statement));
49
48
  const html = useMemo(() => richStatementHtml(field.statement), [field.statement]);
@@ -1 +1 @@
1
- {"version":3,"file":"consent.js","names":[],"sources":["../../../src/react/renderers/consent.tsx"],"sourcesContent":["'use client'\n\nimport { useId, useMemo } from 'react'\nimport { sanitizeUrl } from '../../actions/body/converters'\nimport { serializeBody } from '../../actions/body/serializeBody'\nimport { textOfBody } from '../../actions/body/textOfBody'\nimport { defineFieldRenderer } from '../contract'\nimport { Checkbox } from '../primitives/Checkbox'\nimport { FieldShell } from '../primitives/FieldShell'\n\ntype ConsentLink = { label: string; url: string }\n\nconst text = (raw: unknown): string | undefined =>\n\ttypeof raw === 'string' && raw.trim() !== '' ? raw : undefined\n\n/**\n * HTML for a rich text statement. A plain string never goes through this path: `serializeBody`\n * interpolates a string body unescaped (its pre-richText behavior), so rendering one as HTML would\n * let literal markup execute; the renderer shows a plain string as text instead.\n */\nconst richStatementHtml = (statement: unknown): string | undefined => {\n\tif (typeof statement === 'string' || statement == null) {\n\t\treturn undefined\n\t}\n\tconst html = serializeBody(statement, { values: [], descriptors: [] })\n\treturn html === '' ? undefined : html\n}\n\n/**\n * The resolved policy link, or nothing unless the source carries both a url and a name to show\n * for it: an anchor labelled by a raw URL or a machine key is worse than no anchor. The href is\n * sanitized like every other url the package renders, since a source is admin-authored and can\n * point anywhere (`sanitizeUrl` keeps http(s)/mailto/tel and relative urls, and neutralizes the\n * rest to `#`).\n */\nconst linkOf = (raw: unknown): ConsentLink | undefined => {\n\tconst link = raw as Partial<ConsentLink> | undefined\n\tconst label = text(link?.label)\n\treturn label && typeof link?.url === 'string' && link.url !== ''\n\t\t? { label, url: sanitizeUrl(link.url) }\n\t\t: undefined\n}\n\n/**\n * The `statement` and `link` this reads are server-resolved from the field's consent source and\n * injected by `toFormDocument(doc, { consentStatements })`; the form document itself carries only\n * the source key, so a form rendered without that step shows no statement rather than a stale one.\n */\nexport const consentRenderer = defineFieldRenderer<boolean>(\n\t({ field, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst id = useId()\n\t\tconst describedById = `${id}-desc`\n\n\t\t// The checkbox's accessible name is always plain text, regardless of the statement's shape,\n\t\t// so it stays a single unambiguous string even though the visible statement can carry\n\t\t// formatting and inline links. With no statement resolved the field is misconfigured; the\n\t\t// machine name still beats leaving the control unnamed.\n\t\tconst plainStatement = text(textOfBody(field.statement))\n\t\tconst html = useMemo(() => richStatementHtml(field.statement), [field.statement])\n\t\tconst link = linkOf(field.link)\n\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t{html ? (\n\t\t\t\t\t// The statement is a plain sibling, never a <label>: a native <label> forwards clicks to\n\t\t\t\t\t// its control even for descendant links (they aren't \"labelable\" elements per the HTML\n\t\t\t\t\t// spec), so an inline link here would also silently toggle the checkbox. The checkbox's\n\t\t\t\t\t// name comes from aria-label instead. A <div>, because serializeBody emits block\n\t\t\t\t\t// elements (<p>, headings, lists) that are invalid inside inline wrappers.\n\t\t\t\t\t// Safe to inject: serializeBody HTML-escapes all text and sanitizes link URLs.\n\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is produced by our escaping serializer, never raw user input\n\t\t\t\t\t<div className=\"fb-consent__statement\" dangerouslySetInnerHTML={{ __html: html }} />\n\t\t\t\t) : plainStatement ? (\n\t\t\t\t\t<span className=\"fb-consent__statement\">{plainStatement}</span>\n\t\t\t\t) : null}\n\t\t\t\t{required && (html || plainStatement) ? (\n\t\t\t\t\t<span className=\"fb-field__required\" aria-hidden=\"true\">\n\t\t\t\t\t\t{' *'}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t<Checkbox\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\tchecked={value ?? false}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t\tariaLabel={plainStatement ?? name}\n\t\t\t\t/>\n\t\t\t\t{link ? (\n\t\t\t\t\t<span className=\"fb-consent__links\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref={link.url}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclassName=\"fb-consent__link\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;;;;AAYA,MAAM,QAAQ,QACb,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,KAAK,MAAM,KAAA;;;;;;AAOtD,MAAM,qBAAqB,cAA2C;CACrE,IAAI,OAAO,cAAc,YAAY,aAAa,MACjD;CAED,MAAM,OAAO,cAAc,WAAW;EAAE,QAAQ,CAAC;EAAG,aAAa,CAAC;CAAE,CAAC;CACrE,OAAO,SAAS,KAAK,KAAA,IAAY;AAClC;;;;;;;;AASA,MAAM,UAAU,QAA0C;CACzD,MAAM,OAAO;CACb,MAAM,QAAQ,KAAK,MAAM,KAAK;CAC9B,OAAO,SAAS,OAAO,MAAM,QAAQ,YAAY,KAAK,QAAQ,KAC3D;EAAE;EAAO,KAAK,YAAY,KAAK,GAAG;CAAE,IACpC,KAAA;AACJ;;;;;;AAOA,MAAa,kBAAkB,qBAC7B,EAAE,OAAO,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CACzE,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAM5B,MAAM,iBAAiB,KAAK,WAAW,MAAM,SAAS,CAAC;CACvD,MAAM,OAAO,cAAc,kBAAkB,MAAM,SAAS,GAAG,CAAC,MAAM,SAAS,CAAC;CAChF,MAAM,OAAO,OAAO,MAAM,IAAI;CAE9B,OACC,qBAAC,YAAD;EACK;EACJ,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YALhB;GAOE,OAQA,oBAAC,OAAD;IAAK,WAAU;IAAwB,yBAAyB,EAAE,QAAQ,KAAK;GAAI,CAAA,IAChF,iBACH,oBAAC,QAAD;IAAM,WAAU;cAAyB;GAAqB,CAAA,IAC3D;GACH,aAAa,QAAQ,kBACrB,oBAAC,QAAD;IAAM,WAAU;IAAqB,eAAY;cAC/C;GACI,CAAA,IACH;GACJ,oBAAC,UAAD;IACK;IACE;IACN,SAAS,SAAS;IACR;IACF;IACE;IACA;IACV,SAAS,OAAO,SAAS;IACV;IACf,WAAW,kBAAkB;GAC7B,CAAA;GACA,OACA,oBAAC,QAAD;IAAM,WAAU;cACf,oBAAC,KAAD;KACC,MAAM,KAAK;KACX,QAAO;KACP,KAAI;KACJ,WAAU;eAET,KAAK;IACJ,CAAA;GACE,CAAA,IACH;EACO;;AAEd,CACD"}
1
+ {"version":3,"file":"consent.js","names":[],"sources":["../../../src/react/renderers/consent.tsx"],"sourcesContent":["'use client'\n\nimport { useMemo } from 'react'\nimport { sanitizeUrl } from '../../actions/body/converters'\nimport { serializeBody } from '../../actions/body/serializeBody'\nimport { textOfBody } from '../../actions/body/textOfBody'\nimport { defineFieldRenderer } from '../contract'\nimport { Checkbox } from '../primitives/Checkbox'\nimport { FieldShell } from '../primitives/FieldShell'\n\ntype ConsentLink = { label: string; url: string }\n\nconst text = (raw: unknown): string | undefined =>\n\ttypeof raw === 'string' && raw.trim() !== '' ? raw : undefined\n\n/**\n * HTML for a rich text statement. A plain string never goes through this path: `serializeBody`\n * interpolates a string body unescaped (its pre-richText behavior), so rendering one as HTML would\n * let literal markup execute; the renderer shows a plain string as text instead.\n */\nconst richStatementHtml = (statement: unknown): string | undefined => {\n\tif (typeof statement === 'string' || statement == null) {\n\t\treturn undefined\n\t}\n\tconst html = serializeBody(statement, { values: [], descriptors: [] })\n\treturn html === '' ? undefined : html\n}\n\n/**\n * The resolved policy link, or nothing unless the source carries both a url and a name to show\n * for it: an anchor labelled by a raw URL or a machine key is worse than no anchor. The href is\n * sanitized like every other url the package renders, since a source is admin-authored and can\n * point anywhere (`sanitizeUrl` keeps http(s)/mailto/tel and relative urls, and neutralizes the\n * rest to `#`).\n */\nconst linkOf = (raw: unknown): ConsentLink | undefined => {\n\tconst link = raw as Partial<ConsentLink> | undefined\n\tconst label = text(link?.label)\n\treturn label && typeof link?.url === 'string' && link.url !== ''\n\t\t? { label, url: sanitizeUrl(link.url) }\n\t\t: undefined\n}\n\n/**\n * The `statement` and `link` this reads are server-resolved from the field's consent source and\n * injected by `toFormDocument(doc, { consentStatements })`; the form document itself carries only\n * the source key, so a form rendered without that step shows no statement rather than a stale one.\n */\nexport const consentRenderer = defineFieldRenderer<boolean>(\n\t({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst describedById = `${id}-desc`\n\n\t\t// The checkbox's accessible name is always plain text, regardless of the statement's shape,\n\t\t// so it stays a single unambiguous string even though the visible statement can carry\n\t\t// formatting and inline links. With no statement resolved the field is misconfigured; the\n\t\t// machine name still beats leaving the control unnamed.\n\t\tconst plainStatement = text(textOfBody(field.statement))\n\t\tconst html = useMemo(() => richStatementHtml(field.statement), [field.statement])\n\t\tconst link = linkOf(field.link)\n\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t{html ? (\n\t\t\t\t\t// The statement is a plain sibling, never a <label>: a native <label> forwards clicks to\n\t\t\t\t\t// its control even for descendant links (they aren't \"labelable\" elements per the HTML\n\t\t\t\t\t// spec), so an inline link here would also silently toggle the checkbox. The checkbox's\n\t\t\t\t\t// name comes from aria-label instead. A <div>, because serializeBody emits block\n\t\t\t\t\t// elements (<p>, headings, lists) that are invalid inside inline wrappers.\n\t\t\t\t\t// Safe to inject: serializeBody HTML-escapes all text and sanitizes link URLs.\n\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is produced by our escaping serializer, never raw user input\n\t\t\t\t\t<div className=\"fb-consent__statement\" dangerouslySetInnerHTML={{ __html: html }} />\n\t\t\t\t) : plainStatement ? (\n\t\t\t\t\t<span className=\"fb-consent__statement\">{plainStatement}</span>\n\t\t\t\t) : null}\n\t\t\t\t{required && (html || plainStatement) ? (\n\t\t\t\t\t<span className=\"fb-field__required\" aria-hidden=\"true\">\n\t\t\t\t\t\t{' *'}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t<Checkbox\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\tchecked={value ?? false}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t\tariaLabel={plainStatement ?? name}\n\t\t\t\t/>\n\t\t\t\t{link ? (\n\t\t\t\t\t<span className=\"fb-consent__links\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref={link.url}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclassName=\"fb-consent__link\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;;;;AAYA,MAAM,QAAQ,QACb,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,KAAK,MAAM,KAAA;;;;;;AAOtD,MAAM,qBAAqB,cAA2C;CACrE,IAAI,OAAO,cAAc,YAAY,aAAa,MACjD;CAED,MAAM,OAAO,cAAc,WAAW;EAAE,QAAQ,CAAC;EAAG,aAAa,CAAC;CAAE,CAAC;CACrE,OAAO,SAAS,KAAK,KAAA,IAAY;AAClC;;;;;;;;AASA,MAAM,UAAU,QAA0C;CACzD,MAAM,OAAO;CACb,MAAM,QAAQ,KAAK,MAAM,KAAK;CAC9B,OAAO,SAAS,OAAO,MAAM,QAAQ,YAAY,KAAK,QAAQ,KAC3D;EAAE;EAAO,KAAK,YAAY,KAAK,GAAG;CAAE,IACpC,KAAA;AACJ;;;;;;AAOA,MAAa,kBAAkB,qBAC7B,EAAE,OAAO,IAAI,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CAC7E,MAAM,gBAAgB,GAAG,GAAG;CAM5B,MAAM,iBAAiB,KAAK,WAAW,MAAM,SAAS,CAAC;CACvD,MAAM,OAAO,cAAc,kBAAkB,MAAM,SAAS,GAAG,CAAC,MAAM,SAAS,CAAC;CAChF,MAAM,OAAO,OAAO,MAAM,IAAI;CAE9B,OACC,qBAAC,YAAD;EACK;EACJ,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YALhB;GAOE,OAQA,oBAAC,OAAD;IAAK,WAAU;IAAwB,yBAAyB,EAAE,QAAQ,KAAK;GAAI,CAAA,IAChF,iBACH,oBAAC,QAAD;IAAM,WAAU;cAAyB;GAAqB,CAAA,IAC3D;GACH,aAAa,QAAQ,kBACrB,oBAAC,QAAD;IAAM,WAAU;IAAqB,eAAY;cAC/C;GACI,CAAA,IACH;GACJ,oBAAC,UAAD;IACK;IACE;IACN,SAAS,SAAS;IACR;IACF;IACE;IACA;IACV,SAAS,OAAO,SAAS;IACV;IACf,WAAW,kBAAkB;GAC7B,CAAA;GACA,OACA,oBAAC,QAAD;IAAM,WAAU;cACf,oBAAC,KAAD;KACC,MAAM,KAAK;KACX,QAAO;KACP,KAAI;KACJ,WAAU;eAET,KAAK;IACJ,CAAA;GACE,CAAA,IACH;EACO;;AAEd,CACD"}
@@ -3,10 +3,8 @@ import { defineFieldRenderer } from "../contract.js";
3
3
  import { FieldShell } from "../primitives/FieldShell.js";
4
4
  import { Input } from "../primitives/Input.js";
5
5
  import { jsx } from "react/jsx-runtime";
6
- import { useId } from "react";
7
6
  //#region src/react/renderers/date.tsx
8
- const dateRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, required, disabled }) => {
9
- const id = useId();
7
+ const dateRenderer = defineFieldRenderer(({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {
10
8
  const describedById = `${id}-desc`;
11
9
  return /* @__PURE__ */ jsx(FieldShell, {
12
10
  id,
@@ -1 +1 @@
1
- {"version":3,"file":"date.js","names":[],"sources":["../../../src/react/renderers/date.tsx"],"sourcesContent":["'use client'\n\nimport { useId } from 'react'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Input } from '../primitives/Input'\n\nexport const dateRenderer = defineFieldRenderer<string>(\n\t({ field, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst id = useId()\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"date\"\n\t\t\t\t\tvalue={value ?? ''}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;AAOA,MAAa,eAAe,qBAC1B,EAAE,OAAO,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CACzE,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,OAAD;GACK;GACE;GACN,MAAK;GACL,OAAO,SAAS;GACN;GACF;GACE;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
1
+ {"version":3,"file":"date.js","names":[],"sources":["../../../src/react/renderers/date.tsx"],"sourcesContent":["'use client'\n\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Input } from '../primitives/Input'\n\nexport const dateRenderer = defineFieldRenderer<string>(\n\t({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"date\"\n\t\t\t\t\tvalue={value ?? ''}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;AAMA,MAAa,eAAe,qBAC1B,EAAE,OAAO,IAAI,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CAC7E,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,OAAD;GACK;GACE;GACN,MAAK;GACL,OAAO,SAAS;GACN;GACF;GACE;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
@@ -3,10 +3,8 @@ import { defineFieldRenderer } from "../contract.js";
3
3
  import { FieldShell } from "../primitives/FieldShell.js";
4
4
  import { Input } from "../primitives/Input.js";
5
5
  import { jsx } from "react/jsx-runtime";
6
- import { useId } from "react";
7
6
  //#region src/react/renderers/email.tsx
8
- const emailRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, required, disabled }) => {
9
- const id = useId();
7
+ const emailRenderer = defineFieldRenderer(({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {
10
8
  const describedById = `${id}-desc`;
11
9
  return /* @__PURE__ */ jsx(FieldShell, {
12
10
  id,
@@ -1 +1 @@
1
- {"version":3,"file":"email.js","names":[],"sources":["../../../src/react/renderers/email.tsx"],"sourcesContent":["'use client'\n\nimport { useId } from 'react'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Input } from '../primitives/Input'\n\nexport const emailRenderer = defineFieldRenderer<string>(\n\t({ field, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst id = useId()\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"email\"\n\t\t\t\t\tvalue={value ?? ''}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\tplaceholder={typeof field.placeholder === 'string' ? field.placeholder : undefined}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;AAOA,MAAa,gBAAgB,qBAC3B,EAAE,OAAO,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CACzE,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,OAAD;GACK;GACE;GACN,MAAK;GACL,OAAO,SAAS;GACN;GACF;GACR,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;GAC/D;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
1
+ {"version":3,"file":"email.js","names":[],"sources":["../../../src/react/renderers/email.tsx"],"sourcesContent":["'use client'\n\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Input } from '../primitives/Input'\n\nexport const emailRenderer = defineFieldRenderer<string>(\n\t({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"email\"\n\t\t\t\t\tvalue={value ?? ''}\n\t\t\t\t\tonChange={onChange}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\tplaceholder={typeof field.placeholder === 'string' ? field.placeholder : undefined}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;AAMA,MAAa,gBAAgB,qBAC3B,EAAE,OAAO,IAAI,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CAC7E,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,OAAD;GACK;GACE;GACN,MAAK;GACL,OAAO,SAAS;GACN;GACF;GACR,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;GAC/D;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
@@ -6,7 +6,7 @@ import { defineFieldRenderer } from "../contract.js";
6
6
  import { FieldShell } from "../primitives/FieldShell.js";
7
7
  import { uploadFile } from "../uploadFile.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
- import { useId, useState } from "react";
9
+ import { useState } from "react";
10
10
  //#region src/react/renderers/file.tsx
11
11
  const typesOf = (mimeTypes) => {
12
12
  if (!Array.isArray(mimeTypes)) return [];
@@ -18,8 +18,7 @@ const typesOf = (mimeTypes) => {
18
18
  * stored doc at submit, so the client value is only the id. Shows the uploaded filename with a clear
19
19
  * control and surfaces upload errors. Single file (v1); the upload posts to the default `/api` route.
20
20
  */
21
- const fileRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, required, disabled, t }) => {
22
- const id = useId();
21
+ const fileRenderer = defineFieldRenderer(({ field, id, name, value, onChange, onBlur, errors, required, disabled, t }) => {
23
22
  const describedById = `${id}-desc`;
24
23
  const [uploading, setUploading] = useState(false);
25
24
  const [filename, setFilename] = useState(void 0);
@@ -30,7 +29,7 @@ const fileRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur
30
29
  const maxSize = typeof field.maxSize === "number" && field.maxSize >= 0 ? field.maxSize : void 0;
31
30
  const hints = [types.length > 0 ? resolveMessage(t(keys.fileHintAccepted), { types: types.join(", ") }) : void 0, maxSize !== void 0 ? resolveMessage(t(keys.fileHintMaxSize), { max: formatBytes(maxSize) }) : void 0].filter((hint) => typeof hint === "string");
32
31
  const allErrors = localError ? [...errors, localError] : errors;
33
- const displayName = filename ?? (value !== void 0 && value !== null && value !== "" ? "Uploaded file" : void 0);
32
+ const displayName = filename ?? (value !== void 0 && value !== null && value !== "" ? t(keys.fileUploaded) : void 0);
34
33
  const handleChange = async (event) => {
35
34
  const selected = event.target.files?.[0];
36
35
  if (!selected) return;
@@ -52,7 +51,7 @@ const fileRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur
52
51
  if (result.ok) {
53
52
  setFilename(selected.name);
54
53
  onChange(result.id);
55
- } else setLocalError(result.message ?? "Upload failed");
54
+ } else setLocalError(result.message ?? t(keys.fileUploadFailed));
56
55
  };
57
56
  const clear = () => {
58
57
  setFilename(void 0);
@@ -87,7 +86,7 @@ const fileRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur
87
86
  uploading ? /* @__PURE__ */ jsx("p", {
88
87
  className: "fb-field__file-status",
89
88
  "aria-live": "polite",
90
- children: "Uploading"
89
+ children: t(keys.fileUploading)
91
90
  }) : null,
92
91
  displayName ? /* @__PURE__ */ jsxs("p", {
93
92
  className: "fb-field__file-name",
@@ -96,7 +95,7 @@ const fileRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur
96
95
  className: "fb-field__file-clear",
97
96
  onClick: clear,
98
97
  disabled,
99
- children: "Remove"
98
+ children: t(keys.fileRemove)
100
99
  })]
101
100
  }) : null
102
101
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"file.js","names":[],"sources":["../../../src/react/renderers/file.tsx"],"sourcesContent":["'use client'\n\nimport { type ChangeEvent, useId, useState } from 'react'\nimport { keys } from '../../translations/keys'\nimport { formatBytes } from '../../uploads/formatBytes'\nimport { resolveMessage } from '../../validation/message'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { uploadFile } from '../uploadFile'\n\nconst typesOf = (mimeTypes: unknown): string[] => {\n\tif (!Array.isArray(mimeTypes)) {\n\t\treturn []\n\t}\n\treturn mimeTypes.filter((entry): entry is string => typeof entry === 'string')\n}\n\n/**\n * Headless file renderer: a native file input that uploads the chosen file to the upload collection and\n * stores the returned id as the field value. The server re-derives filename/mimeType/filesize from the\n * stored doc at submit, so the client value is only the id. Shows the uploaded filename with a clear\n * control and surfaces upload errors. Single file (v1); the upload posts to the default `/api` route.\n */\nexport const fileRenderer = defineFieldRenderer<string | number>(\n\t({ field, name, value, onChange, onBlur, errors, required, disabled, t }) => {\n\t\tconst id = useId()\n\t\tconst describedById = `${id}-desc`\n\t\tconst [uploading, setUploading] = useState(false)\n\t\tconst [filename, setFilename] = useState<string | undefined>(undefined)\n\t\tconst [localError, setLocalError] = useState<string | undefined>(undefined)\n\n\t\t// Stamped server-side onto the block from plugin config; absent only for stale form docs saved\n\t\t// before uploads were configured (uploading then fails with a visible error, never a guess).\n\t\tconst collection =\n\t\t\ttypeof field.uploadsCollection === 'string' ? field.uploadsCollection : undefined\n\t\tconst types = typesOf(field.mimeTypes)\n\t\tconst accept = types.length > 0 ? types.join(',') : undefined\n\t\t// Zero is a real limit, matching resolveFileRef server-side (filesize > maxSize rejects any non-empty file).\n\t\tconst maxSize =\n\t\t\ttypeof field.maxSize === 'number' && field.maxSize >= 0 ? field.maxSize : undefined\n\t\tconst hints = [\n\t\t\ttypes.length > 0\n\t\t\t\t? resolveMessage(t(keys.fileHintAccepted), { types: types.join(', ') })\n\t\t\t\t: undefined,\n\t\t\tmaxSize !== undefined\n\t\t\t\t? resolveMessage(t(keys.fileHintMaxSize), { max: formatBytes(maxSize) })\n\t\t\t\t: undefined,\n\t\t].filter((hint): hint is string => typeof hint === 'string')\n\t\tconst allErrors = localError ? [...errors, localError] : errors\n\t\t// Show the just-uploaded filename, or a generic indicator when the field already holds an id (recall/prefill): the filename is not part of the client value.\n\t\tconst hasValue = value !== undefined && value !== null && value !== ''\n\t\tconst displayName = filename ?? (hasValue ? 'Uploaded file' : undefined)\n\n\t\tconst handleChange = async (event: ChangeEvent<HTMLInputElement>) => {\n\t\t\tconst selected = event.target.files?.[0]\n\t\t\tif (!selected) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (maxSize !== undefined && selected.size > maxSize) {\n\t\t\t\tsetLocalError(resolveMessage(t(keys.fileTooLarge), { max: formatBytes(maxSize) }))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!collection) {\n\t\t\t\tsetLocalError(t(keys.fileUploadMisconfigured))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsetUploading(true)\n\t\t\tsetLocalError(undefined)\n\t\t\tconst result = await uploadFile({ file: selected, collection })\n\t\t\tsetUploading(false)\n\t\t\tif (result.ok) {\n\t\t\t\tsetFilename(selected.name)\n\t\t\t\tonChange(result.id)\n\t\t\t} else {\n\t\t\t\tsetLocalError(result.message ?? 'Upload failed')\n\t\t\t}\n\t\t}\n\n\t\tconst clear = () => {\n\t\t\tsetFilename(undefined)\n\t\t\tsetLocalError(undefined)\n\t\t\tonChange('')\n\t\t}\n\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={allErrors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\taccept={accept}\n\t\t\t\t\tdisabled={disabled || uploading}\n\t\t\t\t\tonChange={(event) => {\n\t\t\t\t\t\tvoid handleChange(event)\n\t\t\t\t\t}}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\taria-invalid={allErrors.length > 0}\n\t\t\t\t\taria-describedby={describedById}\n\t\t\t\t/>\n\t\t\t\t{hints.length > 0 ? <p className=\"fb-field__file-hint\">{hints.join(' · ')}</p> : null}\n\t\t\t\t{uploading ? (\n\t\t\t\t\t<p className=\"fb-field__file-status\" aria-live=\"polite\">\n\t\t\t\t\t\tUploading\n\t\t\t\t\t</p>\n\t\t\t\t) : null}\n\t\t\t\t{displayName ? (\n\t\t\t\t\t<p className=\"fb-field__file-name\">\n\t\t\t\t\t\t<span>{displayName}</span>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName=\"fb-field__file-clear\"\n\t\t\t\t\t\t\tonClick={clear}\n\t\t\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</p>\n\t\t\t\t) : null}\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;;;;AAUA,MAAM,WAAW,cAAiC;CACjD,IAAI,CAAC,MAAM,QAAQ,SAAS,GAC3B,OAAO,CAAC;CAET,OAAO,UAAU,QAAQ,UAA2B,OAAO,UAAU,QAAQ;AAC9E;;;;;;;AAQA,MAAa,eAAe,qBAC1B,EAAE,OAAO,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,UAAU,QAAQ;CAC5E,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAC5B,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,UAAU,eAAe,SAA6B,KAAA,CAAS;CACtE,MAAM,CAAC,YAAY,iBAAiB,SAA6B,KAAA,CAAS;CAI1E,MAAM,aACL,OAAO,MAAM,sBAAsB,WAAW,MAAM,oBAAoB,KAAA;CACzE,MAAM,QAAQ,QAAQ,MAAM,SAAS;CACrC,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI,KAAA;CAEpD,MAAM,UACL,OAAO,MAAM,YAAY,YAAY,MAAM,WAAW,IAAI,MAAM,UAAU,KAAA;CAC3E,MAAM,QAAQ,CACb,MAAM,SAAS,IACZ,eAAe,EAAE,KAAK,gBAAgB,GAAG,EAAE,OAAO,MAAM,KAAK,IAAI,EAAE,CAAC,IACpE,KAAA,GACH,YAAY,KAAA,IACT,eAAe,EAAE,KAAK,eAAe,GAAG,EAAE,KAAK,YAAY,OAAO,EAAE,CAAC,IACrE,KAAA,CACJ,EAAE,QAAQ,SAAyB,OAAO,SAAS,QAAQ;CAC3D,MAAM,YAAY,aAAa,CAAC,GAAG,QAAQ,UAAU,IAAI;CAGzD,MAAM,cAAc,aADH,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,KACxB,kBAAkB,KAAA;CAE9D,MAAM,eAAe,OAAO,UAAyC;EACpE,MAAM,WAAW,MAAM,OAAO,QAAQ;EACtC,IAAI,CAAC,UACJ;EAED,IAAI,YAAY,KAAA,KAAa,SAAS,OAAO,SAAS;GACrD,cAAc,eAAe,EAAE,KAAK,YAAY,GAAG,EAAE,KAAK,YAAY,OAAO,EAAE,CAAC,CAAC;GACjF;EACD;EACA,IAAI,CAAC,YAAY;GAChB,cAAc,EAAE,KAAK,uBAAuB,CAAC;GAC7C;EACD;EACA,aAAa,IAAI;EACjB,cAAc,KAAA,CAAS;EACvB,MAAM,SAAS,MAAM,WAAW;GAAE,MAAM;GAAU;EAAW,CAAC;EAC9D,aAAa,KAAK;EAClB,IAAI,OAAO,IAAI;GACd,YAAY,SAAS,IAAI;GACzB,SAAS,OAAO,EAAE;EACnB,OACC,cAAc,OAAO,WAAW,eAAe;CAEjD;CAEA,MAAM,cAAc;EACnB,YAAY,KAAA,CAAS;EACrB,cAAc,KAAA,CAAS;EACvB,SAAS,EAAE;CACZ;CAEA,OACC,qBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACV,QAAQ;EACO;YANhB;GAQC,oBAAC,SAAD;IACK;IACE;IACN,MAAK;IACG;IACR,UAAU,YAAY;IACtB,WAAW,UAAU;KACpB,aAAkB,KAAK;IACxB;IACQ;IACR,gBAAc,UAAU,SAAS;IACjC,oBAAkB;GAClB,CAAA;GACA,MAAM,SAAS,IAAI,oBAAC,KAAD;IAAG,WAAU;cAAuB,MAAM,KAAK,KAAK;GAAK,CAAA,IAAI;GAChF,YACA,oBAAC,KAAD;IAAG,WAAU;IAAwB,aAAU;cAAS;GAErD,CAAA,IACA;GACH,cACA,qBAAC,KAAD;IAAG,WAAU;cAAb,CACC,oBAAC,QAAD,EAAA,UAAO,YAAkB,CAAA,GACzB,oBAAC,UAAD;KACC,MAAK;KACL,WAAU;KACV,SAAS;KACC;eACV;IAEO,CAAA,CACN;QACA;EACO;;AAEd,CACD"}
1
+ {"version":3,"file":"file.js","names":[],"sources":["../../../src/react/renderers/file.tsx"],"sourcesContent":["'use client'\n\nimport { type ChangeEvent, useState } from 'react'\nimport { keys } from '../../translations/keys'\nimport { formatBytes } from '../../uploads/formatBytes'\nimport { resolveMessage } from '../../validation/message'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { uploadFile } from '../uploadFile'\n\nconst typesOf = (mimeTypes: unknown): string[] => {\n\tif (!Array.isArray(mimeTypes)) {\n\t\treturn []\n\t}\n\treturn mimeTypes.filter((entry): entry is string => typeof entry === 'string')\n}\n\n/**\n * Headless file renderer: a native file input that uploads the chosen file to the upload collection and\n * stores the returned id as the field value. The server re-derives filename/mimeType/filesize from the\n * stored doc at submit, so the client value is only the id. Shows the uploaded filename with a clear\n * control and surfaces upload errors. Single file (v1); the upload posts to the default `/api` route.\n */\nexport const fileRenderer = defineFieldRenderer<string | number>(\n\t({ field, id, name, value, onChange, onBlur, errors, required, disabled, t }) => {\n\t\tconst describedById = `${id}-desc`\n\t\tconst [uploading, setUploading] = useState(false)\n\t\tconst [filename, setFilename] = useState<string | undefined>(undefined)\n\t\tconst [localError, setLocalError] = useState<string | undefined>(undefined)\n\n\t\t// Stamped server-side onto the block from plugin config; absent only for stale form docs saved\n\t\t// before uploads were configured (uploading then fails with a visible error, never a guess).\n\t\tconst collection =\n\t\t\ttypeof field.uploadsCollection === 'string' ? field.uploadsCollection : undefined\n\t\tconst types = typesOf(field.mimeTypes)\n\t\tconst accept = types.length > 0 ? types.join(',') : undefined\n\t\t// Zero is a real limit, matching resolveFileRef server-side (filesize > maxSize rejects any non-empty file).\n\t\tconst maxSize =\n\t\t\ttypeof field.maxSize === 'number' && field.maxSize >= 0 ? field.maxSize : undefined\n\t\tconst hints = [\n\t\t\ttypes.length > 0\n\t\t\t\t? resolveMessage(t(keys.fileHintAccepted), { types: types.join(', ') })\n\t\t\t\t: undefined,\n\t\t\tmaxSize !== undefined\n\t\t\t\t? resolveMessage(t(keys.fileHintMaxSize), { max: formatBytes(maxSize) })\n\t\t\t\t: undefined,\n\t\t].filter((hint): hint is string => typeof hint === 'string')\n\t\tconst allErrors = localError ? [...errors, localError] : errors\n\t\t// Show the just-uploaded filename, or a generic indicator when the field already holds an id (recall/prefill): the filename is not part of the client value.\n\t\tconst hasValue = value !== undefined && value !== null && value !== ''\n\t\tconst displayName = filename ?? (hasValue ? t(keys.fileUploaded) : undefined)\n\n\t\tconst handleChange = async (event: ChangeEvent<HTMLInputElement>) => {\n\t\t\tconst selected = event.target.files?.[0]\n\t\t\tif (!selected) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (maxSize !== undefined && selected.size > maxSize) {\n\t\t\t\tsetLocalError(resolveMessage(t(keys.fileTooLarge), { max: formatBytes(maxSize) }))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!collection) {\n\t\t\t\tsetLocalError(t(keys.fileUploadMisconfigured))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsetUploading(true)\n\t\t\tsetLocalError(undefined)\n\t\t\tconst result = await uploadFile({ file: selected, collection })\n\t\t\tsetUploading(false)\n\t\t\tif (result.ok) {\n\t\t\t\tsetFilename(selected.name)\n\t\t\t\tonChange(result.id)\n\t\t\t} else {\n\t\t\t\tsetLocalError(result.message ?? t(keys.fileUploadFailed))\n\t\t\t}\n\t\t}\n\n\t\tconst clear = () => {\n\t\t\tsetFilename(undefined)\n\t\t\tsetLocalError(undefined)\n\t\t\tonChange('')\n\t\t}\n\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={allErrors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\taccept={accept}\n\t\t\t\t\tdisabled={disabled || uploading}\n\t\t\t\t\tonChange={(event) => {\n\t\t\t\t\t\tvoid handleChange(event)\n\t\t\t\t\t}}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\taria-invalid={allErrors.length > 0}\n\t\t\t\t\taria-describedby={describedById}\n\t\t\t\t/>\n\t\t\t\t{hints.length > 0 ? <p className=\"fb-field__file-hint\">{hints.join(' · ')}</p> : null}\n\t\t\t\t{uploading ? (\n\t\t\t\t\t<p className=\"fb-field__file-status\" aria-live=\"polite\">\n\t\t\t\t\t\t{t(keys.fileUploading)}\n\t\t\t\t\t</p>\n\t\t\t\t) : null}\n\t\t\t\t{displayName ? (\n\t\t\t\t\t<p className=\"fb-field__file-name\">\n\t\t\t\t\t\t<span>{displayName}</span>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName=\"fb-field__file-clear\"\n\t\t\t\t\t\t\tonClick={clear}\n\t\t\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{t(keys.fileRemove)}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</p>\n\t\t\t\t) : null}\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;;;;AAUA,MAAM,WAAW,cAAiC;CACjD,IAAI,CAAC,MAAM,QAAQ,SAAS,GAC3B,OAAO,CAAC;CAET,OAAO,UAAU,QAAQ,UAA2B,OAAO,UAAU,QAAQ;AAC9E;;;;;;;AAQA,MAAa,eAAe,qBAC1B,EAAE,OAAO,IAAI,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,UAAU,QAAQ;CAChF,MAAM,gBAAgB,GAAG,GAAG;CAC5B,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,UAAU,eAAe,SAA6B,KAAA,CAAS;CACtE,MAAM,CAAC,YAAY,iBAAiB,SAA6B,KAAA,CAAS;CAI1E,MAAM,aACL,OAAO,MAAM,sBAAsB,WAAW,MAAM,oBAAoB,KAAA;CACzE,MAAM,QAAQ,QAAQ,MAAM,SAAS;CACrC,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI,KAAA;CAEpD,MAAM,UACL,OAAO,MAAM,YAAY,YAAY,MAAM,WAAW,IAAI,MAAM,UAAU,KAAA;CAC3E,MAAM,QAAQ,CACb,MAAM,SAAS,IACZ,eAAe,EAAE,KAAK,gBAAgB,GAAG,EAAE,OAAO,MAAM,KAAK,IAAI,EAAE,CAAC,IACpE,KAAA,GACH,YAAY,KAAA,IACT,eAAe,EAAE,KAAK,eAAe,GAAG,EAAE,KAAK,YAAY,OAAO,EAAE,CAAC,IACrE,KAAA,CACJ,EAAE,QAAQ,SAAyB,OAAO,SAAS,QAAQ;CAC3D,MAAM,YAAY,aAAa,CAAC,GAAG,QAAQ,UAAU,IAAI;CAGzD,MAAM,cAAc,aADH,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,KACxB,EAAE,KAAK,YAAY,IAAI,KAAA;CAEnE,MAAM,eAAe,OAAO,UAAyC;EACpE,MAAM,WAAW,MAAM,OAAO,QAAQ;EACtC,IAAI,CAAC,UACJ;EAED,IAAI,YAAY,KAAA,KAAa,SAAS,OAAO,SAAS;GACrD,cAAc,eAAe,EAAE,KAAK,YAAY,GAAG,EAAE,KAAK,YAAY,OAAO,EAAE,CAAC,CAAC;GACjF;EACD;EACA,IAAI,CAAC,YAAY;GAChB,cAAc,EAAE,KAAK,uBAAuB,CAAC;GAC7C;EACD;EACA,aAAa,IAAI;EACjB,cAAc,KAAA,CAAS;EACvB,MAAM,SAAS,MAAM,WAAW;GAAE,MAAM;GAAU;EAAW,CAAC;EAC9D,aAAa,KAAK;EAClB,IAAI,OAAO,IAAI;GACd,YAAY,SAAS,IAAI;GACzB,SAAS,OAAO,EAAE;EACnB,OACC,cAAc,OAAO,WAAW,EAAE,KAAK,gBAAgB,CAAC;CAE1D;CAEA,MAAM,cAAc;EACnB,YAAY,KAAA,CAAS;EACrB,cAAc,KAAA,CAAS;EACvB,SAAS,EAAE;CACZ;CAEA,OACC,qBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACV,QAAQ;EACO;YANhB;GAQC,oBAAC,SAAD;IACK;IACE;IACN,MAAK;IACG;IACR,UAAU,YAAY;IACtB,WAAW,UAAU;KACpB,aAAkB,KAAK;IACxB;IACQ;IACR,gBAAc,UAAU,SAAS;IACjC,oBAAkB;GAClB,CAAA;GACA,MAAM,SAAS,IAAI,oBAAC,KAAD;IAAG,WAAU;cAAuB,MAAM,KAAK,KAAK;GAAK,CAAA,IAAI;GAChF,YACA,oBAAC,KAAD;IAAG,WAAU;IAAwB,aAAU;cAC7C,EAAE,KAAK,aAAa;GACnB,CAAA,IACA;GACH,cACA,qBAAC,KAAD;IAAG,WAAU;cAAb,CACC,oBAAC,QAAD,EAAA,UAAO,YAAkB,CAAA,GACzB,oBAAC,UAAD;KACC,MAAK;KACL,WAAU;KACV,SAAS;KACC;eAET,EAAE,KAAK,UAAU;IACX,CAAA,CACN;QACA;EACO;;AAEd,CACD"}
@@ -3,10 +3,8 @@ import { defineFieldRenderer } from "../contract.js";
3
3
  import { FieldShell } from "../primitives/FieldShell.js";
4
4
  import { Input } from "../primitives/Input.js";
5
5
  import { jsx } from "react/jsx-runtime";
6
- import { useId } from "react";
7
6
  //#region src/react/renderers/number.tsx
8
- const numberRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, required, disabled }) => {
9
- const id = useId();
7
+ const numberRenderer = defineFieldRenderer(({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {
10
8
  const describedById = `${id}-desc`;
11
9
  return /* @__PURE__ */ jsx(FieldShell, {
12
10
  id,
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","names":[],"sources":["../../../src/react/renderers/number.tsx"],"sourcesContent":["'use client'\n\nimport { useId } from 'react'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Input } from '../primitives/Input'\n\nexport const numberRenderer = defineFieldRenderer<number | undefined>(\n\t({ field, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst id = useId()\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\tvalue={value == null ? '' : String(value)}\n\t\t\t\t\tonChange={(raw) => {\n\t\t\t\t\t\tconst next = Number(raw)\n\t\t\t\t\t\tonChange(raw === '' || Number.isNaN(next) ? undefined : next)\n\t\t\t\t\t}}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\tplaceholder={typeof field.placeholder === 'string' ? field.placeholder : undefined}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;;AAOA,MAAa,iBAAiB,qBAC5B,EAAE,OAAO,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CACzE,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,OAAD;GACK;GACE;GACN,MAAK;GACL,OAAO,SAAS,OAAO,KAAK,OAAO,KAAK;GACxC,WAAW,QAAQ;IAClB,MAAM,OAAO,OAAO,GAAG;IACvB,SAAS,QAAQ,MAAM,OAAO,MAAM,IAAI,IAAI,KAAA,IAAY,IAAI;GAC7D;GACQ;GACR,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;GAC/D;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
1
+ {"version":3,"file":"number.js","names":[],"sources":["../../../src/react/renderers/number.tsx"],"sourcesContent":["'use client'\n\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Input } from '../primitives/Input'\n\nexport const numberRenderer = defineFieldRenderer<number | undefined>(\n\t({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\tconst describedById = `${id}-desc`\n\t\treturn (\n\t\t\t<FieldShell\n\t\t\t\tid={id}\n\t\t\t\tlabel={field.label}\n\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\trequired={required}\n\t\t\t\terrors={errors}\n\t\t\t\tdescribedById={describedById}\n\t\t\t>\n\t\t\t\t<Input\n\t\t\t\t\tid={id}\n\t\t\t\t\tname={name}\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\tvalue={value == null ? '' : String(value)}\n\t\t\t\t\tonChange={(raw) => {\n\t\t\t\t\t\tconst next = Number(raw)\n\t\t\t\t\t\tonChange(raw === '' || Number.isNaN(next) ? undefined : next)\n\t\t\t\t\t}}\n\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\tplaceholder={typeof field.placeholder === 'string' ? field.placeholder : undefined}\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t/>\n\t\t\t</FieldShell>\n\t\t)\n\t}\n)\n"],"mappings":";;;;;;AAMA,MAAa,iBAAiB,qBAC5B,EAAE,OAAO,IAAI,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CAC7E,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,OAAD;GACK;GACE;GACN,MAAK;GACL,OAAO,SAAS,OAAO,KAAK,OAAO,KAAK;GACxC,WAAW,QAAQ;IAClB,MAAM,OAAO,OAAO,GAAG;IACvB,SAAS,QAAQ,MAAM,OAAO,MAAM,IAAI,IAAI,KAAA,IAAY,IAAI;GAC7D;GACQ;GACR,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;GAC/D;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD"}
@@ -4,7 +4,6 @@ import { defineFieldRenderer } from "../contract.js";
4
4
  import { FieldShell } from "../primitives/FieldShell.js";
5
5
  import { Select } from "../primitives/Select.js";
6
6
  import { jsx } from "react/jsx-runtime";
7
- import { useId } from "react";
8
7
  //#region src/react/renderers/region.tsx
9
8
  /**
10
9
  * A `<select>` over a fixed, code-supplied option set, for field types (country/state) whose choices
@@ -12,8 +11,7 @@ import { useId } from "react";
12
11
  * options as a closure argument instead of reading `field.options`, so the stored value is one of the
13
12
  * fixed codes and the placeholder still gates the empty selection.
14
13
  */
15
- const regionRenderer = (options) => defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, required, disabled }) => {
16
- const id = useId();
14
+ const regionRenderer = (options) => defineFieldRenderer(({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {
17
15
  const describedById = `${id}-desc`;
18
16
  return /* @__PURE__ */ jsx(FieldShell, {
19
17
  id,
@@ -1 +1 @@
1
- {"version":3,"file":"region.js","names":[],"sources":["../../../src/react/renderers/region.tsx"],"sourcesContent":["'use client'\n\nimport { useId } from 'react'\nimport { COUNTRIES, US_STATES } from '../../fields/data/regions'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Select, type SelectOption } from '../primitives/Select'\n\n/**\n * A `<select>` over a fixed, code-supplied option set, for field types (country/state) whose choices\n * live in the plugin rather than in per-instance `options`. Mirrors `selectRenderer` but takes the\n * options as a closure argument instead of reading `field.options`, so the stored value is one of the\n * fixed codes and the placeholder still gates the empty selection.\n */\nconst regionRenderer = (options: SelectOption[]) =>\n\tdefineFieldRenderer<string>(\n\t\t({ field, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\t\tconst id = useId()\n\t\t\tconst describedById = `${id}-desc`\n\t\t\treturn (\n\t\t\t\t<FieldShell\n\t\t\t\t\tid={id}\n\t\t\t\t\tlabel={field.label}\n\t\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\t\trequired={required}\n\t\t\t\t\terrors={errors}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t>\n\t\t\t\t\t<Select\n\t\t\t\t\t\tid={id}\n\t\t\t\t\t\tname={name}\n\t\t\t\t\t\tvalue={value ?? ''}\n\t\t\t\t\t\toptions={options}\n\t\t\t\t\t\tonChange={onChange}\n\t\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\t\tplaceholder={typeof field.placeholder === 'string' ? field.placeholder : undefined}\n\t\t\t\t\t\trequired={required}\n\t\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t\t/>\n\t\t\t\t</FieldShell>\n\t\t\t)\n\t\t}\n\t)\n\nexport const countryRenderer = regionRenderer(COUNTRIES)\nexport const stateRenderer = regionRenderer(US_STATES)\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,kBAAkB,YACvB,qBACE,EAAE,OAAO,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CACzE,MAAM,KAAK,MAAM;CACjB,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,QAAD;GACK;GACE;GACN,OAAO,SAAS;GACP;GACC;GACF;GACR,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;GAC/D;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD;AAED,MAAa,kBAAkB,eAAe,SAAS;AACvD,MAAa,gBAAgB,eAAe,SAAS"}
1
+ {"version":3,"file":"region.js","names":[],"sources":["../../../src/react/renderers/region.tsx"],"sourcesContent":["'use client'\n\nimport { COUNTRIES, US_STATES } from '../../fields/data/regions'\nimport { defineFieldRenderer } from '../contract'\nimport { FieldShell } from '../primitives/FieldShell'\nimport { Select, type SelectOption } from '../primitives/Select'\n\n/**\n * A `<select>` over a fixed, code-supplied option set, for field types (country/state) whose choices\n * live in the plugin rather than in per-instance `options`. Mirrors `selectRenderer` but takes the\n * options as a closure argument instead of reading `field.options`, so the stored value is one of the\n * fixed codes and the placeholder still gates the empty selection.\n */\nconst regionRenderer = (options: SelectOption[]) =>\n\tdefineFieldRenderer<string>(\n\t\t({ field, id, name, value, onChange, onBlur, errors, required, disabled }) => {\n\t\t\tconst describedById = `${id}-desc`\n\t\t\treturn (\n\t\t\t\t<FieldShell\n\t\t\t\t\tid={id}\n\t\t\t\t\tlabel={field.label}\n\t\t\t\t\tdescription={typeof field.description === 'string' ? field.description : undefined}\n\t\t\t\t\trequired={required}\n\t\t\t\t\terrors={errors}\n\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t>\n\t\t\t\t\t<Select\n\t\t\t\t\t\tid={id}\n\t\t\t\t\t\tname={name}\n\t\t\t\t\t\tvalue={value ?? ''}\n\t\t\t\t\t\toptions={options}\n\t\t\t\t\t\tonChange={onChange}\n\t\t\t\t\t\tonBlur={onBlur}\n\t\t\t\t\t\tplaceholder={typeof field.placeholder === 'string' ? field.placeholder : undefined}\n\t\t\t\t\t\trequired={required}\n\t\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\t\tinvalid={errors.length > 0}\n\t\t\t\t\t\tdescribedById={describedById}\n\t\t\t\t\t/>\n\t\t\t\t</FieldShell>\n\t\t\t)\n\t\t}\n\t)\n\nexport const countryRenderer = regionRenderer(COUNTRIES)\nexport const stateRenderer = regionRenderer(US_STATES)\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,kBAAkB,YACvB,qBACE,EAAE,OAAO,IAAI,MAAM,OAAO,UAAU,QAAQ,QAAQ,UAAU,eAAe;CAC7E,MAAM,gBAAgB,GAAG,GAAG;CAC5B,OACC,oBAAC,YAAD;EACK;EACJ,OAAO,MAAM;EACb,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;EAC/D;EACF;EACO;YAEf,oBAAC,QAAD;GACK;GACE;GACN,OAAO,SAAS;GACP;GACC;GACF;GACR,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc,KAAA;GAC/D;GACA;GACV,SAAS,OAAO,SAAS;GACV;EACf,CAAA;CACU,CAAA;AAEd,CACD;AAED,MAAa,kBAAkB,eAAe,SAAS;AACvD,MAAa,gBAAgB,eAAe,SAAS"}