@chelone/core 0.5.0 → 0.7.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 (253) hide show
  1. package/dist/assistant/assistant-session.d.ts +28 -0
  2. package/dist/assistant/assistant-session.js +208 -0
  3. package/dist/assistant/echo-transport.d.ts +7 -0
  4. package/dist/assistant/echo-transport.js +28 -0
  5. package/dist/assistant/http-transport.d.ts +10 -0
  6. package/dist/assistant/http-transport.js +98 -0
  7. package/dist/assistant/types.d.ts +54 -0
  8. package/dist/auth/access-control.d.ts +13 -5
  9. package/dist/auth/access-control.js +25 -21
  10. package/dist/auth/auth-options.d.ts +21 -17
  11. package/dist/auth/auth-options.js +80 -48
  12. package/dist/auth/permission-matrix.d.ts +12 -0
  13. package/dist/auth/permission-matrix.js +67 -0
  14. package/dist/auth/types.d.ts +14 -4
  15. package/dist/block-editor/block-document.d.ts +60 -0
  16. package/dist/block-editor/block-document.js +143 -0
  17. package/dist/block-editor/data-blocks.d.ts +63 -0
  18. package/dist/block-editor/data-blocks.js +117 -0
  19. package/dist/chart/chart-data.d.ts +25 -0
  20. package/dist/chart/chart-data.js +61 -0
  21. package/dist/command-menu/hotkey.js +58 -47
  22. package/dist/command-menu/recents.js +8 -11
  23. package/dist/command-menu/registry.d.ts +9 -5
  24. package/dist/command-menu/registry.js +36 -46
  25. package/dist/controller/calendar-dates.js +82 -113
  26. package/dist/controller/calendar-events.js +93 -88
  27. package/dist/controller/client-list-controller.d.ts +2 -2
  28. package/dist/controller/client-list-controller.js +251 -254
  29. package/dist/controller/data-table-columns.d.ts +2 -0
  30. package/dist/controller/data-table-columns.js +50 -56
  31. package/dist/controller/deep-equal.d.ts +1 -0
  32. package/dist/controller/deep-equal.js +14 -0
  33. package/dist/controller/filter-toggle.js +36 -44
  34. package/dist/controller/in-place-editor.d.ts +42 -0
  35. package/dist/controller/in-place-editor.js +42 -0
  36. package/dist/controller/infinite-list-controller.js +220 -190
  37. package/dist/controller/inspector.d.ts +49 -0
  38. package/dist/controller/inspector.js +64 -0
  39. package/dist/controller/job-monitor.d.ts +41 -0
  40. package/dist/controller/job-monitor.js +49 -0
  41. package/dist/controller/kanban-board.d.ts +2 -3
  42. package/dist/controller/kanban-board.js +43 -24
  43. package/dist/controller/list-controller.d.ts +5 -5
  44. package/dist/controller/list-controller.js +444 -400
  45. package/dist/controller/list-params.js +96 -87
  46. package/dist/controller/pivot-grid.d.ts +36 -0
  47. package/dist/controller/pivot-grid.js +99 -0
  48. package/dist/controller/prev-next.d.ts +2 -2
  49. package/dist/controller/prev-next.js +74 -54
  50. package/dist/controller/record-controllers.d.ts +14 -6
  51. package/dist/controller/record-controllers.js +288 -257
  52. package/dist/controller/record-diff.d.ts +21 -0
  53. package/dist/controller/record-diff.js +50 -0
  54. package/dist/controller/record-timeline.d.ts +35 -0
  55. package/dist/controller/record-timeline.js +48 -0
  56. package/dist/controller/record-tree.js +57 -47
  57. package/dist/controller/record-workflow.d.ts +38 -16
  58. package/dist/controller/record-workflow.js +85 -64
  59. package/dist/controller/reference-many-controller.js +198 -192
  60. package/dist/controller/reference-one-controller.d.ts +1 -1
  61. package/dist/controller/reference-one-controller.js +63 -56
  62. package/dist/controller/saved-queries.js +104 -91
  63. package/dist/controller/translate-fallback.js +10 -6
  64. package/dist/csv/export.d.ts +2 -1
  65. package/dist/csv/export.js +43 -40
  66. package/dist/csv/import-controller.js +185 -169
  67. package/dist/csv/match-headers.js +29 -22
  68. package/dist/csv/parse-with.js +60 -54
  69. package/dist/csv/run-import.js +69 -72
  70. package/dist/csv/validate-rows.d.ts +1 -1
  71. package/dist/csv/validate-rows.js +65 -52
  72. package/dist/data/cache-ops.d.ts +5 -4
  73. package/dist/data/cache-ops.js +208 -190
  74. package/dist/data/combine-data-providers.js +25 -23
  75. package/dist/data/data-provider-log.d.ts +45 -0
  76. package/dist/data/data-provider-log.js +93 -0
  77. package/dist/data/errors.js +14 -10
  78. package/dist/data/fake-provider.d.ts +6 -3
  79. package/dist/data/fake-provider.js +129 -114
  80. package/dist/data/fetch-utils.js +61 -44
  81. package/dist/data/filter-branch.d.ts +5 -0
  82. package/dist/data/filter-branch.js +10 -0
  83. package/dist/data/get-many-aggregate.js +84 -76
  84. package/dist/data/http-error.js +17 -12
  85. package/dist/data/keys.js +50 -36
  86. package/dist/data/mutation-options.d.ts +14 -9
  87. package/dist/data/mutation-options.js +93 -109
  88. package/dist/data/query-options.js +73 -68
  89. package/dist/data/remove-empty.d.ts +3 -0
  90. package/dist/data/remove-empty.js +11 -0
  91. package/dist/data/undoable.d.ts +2 -2
  92. package/dist/data/undoable.js +190 -174
  93. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  94. package/dist/data/with-lifecycle-callbacks.js +66 -52
  95. package/dist/environment/application-update-store.js +58 -55
  96. package/dist/environment/offline-store.js +21 -21
  97. package/dist/form/array-binding.d.ts +30 -11
  98. package/dist/form/array-binding.js +184 -166
  99. package/dist/form/choice-ids.d.ts +7 -0
  100. package/dist/form/choice-ids.js +11 -0
  101. package/dist/form/choices-controller.d.ts +3 -2
  102. package/dist/form/choices-controller.js +302 -275
  103. package/dist/form/date-format.d.ts +38 -0
  104. package/dist/form/date-format.js +89 -0
  105. package/dist/form/dirty-values.d.ts +2 -2
  106. package/dist/form/dirty-values.js +22 -11
  107. package/dist/form/duration.d.ts +22 -0
  108. package/dist/form/duration.js +49 -0
  109. package/dist/form/edit-create-form.d.ts +12 -5
  110. package/dist/form/edit-create-form.js +243 -188
  111. package/dist/form/event-value.d.ts +6 -0
  112. package/dist/form/event-value.js +17 -0
  113. package/dist/form/field-error.d.ts +14 -0
  114. package/dist/form/field-error.js +28 -0
  115. package/dist/form/field-name.d.ts +20 -0
  116. package/dist/form/field-name.js +6 -0
  117. package/dist/form/filter-form-values.js +63 -49
  118. package/dist/form/form-controller.d.ts +9 -1
  119. package/dist/form/form-controller.js +165 -160
  120. package/dist/form/form-groups.d.ts +4 -3
  121. package/dist/form/form-groups.js +79 -79
  122. package/dist/form/form-handle.d.ts +175 -0
  123. package/dist/form/form-handle.js +68 -0
  124. package/dist/form/input-binding.d.ts +56 -31
  125. package/dist/form/input-binding.js +169 -118
  126. package/dist/form/input-values.d.ts +130 -0
  127. package/dist/form/input-values.js +177 -0
  128. package/dist/form/json-text.js +51 -37
  129. package/dist/form/save-pipeline.d.ts +6 -8
  130. package/dist/form/save-pipeline.js +91 -82
  131. package/dist/form/set-form-values.d.ts +6 -0
  132. package/dist/form/set-form-values.js +10 -0
  133. package/dist/form/source.js +97 -65
  134. package/dist/form/text-of.d.ts +2 -0
  135. package/dist/form/text-of.js +7 -0
  136. package/dist/form/validators.d.ts +58 -19
  137. package/dist/form/validators.js +97 -71
  138. package/dist/geo/bbox.d.ts +5 -4
  139. package/dist/geo/bbox.js +69 -48
  140. package/dist/geo/bounds.d.ts +14 -3
  141. package/dist/geo/bounds.js +98 -68
  142. package/dist/geo/constraints.js +88 -93
  143. package/dist/geo/coordinates.js +31 -23
  144. package/dist/geo/editor.d.ts +13 -13
  145. package/dist/geo/editor.js +176 -153
  146. package/dist/geo/geocoding.d.ts +4 -4
  147. package/dist/geo/geocoding.js +38 -41
  148. package/dist/geo/geojson.d.ts +12 -74
  149. package/dist/geo/geojson.js +52 -40
  150. package/dist/geo/geometry-math.d.ts +4 -4
  151. package/dist/geo/geometry-math.js +100 -93
  152. package/dist/geo/index.d.ts +7 -6
  153. package/dist/geo/map-layers.d.ts +42 -7
  154. package/dist/geo/map-layers.js +88 -62
  155. package/dist/geo/map-style.js +31 -32
  156. package/dist/geo/messages.js +48 -45
  157. package/dist/geo/nominatim.js +88 -80
  158. package/dist/geo/osm-presets.d.ts +4 -3
  159. package/dist/geo/osm-presets.js +108 -86
  160. package/dist/geo/osm-tags.js +14 -12
  161. package/dist/geo/overpass.d.ts +14 -2
  162. package/dist/geo/overpass.js +83 -90
  163. package/dist/geo/shape-value.d.ts +9 -5
  164. package/dist/geo/shape-value.js +169 -146
  165. package/dist/geo/snap-to-roads.d.ts +3 -3
  166. package/dist/geo/snap-to-roads.js +55 -50
  167. package/dist/geo/throttle.js +36 -39
  168. package/dist/guards.d.ts +37 -0
  169. package/dist/guards.js +62 -0
  170. package/dist/i18n/default-messages.js +34 -29
  171. package/dist/i18n/english-messages.js +187 -189
  172. package/dist/i18n/i18n.d.ts +8 -2
  173. package/dist/i18n/i18n.js +47 -48
  174. package/dist/i18n/inflection.d.ts +34 -0
  175. package/dist/i18n/inflection.js +82 -0
  176. package/dist/i18n/messages.d.ts +10 -2
  177. package/dist/i18n/messages.js +17 -4
  178. package/dist/i18n/missing-keys.d.ts +19 -0
  179. package/dist/i18n/missing-keys.js +68 -0
  180. package/dist/i18n/translatable.d.ts +16 -1
  181. package/dist/i18n/translatable.js +40 -47
  182. package/dist/index.d.ts +96 -22
  183. package/dist/index.js +52 -257
  184. package/dist/json/deep-equal.d.ts +8 -0
  185. package/dist/json/deep-equal.js +24 -0
  186. package/dist/json/strip-json-comments.js +38 -42
  187. package/dist/noop.d.ts +2 -0
  188. package/dist/noop.js +4 -0
  189. package/dist/notification/error-message.d.ts +2 -0
  190. package/dist/notification/error-message.js +10 -0
  191. package/dist/notification/notification-store.d.ts +3 -2
  192. package/dist/notification/notification-store.js +41 -45
  193. package/dist/onboarding-tour/placement.d.ts +34 -0
  194. package/dist/onboarding-tour/placement.js +67 -0
  195. package/dist/onboarding-tour/tour-controller.d.ts +25 -0
  196. package/dist/onboarding-tour/tour-controller.js +50 -0
  197. package/dist/realtime/add-events-for-mutations.js +75 -81
  198. package/dist/realtime/announce-lock.d.ts +7 -0
  199. package/dist/realtime/announce-lock.js +18 -0
  200. package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
  201. package/dist/realtime/broadcast-channel-transport.js +69 -0
  202. package/dist/realtime/copy-members.js +22 -14
  203. package/dist/realtime/in-memory-lock-provider.js +41 -60
  204. package/dist/realtime/live.js +95 -101
  205. package/dist/realtime/lock-options.js +16 -16
  206. package/dist/realtime/lock-session.js +91 -78
  207. package/dist/realtime/memory-transport.js +26 -67
  208. package/dist/realtime/presence.js +120 -122
  209. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  210. package/dist/realtime/realtime-data-provider.js +45 -42
  211. package/dist/realtime/reconnect-backoff.d.ts +15 -0
  212. package/dist/realtime/reconnect-backoff.js +17 -0
  213. package/dist/realtime/sse-transport.d.ts +31 -0
  214. package/dist/realtime/sse-transport.js +235 -0
  215. package/dist/realtime/status-store.js +23 -28
  216. package/dist/realtime/topic-hub.d.ts +24 -0
  217. package/dist/realtime/topic-hub.js +66 -0
  218. package/dist/realtime/topics.js +10 -12
  219. package/dist/realtime/types.js +10 -12
  220. package/dist/realtime/unread-count.js +21 -21
  221. package/dist/realtime/websocket-transport.d.ts +32 -0
  222. package/dist/realtime/websocket-transport.js +306 -0
  223. package/dist/resource/describe-resource.d.ts +2 -0
  224. package/dist/resource/describe-resource.js +232 -219
  225. package/dist/resource/get-at-path.js +29 -14
  226. package/dist/resource/identifier.d.ts +18 -0
  227. package/dist/resource/identifier.js +27 -0
  228. package/dist/resource/record.d.ts +4 -0
  229. package/dist/resource/record.js +10 -0
  230. package/dist/resource/register.d.ts +7 -0
  231. package/dist/resource/registry-store.d.ts +45 -15
  232. package/dist/resource/registry-store.js +67 -60
  233. package/dist/resource/registry.d.ts +10 -0
  234. package/dist/resource/registry.js +8 -4
  235. package/dist/resource/source-path.d.ts +16 -1
  236. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  237. package/dist/rich-text/rich-text-extras.js +179 -0
  238. package/dist/rich-text/rich-text-image.d.ts +124 -0
  239. package/dist/rich-text/rich-text-image.js +163 -0
  240. package/dist/rich-text/rich-text.d.ts +62 -0
  241. package/dist/rich-text/rich-text.js +142 -0
  242. package/dist/router/location.d.ts +22 -4
  243. package/dist/router/location.js +41 -18
  244. package/dist/router/match.js +47 -39
  245. package/dist/router/paths.js +40 -34
  246. package/dist/router/record-from-location.d.ts +1 -1
  247. package/dist/router/record-from-location.js +25 -17
  248. package/dist/store/adapters.js +51 -55
  249. package/dist/store/micro-store.js +57 -59
  250. package/dist/store/shallow-equal.d.ts +12 -0
  251. package/dist/store/shallow-equal.js +20 -0
  252. package/dist/store/store.js +54 -57
  253. package/package.json +13 -11
