@stapel/forms-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/MODULE.md +172 -0
  3. package/README.md +207 -0
  4. package/dist/api/export.d.ts +66 -0
  5. package/dist/api/export.d.ts.map +1 -0
  6. package/dist/api/export.js +138 -0
  7. package/dist/api/export.js.map +1 -0
  8. package/dist/api/extensions.d.ts +8 -0
  9. package/dist/api/extensions.d.ts.map +1 -0
  10. package/dist/api/extensions.js +8 -0
  11. package/dist/api/extensions.js.map +1 -0
  12. package/dist/api/formsApi.d.ts +113 -0
  13. package/dist/api/formsApi.d.ts.map +1 -0
  14. package/dist/api/formsApi.js +81 -0
  15. package/dist/api/formsApi.js.map +1 -0
  16. package/dist/api/generated/schema.d.ts +987 -0
  17. package/dist/api/generated/schema.d.ts.map +1 -0
  18. package/dist/api/generated/schema.js +2 -0
  19. package/dist/api/generated/schema.js.map +1 -0
  20. package/dist/api/types.d.ts +222 -0
  21. package/dist/api/types.d.ts.map +1 -0
  22. package/dist/api/types.js +14 -0
  23. package/dist/api/types.js.map +1 -0
  24. package/dist/default/ConfigField.d.ts +32 -0
  25. package/dist/default/ConfigField.d.ts.map +1 -0
  26. package/dist/default/ConfigField.js +101 -0
  27. package/dist/default/ConfigField.js.map +1 -0
  28. package/dist/default/ErrorAlert.d.ts +9 -0
  29. package/dist/default/ErrorAlert.d.ts.map +1 -0
  30. package/dist/default/ErrorAlert.js +27 -0
  31. package/dist/default/ErrorAlert.js.map +1 -0
  32. package/dist/default/FormBuilderPane.d.ts +36 -0
  33. package/dist/default/FormBuilderPane.d.ts.map +1 -0
  34. package/dist/default/FormBuilderPane.js +70 -0
  35. package/dist/default/FormBuilderPane.js.map +1 -0
  36. package/dist/default/FormsListPane.d.ts +18 -0
  37. package/dist/default/FormsListPane.d.ts.map +1 -0
  38. package/dist/default/FormsListPane.js +28 -0
  39. package/dist/default/FormsListPane.js.map +1 -0
  40. package/dist/default/ResponsesPane.d.ts +21 -0
  41. package/dist/default/ResponsesPane.d.ts.map +1 -0
  42. package/dist/default/ResponsesPane.js +118 -0
  43. package/dist/default/ResponsesPane.js.map +1 -0
  44. package/dist/default/StapelForm.d.ts +76 -0
  45. package/dist/default/StapelForm.d.ts.map +1 -0
  46. package/dist/default/StapelForm.js +118 -0
  47. package/dist/default/StapelForm.js.map +1 -0
  48. package/dist/default/fields.d.ts +11 -0
  49. package/dist/default/fields.d.ts.map +1 -0
  50. package/dist/default/fields.js +273 -0
  51. package/dist/default/fields.js.map +1 -0
  52. package/dist/default/index.d.ts +42 -0
  53. package/dist/default/index.d.ts.map +1 -0
  54. package/dist/default/index.js +37 -0
  55. package/dist/default/index.js.map +1 -0
  56. package/dist/default/slots.d.ts +59 -0
  57. package/dist/default/slots.d.ts.map +1 -0
  58. package/dist/default/slots.js +25 -0
  59. package/dist/default/slots.js.map +1 -0
  60. package/dist/default/theme.d.ts +23 -0
  61. package/dist/default/theme.d.ts.map +1 -0
  62. package/dist/default/theme.js +38 -0
  63. package/dist/default/theme.js.map +1 -0
  64. package/dist/default/types.d.ts +12 -0
  65. package/dist/default/types.d.ts.map +1 -0
  66. package/dist/default/types.js +2 -0
  67. package/dist/default/types.js.map +1 -0
  68. package/dist/flows/errors.d.ts +12 -0
  69. package/dist/flows/errors.d.ts.map +1 -0
  70. package/dist/flows/errors.js +13 -0
  71. package/dist/flows/errors.js.map +1 -0
  72. package/dist/flows/registry.d.ts +22 -0
  73. package/dist/flows/registry.d.ts.map +1 -0
  74. package/dist/flows/registry.js +20 -0
  75. package/dist/flows/registry.js.map +1 -0
  76. package/dist/headless/FormBuilder.d.ts +100 -0
  77. package/dist/headless/FormBuilder.d.ts.map +1 -0
  78. package/dist/headless/FormBuilder.js +278 -0
  79. package/dist/headless/FormBuilder.js.map +1 -0
  80. package/dist/headless/FormFill.d.ts +88 -0
  81. package/dist/headless/FormFill.d.ts.map +1 -0
  82. package/dist/headless/FormFill.js +279 -0
  83. package/dist/headless/FormFill.js.map +1 -0
  84. package/dist/headless/FormList.d.ts +34 -0
  85. package/dist/headless/FormList.d.ts.map +1 -0
  86. package/dist/headless/FormList.js +37 -0
  87. package/dist/headless/FormList.js.map +1 -0
  88. package/dist/headless/FormsProvider.d.ts +19 -0
  89. package/dist/headless/FormsProvider.d.ts.map +1 -0
  90. package/dist/headless/FormsProvider.js +15 -0
  91. package/dist/headless/FormsProvider.js.map +1 -0
  92. package/dist/headless/ResponsesTable.d.ts +82 -0
  93. package/dist/headless/ResponsesTable.d.ts.map +1 -0
  94. package/dist/headless/ResponsesTable.js +195 -0
  95. package/dist/headless/ResponsesTable.js.map +1 -0
  96. package/dist/i18n/errorsMap.d.ts +10 -0
  97. package/dist/i18n/errorsMap.d.ts.map +1 -0
  98. package/dist/i18n/errorsMap.js +20 -0
  99. package/dist/i18n/errorsMap.js.map +1 -0
  100. package/dist/i18n/es.d.ts +7 -0
  101. package/dist/i18n/es.d.ts.map +1 -0
  102. package/dist/i18n/es.js +118 -0
  103. package/dist/i18n/es.js.map +1 -0
  104. package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
  105. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
  106. package/dist/i18n/generated/errors.es.gen.js +79 -0
  107. package/dist/i18n/generated/errors.es.gen.js.map +1 -0
  108. package/dist/i18n/generated/errors.gen.d.ts +485 -0
  109. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  110. package/dist/i18n/generated/errors.gen.js +246 -0
  111. package/dist/i18n/generated/errors.gen.js.map +1 -0
  112. package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
  113. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  114. package/dist/i18n/generated/errors.ru.gen.js +79 -0
  115. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  116. package/dist/i18n/keys.d.ts +141 -0
  117. package/dist/i18n/keys.d.ts.map +1 -0
  118. package/dist/i18n/keys.js +233 -0
  119. package/dist/i18n/keys.js.map +1 -0
  120. package/dist/i18n/ru.d.ts +7 -0
  121. package/dist/i18n/ru.d.ts.map +1 -0
  122. package/dist/i18n/ru.js +127 -0
  123. package/dist/i18n/ru.js.map +1 -0
  124. package/dist/index.d.ts +61 -0
  125. package/dist/index.d.ts.map +1 -0
  126. package/dist/index.js +55 -0
  127. package/dist/index.js.map +1 -0
  128. package/dist/model/context.d.ts +11 -0
  129. package/dist/model/context.d.ts.map +1 -0
  130. package/dist/model/context.js +16 -0
  131. package/dist/model/context.js.map +1 -0
  132. package/dist/model/mutations.d.ts +135 -0
  133. package/dist/model/mutations.d.ts.map +1 -0
  134. package/dist/model/mutations.js +223 -0
  135. package/dist/model/mutations.js.map +1 -0
  136. package/dist/model/queries.d.ts +59 -0
  137. package/dist/model/queries.d.ts.map +1 -0
  138. package/dist/model/queries.js +117 -0
  139. package/dist/model/queries.js.map +1 -0
  140. package/dist/model/queryKeys.d.ts +28 -0
  141. package/dist/model/queryKeys.d.ts.map +1 -0
  142. package/dist/model/queryKeys.js +23 -0
  143. package/dist/model/queryKeys.js.map +1 -0
  144. package/dist/model/runtime.d.ts +28 -0
  145. package/dist/model/runtime.d.ts.map +1 -0
  146. package/dist/model/runtime.js +14 -0
  147. package/dist/model/runtime.js.map +1 -0
  148. package/dist/widgets/configForms.d.ts +112 -0
  149. package/dist/widgets/configForms.d.ts.map +1 -0
  150. package/dist/widgets/configForms.js +211 -0
  151. package/dist/widgets/configForms.js.map +1 -0
  152. package/dist/widgets/registry.d.ts +83 -0
  153. package/dist/widgets/registry.d.ts.map +1 -0
  154. package/dist/widgets/registry.js +27 -0
  155. package/dist/widgets/registry.js.map +1 -0
  156. package/dist/widgets/validate.d.ts +57 -0
  157. package/dist/widgets/validate.d.ts.map +1 -0
  158. package/dist/widgets/validate.js +173 -0
  159. package/dist/widgets/validate.js.map +1 -0
  160. package/llms.txt +124 -0
  161. package/manifest.json +1038 -0
  162. package/package.json +108 -0
  163. package/src/analytics/generated/events.json +7 -0
  164. package/src/api/export.ts +195 -0
  165. package/src/api/extensions.ts +7 -0
  166. package/src/api/formsApi.ts +329 -0
  167. package/src/api/generated/schema.ts +989 -0
  168. package/src/api/types.ts +276 -0
  169. package/src/default/ConfigField.tsx +227 -0
  170. package/src/default/ErrorAlert.tsx +46 -0
  171. package/src/default/FormBuilderPane.tsx +414 -0
  172. package/src/default/FormsListPane.tsx +144 -0
  173. package/src/default/ResponsesPane.tsx +332 -0
  174. package/src/default/StapelForm.tsx +377 -0
  175. package/src/default/fields.tsx +454 -0
  176. package/src/default/index.ts +71 -0
  177. package/src/default/slots.ts +78 -0
  178. package/src/default/theme.tsx +65 -0
  179. package/src/default/types.ts +12 -0
  180. package/src/flows/errors.ts +16 -0
  181. package/src/flows/registry.ts +32 -0
  182. package/src/headless/FormBuilder.tsx +453 -0
  183. package/src/headless/FormFill.tsx +396 -0
  184. package/src/headless/FormList.tsx +74 -0
  185. package/src/headless/FormsProvider.tsx +20 -0
  186. package/src/headless/ResponsesTable.tsx +341 -0
  187. package/src/i18n/errorsMap.ts +31 -0
  188. package/src/i18n/es.ts +140 -0
  189. package/src/i18n/generated/errors.es.gen.ts +85 -0
  190. package/src/i18n/generated/errors.gen.ts +277 -0
  191. package/src/i18n/generated/errors.json +583 -0
  192. package/src/i18n/generated/errors.ru.gen.ts +85 -0
  193. package/src/i18n/keys.ts +264 -0
  194. package/src/i18n/ru.ts +150 -0
  195. package/src/index.ts +184 -0
  196. package/src/model/context.tsx +28 -0
  197. package/src/model/mutations.ts +387 -0
  198. package/src/model/queries.ts +154 -0
  199. package/src/model/queryKeys.ts +61 -0
  200. package/src/model/runtime.ts +48 -0
  201. package/src/widgets/registry.ts +101 -0
  202. package/src/widgets/validate.ts +224 -0
  203. package/tsconfig.json +26 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE9E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;AAEnD,MAAM,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AAEnE,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GACjC,YAAY,CAcd"}
