@fieldia/core 0.27.0 → 0.29.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 (267) hide show
  1. package/README.md +148 -5
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +266 -7
  4. package/src/index.d.mts +10 -2
  5. package/src/index.d.ts +10 -2
  6. package/src/index.js +33 -2
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +13 -2
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/format/approval-checks.d.mts +8 -0
  11. package/src/lib/format/approval-checks.d.ts +8 -0
  12. package/src/lib/format/approval-checks.js +29 -0
  13. package/src/lib/format/approval-checks.js.map +1 -0
  14. package/src/lib/format/approval-checks.mjs +26 -0
  15. package/src/lib/format/approval-checks.mjs.map +1 -0
  16. package/src/lib/format/approval.d.mts +20 -0
  17. package/src/lib/format/approval.d.ts +20 -0
  18. package/src/lib/format/approval.js +25 -0
  19. package/src/lib/format/approval.js.map +1 -0
  20. package/src/lib/format/approval.mjs +22 -0
  21. package/src/lib/format/approval.mjs.map +1 -0
  22. package/src/lib/format/assist-checks.d.mts +14 -0
  23. package/src/lib/format/assist-checks.d.ts +14 -0
  24. package/src/lib/format/assist-checks.js +68 -0
  25. package/src/lib/format/assist-checks.js.map +1 -0
  26. package/src/lib/format/assist-checks.mjs +64 -0
  27. package/src/lib/format/assist-checks.mjs.map +1 -0
  28. package/src/lib/format/assist.d.mts +41 -0
  29. package/src/lib/format/assist.d.ts +41 -0
  30. package/src/lib/format/assist.js +29 -0
  31. package/src/lib/format/assist.js.map +1 -0
  32. package/src/lib/format/assist.mjs +26 -0
  33. package/src/lib/format/assist.mjs.map +1 -0
  34. package/src/lib/format/editable-checks.d.mts +8 -0
  35. package/src/lib/format/editable-checks.d.ts +8 -0
  36. package/src/lib/format/editable-checks.js +38 -0
  37. package/src/lib/format/editable-checks.js.map +1 -0
  38. package/src/lib/format/editable-checks.mjs +35 -0
  39. package/src/lib/format/editable-checks.mjs.map +1 -0
  40. package/src/lib/format/field.d.mts +86 -0
  41. package/src/lib/format/field.d.ts +86 -0
  42. package/src/lib/format/field.js +56 -10
  43. package/src/lib/format/field.js.map +1 -1
  44. package/src/lib/format/field.mjs +55 -9
  45. package/src/lib/format/field.mjs.map +1 -1
  46. package/src/lib/format/languages-checks.d.mts +8 -0
  47. package/src/lib/format/languages-checks.d.ts +8 -0
  48. package/src/lib/format/languages-checks.js +40 -0
  49. package/src/lib/format/languages-checks.js.map +1 -0
  50. package/src/lib/format/languages-checks.mjs +37 -0
  51. package/src/lib/format/languages-checks.mjs.map +1 -0
  52. package/src/lib/format/later-checks.d.mts +4 -3
  53. package/src/lib/format/later-checks.d.ts +4 -3
  54. package/src/lib/format/later-checks.js +4 -3
  55. package/src/lib/format/later-checks.js.map +1 -1
  56. package/src/lib/format/later-checks.mjs +4 -3
  57. package/src/lib/format/later-checks.mjs.map +1 -1
  58. package/src/lib/format/layout.d.mts +123 -28
  59. package/src/lib/format/layout.d.ts +123 -28
  60. package/src/lib/format/layout.js +20 -4
  61. package/src/lib/format/layout.js.map +1 -1
  62. package/src/lib/format/layout.mjs +18 -2
  63. package/src/lib/format/layout.mjs.map +1 -1
  64. package/src/lib/format/layout.types.d.mts +2 -1
  65. package/src/lib/format/layout.types.d.ts +2 -1
  66. package/src/lib/format/list-editable.d.mts +20 -0
  67. package/src/lib/format/list-editable.d.ts +20 -0
  68. package/src/lib/format/list-editable.js +33 -0
  69. package/src/lib/format/list-editable.js.map +1 -0
  70. package/src/lib/format/list-editable.mjs +28 -0
  71. package/src/lib/format/list-editable.mjs.map +1 -0
  72. package/src/lib/format/page.d.mts +91 -9
  73. package/src/lib/format/page.d.ts +91 -9
  74. package/src/lib/format/page.js +4 -0
  75. package/src/lib/format/page.js.map +1 -1
  76. package/src/lib/format/page.mjs +4 -0
  77. package/src/lib/format/page.mjs.map +1 -1
  78. package/src/lib/format/photos-options.d.mts +29 -0
  79. package/src/lib/format/photos-options.d.ts +29 -0
  80. package/src/lib/format/photos-options.js +49 -0
  81. package/src/lib/format/photos-options.js.map +1 -0
  82. package/src/lib/format/photos-options.mjs +45 -0
  83. package/src/lib/format/photos-options.mjs.map +1 -0
  84. package/src/lib/format/references.js +31 -1
  85. package/src/lib/format/references.js.map +1 -1
  86. package/src/lib/format/references.mjs +31 -1
  87. package/src/lib/format/references.mjs.map +1 -1
  88. package/src/lib/format/scan-checks.d.mts +12 -0
  89. package/src/lib/format/scan-checks.d.ts +12 -0
  90. package/src/lib/format/scan-checks.js +89 -0
  91. package/src/lib/format/scan-checks.js.map +1 -0
  92. package/src/lib/format/scan-checks.mjs +86 -0
  93. package/src/lib/format/scan-checks.mjs.map +1 -0
  94. package/src/lib/format/scan-options.d.mts +84 -0
  95. package/src/lib/format/scan-options.d.ts +84 -0
  96. package/src/lib/format/scan-options.js +58 -0
  97. package/src/lib/format/scan-options.js.map +1 -0
  98. package/src/lib/format/scan-options.mjs +54 -0
  99. package/src/lib/format/scan-options.mjs.map +1 -0
  100. package/src/lib/format/upgrade.d.mts +4 -1
  101. package/src/lib/format/upgrade.d.ts +4 -1
  102. package/src/lib/format/upgrade.js +4 -1
  103. package/src/lib/format/upgrade.js.map +1 -1
  104. package/src/lib/format/upgrade.mjs +4 -1
  105. package/src/lib/format/upgrade.mjs.map +1 -1
  106. package/src/lib/format/version.d.mts +2 -2
  107. package/src/lib/format/version.d.ts +2 -2
  108. package/src/lib/format/version.js +2 -2
  109. package/src/lib/format/version.js.map +1 -1
  110. package/src/lib/format/version.mjs +2 -2
  111. package/src/lib/format/version.mjs.map +1 -1
  112. package/src/lib/record/approvals-part.d.mts +6 -0
  113. package/src/lib/record/approvals-part.d.ts +6 -0
  114. package/src/lib/record/approvals-part.js +14 -0
  115. package/src/lib/record/approvals-part.js.map +1 -0
  116. package/src/lib/record/approvals-part.mjs +7 -0
  117. package/src/lib/record/approvals-part.mjs.map +1 -0
  118. package/src/lib/record/approving.d.mts +45 -0
  119. package/src/lib/record/approving.d.ts +45 -0
  120. package/src/lib/record/approving.js +10 -0
  121. package/src/lib/record/approving.js.map +1 -0
  122. package/src/lib/record/approving.mjs +7 -0
  123. package/src/lib/record/approving.mjs.map +1 -0
  124. package/src/lib/record/check.js +7 -1
  125. package/src/lib/record/check.js.map +1 -1
  126. package/src/lib/record/check.mjs +7 -1
  127. package/src/lib/record/check.mjs.map +1 -1
  128. package/src/lib/record/data-source.d.mts +270 -0
  129. package/src/lib/record/data-source.d.ts +270 -0
  130. package/src/lib/record/data-source.js.map +1 -1
  131. package/src/lib/record/data-source.mjs.map +1 -1
  132. package/src/lib/record/day-words.d.mts +10 -0
  133. package/src/lib/record/day-words.d.ts +10 -0
  134. package/src/lib/record/day-words.js +23 -0
  135. package/src/lib/record/day-words.js.map +1 -0
  136. package/src/lib/record/day-words.mjs +20 -0
  137. package/src/lib/record/day-words.mjs.map +1 -0
  138. package/src/lib/record/files-part.d.mts +7 -0
  139. package/src/lib/record/files-part.d.ts +7 -0
  140. package/src/lib/record/files-part.js +167 -0
  141. package/src/lib/record/files-part.js.map +1 -0
  142. package/src/lib/record/files-part.mjs +163 -0
  143. package/src/lib/record/files-part.mjs.map +1 -0
  144. package/src/lib/record/filter.d.mts +5 -0
  145. package/src/lib/record/filter.d.ts +5 -0
  146. package/src/lib/record/filter.js +1 -0
  147. package/src/lib/record/filter.js.map +1 -1
  148. package/src/lib/record/filter.mjs +1 -1
  149. package/src/lib/record/filter.mjs.map +1 -1
  150. package/src/lib/record/form-approvals.d.mts +47 -0
  151. package/src/lib/record/form-approvals.d.ts +47 -0
  152. package/src/lib/record/form-approvals.js +186 -0
  153. package/src/lib/record/form-approvals.js.map +1 -0
  154. package/src/lib/record/form-approvals.mjs +180 -0
  155. package/src/lib/record/form-approvals.mjs.map +1 -0
  156. package/src/lib/record/form-limits.d.mts +5 -8
  157. package/src/lib/record/form-limits.d.ts +5 -8
  158. package/src/lib/record/form-limits.js +20 -21
  159. package/src/lib/record/form-limits.js.map +1 -1
  160. package/src/lib/record/form-limits.mjs +21 -22
  161. package/src/lib/record/form-limits.mjs.map +1 -1
  162. package/src/lib/record/form.d.mts +41 -8
  163. package/src/lib/record/form.d.ts +41 -8
  164. package/src/lib/record/form.js +119 -11
  165. package/src/lib/record/form.js.map +1 -1
  166. package/src/lib/record/form.mjs +119 -11
  167. package/src/lib/record/form.mjs.map +1 -1
  168. package/src/lib/record/languages-part.d.mts +32 -0
  169. package/src/lib/record/languages-part.d.ts +32 -0
  170. package/src/lib/record/languages-part.js +104 -0
  171. package/src/lib/record/languages-part.js.map +1 -0
  172. package/src/lib/record/languages-part.mjs +96 -0
  173. package/src/lib/record/languages-part.mjs.map +1 -0
  174. package/src/lib/record/languages.d.mts +26 -0
  175. package/src/lib/record/languages.d.ts +26 -0
  176. package/src/lib/record/languages.js +20 -0
  177. package/src/lib/record/languages.js.map +1 -0
  178. package/src/lib/record/languages.mjs +15 -0
  179. package/src/lib/record/languages.mjs.map +1 -0
  180. package/src/lib/record/limits.js +4 -2
  181. package/src/lib/record/limits.js.map +1 -1
  182. package/src/lib/record/limits.mjs +4 -2
  183. package/src/lib/record/limits.mjs.map +1 -1
  184. package/src/lib/record/locales/ar.js +6 -0
  185. package/src/lib/record/locales/ar.js.map +1 -1
  186. package/src/lib/record/locales/ar.mjs +6 -0
  187. package/src/lib/record/locales/ar.mjs.map +1 -1
  188. package/src/lib/record/locales/de.js +6 -0
  189. package/src/lib/record/locales/de.js.map +1 -1
  190. package/src/lib/record/locales/de.mjs +6 -0
  191. package/src/lib/record/locales/de.mjs.map +1 -1
  192. package/src/lib/record/locales/fr.js +6 -0
  193. package/src/lib/record/locales/fr.js.map +1 -1
  194. package/src/lib/record/locales/fr.mjs +6 -0
  195. package/src/lib/record/locales/fr.mjs.map +1 -1
  196. package/src/lib/record/memory-approvals.d.mts +47 -0
  197. package/src/lib/record/memory-approvals.d.ts +47 -0
  198. package/src/lib/record/memory-approvals.js +78 -0
  199. package/src/lib/record/memory-approvals.js.map +1 -0
  200. package/src/lib/record/memory-approvals.mjs +75 -0
  201. package/src/lib/record/memory-approvals.mjs.map +1 -0
  202. package/src/lib/record/memory-assist.d.mts +53 -0
  203. package/src/lib/record/memory-assist.d.ts +53 -0
  204. package/src/lib/record/memory-assist.js +519 -0
  205. package/src/lib/record/memory-assist.js.map +1 -0
  206. package/src/lib/record/memory-assist.mjs +513 -0
  207. package/src/lib/record/memory-assist.mjs.map +1 -0
  208. package/src/lib/record/memory-clusters.d.mts +18 -0
  209. package/src/lib/record/memory-clusters.d.ts +18 -0
  210. package/src/lib/record/memory-clusters.js +83 -0
  211. package/src/lib/record/memory-clusters.js.map +1 -0
  212. package/src/lib/record/memory-clusters.mjs +80 -0
  213. package/src/lib/record/memory-clusters.mjs.map +1 -0
  214. package/src/lib/record/memory-data-source.d.mts +43 -3
  215. package/src/lib/record/memory-data-source.d.ts +43 -3
  216. package/src/lib/record/memory-data-source.js +105 -19
  217. package/src/lib/record/memory-data-source.js.map +1 -1
  218. package/src/lib/record/memory-data-source.mjs +105 -19
  219. package/src/lib/record/memory-data-source.mjs.map +1 -1
  220. package/src/lib/record/memory-later.d.mts +1 -1
  221. package/src/lib/record/memory-later.d.ts +1 -1
  222. package/src/lib/record/memory-later.js +4 -1
  223. package/src/lib/record/memory-later.js.map +1 -1
  224. package/src/lib/record/memory-later.mjs +4 -1
  225. package/src/lib/record/memory-later.mjs.map +1 -1
  226. package/src/lib/record/messages.d.mts +24 -2
  227. package/src/lib/record/messages.d.ts +24 -2
  228. package/src/lib/record/messages.js +8 -4
  229. package/src/lib/record/messages.js.map +1 -1
  230. package/src/lib/record/messages.mjs +8 -4
  231. package/src/lib/record/messages.mjs.map +1 -1
  232. package/src/lib/record/option-rules.d.mts +7 -1
  233. package/src/lib/record/option-rules.d.ts +7 -1
  234. package/src/lib/record/option-rules.js +9 -3
  235. package/src/lib/record/option-rules.js.map +1 -1
  236. package/src/lib/record/option-rules.mjs +9 -3
  237. package/src/lib/record/option-rules.mjs.map +1 -1
  238. package/src/lib/record/record-words.d.mts +13 -1
  239. package/src/lib/record/record-words.d.ts +13 -1
  240. package/src/lib/record/record-words.js +29 -1
  241. package/src/lib/record/record-words.js.map +1 -1
  242. package/src/lib/record/record-words.mjs +27 -1
  243. package/src/lib/record/record-words.mjs.map +1 -1
  244. package/src/lib/record/uploads.d.mts +35 -9
  245. package/src/lib/record/uploads.d.ts +35 -9
  246. package/src/lib/record/uploads.js +49 -169
  247. package/src/lib/record/uploads.js.map +1 -1
  248. package/src/lib/record/uploads.mjs +42 -164
  249. package/src/lib/record/uploads.mjs.map +1 -1
  250. package/src/lib/record/values.d.mts +23 -0
  251. package/src/lib/record/values.d.ts +23 -0
  252. package/src/lib/record/values.js +4 -0
  253. package/src/lib/record/values.js.map +1 -1
  254. package/src/lib/record/values.mjs +4 -0
  255. package/src/lib/record/values.mjs.map +1 -1
  256. package/src/lib/record/waits.d.mts +7 -5
  257. package/src/lib/record/waits.d.ts +7 -5
  258. package/src/lib/record/waits.js +7 -10
  259. package/src/lib/record/waits.js.map +1 -1
  260. package/src/lib/record/waits.mjs +7 -10
  261. package/src/lib/record/waits.mjs.map +1 -1
  262. package/src/runtime.d.mts +13 -6
  263. package/src/runtime.d.ts +13 -6
  264. package/src/runtime.js +22 -3
  265. package/src/runtime.js.map +1 -1
  266. package/src/runtime.mjs +11 -2
  267. package/src/runtime.mjs.map +1 -1