@@ -1,47 +1,61 @@
1
+ // src/form/json-text.ts
1
2
  function detectJsonShape(value) {
2
- if (value === void 0) return null;
3
- return typeof value === "string" ? "string" : "object";
3
+ if (value === void 0) return null;
4
+ return typeof value === "string" ? "string" : "object";
4
5
  }
5
6
  function toJsonText(value, shape, indent = 2) {
6
- if (shape === "string") return typeof value === "string" ? value : "";
7
- if (value === void 0) return "";
8
- try {
9
- return JSON.stringify(value, null, indent) ?? "";
10
- } catch {
11
- return "";
12
- }
7
+ if (shape === "string") return typeof value === "string" ? value : "";
8
+ if (value === void 0) return "";
9
+ try {
10
+ return JSON.stringify(value, null, indent) ?? "";
11
+ } catch {
12
+ return "";
13
+ }
13
14
  }
14
15
  function fromJsonText(text, shape) {
15
- if (shape === "string") return { value: text, parseError: null };
16
- if (text.trim() === "") return { value: null, parseError: null };
17
- try {
18
- return { value: JSON.parse(text), parseError: null };
19
- } catch (error) {
20
- return {
21
- value: void 0,
22
- parseError: error instanceof Error ? error : new Error(String(error))
23
- };
24
- }
16
+ if (shape === "string") return {
17
+ value: text,
18
+ parseError: null
19
+ };
20
+ if (text.trim() === "") return {
21
+ value: null,
22
+ parseError: null
23
+ };
24
+ try {
25
+ return {
26
+ value: JSON.parse(text),
27
+ parseError: null
28
+ };
29
+ } catch (error) {
30
+ return {
31
+ value: void 0,
32
+ parseError: error instanceof Error ? error : new Error(String(error))
33
+ };
34
+ }
25
35
  }