@@ -0,0 +1,14 @@
1
+ import { createModuleRuntime } from "@stapel/core";
2
+ import { createFormsApi } from "../api/formsApi.js";
3
+ export function createFormsRuntime(options) {
4
+ return createModuleRuntime((client) => createFormsApi(client, {
5
+ ...(options.fetch !== undefined ? { fetch: options.fetch } : {}),
6
+ ...(options.credentials !== undefined
7
+ ? { credentials: options.credentials }
8
+ : {}),
9
+ ...(options.defaultHeaders !== undefined
10
+ ? { defaultHeaders: options.defaultHeaders }
11
+ : {}),
12
+ }), options);
13
+ }
14
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA6BpD,MAAM,UAAU,kBAAkB,CAChC,OAAkC;IAElC,OAAO,mBAAmB,CACxB,CAAC,MAAM,EAAE,EAAE,CACT,cAAc,CAAC,MAAM,EAAE;QACrB,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS;YACnC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;YACtC,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,SAAS;YACtC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE;YAC5C,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,EACJ,OAAO,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The field-kind CONFIG CONTRACT — what `<FormBuilder>` is data-driven off.
3
+ *
4
+ * ── Why this table exists in TypeScript at all ─────────────────────────────
5
+ *
6
+ * `stapel_attributes.config_form` declares every feature type's admin config
7
+ * form as DATA (`FormField(name, kind, label_key, required, default, params)`)
8
+ * precisely so a builder needs no per-type UI. Its own docstring, however,
9
+ * records how that data reaches a client: *"Emitted server-side onto the admin
10
+ * page via the widget (**no endpoint**)."* There is no REST surface serving
11
+ * `form_declarations()` — not in stapel-attributes, and not among
12
+ * stapel-forms' 13 paths (verified against `docs/schema.json`).
13
+ *
14
+ * So this module MIRRORS the upstream declarations rather than fetching them.
15
+ * That is a real, named cost, not a design preference — see the spec delta
16
+ * filed for it. What the mirror buys is the property §8 actually asked for:
17
+ * the builder iterates a table, so a kind is configured by DATA and no kind
18
+ * gets a bespoke hand-written form. What it costs is that the table can drift
19
+ * from upstream until the endpoint exists; the drift is bounded by
20
+ * `test/configForms.test.ts`, which pins every entry against the values quoted
21
+ * from `config_form.py` here.
22
+ *
23
+ * Source: `stapel_attributes/config_form.py::BUILTIN_FORMS` (attributes 0.4.6).
24
+ *
25
+ * ── v1 scope, and the two kinds that ship builder-less ─────────────────────
26
+ *
27
+ * Spec §12 risk 5: *"if a kind's config form turns out unrepresentable, that
28
+ * kind ships builder-less (authorable via draft PUT) rather than growing
29
+ * bespoke UI."* Applying it honestly:
30
+ *
31
+ * - **`convertible_unit`** declares NO config form upstream (it is absent
32
+ * from `BUILTIN_FORMS`). Nothing to render → builder-less.
33
+ * - **`hierarchical_select`** declares exactly one field, of kind
34
+ * `hierarchical_options` — a tree editor. Its whole form is the
35
+ * unrepresentable part → builder-less.
36
+ *
37
+ * Both remain fully RENDERABLE (they have widgets) and fully authorable
38
+ * through `PUT /forms/<id>/draft`. The builder says so instead of pretending.
39
+ *
40
+ * Two individual config FIELDS are also v1-unrepresentable and are marked
41
+ * {@link ConfigFieldSpec.unsupported}: `date.options` (`timestamp_array`) and
42
+ * anything else using `hierarchical_options`. The builder renders the rest of
43
+ * that kind's form and states which options it cannot edit — dropping them
44
+ * silently would present a partial form as a complete one.
45
+ */
46
+ /**
47
+ * The config-widget vocabulary — `stapel_attributes.config_form.FIELD_KINDS`.
48
+ * A kind here is a WIDGET, not a feature type: this is why the builder scales
49
+ * to a host-registered type for free, exactly as the upstream docstring
50
+ * promises ("a newly registered type gets an admin form with zero JS as long
51
+ * as it uses the standard field-kinds").
52
+ */
53
+ export type ConfigFieldKind = "number" | "text" | "checkbox" | "translatable_text" | "number_options" | "string_options" | "color_options" | "select" | "select_options_with_default" | "max_selected_dropdown" | "hierarchical_options" | "timestamp" | "timestamp_array";
54
+ /** One inline choice of a `select` config widget. */
55
+ export interface ConfigSelectOption {
56
+ readonly value: string;
57
+ readonly label: string;
58
+ }
59
+ /** One declared config-form field (upstream `FormField.to_dict()`). */
60
+ export interface ConfigFieldSpec {
61
+ /** The config key this field edits. Order is significant. */
62
+ readonly name: string;
63
+ readonly kind: ConfigFieldKind;
64
+ /** Upstream i18n key, `admin.attributes.form.<type>.<field>`. The builder
65
+ * renders `t(labelKey)` and falls back to a humanized `name`. */
66
+ readonly labelKey: string;
67
+ /** Cosmetic `*` marker only — real validation is server-side. */
68
+ readonly required?: boolean;
69
+ /** Applied when the config key is absent. */
70
+ readonly default?: unknown;
71
+ /** Inline choices, for `kind: "select"`. */
72
+ readonly options?: readonly ConfigSelectOption[];
73
+ /** Numeric step, for `kind: "number"`. */
74
+ readonly step?: number;
75
+ /** Placeholder, for the text-ish kinds. */
76
+ readonly placeholder?: string;
77
+ /**
78
+ * Set when v1 has no widget for this config kind. The builder shows the row
79
+ * disabled with an explanation rather than omitting it — a config form that
80
+ * silently hides an option looks complete when it is not.
81
+ */
82
+ readonly unsupported?: true;
83
+ }
84
+ /** A field kind's full config declaration. */
85
+ export interface KindConfigForm {
86
+ /** Upstream i18n key, `admin.attributes.type.<slug>`. */
87
+ readonly labelKey: string;
88
+ readonly fields: readonly ConfigFieldSpec[];
89
+ }
90
+ /**
91
+ * Field kind → its config form. A kind ABSENT from this table ships
92
+ * builder-less (see the module header): `convertible_unit` (no upstream
93
+ * declaration) and `hierarchical_select` (its only field is a tree editor).
94
+ */
95
+ export declare const FIELD_KIND_CONFIG_FORMS: Readonly<Record<string, KindConfigForm>>;
96
+ /**
97
+ * The kinds the builder can configure, in offer order. Anything in
98
+ * `BUILTIN_FIELD_KINDS` but not here is builder-less by the §12 risk-5 rule.
99
+ */
100
+ export declare const BUILDER_KINDS: readonly string[];
101
+ /** Can `<FormBuilder>` configure this kind? */
102
+ export declare function isBuilderSupportedKind(kind: string): boolean;
103
+ /** The kind's config declaration, or `undefined` when it ships builder-less. */
104
+ export declare function configFormFor(kind: string): KindConfigForm | undefined;
105
+ /**
106
+ * The config defaults a freshly-added field of this kind starts with — every
107
+ * declared `default`, and nothing else. Keys with no upstream default stay
108
+ * ABSENT rather than being written as `null`: the engine's dataclass reads an
109
+ * absent key as "use my own default", and writing one changes behaviour.
110
+ */
111
+ export declare function defaultConfigFor(kind: string): Record<string, unknown>;
112
+ //# sourceMappingURL=configForms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configForms.d.ts","sourceRoot":"","sources":["../../src/widgets/configForms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,MAAM,GACN,UAAU,GACV,mBAAmB,GACnB,gBAAgB,GAChB,gBAAgB,GAChB,eAAe,GACf,QAAQ,GACR,6BAA6B,GAC7B,uBAAuB,GACvB,sBAAsB,GACtB,WAAW,GACX,iBAAiB,CAAC;AAEtB,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B;qEACiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACjD,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC;CAC7B;AAED,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;CAC7C;AA0HD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAS5E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAS1C,CAAC;AAEF,+CAA+C;AAC/C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEtE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQtE"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The field-kind CONFIG CONTRACT — what `<FormBuilder>` is data-driven off.
3
+ *
4
+ * ── Why this table exists in TypeScript at all ─────────────────────────────
5
+ *
6
+ * `stapel_attributes.config_form` declares every feature type's admin config
7
+ * form as DATA (`FormField(name, kind, label_key, required, default, params)`)
8
+ * precisely so a builder needs no per-type UI. Its own docstring, however,
9
+ * records how that data reaches a client: *"Emitted server-side onto the admin
10
+ * page via the widget (**no endpoint**)."* There is no REST surface serving
11
+ * `form_declarations()` — not in stapel-attributes, and not among
12
+ * stapel-forms' 13 paths (verified against `docs/schema.json`).
13
+ *
14
+ * So this module MIRRORS the upstream declarations rather than fetching them.
15
+ * That is a real, named cost, not a design preference — see the spec delta
16
+ * filed for it. What the mirror buys is the property §8 actually asked for:
17
+ * the builder iterates a table, so a kind is configured by DATA and no kind
18
+ * gets a bespoke hand-written form. What it costs is that the table can drift
19
+ * from upstream until the endpoint exists; the drift is bounded by
20
+ * `test/configForms.test.ts`, which pins every entry against the values quoted
21
+ * from `config_form.py` here.
22
+ *
23
+ * Source: `stapel_attributes/config_form.py::BUILTIN_FORMS` (attributes 0.4.6).
24
+ *
25
+ * ── v1 scope, and the two kinds that ship builder-less ─────────────────────
26
+ *
27
+ * Spec §12 risk 5: *"if a kind's config form turns out unrepresentable, that
28
+ * kind ships builder-less (authorable via draft PUT) rather than growing
29
+ * bespoke UI."* Applying it honestly:
30
+ *
31
+ * - **`convertible_unit`** declares NO config form upstream (it is absent
32
+ * from `BUILTIN_FORMS`). Nothing to render → builder-less.
33
+ * - **`hierarchical_select`** declares exactly one field, of kind
34
+ * `hierarchical_options` — a tree editor. Its whole form is the
35
+ * unrepresentable part → builder-less.
36
+ *
37
+ * Both remain fully RENDERABLE (they have widgets) and fully authorable
38
+ * through `PUT /forms/<id>/draft`. The builder says so instead of pretending.
39
+ *
40
+ * Two individual config FIELDS are also v1-unrepresentable and are marked
41
+ * {@link ConfigFieldSpec.unsupported}: `date.options` (`timestamp_array`) and
42
+ * anything else using `hierarchical_options`. The builder renders the rest of
43
+ * that kind's form and states which options it cannot edit — dropping them
44
+ * silently would present a partial form as a complete one.
45
+ */
46
+ function lk(type, field) {
47
+ return `admin.attributes.form.${type}.${field}`;
48
+ }
49
+ const INT_FIELDS = [
50
+ { name: "min", kind: "number", labelKey: lk("int", "min"), step: 1 },
51
+ { name: "max", kind: "number", labelKey: lk("int", "max"), step: 1 },
52
+ { name: "options", kind: "number_options", labelKey: lk("int", "options") },
53
+ { name: "allowCustom", kind: "checkbox", labelKey: lk("int", "allowCustom"), default: true },
54
+ { name: "prefix", kind: "translatable_text", labelKey: lk("int", "prefix"), placeholder: "$" },
55
+ { name: "postfix", kind: "translatable_text", labelKey: lk("int", "postfix"), placeholder: "km" },
56
+ { name: "postfix1000", kind: "translatable_text", labelKey: lk("int", "postfix1000"), placeholder: "k" },
57
+ { name: "placeholder", kind: "translatable_text", labelKey: lk("int", "placeholder") },
58
+ ];
59
+ const FLOAT_FIELDS = [
60
+ { name: "min", kind: "number", labelKey: lk("float", "min"), step: 0.01 },
61
+ { name: "max", kind: "number", labelKey: lk("float", "max"), step: 0.01 },
62
+ { name: "precision", kind: "number", labelKey: lk("float", "precision"), default: 2, step: 1 },
63
+ { name: "options", kind: "number_options", labelKey: lk("float", "options") },
64
+ { name: "allowCustom", kind: "checkbox", labelKey: lk("float", "allowCustom"), default: true },
65
+ { name: "prefix", kind: "translatable_text", labelKey: lk("float", "prefix"), placeholder: "$" },
66
+ { name: "postfix", kind: "translatable_text", labelKey: lk("float", "postfix"), placeholder: "m²" },
67
+ { name: "postfix1000", kind: "translatable_text", labelKey: lk("float", "postfix1000"), placeholder: "k" },
68
+ { name: "placeholder", kind: "translatable_text", labelKey: lk("float", "placeholder") },
69
+ ];
70
+ const STRING_FIELDS = [
71
+ { name: "minLength", kind: "number", labelKey: lk("string", "minLength"), step: 1 },
72
+ { name: "maxLength", kind: "number", labelKey: lk("string", "maxLength"), step: 1 },
73
+ // stapel-attributes 0.4.6. Rendering metadata only — the string widget draws
74
+ // a textarea instead of an input. Absent means false, which is what every
75
+ // schema published against 0.4.5 (including stapel-forms 0.1.0's own tests)
76
+ // carries, so nothing stored needs a migration.
77
+ { name: "multiline", kind: "checkbox", labelKey: lk("string", "multiline"), default: false },
78
+ { name: "pattern", kind: "text", labelKey: lk("string", "pattern") },
79
+ { name: "options", kind: "string_options", labelKey: lk("string", "options") },
80
+ { name: "allowCustom", kind: "checkbox", labelKey: lk("string", "allowCustom"), default: true },
81
+ { name: "prefix", kind: "translatable_text", labelKey: lk("string", "prefix"), placeholder: "$" },
82
+ { name: "postfix", kind: "translatable_text", labelKey: lk("string", "postfix"), placeholder: "m²" },
83
+ { name: "placeholder", kind: "translatable_text", labelKey: lk("string", "placeholder") },
84
+ ];
85
+ const BOOL_FIELDS = [
86
+ { name: "trueLabel", kind: "translatable_text", labelKey: lk("bool", "trueLabel"), placeholder: "yes" },
87
+ { name: "falseLabel", kind: "translatable_text", labelKey: lk("bool", "falseLabel"), placeholder: "no" },
88
+ ];
89
+ const HEX_COLOR_FIELDS = [
90
+ { name: "options", kind: "color_options", labelKey: lk("hex_color", "options") },
91
+ // LN-B15: hex_color's allowCustom defaults FALSE, unlike int/float/string.
92
+ { name: "allowCustom", kind: "checkbox", labelKey: lk("hex_color", "allowCustom"), default: false },
93
+ ];
94
+ const SELECT_FIELDS = [
95
+ { name: "options", kind: "select_options_with_default", labelKey: lk("select", "options") },
96
+ {
97
+ name: "uiStyle",
98
+ kind: "select",
99
+ labelKey: lk("select", "uiStyle"),
100
+ required: true,
101
+ // B2 canon: the untouched-form default MUST equal the engine dataclass
102
+ // default (SelectConfig.uiStyle = "dropdown"), so a select saved without
103
+ // touching this round-trips to what the UI displayed.
104
+ default: "dropdown",
105
+ options: [
106
+ { value: "chips", label: "Chips/Tags" },
107
+ { value: "checkboxes", label: "Checkboxes (like checklist)" },
108
+ { value: "dropdown", label: "Dropdown" },
109
+ ],
110
+ },
111
+ { name: "minSelected", kind: "number", labelKey: lk("select", "minSelected"), default: 0, step: 1 },
112
+ // No default: SelectConfig.maxSelected is None = unlimited, and the widget
113
+ // shows "Unlimited" for an absent value.
114
+ { name: "maxSelected", kind: "max_selected_dropdown", labelKey: lk("select", "maxSelected") },
115
+ { name: "lockUserInput", kind: "checkbox", labelKey: lk("select", "lockUserInput") },
116
+ ];
117
+ const DATE_FIELDS = [
118
+ {
119
+ name: "precision",
120
+ kind: "select",
121
+ labelKey: lk("date", "precision"),
122
+ required: true,
123
+ default: "date",
124
+ options: [
125
+ { value: "year", label: "Year only" },
126
+ { value: "month", label: "Month (year + month)" },
127
+ { value: "date", label: "Date (year + month + day)" },
128
+ { value: "datetime", label: "Date & Time" },
129
+ ],
130
+ },
131
+ { name: "minDate", kind: "timestamp", labelKey: lk("date", "minDate") },
132
+ { name: "maxDate", kind: "timestamp", labelKey: lk("date", "maxDate") },
133
+ { name: "allowFuture", kind: "checkbox", labelKey: lk("date", "allowFuture"), default: true },
134
+ { name: "allowPast", kind: "checkbox", labelKey: lk("date", "allowPast"), default: true },
135
+ // Upstream names this field literally "default".
136
+ { name: "default", kind: "timestamp", labelKey: lk("date", "default") },
137
+ { name: "options", kind: "timestamp_array", labelKey: lk("date", "options"), unsupported: true },
138
+ { name: "lockInput", kind: "checkbox", labelKey: lk("date", "lockInput") },
139
+ { name: "placeholder", kind: "text", labelKey: lk("date", "placeholder") },
140
+ ];
141
+ const HEADER_FIELDS = [
142
+ {
143
+ name: "style",
144
+ kind: "select",
145
+ labelKey: lk("header", "style"),
146
+ required: true,
147
+ // LN-B01, preserved deliberately: the upstream default "h2" matches
148
+ // NEITHER option value. Mirrored rather than "fixed" — silently changing
149
+ // it here would make the builder disagree with the engine.
150
+ default: "h2",
151
+ options: [
152
+ { value: "l", label: "H1 - Large" },
153
+ { value: "m", label: "H2 - Medium" },
154
+ ],
155
+ },
156
+ ];
157
+ /**
158
+ * Field kind → its config form. A kind ABSENT from this table ships
159
+ * builder-less (see the module header): `convertible_unit` (no upstream
160
+ * declaration) and `hierarchical_select` (its only field is a tree editor).
161
+ */
162
+ export const FIELD_KIND_CONFIG_FORMS = {
163
+ int: { labelKey: "admin.attributes.type.int", fields: INT_FIELDS },
164
+ float: { labelKey: "admin.attributes.type.float", fields: FLOAT_FIELDS },
165
+ string: { labelKey: "admin.attributes.type.string", fields: STRING_FIELDS },
166
+ bool: { labelKey: "admin.attributes.type.bool", fields: BOOL_FIELDS },
167
+ hex_color: { labelKey: "admin.attributes.type.hex_color", fields: HEX_COLOR_FIELDS },
168
+ select: { labelKey: "admin.attributes.type.select", fields: SELECT_FIELDS },
169
+ date: { labelKey: "admin.attributes.type.date", fields: DATE_FIELDS },
170
+ header: { labelKey: "admin.attributes.type.header", fields: HEADER_FIELDS },
171
+ };
172
+ /**
173
+ * The kinds the builder can configure, in offer order. Anything in
174
+ * `BUILTIN_FIELD_KINDS` but not here is builder-less by the §12 risk-5 rule.
175
+ */
176
+ export const BUILDER_KINDS = [
177
+ "string",
178
+ "int",
179
+ "float",
180
+ "bool",
181
+ "select",
182
+ "date",
183
+ "header",
184
+ "hex_color",
185
+ ];
186
+ /** Can `<FormBuilder>` configure this kind? */
187
+ export function isBuilderSupportedKind(kind) {
188
+ return Object.prototype.hasOwnProperty.call(FIELD_KIND_CONFIG_FORMS, kind);
189
+ }
190
+ /** The kind's config declaration, or `undefined` when it ships builder-less. */
191
+ export function configFormFor(kind) {
192
+ return FIELD_KIND_CONFIG_FORMS[kind];
193
+ }
194
+ /**
195
+ * The config defaults a freshly-added field of this kind starts with — every
196
+ * declared `default`, and nothing else. Keys with no upstream default stay
197
+ * ABSENT rather than being written as `null`: the engine's dataclass reads an
198
+ * absent key as "use my own default", and writing one changes behaviour.
199
+ */
200
+ export function defaultConfigFor(kind) {
201
+ const form = configFormFor(kind);
202
+ if (form === undefined)
203
+ return {};
204
+ const config = {};
205
+ for (const field of form.fields) {
206
+ if (field.default !== undefined)
207
+ config[field.name] = field.default;
208
+ }
209
+ return config;
210
+ }
211
+ //# sourceMappingURL=configForms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configForms.js","sourceRoot":"","sources":["../../src/widgets/configForms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The field-widget registry — THE customer seam of this pair, and the direct
3
+ * descendant of docs-react's editor registry.
4
+ *
5
+ * A form schema's fields are `stapel_attributes` `FeatureDef`s, and that type
6
+ * vocabulary is an OPEN registry server-side (builtins →
7
+ * `STAPEL_ATTRIBUTES["EXTRA_TYPES"]` → runtime `register_feature_type`). A
8
+ * host that adds a type on the backend must be able to render it on the front
9
+ * without forking the skin:
10
+ *
11
+ * ```tsx
12
+ * registerFormFieldWidget("signature", SignaturePad); // at startup
13
+ * ```
14
+ *
15
+ * ── Resolution, and what happens when nothing matches ──────────────────────
16
+ *
17
+ * This module answers only the FIRST rung: an explicit registration, or
18
+ * `null`. The `/default` skin completes the ladder — explicit registration >
19
+ * skin builtin (antd) > **unsupported-field notice** — mirroring
20
+ * docs-react's `DocSurface`, where an explicit `registerDocEditor` outranks
21
+ * the skin's own styled default so a host's swap is never shadowed.
22
+ *
23
+ * The last rung is a notice, deliberately, and never a skipped field. A
24
+ * schema can legally contain a kind this build has no widget for; rendering
25
+ * nothing would silently drop a field that may be REQUIRED, and the person
26
+ * would submit a form they could not complete and be told, by the server,
27
+ * that a field they never saw is missing. `<FormFill>` therefore also blocks
28
+ * the submit with `forms.unsupported_kind` while such a field is present —
29
+ * degrade loudly (spec §7.2).
30
+ *
31
+ * The registry lives in the main entry, not in `/default`, so a host building
32
+ * its own renderer over `<FormFill>` uses the same seam as the skin does
33
+ * rather than a parallel one.
34
+ */
35
+ import type { ComponentType } from "react";
36
+ import type { FlowError } from "@stapel/core";
37
+ import type { FormFieldDef } from "../api/types.js";
38
+ /**
39
+ * What every field widget receives. One field, one value, one setter, one
40
+ * error — the widget never touches the API layer or the rest of the form.
41
+ */
42
+ export interface FormFieldWidgetProps {
43
+ /** The field's `FeatureDef`. `field.config` carries the kind's camelCase
44
+ * options (`maxLength`, `minSelected`, `precision`, `multiline`, …). */
45
+ readonly field: FormFieldDef;
46
+ /** The current answer, or `undefined` while unanswered. */
47
+ readonly value: unknown;
48
+ /** Report a new answer. Pass the bare scalar — `select` normalizes to a
49
+ * list server-side, so a single choice need not be wrapped. */
50
+ onChange(value: unknown): void;
51
+ /** The field's current refusal (client mirror or server verdict), or
52
+ * `undefined`. */
53
+ readonly error: FlowError | undefined;
54
+ /** True while a submit is in flight — widgets should go read-only. */
55
+ readonly disabled: boolean;
56
+ /**
57
+ * DOM id the widget MUST put on its primary control. The skin's field row
58
+ * points its `<label for>` at this, so the label actually names the input
59
+ * for a screen reader (and for a click). A widget that drops it renders an
60
+ * unlabelled control — which antd's `Form.Item` cannot detect and will
61
+ * happily draw a label beside.
62
+ */
63
+ readonly id: string;
64
+ }
65
+ export type FormFieldWidget = ComponentType<FormFieldWidgetProps>;
66
+ /**
67
+ * Register (or override) the widget for a field kind. Call at startup, before
68
+ * the first render — the registry is module-global, like the i18n bundle
69
+ * registration and docs-react's editor registry.
70
+ */
71
+ export declare function registerFormFieldWidget(kind: string, widget: FormFieldWidget): void;
72
+ /** Remove an explicit registration (the skin's builtin, if any, resolves
73
+ * again). */
74
+ export declare function unregisterFormFieldWidget(kind: string): void;
75
+ /**
76
+ * The EXPLICIT registration for a kind, or `null`. Named for what it is: the
77
+ * skin calls this FIRST and only falls back to its own builtin when it
78
+ * returns `null`, so a host registration always wins.
79
+ */
80
+ export declare function resolveFormFieldWidget(kind: string): FormFieldWidget | null;
81
+ /** Every kind with an explicit registration, sorted. */
82
+ export declare function registeredFormFieldKinds(): readonly string[];
83
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/widgets/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;4EACwE;IACxE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;mEAC+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B;sBACkB;IAClB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IACtC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC,oBAAoB,CAAC,CAAC;AAIlE;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,GACtB,IAAI,CAEN;AAED;aACa;AACb,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAE3E;AAED,wDAAwD;AACxD,wBAAgB,wBAAwB,IAAI,SAAS,MAAM,EAAE,CAE5D"}
@@ -0,0 +1,27 @@
1
+ const registered = new Map();
2
+ /**
3
+ * Register (or override) the widget for a field kind. Call at startup, before
4
+ * the first render — the registry is module-global, like the i18n bundle
5
+ * registration and docs-react's editor registry.
6
+ */
7
+ export function registerFormFieldWidget(kind, widget) {
8
+ registered.set(kind, widget);
9
+ }
10
+ /** Remove an explicit registration (the skin's builtin, if any, resolves
11
+ * again). */
12
+ export function unregisterFormFieldWidget(kind) {
13
+ registered.delete(kind);
14
+ }
15
+ /**
16
+ * The EXPLICIT registration for a kind, or `null`. Named for what it is: the
17
+ * skin calls this FIRST and only falls back to its own builtin when it
18
+ * returns `null`, so a host registration always wins.
19
+ */
20
+ export function resolveFormFieldWidget(kind) {
21
+ return registered.get(kind) ?? null;
22
+ }
23
+ /** Every kind with an explicit registration, sorted. */
24
+ export function registeredFormFieldKinds() {
25
+ return [...registered.keys()].sort();
26
+ }
27
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/widgets/registry.ts"],"names":[],"mappings":"AAoEA,MAAM,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAY,EACZ,MAAuB;IAEvB,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED;aACa;AACb,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACvC,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The client-side validation MIRROR — instant feedback, never a verdict.
3
+ *
4
+ * "Client-side validation mirrors, server decides" (spec §7.2). Every rule
5
+ * here is derived from the schema's own `config` so the person sees a problem
6
+ * as they type instead of after a round trip; none of it is trusted. The
7
+ * server re-runs `stapel_attributes.validate_dto_structured` on every submit
8
+ * and its answer is the one that counts.
9
+ *
10
+ * ── Why the mirror emits the SERVER's error keys ───────────────────────────
11
+ *
12
+ * Each rule below reports the exact `error.400.feature_*` key
13
+ * (`stapel_attributes.errors.ERROR_CODE_TO_KEY`) the backend would have
14
+ * returned for the same input, with the same `{feature}`/`{field}` params. So
15
+ * a "too long" caught locally and a "too long" caught by the server render
16
+ * the SAME sentence in the SAME language. The alternative — pair-invented
17
+ * copy for the local half — gives the person two different wordings for one
18
+ * problem and makes the local one a lie the moment the backend's rule moves.
19
+ *
20
+ * ── camelCase, and the cap that does not exist ─────────────────────────────
21
+ *
22
+ * Config keys are camelCase (`maxLength`, not `max_length`) — backend delta
23
+ * note 1. A type's dataclass parser drops keys it does not know, so a
24
+ * snake_case typo is a constraint that silently does not exist. `publish`
25
+ * refuses those server-side; this mirror simply reads the camelCase names and
26
+ * therefore agrees with the engine by construction.
27
+ */
28
+ import type { FlowError } from "@stapel/core";
29
+ import type { FormFieldDef } from "../api/types.js";
30
+ /** The `error.400.feature_*` keys this mirror can raise — a subset of
31
+ * `stapel_attributes.errors.ATTRIBUTES_ERRORS`, listed so the i18n bundle can
32
+ * be checked against it. */
33
+ export declare const FEATURE_ERROR_CODES: readonly string[];
34
+ /** The declared choice values of an options-bearing config, or `undefined`
35
+ * when the kind has none. Accepts both the bare-scalar and the
36
+ * `{value, label}` option shapes the attributes types allow. */
37
+ export declare function optionValues(field: FormFieldDef): readonly unknown[] | undefined;
38
+ /** True when the answer is "nothing was entered" — the one shape the
39
+ * mandatory rule fires on. An explicit `false` is an ANSWER for a `bool`, not
40
+ * an absence, which is why this is not a falsiness check. */
41
+ export declare function isBlank(value: unknown): boolean;
42
+ /**
43
+ * Validate one answer against its field's config. Returns the mirrored
44
+ * refusal, or `undefined` when this side of the wire is satisfied.
45
+ *
46
+ * `header` fields are skipped outright: the engine regenerates a header's DAO
47
+ * from its config and REJECTS an answer to one (backend delta note 1), so a
48
+ * header has nothing to validate and must never be given a value.
49
+ */
50
+ export declare function validateFieldValue(field: FormFieldDef, value: unknown): FlowError | undefined;
51
+ /**
52
+ * Validate a whole answer set. Returns `{slug: FlowError}` for the fields that
53
+ * failed — the exact shape `FormFillBag.fieldErrors` carries, so a server
54
+ * response can be merged into it without a translation step.
55
+ */
56
+ export declare function validateAnswers(fields: readonly FormFieldDef[], values: Readonly<Record<string, unknown>>): Readonly<Record<string, FlowError>>;
57
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/widgets/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;4BAE4B;AAC5B,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAOhD,CAAC;AA8BF;;gEAEgE;AAChE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,GAClB,SAAS,OAAO,EAAE,GAAG,SAAS,CAQhC;AAED;;6DAE6D;AAC7D,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK/C;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,OAAO,GACb,SAAS,GAAG,SAAS,CAoGvB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,YAAY,EAAE,EAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAOrC"}