@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
@@ -21,6 +21,8 @@ import { recordSentence } from './record-words.mjs';
21
21
  import { askKey, cellOf, listedColumns, notOffered } from './line-choices.mjs';
22
22
  import { loadedRecordOf, saveProblemOf, } from './data-source.mjs';
23
23
  import { formLimitsPart, hasLimits } from './taking.mjs';
24
+ import { formLanguagesPart, hasLanguages } from './languages.mjs';
25
+ import { formApprovalsPart } from './approving.mjs';
24
26
  import { draftRead, resumeStep } from './drafts.mjs';
25
27
  import { waitingOn, workedOutFrom } from './waits.mjs';
26
28
  import { createUploads, fileBytesIn, uploadGoing, withoutFileBytes } from './uploads.mjs';
@@ -85,7 +87,9 @@ function innerForm(options, within, place, outer) {
85
87
  const page = zonedPage(options.page);
86
88
  const scheduler = options.scheduler ?? hostScheduler;
87
89
  // A field's label small inside a message's sentence where the page asks (`lowerLabels`).
88
- const messages = page.lowerLabels ? { ...(options.messages ?? MESSAGES.en), lowerLabels: true } : (options.messages ?? MESSAGES.en);
90
+ // …and a day written in the page's order where it says one (`look.dateOrder`): "can't be after Oct 20, 2026".
91
+ const dateOrder = page.look?.dateOrder;
92
+ const messages = page.lowerLabels || dateOrder ? { ...(options.messages ?? MESSAGES.en), lowerLabels: page.lowerLabels, dateOrder } : (options.messages ?? MESSAGES.en);
89
93
  /** One of the form's sentences in the words the page names its records by (`records`), else as the language says it plainly. */
90
94
  const named = (noun, plain) => recordSentence(page.records, noun, plain, messages.locale);
91
95
  /** The app's functions the page's expressions call: a name none may call is refused now. */
@@ -359,6 +363,7 @@ function innerForm(options, within, place, outer) {
359
363
  // Worked out once the form has its state, as the limits part comes (`limitsCode`).
360
364
  limits: null,
361
365
  resume: null,
366
+ approval: null,
362
367
  };
363
368
  started = true;
364
369
  /**
@@ -550,6 +555,7 @@ function innerForm(options, within, place, outer) {
550
555
  let edit = false;
551
556
  function set(patch) {
552
557
  const was = state.values;
558
+ const choicesWas = state.choices;
553
559
  const shown = state.errors;
554
560
  const edited = edit;
555
561
  edit = false;
@@ -558,6 +564,12 @@ function innerForm(options, within, place, outer) {
558
564
  // A line's choices go with their line.
559
565
  if (state.values !== was)
560
566
  state = { ...state, choices: choicesNow(state.choices) };
567
+ // What was said taken out is said while it is true: an option offered again, or picked again, is said no more.
568
+ if ((state.values !== was || state.choices !== choicesWas) && Object.keys(state.takenOut).length) {
569
+ const takenOut = takenOutTrue();
570
+ if (takenOut !== state.takenOut)
571
+ state = { ...state, takenOut };
572
+ }
561
573
  // What waits for an app's answer, as the values shown last settled.
562
574
  if (patch.values !== undefined && patch.asked === undefined && !sameAsked(state.asked, askedNow))
563
575
  state = { ...state, asked: Object.fromEntries(askedNow) };
@@ -858,7 +870,9 @@ function innerForm(options, within, place, outer) {
858
870
  const written = line
859
871
  ? (values[field] ?? []).map((l) => (l.key === line.key ? { ...l, values: { ...l.values, [line.column]: without(l.values[line.column]) } } : l))
860
872
  : without(values[field]);
861
- writeValues({ ...values, [field]: written }, {}, false, { [line ? `${field}.${line.key}.${line.column}` : field]: takenOutWords(labels) });
873
+ const path = line ? `${field}.${line.key}.${line.column}` : field;
874
+ takenOutOf.set(path, lost.map((value, i) => ({ value, label: labels[i] })));
875
+ writeValues({ ...values, [field]: written }, {}, false, { [path]: takenOutWords(labels) });
862
876
  afterEdit(field);
863
877
  changed(field, 'step', { keep: true });
864
878
  }
@@ -946,6 +960,10 @@ function innerForm(options, within, place, outer) {
946
960
  }
947
961
  if (hasOptionRules(page))
948
962
  void track(optionsCode());
963
+ // A text in several languages is checked by the languages part: asked for at once, a save waiting for it.
964
+ const languagesCode = () => formLanguagesPart.load().then(() => undefined, () => undefined);
965
+ if (hasLanguages(page))
966
+ void track(languagesCode());
949
967
  /** 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. */
950
968
  function lineOffered(lines, line, column, record) {
951
969
  if (optionKit)
@@ -967,6 +985,38 @@ function innerForm(options, within, place, outer) {
967
985
  * "Garden is no longer offered, so it was taken out".
968
986
  */
969
987
  const takenOutWords = (labels) => fillMessage(messages, labels.length > 1 ? messages.notOfferedNowMany : messages.notOfferedNow, { name: listedLabels(labels.map((label) => (label.includes('{') ? wordsNow(label) : label))) });
988
+ /** The options each note of `takenOut` names, by its path: a note goes once none of them is still out. */
989
+ const takenOutOf = new Map();
990
+ /**
991
+ * The notes of options taken out (`FormState.takenOut`) that are still true for the values and the choices now: one
992
+ * whose options are all offered again — the type that offers them picked again, the treatment ticked again above, the
993
+ * app's list asked again offering it — or picked again goes; one with some still out names those. A list being asked
994
+ * again says nothing yet. The same notes, when none changed.
995
+ */
996
+ function takenOutTrue() {
997
+ let kept = null;
998
+ for (const path of Object.keys(state.takenOut)) {
999
+ const options = takenOutOf.get(path);
1000
+ const held = valueAt(state.values, path);
1001
+ if (!options || held === gone || state.choices[path]?.loading)
1002
+ continue;
1003
+ const offered = form.offered(path);
1004
+ if (!offered)
1005
+ continue;
1006
+ const chosen = [held].flat();
1007
+ const left = options.filter((option) => !chosen.includes(option.value) && !offered.some((one) => one.value === option.value));
1008
+ if (left.length === options.length)
1009
+ continue;
1010
+ kept ??= { ...state.takenOut };
1011
+ if (left.length) {
1012
+ takenOutOf.set(path, left);
1013
+ kept[path] = takenOutWords(left.map((option) => option.label));
1014
+ }
1015
+ else
1016
+ delete kept[path];
1017
+ }
1018
+ return kept ?? state.takenOut;
1019
+ }
970
1020
  /** A line's field as its value is checked: its choices from the app's list, those loaded for its line. */
971
1021
  function checkedLine(path, def) {
972
1022
  const loaded = def.type === 'selection' && optionsList(def.optionsFrom) ? state.choices[path]?.options : null;
@@ -1081,6 +1131,9 @@ function innerForm(options, within, place, outer) {
1081
1131
  const answer = answerOf(node);
1082
1132
  if (answer !== undefined && signedNow().has(answer))
1083
1133
  readonly = true;
1134
+ // A record waiting for approval: locked but for the fields the page leaves open (the approvals part).
1135
+ if (answer !== undefined && approvalKit?.locks(answer))
1136
+ readonly = true;
1084
1137
  return {
1085
1138
  invisible,
1086
1139
  readonly: readonly || (def !== undefined && lockedField(def)),
@@ -1152,6 +1205,23 @@ function innerForm(options, within, place, outer) {
1152
1205
  const name = node.field;
1153
1206
  return unticked(node) ?? checkInput(page.fields[name], node.source, state.values[name], messages) ?? checkValue(checked(name), state.values[name], nodeState(node.id).required, messages, today(), node.source.widget) ?? ruleCheck(node).error;
1154
1207
  }
1208
+ /** What a check would refuse now (`collectErrors`), for the state it was found for. */
1209
+ let refusals = null;
1210
+ /**
1211
+ * Whether the form refuses the answer at a path — a field's, or a table line's cell (`field.lineKey.subfield`) — as a
1212
+ * check would now: one it shows that holds a value its rules do not take (under its least, past today, a pattern not
1213
+ * met, an answer rule's error). An empty answer is none, required or not: there is nothing worked out from it to wait.
1214
+ */
1215
+ function refusedAt(path) {
1216
+ if (refusals?.state !== state)
1217
+ refusals = { state, errors: collectErrors() };
1218
+ if (!refusals.errors[path])
1219
+ return false;
1220
+ const parts = path.split('.');
1221
+ const def = page.fields[parts[0]];
1222
+ const held = valueAt(state.values, path);
1223
+ return held !== gone && !isEmpty((parts.length > 2 && def?.type === 'one2many' ? def.fields[parts[parts.length - 1]] : def), held);
1224
+ }
1155
1225
  /** Warnings for the visible fields of the page, without publishing them. */
1156
1226
  function collectWarnings() {
1157
1227
  const warnings = {};
@@ -1420,6 +1490,8 @@ function innerForm(options, within, place, outer) {
1420
1490
  if (out) {
1421
1491
  settled = out.values;
1422
1492
  takenOut = { ...takenOut, ...out.said };
1493
+ for (const [path, gone] of Object.entries(out.gone))
1494
+ takenOutOf.set(path, gone);
1423
1495
  }
1424
1496
  if (Object.keys(takenOut).length || Object.keys(state.takenOut).length)
1425
1497
  extra = { takenOut, ...extra };
@@ -1700,6 +1772,15 @@ function innerForm(options, within, place, outer) {
1700
1772
  }
1701
1773
  /** A choice of a full option, picked by the person, refused under its field: the field keeps what it held. */
1702
1774
  const refusedFull = (name, value) => !!limitsKit?.refusedFull(name, value);
1775
+ // ---- a record's approvals (the approvals part: form-approvals.ts) ---------------------------------------------------
1776
+ /** The approvals made for this form from the approvals part, once it came: none before, nor on a page without them. */
1777
+ let approvalKit = null;
1778
+ /** Whether the form asks for approvals: a page with `approval` (a record's own, as its check says), its source telling them. */
1779
+ const keepsApprovals = !outer && !!page.approval && !!options.dataSource?.approvals;
1780
+ /** The approvals, made from their part once it came; null without them, or where it could not come. */
1781
+ const approvalsCode = () => keepsApprovals ? formApprovalsPart.load().then((part) => (approvalKit ??= part.formApprovals({ form, dataSource: options.dataSource, messages, set, reload: () => load(true) })), () => null) : Promise.resolve(null);
1782
+ /** Ask the record shown's approvals, once their part came; nothing on a page without them. */
1783
+ const askApprovals = () => approvalsCode().then((kit) => kit?.ask());
1703
1784
  function scheduleAutosave() {
1704
1785
  const autosave = options.autosave;
1705
1786
  // A record the person may not change is never saved unasked: it would only be refused.
@@ -1846,6 +1927,12 @@ function innerForm(options, within, place, outer) {
1846
1927
  if (asked !== generation)
1847
1928
  return elsewhere();
1848
1929
  }
1930
+ // A text in several languages, checked in each once its part has come.
1931
+ if (!formLanguagesPart.loaded && hasLanguages(page)) {
1932
+ await languagesCode();
1933
+ if (asked !== generation)
1934
+ return elsewhere();
1935
+ }
1849
1936
  // Values the app's functions are asked for are waited for — those that failed asked again — so what is saved is worked out.
1850
1937
  if (asking.size || Object.values(state.asked).some((found) => found.failed !== undefined)) {
1851
1938
  set({ status: 'saving', error: null, saveProblem: null });
@@ -1969,6 +2056,9 @@ function innerForm(options, within, place, outer) {
1969
2056
  scheduleDraft();
1970
2057
  scheduleAutosave();
1971
2058
  }
2059
+ // A save may have sent it for approval, or moved it on: its approvals asked again.
2060
+ if (keepsApprovals)
2061
+ void track(askApprovals());
1972
2062
  // The record as this save left it: a change made while it was on its way is no value of it.
1973
2063
  emit('save', { recordId: state.recordId, values: baseline });
1974
2064
  void track(moment('on.afterSave', page.on?.afterSave, chain, false));
@@ -2175,7 +2265,7 @@ function innerForm(options, within, place, outer) {
2175
2265
  throw new Error(`"${name}" cannot make records: its data source has no create, or it is not a link`);
2176
2266
  return track(source.create({ model, name: typed.trim(), ...(Object.keys(values).length ? { values } : {}) }));
2177
2267
  }
2178
- async function runSearch(def, name, ctx, query, limit, { model, ids, exact } = {}) {
2268
+ async function runSearch(def, name, ctx, query, limit, { model, ids, exact, field } = {}) {
2179
2269
  // Formatted text names people and records in place (@mention, #4127): searched by the model they are of.
2180
2270
  const byModel = def.type === 'reference' || def.type === 'json' || def.type === 'html';
2181
2271
  if (def.type !== 'many2one' && def.type !== 'many2many' && !byModel) {
@@ -2188,9 +2278,11 @@ function innerForm(options, within, place, outer) {
2188
2278
  return [];
2189
2279
  // Every valueFrom, in groups too, becomes the value it names before the search goes out.
2190
2280
  const own = byModel || !def.filter ? undefined : resolveFilter(def.filter, ctx);
2191
- const filter = ids ? [...(own ?? []), { field: 'id', op: 'in', value: ids }] : own;
2281
+ const kept = ids ? [...(own ?? []), { field: 'id', op: 'in', value: ids }] : own;
2282
+ // A code read by a scanner: the records whose field holds it, whatever their names.
2283
+ const filter = field ? [...(kept ?? []), { field, op: '=', value: query }] : kept;
2192
2284
  const relation = byModel ? model : def.relation;
2193
- return track(source.search({ model: relation, query, ...(filter ? { filter } : {}), limit, ...(exact ? { exact: true } : {}) }));
2285
+ return track(source.search({ model: relation, query: field ? '' : query, ...(filter ? { filter } : {}), limit, ...(exact ? { exact: true } : {}) }));
2194
2286
  }
2195
2287
  function lineField(field) {
2196
2288
  const def = fieldDef(field);
@@ -2231,6 +2323,9 @@ function innerForm(options, within, place, outer) {
2231
2323
  loadedOnce = true;
2232
2324
  if (asksLimits())
2233
2325
  void track(askLimits());
2326
+ // Its approvals: where it stands, what was decided, what this person may decide — its fields locked while it waits.
2327
+ if (keepsApprovals)
2328
+ void track(askApprovals());
2234
2329
  if (again)
2235
2330
  return;
2236
2331
  offerDraft();
@@ -2263,7 +2358,7 @@ function innerForm(options, within, place, outer) {
2263
2358
  found = null;
2264
2359
  serverKey = null;
2265
2360
  draftEpoch++;
2266
- 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 };
2361
+ 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 };
2267
2362
  if (id != null) {
2268
2363
  set({ ...fresh, recordId: id, status: 'idle' });
2269
2364
  await load();
@@ -2682,7 +2777,7 @@ function innerForm(options, within, place, outer) {
2682
2777
  // Nothing of the record changed; what shows of it may have.
2683
2778
  set({});
2684
2779
  },
2685
- fieldReadonly: (name) => lockedField(fieldDef(name)) || state.can?.edit === false || state.can?.readonlyFields?.includes(name) === true || signedNow().has(name),
2780
+ fieldReadonly: (name) => lockedField(fieldDef(name)) || state.can?.edit === false || state.can?.readonlyFields?.includes(name) === true || signedNow().has(name) || !!approvalKit?.locks(name),
2686
2781
  loadChoices: (field) => loadChoices(field),
2687
2782
  loadDefinitions,
2688
2783
  addDefinition,
@@ -2707,11 +2802,13 @@ function innerForm(options, within, place, outer) {
2707
2802
  return changesOptions(cell ? (lines?.type === 'one2many' ? lines.fields[cell.column] : undefined) : page.fields[name]);
2708
2803
  },
2709
2804
  waitsFor(name) {
2710
- const found = state.asked[name];
2711
- if (found)
2712
- return { field: name, asking: true, ...(found.failed !== undefined ? { failed: found.failed } : {}) };
2805
+ // What it is worked out from first: an answer that does not read, or that the form refuses, is never asked about.
2713
2806
  const from = workedOut.get(name);
2714
- return from ? waitingOn(page, from, state.values, state.unread) : null;
2807
+ const waits = from ? waitingOn(page, from, state.values, state.unread, refusedAt) : null;
2808
+ if (waits)
2809
+ return waits;
2810
+ const found = state.asked[name];
2811
+ return found ? { field: name, asking: true, ...(found.failed !== undefined ? { failed: found.failed } : {}) } : null;
2715
2812
  },
2716
2813
  problem(name) {
2717
2814
  fieldDef(name);
@@ -3178,6 +3275,14 @@ function innerForm(options, within, place, outer) {
3178
3275
  return saved;
3179
3276
  },
3180
3277
  refreshLimits: () => askLimits(),
3278
+ refreshApprovals: askApprovals,
3279
+ async decide(level, decision, reason) {
3280
+ const kit = await approvalsCode();
3281
+ if (!kit)
3282
+ throw new Error('No approvals on this page');
3283
+ return track(kit.decide(level, decision, reason));
3284
+ },
3285
+ approvalLock: (name) => approvalKit?.lockWords(name) ?? null,
3181
3286
  async reloadTheirs() {
3182
3287
  // A save on its way first: its answer is not dropped by the load.
3183
3288
  const asked = shownNow();
@@ -3362,6 +3467,9 @@ function innerForm(options, within, place, outer) {
3362
3467
  // choice's places as the source tells them: a record's own once it is loaded, its own choices not counted against it.
3363
3468
  if (keepsLimits())
3364
3469
  void track(limitsCode());
3470
+ // A record's approvals: their part asked for as the form is made, ready by the time its record is loaded.
3471
+ if (keepsApprovals)
3472
+ void track(approvalsCode());
3365
3473
  // A new form has its values now — a draft continued from its key once it is laid — and opens once whoever made it
3366
3474
  // has had the chance to listen.
3367
3475
  if (state.recordId == null) {