26
36
  function formatJsonValue(value, indent = 2) {
27
- try {
28
- const parsed = typeof value === "string" ? parseOrKeep(value) : { value, ok: true };
29
- if (!parsed.ok) return value;
30
- return JSON.stringify(parsed.value, null, indent) ?? String(value);
31
- } catch {
32
- return String(value);
33
- }
37
+ try {
38
+ if (typeof value === "string") {
39
+ const parsed = parseOrKeep(value);
40
+ if (!parsed.ok) return value;
41
+ return JSON.stringify(parsed.value, null, indent) ?? value;
42
+ }
43
+ return JSON.stringify(value, null, indent) ?? String(value);
44
+ } catch {
45
+ return String(value);
46
+ }
34
47
  }
35
48
  function parseOrKeep(text) {
36
- try {
37
- return { value: JSON.parse(text), ok: true };
38
- } catch {
39
- return { value: text, ok: false };
40
- }
49
+ try {
50
+ return {
51
+ value: JSON.parse(text),
52
+ ok: true
53
+ };
54
+ } catch {
55
+ return {
56
+ value: text,
57
+ ok: false
58
+ };
59
+ }
41
60
  }
42
- export {
43
- detectJsonShape,
44
- formatJsonValue,
45
- fromJsonText,
46
- toJsonText
47
- };
61
+ export { detectJsonShape, formatJsonValue, fromJsonText, toJsonText };
@@ -1,4 +1,4 @@
1
- import { type StandardSchemaV1 } from "@tanstack/form-core";
1
+ import { type FormValidateFn, type FormValidateOrFn, type StandardSchemaV1 } from "@tanstack/form-core";
2
2
  import type { FormLevelValidate } from "./form-controller.js";