@@ -8,8 +8,9 @@ import { type AppFunctions } from '../expression/app-functions';
8
8
  import { type AskedFor } from './compute';
9
9
  import { type FieldTone, type LineState } from './cells';
10
10
  import { type Messages } from './messages';
11
- import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type ResolvedFilter, type SaveProblem, type BotReport, type SlotsResult, type UploadFile, type DuplicateMatch, type MergeResult, type SavedDraft } from './data-source';
11
+ import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type ResolvedFilter, type SaveProblem, type BotReport, type SlotsResult, type UploadFile, type DuplicateMatch, type MergeResult, type SavedDraft, type ApprovalDecision } from './data-source';
12
12
  import { type FormLimits } from './taking';
13
+ import { type FormApproval } from './approving';
13
14
  import { type WaitsFor } from './waits';
14
15
  import { type FileUpload, type UploadHow } from './uploads';
15
16
  import { type Scheduler } from './scheduler';
@@ -123,6 +124,12 @@ export interface FormState {
123
124
  readonly key: string;
124
125
  readonly missing?: boolean;
125
126
  } | null;
127
+ /**
128
+ * The record's approvals, on a page with `approval` whose data source tells them (`approvals`): its levels and where
129
+ * each stands, what was decided, the levels this person may decide, and whether it waits — when its fields lock but
130
+ * for those the page leaves open. Null for a new record, or a page without them.
131
+ */
132
+ readonly approval?: FormApproval | null;
126
133
  }