3
3
  /**
4
4
  * ra parity (exact semantics, TOP-LEVEL keys only): `null`, `''` and
@@ -10,7 +10,7 @@ import type { FormLevelValidate } from "./form-controller.js";
10
10
  * narrower than `TValues` (ra types it the same way; the DataProvider
11
11
  * receives partials by contract).
12
12
  */
13
- export declare function sanitizeEmptyValues<TValues extends object>(values: TValues, record?: object): TValues;
13
+ export declare function sanitizeEmptyValues<TValues>(values: TValues, record?: object): TValues;
14
14
  /**
15
15
  * form-core's "global form validation error" shape: `form` is the form-level
16
16
  * error, `fields` maps field paths (dot/bracket, `items[0].sku`) to errors.
@@ -27,8 +27,6 @@ export interface FormErrorMapValue {
27
27
  * Nullish entries are dropped; returns `undefined` when nothing remains.
28
28
  */
29
29
  export declare function toFormErrorMapValue(errors: Record<string, unknown> | null | undefined): FormErrorMapValue | undefined;
30
- /** Thenable detection — how the pipeline tells async validate fns from sync ones. */
31
- export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
32
30
  export interface ComposeFormValidatorConfig<TFormData> {
33
31
  /** Resource schema (zod or any Standard Schema) — the D6 default validator. */
34
32
  schema?: StandardSchemaV1<TFormData, unknown>;
@@ -38,8 +36,10 @@ export interface ComposeFormValidatorConfig<TFormData> {
38
36
  * The user's own `formOptions.validators.onDynamic` escape hatch (fn or
39
37
  * Standard Schema) — composed after the schema and validate fn.
40
38
  */
41
- userValidator?: unknown;
39
+ userValidator?: FormValidateOrFn<TFormData>;
42
40
  }