127
134
  /** A response sent, as `FormState.sent` keeps it. */
128
135
  export interface SentResponse {
@@ -139,6 +146,11 @@ export interface SearchOptions {
139
146
  ids?: RecordId[];
140
147
  /** Only the records named exactly the query, in any case (`SearchRequest.exact`): a typed name read as a record. */
141
148
  exact?: boolean;
149
+ /**
150
+ * Only the records whose `field` holds the query exactly — a code read by a scanner, by the field holding the linked
151
+ * records' codes ("barcode") — asked through the search's filter, whatever their names.
152
+ */
153
+ field?: string;
142
154
  }
143
155
  /** Text left in a box that does not read, and why, as `FormState.unread` keeps it. */
144
156
  export interface UnreadText {
@@ -368,8 +380,8 @@ export interface FormOptions {
368
380
  };
369
381
  /**
370
382
  * How a field's value reads inside the page's own words — a `{field}` in a label, a help, a choice, a step's title, a
371
- * button, an ending — and so inside the form's messages that name a label ("How was your visit to Zamalek? is
372
- * required"). The viewer gives its own, as each field shows its value at rest: a choice by its label, money with its
383
+ * button, an ending — and so inside the form's messages that name a label ("Please answer “How was your visit to
384
+ * Zamalek?”"). The viewer gives its own, as each field shows its value at rest: a choice by its label, money with its
373
385
  * currency, a day in the page's order. Left out, a choice by its label, a link by its name, a yes or no as Yes or No,
374
386
  * anything else as it is held.
375
387
  */
@@ -429,11 +441,13 @@ export interface Form {
429
441
  /** What a field would be told if the form were checked now, or null when it passes. Nothing is shown. */
430
442
  problem(field: string): string | null;
431
443
  /**
432
- * What a value worked out from others waits for while one of them holds text that does not read (`unread`): a
433
- * total of the page's own (`compute`) over a table whose line says "abc" in its Quantity, or an amount the app
434
- * works out as the lines change (read-only, from a source with an onchange) — the line, as people count them, and
435
- * its cell. Shown, it is a dash said in words, as the table's own total waits: never an amount worked out from the
436
- * value that text replaced. Null when it shows its value.
444
+ * What a value worked out from others waits for while one of them holds text that does not read (`unread`), or an
445
+ * answer the form refuses (shown, not empty, and refused by its checks: an area under its least, a day past today):
446
+ * a total of the page's own (`compute`) over a table whose line says "abc" in its Quantity, a price per m² over an
447
+ * area of −50, or an amount the app works out as the lines change (read-only, from a source with an onchange) — the
448
+ * line, as people count them, and its cell. Shown, it is a dash said in words, as the table's own total waits: never
449
+ * an amount worked out from the value that text replaced, or from one refused. Then what is asked of the app's
450
+ * server for it (`asking`). Null when it shows its value.
437
451
  */
438
452
  waitsFor(field: string): WaitsFor | null;
439
453
  /**
@@ -693,6 +707,23 @@ export interface Form {
693
707
  * show what it tells (`limits`). The form asks it itself as it opens, before its answers go, and after they went.
694
708
  */
695
709
  refreshLimits(): Promise<void>;
710
+ /**
711
+ * Ask the data source's `approvals` again for the record shown, and keep what it tells (`approval`). The form asks it
712
+ * itself as a saved record is shown, after each save, and after a decision.
713
+ */
714
+ refreshApprovals(): Promise<void>;
715
+ /**
716
+ * Decide at a level of the record's approvals this person may decide now (`approval.mine`): approved, or rejected
717
+ * with why — a rejection always says why. What is typed is read and the person's changes saved first; the source's
718
+ * `decide` takes it; the record is loaded again after, its approvals with it. Rejects in words where the level is not
719
+ * theirs to decide now, a rejection gives no reason, the save is refused, or the source refused the decision.
720
+ */
721
+ decide(level: string, decision: ApprovalDecision, reason?: string): Promise<void>;
722
+ /**
723
+ * What a field's lock says while the record waits for approval — locked but for the fields the page leaves open
724
+ * (`approval.open`) — or null where no approval locks it.
725
+ */
726
+ approvalLock(name: string): string | null;
696
727
  /**
697
728
  * Whether a file picked for a file or picture field is stored at once,
698
729
  * through the data source's `upload`. False without one: a file then
@@ -772,6 +803,8 @@ export interface Form {
772
803
  * makes each ready first, one at a time (a photo made smaller). A field of
773
804
  * one file keeps the last one picked. Gives each upload's id. A save waits
774
805
  * for those still going, and stops, under the field, for one that failed.
806
+ * `how.replaces`: the file is stored for bytes the field holds (a photo
807
+ * cropped), its link taking their place once stored, as a save's would.
775
808
  */
776
809
  upload(field: string, files: readonly UploadFile[], how?: UploadHow): string[];
777
810
  /**
@@ -27,6 +27,8 @@ const record_words_1 = require("./record-words");
27
27
  const line_choices_1 = require("./line-choices");
28
28
  const data_source_1 = require("./data-source");
29
29
  const taking_1 = require("./taking");
30
+ const languages_1 = require("./languages");
31
+ const approving_1 = require("./approving");
30
32
  const drafts_1 = require("./drafts");
31
33
  const waits_1 = require("./waits");
32
34
  const uploads_1 = require("./uploads");
@@ -91,7 +93,9 @@ function innerForm(options, within, place, outer) {
91
93
  const page = (0, zoned_1.zonedPage)(options.page);
92
94
  const scheduler = options.scheduler ?? scheduler_1.hostScheduler;
93
95
  // A field's label small inside a message's sentence where the page asks (`lowerLabels`).
94
- const messages = page.lowerLabels ? { ...(options.messages ?? messages_1.MESSAGES.en), lowerLabels: true } : (options.messages ?? messages_1.MESSAGES.en);
96
+ // …and a day written in the page's order where it says one (`look.dateOrder`): "can't be after Oct 20, 2026".
97
+ const dateOrder = page.look?.dateOrder;
98
+ const messages = page.lowerLabels || dateOrder ? { ...(options.messages ?? messages_1.MESSAGES.en), lowerLabels: page.lowerLabels, dateOrder } : (options.messages ?? messages_1.MESSAGES.en);
95
99
  /** One of the form's sentences in the words the page names its records by (`records`), else as the language says it plainly. */
96
100
  const named = (noun, plain) => (0, record_words_1.recordSentence)(page.records, noun, plain, messages.locale);
97
101
  /** The app's functions the page's expressions call: a name none may call is refused now. */
@@ -365,6 +369,7 @@ function innerForm(options, within, place, outer) {
365
369
  // Worked out once the form has its state, as the limits part comes (`limitsCode`).
366
370
  limits: null,
367
371
  resume: null,
372
+ approval: null,
368
373
  };
369
374
  started = true;
370
375
  /**
@@ -556,6 +561,7 @@ function innerForm(options, within, place, outer) {
556
561
  let edit = false;
557
562
  function set(patch) {
558
563
  const was = state.values;
564
+ const choicesWas = state.choices;
559
565
  const shown = state.errors;
560
566
  const edited = edit;
561
567
  edit = false;
@@ -564,6 +570,12 @@ function innerForm(options, within, place, outer) {
564
570
  // A line's choices go with their line.
565
571
  if (state.values !== was)
566
572
  state = { ...state, choices: choicesNow(state.choices) };
573
+ // What was said taken out is said while it is true: an option offered again, or picked again, is said no more.
574
+ if ((state.values !== was || state.choices !== choicesWas) && Object.keys(state.takenOut).length) {
575
+ const takenOut = takenOutTrue();
576
+ if (takenOut !== state.takenOut)
577
+ state = { ...state, takenOut };
578
+ }
567
579
  // What waits for an app's answer, as the values shown last settled.
568
580
  if (patch.values !== undefined && patch.asked === undefined && !sameAsked(state.asked, askedNow))
569
581
  state = { ...state, asked: Object.fromEntries(askedNow) };
@@ -864,7 +876,9 @@ function innerForm(options, within, place, outer) {
864
876
  const written = line
865
877
  ? (values[field] ?? []).map((l) => (l.key === line.key ? { ...l, values: { ...l.values, [line.column]: without(l.values[line.column]) } } : l))
866
878
  : without(values[field]);
867
- writeValues({ ...values, [field]: written }, {}, false, { [line ? `${field}.${line.key}.${line.column}` : field]: takenOutWords(labels) });
879
+ const path = line ? `${field}.${line.key}.${line.column}` : field;
880
+ takenOutOf.set(path, lost.map((value, i) => ({ value, label: labels[i] })));
881
+ writeValues({ ...values, [field]: written }, {}, false, { [path]: takenOutWords(labels) });
868
882
  afterEdit(field);
869
883
  changed(field, 'step', { keep: true });
870
884
  }
@@ -952,6 +966,10 @@ function innerForm(options, within, place, outer) {
952
966
  }
953
967
  if ((0, choice_options_1.hasOptionRules)(page))
954
968
  void track(optionsCode());
969
+ // A text in several languages is checked by the languages part: asked for at once, a save waiting for it.
970
+ const languagesCode = () => languages_1.formLanguagesPart.load().then(() => undefined, () => undefined);
971
+ if ((0, languages_1.hasLanguages)(page))
972
+ void track(languagesCode());
955
973
  /** A table line's choice's options as they are offered on that line: its list's once loaded, or its own, by their conditions once they came. */
956
974
  function lineOffered(lines, line, column, record) {
957
975
  if (optionKit)
@@ -973,6 +991,38 @@ function innerForm(options, within, place, outer) {
973
991
  * "Garden is no longer offered, so it was taken out".
974
992
  */
975
993
  const takenOutWords = (labels) => (0, messages_1.fillMessage)(messages, labels.length > 1 ? messages.notOfferedNowMany : messages.notOfferedNow, { name: listedLabels(labels.map((label) => (label.includes('{') ? wordsNow(label) : label))) });
994
+ /** The options each note of `takenOut` names, by its path: a note goes once none of them is still out. */
995
+ const takenOutOf = new Map();
996
+ /**
997
+ * The notes of options taken out (`FormState.takenOut`) that are still true for the values and the choices now: one
998
+ * whose options are all offered again — the type that offers them picked again, the treatment ticked again above, the
999
+ * app's list asked again offering it — or picked again goes; one with some still out names those. A list being asked
1000
+ * again says nothing yet. The same notes, when none changed.
1001
+ */
1002
+ function takenOutTrue() {
1003
+ let kept = null;
1004
+ for (const path of Object.keys(state.takenOut)) {
1005
+ const options = takenOutOf.get(path);
1006
+ const held = valueAt(state.values, path);
1007
+ if (!options || held === gone || state.choices[path]?.loading)
1008
+ continue;
1009
+ const offered = form.offered(path);
1010
+ if (!offered)
1011
+ continue;
1012
+ const chosen = [held].flat();
1013
+ const left = options.filter((option) => !chosen.includes(option.value) && !offered.some((one) => one.value === option.value));
1014
+ if (left.length === options.length)
1015
+ continue;
1016
+ kept ??= { ...state.takenOut };
1017
+ if (left.length) {
1018
+ takenOutOf.set(path, left);
1019
+ kept[path] = takenOutWords(left.map((option) => option.label));
1020
+ }
1021
+ else
1022
+ delete kept[path];
1023
+ }
1024
+ return kept ?? state.takenOut;
1025
+ }
976
1026
  /** A line's field as its value is checked: its choices from the app's list, those loaded for its line. */
977
1027
  function checkedLine(path, def) {
978
1028
  const loaded = def.type === 'selection' && (0, choice_options_1.optionsList)(def.optionsFrom) ? state.choices[path]?.options : null;
@@ -1087,6 +1137,9 @@ function innerForm(options, within, place, outer) {
1087
1137
  const answer = answerOf(node);
1088
1138
  if (answer !== undefined && signedNow().has(answer))
1089
1139
  readonly = true;
1140
+ // A record waiting for approval: locked but for the fields the page leaves open (the approvals part).
1141
+ if (answer !== undefined && approvalKit?.locks(answer))
1142
+ readonly = true;
1090
1143
  return {
1091
1144
  invisible,
1092
1145
  readonly: readonly || (def !== undefined && lockedField(def)),
@@ -1158,6 +1211,23 @@ function innerForm(options, within, place, outer) {
1158
1211
  const name = node.field;
1159
1212
  return unticked(node) ?? (0, inputs_1.checkInput)(page.fields[name], node.source, state.values[name], messages) ?? (0, check_1.checkValue)(checked(name), state.values[name], nodeState(node.id).required, messages, today(), node.source.widget) ?? ruleCheck(node).error;
1160
1213
  }
1214
+ /** What a check would refuse now (`collectErrors`), for the state it was found for. */
1215
+ let refusals = null;
1216
+ /**
1217
+ * Whether the form refuses the answer at a path — a field's, or a table line's cell (`field.lineKey.subfield`) — as a
1218
+ * check would now: one it shows that holds a value its rules do not take (under its least, past today, a pattern not
1219
+ * met, an answer rule's error). An empty answer is none, required or not: there is nothing worked out from it to wait.
1220
+ */
1221
+ function refusedAt(path) {
1222
+ if (refusals?.state !== state)
1223
+ refusals = { state, errors: collectErrors() };
1224
+ if (!refusals.errors[path])
1225
+ return false;
1226
+ const parts = path.split('.');
1227
+ const def = page.fields[parts[0]];
1228
+ const held = valueAt(state.values, path);
1229
+ return held !== gone && !(0, values_1.isEmpty)((parts.length > 2 && def?.type === 'one2many' ? def.fields[parts[parts.length - 1]] : def), held);
1230
+ }
1161
1231
  /** Warnings for the visible fields of the page, without publishing them. */
1162
1232
  function collectWarnings() {
1163
1233
  const warnings = {};
@@ -1426,6 +1496,8 @@ function innerForm(options, within, place, outer) {
1426
1496
  if (out) {
1427
1497
  settled = out.values;
1428
1498
  takenOut = { ...takenOut, ...out.said };
1499
+ for (const [path, gone] of Object.entries(out.gone))
1500
+ takenOutOf.set(path, gone);
1429
1501
  }
1430
1502
  if (Object.keys(takenOut).length || Object.keys(state.takenOut).length)
1431
1503
  extra = { takenOut, ...extra };
@@ -1706,6 +1778,15 @@ function innerForm(options, within, place, outer) {
1706
1778
  }
1707
1779
  /** A choice of a full option, picked by the person, refused under its field: the field keeps what it held. */
1708
1780
  const refusedFull = (name, value) => !!limitsKit?.refusedFull(name, value);
1781
+ // ---- a record's approvals (the approvals part: form-approvals.ts) ---------------------------------------------------
1782
+ /** The approvals made for this form from the approvals part, once it came: none before, nor on a page without them. */
1783
+ let approvalKit = null;
1784
+ /** Whether the form asks for approvals: a page with `approval` (a record's own, as its check says), its source telling them. */
1785
+ const keepsApprovals = !outer && !!page.approval && !!options.dataSource?.approvals;
1786
+ /** The approvals, made from their part once it came; null without them, or where it could not come. */
1787
+ const approvalsCode = () => keepsApprovals ? approving_1.formApprovalsPart.load().then((part) => (approvalKit ??= part.formApprovals({ form, dataSource: options.dataSource, messages, set, reload: () => load(true) })), () => null) : Promise.resolve(null);
1788
+ /** Ask the record shown's approvals, once their part came; nothing on a page without them. */
1789
+ const askApprovals = () => approvalsCode().then((kit) => kit?.ask());
1709
1790
  function scheduleAutosave() {
1710
1791
  const autosave = options.autosave;
1711
1792
  // A record the person may not change is never saved unasked: it would only be refused.
@@ -1852,6 +1933,12 @@ function innerForm(options, within, place, outer) {
1852
1933
  if (asked !== generation)
1853
1934
  return elsewhere();
1854
1935
  }
1936
+ // A text in several languages, checked in each once its part has come.
1937
+ if (!languages_1.formLanguagesPart.loaded && (0, languages_1.hasLanguages)(page)) {
1938
+ await languagesCode();
1939
+ if (asked !== generation)
1940
+ return elsewhere();
1941
+ }
1855
1942
  // Values the app's functions are asked for are waited for — those that failed asked again — so what is saved is worked out.
1856
1943
  if (asking.size || Object.values(state.asked).some((found) => found.failed !== undefined)) {
1857
1944
  set({ status: 'saving', error: null, saveProblem: null });
@@ -1975,6 +2062,9 @@ function innerForm(options, within, place, outer) {
1975
2062
  scheduleDraft();
1976
2063
  scheduleAutosave();
1977
2064
  }
2065
+ // A save may have sent it for approval, or moved it on: its approvals asked again.
2066
+ if (keepsApprovals)
2067
+ void track(askApprovals());
1978
2068
  // The record as this save left it: a change made while it was on its way is no value of it.
1979
2069
  emit('save', { recordId: state.recordId, values: baseline });
1980
2070
  void track(moment('on.afterSave', page.on?.afterSave, chain, false));
@@ -2181,7 +2271,7 @@ function innerForm(options, within, place, outer) {
2181
2271
  throw new Error(`"${name}" cannot make records: its data source has no create, or it is not a link`);
2182
2272
  return track(source.create({ model, name: typed.trim(), ...(Object.keys(values).length ? { values } : {}) }));
2183
2273
  }
2184
- async function runSearch(def, name, ctx, query, limit, { model, ids, exact } = {}) {
2274
+ async function runSearch(def, name, ctx, query, limit, { model, ids, exact, field } = {}) {
2185
2275
  // Formatted text names people and records in place (@mention, #4127): searched by the model they are of.
2186
2276
  const byModel = def.type === 'reference' || def.type === 'json' || def.type === 'html';
2187
2277
  if (def.type !== 'many2one' && def.type !== 'many2many' && !byModel) {
@@ -2194,9 +2284,11 @@ function innerForm(options, within, place, outer) {
2194
2284
  return [];
2195
2285
  // Every valueFrom, in groups too, becomes the value it names before the search goes out.
2196
2286
  const own = byModel || !def.filter ? undefined : (0, filter_1.resolveFilter)(def.filter, ctx);
2197
- const filter = ids ? [...(own ?? []), { field: 'id', op: 'in', value: ids }] : own;
2287
+ const kept = ids ? [...(own ?? []), { field: 'id', op: 'in', value: ids }] : own;
2288
+ // A code read by a scanner: the records whose field holds it, whatever their names.
2289
+ const filter = field ? [...(kept ?? []), { field, op: '=', value: query }] : kept;
2198
2290
  const relation = byModel ? model : def.relation;
2199
- return track(source.search({ model: relation, query, ...(filter ? { filter } : {}), limit, ...(exact ? { exact: true } : {}) }));
2291
+ return track(source.search({ model: relation, query: field ? '' : query, ...(filter ? { filter } : {}), limit, ...(exact ? { exact: true } : {}) }));
2200
2292
  }
2201
2293
  function lineField(field) {
2202
2294
  const def = fieldDef(field);
@@ -2237,6 +2329,9 @@ function innerForm(options, within, place, outer) {
2237
2329
  loadedOnce = true;
2238
2330
  if (asksLimits())
2239
2331
  void track(askLimits());
2332
+ // Its approvals: where it stands, what was decided, what this person may decide — its fields locked while it waits.
2333
+ if (keepsApprovals)
2334
+ void track(askApprovals());
2240
2335
  if (again)
2241
2336
  return;
2242
2337
  offerDraft();
@@ -2269,7 +2364,7 @@ function innerForm(options, within, place, outer) {
2269
2364
  found = null;
2270
2365
  serverKey = null;
2271
2366
  draftEpoch++;
2272
- const fresh = { dirty: [], errors: {}, warning: null, warningField: null, error: null, draft: null, step: steps[0] ?? null, skipped: [], saveProblem: null, version: null, can: null, conflict: null, setAside: null, unsent: [], unread: {}, takenOut: {}, resume: null };
2367
+ const fresh = { dirty: [], errors: {}, warning: null, warningField: null, error: null, draft: null, step: steps[0] ?? null, skipped: [], saveProblem: null, version: null, can: null, conflict: null, setAside: null, unsent: [], unread: {}, takenOut: {}, resume: null, approval: null };
2273
2368
  if (id != null) {
2274
2369
  set({ ...fresh, recordId: id, status: 'idle' });
2275
2370
  await load();
@@ -2688,7 +2783,7 @@ function innerForm(options, within, place, outer) {
2688
2783
  // Nothing of the record changed; what shows of it may have.
2689
2784
  set({});
2690
2785
  },
2691
- fieldReadonly: (name) => lockedField(fieldDef(name)) || state.can?.edit === false || state.can?.readonlyFields?.includes(name) === true || signedNow().has(name),
2786
+ fieldReadonly: (name) => lockedField(fieldDef(name)) || state.can?.edit === false || state.can?.readonlyFields?.includes(name) === true || signedNow().has(name) || !!approvalKit?.locks(name),
2692
2787
  loadChoices: (field) => loadChoices(field),
2693
2788
  loadDefinitions,
2694
2789
  addDefinition,
@@ -2713,11 +2808,13 @@ function innerForm(options, within, place, outer) {
2713
2808
  return (0, choice_options_1.changesOptions)(cell ? (lines?.type === 'one2many' ? lines.fields[cell.column] : undefined) : page.fields[name]);
2714
2809
  },
2715
2810
  waitsFor(name) {
2716
- const found = state.asked[name];
2717
- if (found)
2718
- return { field: name, asking: true, ...(found.failed !== undefined ? { failed: found.failed } : {}) };
2811
+ // What it is worked out from first: an answer that does not read, or that the form refuses, is never asked about.
2719
2812
  const from = workedOut.get(name);
2720
- return from ? (0, waits_1.waitingOn)(page, from, state.values, state.unread) : null;
2813
+ const waits = from ? (0, waits_1.waitingOn)(page, from, state.values, state.unread, refusedAt) : null;
2814
+ if (waits)
2815
+ return waits;
2816
+ const found = state.asked[name];
2817
+ return found ? { field: name, asking: true, ...(found.failed !== undefined ? { failed: found.failed } : {}) } : null;
2721
2818
  },
2722
2819
  problem(name) {
2723
2820
  fieldDef(name);
@@ -3184,6 +3281,14 @@ function innerForm(options, within, place, outer) {
3184
3281
  return saved;
3185
3282
  },
3186
3283
  refreshLimits: () => askLimits(),
3284
+ refreshApprovals: askApprovals,
3285
+ async decide(level, decision, reason) {
3286
+ const kit = await approvalsCode();
3287
+ if (!kit)
3288
+ throw new Error('No approvals on this page');
3289
+ return track(kit.decide(level, decision, reason));
3290
+ },
3291
+ approvalLock: (name) => approvalKit?.lockWords(name) ?? null,
3187
3292
  async reloadTheirs() {
3188
3293
  // A save on its way first: its answer is not dropped by the load.
3189
3294
  const asked = shownNow();
@@ -3368,6 +3473,9 @@ function innerForm(options, within, place, outer) {
3368
3473
  // choice's places as the source tells them: a record's own once it is loaded, its own choices not counted against it.
3369
3474
  if (keepsLimits())
3370
3475
  void track(limitsCode());
3476
+ // A record's approvals: their part asked for as the form is made, ready by the time its record is loaded.
3477
+ if (keepsApprovals)
3478
+ void track(approvalsCode());
3371
3479
  // A new form has its values now — a draft continued from its key once it is laid — and opens once whoever made it
3372
3480
  // has had the chance to listen.
3373
3481
  if (state.recordId == null) {