41
+ /** What form-core hands a form-level validator fn: `value` and `formApi`. */
42
+ export type FormValidatorProps<TFormData> = Parameters<FormValidateFn<TFormData>>[0];
43
43
  /**
44
44
  * Composes the D6 validator stack into ONE sync `onDynamic` validator:
45
45
  * schema -> validate fn (sync results only) -> user onDynamic. Field errors
@@ -57,6 +57,4 @@ export interface ComposeFormValidatorConfig<TFormData> {
57
57
  *
58
58
  * Returns `undefined` when there is nothing to compose.
59
59
  */
60
- export declare function composeFormValidator<TFormData>(config: ComposeFormValidatorConfig<TFormData>): ((props: {
61
- value: TFormData;
62
- }) => unknown) | undefined;
60
+ export declare function composeFormValidator<TFormData>(config: ComposeFormValidatorConfig<TFormData>): ((props: FormValidatorProps<TFormData>) => FormErrorMapValue | undefined) | undefined;
@@ -1,88 +1,97 @@
1
- import { standardSchemaValidators, isStandardSchemaValidator } from "@tanstack/form-core";
1
+ import { isRecord, isThenable } from "../guards.js";
2
+ import { isStandardSchemaValidator, standardSchemaValidators } from "@tanstack/form-core";
3
+ // src/form/save-pipeline.ts
4
+ /**
5
+ * ra parity (exact semantics, TOP-LEVEL keys only): `null`, `''` and
6
+ * `undefined` values become `null` when the key exists on `record`, and are
7
+ * OMITTED otherwise; `0`, `false`, empty arrays and empty objects are NOT
8
+ * empty and pass through. Always returns a fresh object.
9
+ *
10
+ * NOTE the return type is nominal — omitted keys make the runtime value
11
+ * narrower than `TValues` (ra types it the same way; the DataProvider
12
+ * receives partials by contract).
13
+ */
2
14
  function sanitizeEmptyValues(values, record) {
3
- const result = {};
4
- for (const [key, value] of Object.entries(values)) {
5
- if (value === void 0 || value === null || value === "") {
6
- if (record && key in record) result[key] = null;
7
- } else {
8
- result[key] = value;
9
- }
10
- }
11
- return result;
15
+ if (typeof values !== "object" || values === null) return values;
16
+ const result = {};
17
+ for (const [key, value] of Object.entries(values)) if (value === void 0 || value === null || value === "") {
18
+ if (record && key in record) result[key] = null;
19
+ } else result[key] = value;
20
+ return result;
12
21
  }
22
+ /**
23
+ * Converts an ra-shaped error record (field path -> error, `root` reserved
24
+ * for the form-level message) — the form-level validate fn contract AND the
25
+ * P4 save `FieldErrors` contract — into form-core's global error-map value.
26
+ * Nullish entries are dropped; returns `undefined` when nothing remains.
27
+ */
13
28
  function toFormErrorMapValue(errors) {
14
- if (errors == null) return void 0;
15
- const { root, ...rest } = errors;
16
- const fields = {};
17
- for (const [key, value] of Object.entries(rest)) {
18
- if (value != null) fields[key] = value;
19
- }
20
- if (root == null && Object.keys(fields).length === 0) return void 0;
21
- return { form: root ?? void 0, fields };
22
- }
23
- function isThenable(value) {
24
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
25
- }
26
- function isRecordObject(value) {
27
- return typeof value === "object" && value !== null && !Array.isArray(value);
29
+ if (errors == null) return void 0;
30
+ const { root, ...rest } = errors;
31
+ const fields = {};
32
+ for (const [key, value] of Object.entries(rest)) if (value != null) fields[key] = value;
33
+ if (root == null && Object.keys(fields).length === 0) return void 0;
34
+ return {
35
+ form: root ?? void 0,
36
+ fields
37
+ };
28
38
  }
39
+ /**
40
+ * Composes the D6 validator stack into ONE sync `onDynamic` validator:
41
+ * schema -> validate fn (sync results only) -> user onDynamic. Field errors
42
+ * merge FIRST-WINS per path (ra's composeValidators short-circuit parity);
43
+ * the form-level slot also keeps the first non-nullish value.
44
+ *
45
+ * The composed validator runs on the SYNC slot deliberately: async
46
+ * form-level validators do not block a field-less `handleSubmit` (probed on
47
+ * form-core 1.33), so vanilla submit-only forms would save invalid data.
48
+ * An async validate fn is detected on its first call, skipped here from then
49
+ * on, and re-run by the submit pipeline instead (on-submit semantics). The
50
+ * pipeline absorbs sync AND async results from the latched fn — ra's
51
+ * contract legally mixes sync fast-path errors with async server checks, and
52
+ * a latched fn's later sync error can only surface through the pipeline.
53
+ *
54
+ * Returns `undefined` when there is nothing to compose.
55
+ */
29
56
  function composeFormValidator(config) {
30
- const { schema, validate, userValidator } = config;
31
- if (!schema && !validate && !userValidator) return void 0;
32
- let validateIsAsync = false;
33
- return (props) => {
34
- let formError;
35
- const fields = {};
36
- const absorb = (result) => {
37
- if (result == null) return;
38
- if (isRecordObject(result) && "fields" in result) {
39
- const global = result;
40
- if (formError === void 0 && global.form != null) {
41
- formError = global.form;
42
- }
43
- if (isRecordObject(global.fields)) {
44
- for (const [key, error] of Object.entries(global.fields)) {
45
- if (error != null && !(key in fields)) fields[key] = error;
46
- }
47
- }
48
- return;
49
- }
50
- if (formError === void 0) formError = result;
51
- };
52
- if (schema) {
53
- absorb(
54
- standardSchemaValidators.validate(
55
- { value: props.value, validationSource: "form" },
56
- schema
57
- )
58
- );
59
- }
60
- if (validate && !validateIsAsync) {
61
- const result = validate(props.value);
62
- if (isThenable(result)) {
63
- validateIsAsync = true;
64
- void Promise.resolve(result).catch(() => void 0);
65
- } else {
66
- absorb(toFormErrorMapValue(result));
67
- }
68
- }
69
- if (userValidator) {
70
- absorb(
71
- isStandardSchemaValidator(userValidator) ? standardSchemaValidators.validate(
72
- { value: props.value, validationSource: "form" },
73
- userValidator
74
- ) : userValidator(props)
75
- );
76
- }
77
- if (formError === void 0 && Object.keys(fields).length === 0) {
78
- return void 0;
79
- }
80
- return { form: formError, fields };
81
- };
57
+ const { schema, validate, userValidator } = config;
58
+ if (!schema && !validate && !userValidator) return void 0;
59
+ /** Latched on the first thenable result — async fns never run in the sync slot again. */
60
+ let validateIsAsync = false;
61
+ return (props) => {
62
+ let formError;
63
+ const fields = {};
64
+ const absorb = (result) => {
65
+ if (result == null) return;
66
+ if (isRecord(result) && "fields" in result) {
67
+ if (formError === void 0 && result.form != null) formError = result.form;
68
+ if (isRecord(result.fields)) {
69
+ for (const [key, error] of Object.entries(result.fields)) if (error != null && !(key in fields)) fields[key] = error;
70
+ }
71
+ return;
72
+ }
73
+ if (formError === void 0) formError = result;
74
+ };
75
+ if (schema) absorb(standardSchemaValidators.validate({
76
+ value: props.value,
77
+ validationSource: "form"
78
+ }, schema));
79
+ if (validate && !validateIsAsync) {
80
+ const result = validate(props.value);
81
+ if (isThenable(result)) {
82
+ validateIsAsync = true;
83
+ Promise.resolve(result).catch(() => void 0);
84
+ } else absorb(toFormErrorMapValue(result));
85
+ }
86
+ if (userValidator) absorb(isStandardSchemaValidator(userValidator) ? standardSchemaValidators.validate({
87
+ value: props.value,
88
+ validationSource: "form"
89
+ }, userValidator) : userValidator(props));
90
+ if (formError === void 0 && Object.keys(fields).length === 0) return;
91
+ return {
92
+ form: formError,
93
+ fields
94
+ };
95
+ };
82
96
  }
83
- export {
84
- composeFormValidator,
85
- isThenable,
86
- sanitizeEmptyValues,
87
- toFormErrorMapValue
88
- };
97
+ export { composeFormValidator, sanitizeEmptyValues, toFormErrorMapValue };
@@ -0,0 +1,6 @@
1
+ import type { CheloneFormApi } from "./form-controller.js";
2
+ /**
3
+ * Writes each key of `data` into the form, so a programmatic save sees the
4
+ * values it was handed. A key not in `data` keeps its value.
5
+ */
6
+ export declare function setFormValues<TValues>(form: CheloneFormApi<TValues>, data: Partial<NoInfer<TValues>>): void;
@@ -0,0 +1,10 @@
1
+ import { toFieldName } from "./field-name.js";
2
+ // src/form/set-form-values.ts
3
+ /**
4
+ * Writes each key of `data` into the form, so a programmatic save sees the
5
+ * values it was handed. A key not in `data` keeps its value.
6
+ */
7
+ function setFormValues(form, data) {
8
+ for (const [key, value] of Object.entries(data)) form.setFieldValue(toFieldName(key), value);
9
+ }
10
+ export { setFormValues };
@@ -1,76 +1,108 @@
1
+ // src/form/source.ts
2
+ /**
3
+ * Joins two path fragments: bracket fragments (`[0]`, `[0].sku`) concatenate
4
+ * directly (form-core index syntax), everything else joins with a dot.
5
+ */
1
6
  function composeSource(parent, child) {
2
- if (!child) return parent;
3
- if (!parent) return child;
4
- return child.startsWith("[") ? `${parent}${child}` : `${parent}.${child}`;
7
+ if (!child) return parent;
8
+ if (!parent) return child;
9
+ return child.startsWith("[") ? `${parent}${child}` : `${parent}.${child}`;
5
10
  }
11
+ /**
12
+ * Rewrite a dot-spelled array index as form-core's bracket spelling:
13
+ * `tags.0.label` -> `tags[0].label`. Without it an input written with a dot
14
+ * index binds a field named `tags.0.label`, which no validator, error or
15
+ * field group matches, while the same path read as data (a `DataTable.Col`)
16
+ * works — form-core's `getBy`/`setBy` accept both spellings, so only the
17
+ * FIELD NAME differs.
18
+ *
19
+ * Only a digits-only member that FOLLOWS another member is an index: a
20
+ * leading numeral (`0.a`) is a field name, and bracket spellings, non-numeric
21
+ * members and the empty string pass through untouched. Labels never go
22
+ * through this — translation keys keep the spelling as written.
23
+ */
6
24
  function normalizeSource(source) {
7
- let out = "";
8
- let depth = 0;
9
- let afterMember = false;
10
- for (let i = 0; i < source.length; i++) {
11
- const char = source[i];
12
- if (char === "[") depth++;
13
- if (char === "]") depth--;
14
- if (char === "." && depth === 0 && afterMember) {
15
- let end = i + 1;
16
- while (end < source.length && source[end] !== "." && source[end] !== "[") {
17
- end++;
18
- }
19
- const member = source.slice(i + 1, end);
20
- if (/^\d+$/.test(member)) {
21
- out += `[${member}]`;
22
- i = end - 1;
23
- continue;
24
- }
25
- }
26
- out += char;
27
- if (char !== ".") afterMember = true;
28
- }
29
- return out;
25
+ let out = "";
26
+ /** Bracket depth: inside a group the spelling is already form-core's. */
27
+ let depth = 0;
28
+ /** True once a member has been emitted — a dot before the next one is a join. */
29
+ let afterMember = false;
30
+ for (let i = 0; i < source.length; i++) {
31
+ const char = source[i];
32
+ if (char === "[") depth++;
33
+ if (char === "]") depth--;
34
+ if (char === "." && depth === 0 && afterMember) {
35
+ let end = i + 1;
36
+ while (end < source.length && source[end] !== "." && source[end] !== "[") end++;
37
+ const member = source.slice(i + 1, end);
38
+ if (/^\d+$/.test(member)) {
39
+ out += `[${member}]`;
40
+ i = end - 1;
41
+ continue;
42
+ }
43
+ }
44
+ out += char;
45
+ if (char !== ".") afterMember = true;
46
+ }
47
+ return out;
30
48
  }
31
- const rootSourceScope = {
32
- getSource: (source) => normalizeSource(source),
33
- getLabel: (source) => source
49
+ /**
50
+ * What a top-level form provides: the identity scope for everything but a
51
+ * dot-spelled index, which it rewrites to form-core's bracket spelling.
52
+ */
53
+ var rootSourceScope = {
54
+ getSource: (source) => normalizeSource(source),
55
+ getLabel: (source) => source
34
56
  };
57
+ /**
58
+ * The scope an array input provides to its iterator: sources nest under the
59
+ * array's own (scoped) path, labels under its index-free label path.
60
+ *
61
+ * `getSource('')` resolves the array field itself — how an iterator finds
62
+ * the path it pushes/removes on.
63
+ */
35
64
  function createArraySourceScope(parent, arraySource) {
36
- const arrayPath = parent.getSource(arraySource);
37
- return {
38
- // Normalizing the JOIN, not just the child: a child of `0` composes to
39
- // `tags.0`, where the index only follows a member once the array is there.
40
- // Row and locale scopes need neither — they hand the whole path to a
41
- // parent that normalizes it.
42
- getSource: (source) => normalizeSource(composeSource(arrayPath, source)),
43
- getLabel: (source) => parent.getLabel(composeSource(arraySource, source))
44
- };
65
+ const arrayPath = parent.getSource(arraySource);
66
+ return {
67
+ getSource: (source) => normalizeSource(composeSource(arrayPath, source)),
68
+ getLabel: (source) => parent.getLabel(composeSource(arraySource, source))
69
+ };
45
70
  }
71
+ /**
72
+ * The scope an iterator row provides to its children: sources gain the row's
73
+ * `[index]` fragment; labels pass through UNCHANGED so translation keys stay
74
+ * index-free (`orders.date`, not `orders.0.date`).
75
+ *
76
+ * Scalar rows (`<ArrayInput source="tags">` with a sourceless child) call
77
+ * `getSource('')` and bind the row itself (`tags[2]`).
78
+ */
46
79
  function createRowSourceScope(parent, index) {
47
- return {
48
- getSource: (source) => parent.getSource(source ? `[${index}].${source}` : `[${index}]`),
49
- getLabel: (source) => parent.getLabel(source)
50
- };
80
+ return {
81
+ getSource: (source) => parent.getSource(source ? `[${index}].${source}` : `[${index}]`),
82
+ getLabel: (source) => parent.getLabel(source)
83
+ };
51
84
  }
85
+ /**
86
+ * The scope a translatable group provides for one locale: each child source
87
+ * gains the locale as a trailing member (`title` -> `title.fr`). Labels pass
88
+ * through so every locale shares one translation key.
89
+ *
90
+ * A translatable group has no value of its own, so a sourceless child has
91
+ * nothing to bind and throws.
92
+ */
52
93
  function createLocaleSourceScope(parent, locale) {
53
- const requireSource = (source) => {
54
- if (!source) {
55
- throw new Error("Children of a translatable group must have a source");
56
- }
57
- };
58
- return {
59
- getSource: (source) => {
60
- requireSource(source);
61
- return parent.getSource(`${source}.${locale}`);
62
- },
63
- getLabel: (source) => {
64
- requireSource(source);
65
- return parent.getLabel(source);
66
- }
67
- };
94
+ const requireSource = (source) => {
95
+ if (!source) throw new Error("Children of a translatable group must have a source");
96
+ };
97
+ return {
98
+ getSource: (source) => {
99
+ requireSource(source);
100
+ return parent.getSource(`${source}.${locale}`);
101
+ },
102
+ getLabel: (source) => {
103
+ requireSource(source);
104
+ return parent.getLabel(source);
105
+ }
106
+ };
68
107
  }
69
- export {
70
- composeSource,
71
- createArraySourceScope,
72
- createLocaleSourceScope,
73
- createRowSourceScope,
74
- normalizeSource,
75
- rootSourceScope
76
- };
108
+ export { composeSource, createArraySourceScope, createLocaleSourceScope, createRowSourceScope, normalizeSource, rootSourceScope };
@@ -0,0 +1,2 @@
1
+ /** A form value as text: `""` for `null` and `undefined`, the string itself, `String(value)` for anything else. */
2
+ export declare function textOf(value: unknown): string;
@@ -0,0 +1,7 @@
1
+ // src/form/text-of.ts
2
+ /** A form value as text: `""` for `null` and `undefined`, the string itself, `String(value)` for anything else. */
3
+ function textOf(value) {
4
+ if (value == null) return "";
5
+ return typeof value === "string" ? value : String(value);
6
+ }
7
+ export { textOf };