@fieldia/core 0.26.1 → 0.27.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 (415) hide show
  1. package/README.md +307 -3
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +227 -23
  4. package/src/index.d.mts +8 -2
  5. package/src/index.d.ts +8 -2
  6. package/src/index.js +22 -1
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +9 -2
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/expression/app-functions.d.mts +47 -0
  11. package/src/lib/expression/app-functions.d.ts +47 -0
  12. package/src/lib/expression/app-functions.js +64 -0
  13. package/src/lib/expression/app-functions.js.map +1 -0
  14. package/src/lib/expression/app-functions.mjs +58 -0
  15. package/src/lib/expression/app-functions.mjs.map +1 -0
  16. package/src/lib/expression/dates.d.mts +21 -0
  17. package/src/lib/expression/dates.d.ts +21 -0
  18. package/src/lib/expression/dates.js +73 -0
  19. package/src/lib/expression/dates.js.map +1 -0
  20. package/src/lib/expression/dates.mjs +67 -0
  21. package/src/lib/expression/dates.mjs.map +1 -0
  22. package/src/lib/expression/evaluator.js +12 -6
  23. package/src/lib/expression/evaluator.js.map +1 -1
  24. package/src/lib/expression/evaluator.mjs +12 -6
  25. package/src/lib/expression/evaluator.mjs.map +1 -1
  26. package/src/lib/expression/expression.d.mts +7 -3
  27. package/src/lib/expression/expression.d.ts +7 -3
  28. package/src/lib/expression/expression.js +8 -2
  29. package/src/lib/expression/expression.js.map +1 -1
  30. package/src/lib/expression/expression.mjs +7 -2
  31. package/src/lib/expression/expression.mjs.map +1 -1
  32. package/src/lib/expression/functions-part.d.mts +7 -0
  33. package/src/lib/expression/functions-part.d.ts +7 -0
  34. package/src/lib/expression/functions-part.js +13 -0
  35. package/src/lib/expression/functions-part.js.map +1 -0
  36. package/src/lib/expression/functions-part.mjs +8 -0
  37. package/src/lib/expression/functions-part.mjs.map +1 -0
  38. package/src/lib/expression/functions.d.mts +60 -3
  39. package/src/lib/expression/functions.d.ts +60 -3
  40. package/src/lib/expression/functions.js +109 -15
  41. package/src/lib/expression/functions.js.map +1 -1
  42. package/src/lib/expression/functions.mjs +103 -16
  43. package/src/lib/expression/functions.mjs.map +1 -1
  44. package/src/lib/expression/kinds.d.mts +31 -0
  45. package/src/lib/expression/kinds.d.ts +31 -0
  46. package/src/lib/expression/kinds.js +159 -0
  47. package/src/lib/expression/kinds.js.map +1 -0
  48. package/src/lib/expression/kinds.mjs +151 -0
  49. package/src/lib/expression/kinds.mjs.map +1 -0
  50. package/src/lib/expression/modifier.d.mts +4 -3
  51. package/src/lib/expression/modifier.d.ts +4 -3
  52. package/src/lib/expression/modifier.js +4 -3
  53. package/src/lib/expression/modifier.js.map +1 -1
  54. package/src/lib/expression/modifier.mjs +4 -3
  55. package/src/lib/expression/modifier.mjs.map +1 -1
  56. package/src/lib/expression/more-functions.d.mts +8 -0
  57. package/src/lib/expression/more-functions.d.ts +8 -0
  58. package/src/lib/expression/more-functions.js +235 -0
  59. package/src/lib/expression/more-functions.js.map +1 -0
  60. package/src/lib/expression/more-functions.mjs +230 -0
  61. package/src/lib/expression/more-functions.mjs.map +1 -0
  62. package/src/lib/expression/parser.d.mts +4 -1
  63. package/src/lib/expression/parser.d.ts +4 -1
  64. package/src/lib/expression/parser.js +6 -3
  65. package/src/lib/expression/parser.js.map +1 -1
  66. package/src/lib/expression/parser.mjs +6 -3
  67. package/src/lib/expression/parser.mjs.map +1 -1
  68. package/src/lib/expression/reads.d.mts +2 -2
  69. package/src/lib/expression/reads.d.ts +2 -2
  70. package/src/lib/expression/reads.js +6 -4
  71. package/src/lib/expression/reads.js.map +1 -1
  72. package/src/lib/expression/reads.mjs +6 -4
  73. package/src/lib/expression/reads.mjs.map +1 -1
  74. package/src/lib/format/actions.d.mts +16 -16
  75. package/src/lib/format/actions.d.ts +16 -16
  76. package/src/lib/format/check-page.d.mts +7 -2
  77. package/src/lib/format/check-page.d.ts +7 -2
  78. package/src/lib/format/check-page.js +10 -4
  79. package/src/lib/format/check-page.js.map +1 -1
  80. package/src/lib/format/check-page.mjs +10 -4
  81. package/src/lib/format/check-page.mjs.map +1 -1
  82. package/src/lib/format/choice-options.d.mts +34 -0
  83. package/src/lib/format/choice-options.d.ts +34 -0
  84. package/src/lib/format/choice-options.js +60 -0
  85. package/src/lib/format/choice-options.js.map +1 -0
  86. package/src/lib/format/choice-options.mjs +51 -0
  87. package/src/lib/format/choice-options.mjs.map +1 -0
  88. package/src/lib/format/endings-checks.d.mts +8 -0
  89. package/src/lib/format/endings-checks.d.ts +8 -0
  90. package/src/lib/format/endings-checks.js +23 -0
  91. package/src/lib/format/endings-checks.js.map +1 -0
  92. package/src/lib/format/endings-checks.mjs +20 -0
  93. package/src/lib/format/endings-checks.mjs.map +1 -0
  94. package/src/lib/format/endings.d.mts +40 -0
  95. package/src/lib/format/endings.d.ts +40 -0
  96. package/src/lib/format/endings.js +15 -0
  97. package/src/lib/format/endings.js.map +1 -0
  98. package/src/lib/format/endings.mjs +12 -0
  99. package/src/lib/format/endings.mjs.map +1 -0
  100. package/src/lib/format/field.d.mts +151 -16
  101. package/src/lib/format/field.d.ts +151 -16
  102. package/src/lib/format/field.js +64 -11
  103. package/src/lib/format/field.js.map +1 -1
  104. package/src/lib/format/field.mjs +63 -10
  105. package/src/lib/format/field.mjs.map +1 -1
  106. package/src/lib/format/head-checks.d.mts +2 -0
  107. package/src/lib/format/head-checks.d.ts +2 -0
  108. package/src/lib/format/head-checks.js +2 -59
  109. package/src/lib/format/head-checks.js.map +1 -1
  110. package/src/lib/format/head-checks.mjs +1 -58
  111. package/src/lib/format/head-checks.mjs.map +1 -1
  112. package/src/lib/format/later-checks.d.mts +17 -0
  113. package/src/lib/format/later-checks.d.ts +17 -0
  114. package/src/lib/format/later-checks.js +20 -0
  115. package/src/lib/format/later-checks.js.map +1 -0
  116. package/src/lib/format/later-checks.mjs +17 -0
  117. package/src/lib/format/later-checks.mjs.map +1 -0
  118. package/src/lib/format/layout.d.mts +241 -188
  119. package/src/lib/format/layout.d.ts +241 -188
  120. package/src/lib/format/layout.js +33 -28
  121. package/src/lib/format/layout.js.map +1 -1
  122. package/src/lib/format/layout.mjs +13 -3
  123. package/src/lib/format/layout.mjs.map +1 -1
  124. package/src/lib/format/layout.types.d.mts +2 -1
  125. package/src/lib/format/layout.types.d.ts +2 -1
  126. package/src/lib/format/limits-checks.d.mts +14 -0
  127. package/src/lib/format/limits-checks.d.ts +14 -0
  128. package/src/lib/format/limits-checks.js +36 -0
  129. package/src/lib/format/limits-checks.js.map +1 -0
  130. package/src/lib/format/limits-checks.mjs +33 -0
  131. package/src/lib/format/limits-checks.mjs.map +1 -0
  132. package/src/lib/format/limits.d.mts +24 -0
  133. package/src/lib/format/limits.d.ts +24 -0
  134. package/src/lib/format/limits.js +35 -0
  135. package/src/lib/format/limits.js.map +1 -0
  136. package/src/lib/format/limits.mjs +32 -0
  137. package/src/lib/format/limits.mjs.map +1 -0
  138. package/src/lib/format/list-checks.d.mts +44 -0
  139. package/src/lib/format/list-checks.d.ts +44 -0
  140. package/src/lib/format/list-checks.js +340 -0
  141. package/src/lib/format/list-checks.js.map +1 -0
  142. package/src/lib/format/list-checks.mjs +336 -0
  143. package/src/lib/format/list-checks.mjs.map +1 -0
  144. package/src/lib/format/map-checks.d.mts +33 -0
  145. package/src/lib/format/map-checks.d.ts +33 -0
  146. package/src/lib/format/map-checks.js +180 -0
  147. package/src/lib/format/map-checks.js.map +1 -0
  148. package/src/lib/format/map-checks.mjs +175 -0
  149. package/src/lib/format/map-checks.mjs.map +1 -0
  150. package/src/lib/format/mapchecks-part.d.mts +5 -0
  151. package/src/lib/format/mapchecks-part.d.ts +5 -0
  152. package/src/lib/format/mapchecks-part.js +11 -0
  153. package/src/lib/format/mapchecks-part.js.map +1 -0
  154. package/src/lib/format/mapchecks-part.mjs +6 -0
  155. package/src/lib/format/mapchecks-part.mjs.map +1 -0
  156. package/src/lib/format/option-checks.d.mts +16 -0
  157. package/src/lib/format/option-checks.d.ts +16 -0
  158. package/src/lib/format/option-checks.js +39 -0
  159. package/src/lib/format/option-checks.js.map +1 -0
  160. package/src/lib/format/option-checks.mjs +36 -0
  161. package/src/lib/format/option-checks.mjs.map +1 -0
  162. package/src/lib/format/options-part.d.mts +6 -0
  163. package/src/lib/format/options-part.d.ts +6 -0
  164. package/src/lib/format/options-part.js +11 -0
  165. package/src/lib/format/options-part.js.map +1 -0
  166. package/src/lib/format/options-part.mjs +7 -0
  167. package/src/lib/format/options-part.mjs.map +1 -0
  168. package/src/lib/format/page.d.mts +147 -71
  169. package/src/lib/format/page.d.ts +147 -71
  170. package/src/lib/format/page.js +5 -0
  171. package/src/lib/format/page.js.map +1 -1
  172. package/src/lib/format/page.mjs +5 -0
  173. package/src/lib/format/page.mjs.map +1 -1
  174. package/src/lib/format/part-checks.d.mts +20 -0
  175. package/src/lib/format/part-checks.d.ts +20 -0
  176. package/src/lib/format/part-checks.js +3 -0
  177. package/src/lib/format/part-checks.js.map +1 -0
  178. package/src/lib/format/part-checks.mjs +2 -0
  179. package/src/lib/format/part-checks.mjs.map +1 -0
  180. package/src/lib/format/references.d.mts +71 -65
  181. package/src/lib/format/references.d.ts +71 -65
  182. package/src/lib/format/references.js +194 -631
  183. package/src/lib/format/references.js.map +1 -1
  184. package/src/lib/format/references.mjs +194 -631
  185. package/src/lib/format/references.mjs.map +1 -1
  186. package/src/lib/format/seats-checks.d.mts +3 -0
  187. package/src/lib/format/seats-checks.d.ts +3 -0
  188. package/src/lib/format/seats-checks.js +13 -0
  189. package/src/lib/format/seats-checks.js.map +1 -0
  190. package/src/lib/format/seats-checks.mjs +10 -0
  191. package/src/lib/format/seats-checks.mjs.map +1 -0
  192. package/src/lib/format/sheet-head-checks.d.mts +4 -0
  193. package/src/lib/format/sheet-head-checks.d.ts +4 -0
  194. package/src/lib/format/sheet-head-checks.js +68 -0
  195. package/src/lib/format/sheet-head-checks.js.map +1 -0
  196. package/src/lib/format/sheet-head-checks.mjs +65 -0
  197. package/src/lib/format/sheet-head-checks.mjs.map +1 -0
  198. package/src/lib/format/signature-checks.d.mts +7 -0
  199. package/src/lib/format/signature-checks.d.ts +7 -0
  200. package/src/lib/format/signature-checks.js +32 -0
  201. package/src/lib/format/signature-checks.js.map +1 -0
  202. package/src/lib/format/signature-checks.mjs +29 -0
  203. package/src/lib/format/signature-checks.mjs.map +1 -0
  204. package/src/lib/format/signature-options.d.mts +24 -0
  205. package/src/lib/format/signature-options.d.ts +24 -0
  206. package/src/lib/format/signature-options.js +30 -0
  207. package/src/lib/format/signature-options.js.map +1 -0
  208. package/src/lib/format/signature-options.mjs +26 -0
  209. package/src/lib/format/signature-options.mjs.map +1 -0
  210. package/src/lib/format/slots-checks.d.mts +4 -0
  211. package/src/lib/format/slots-checks.d.ts +4 -0
  212. package/src/lib/format/slots-checks.js +21 -0
  213. package/src/lib/format/slots-checks.js.map +1 -0
  214. package/src/lib/format/slots-checks.mjs +18 -0
  215. package/src/lib/format/slots-checks.mjs.map +1 -0
  216. package/src/lib/format/statusbar-checks.d.mts +8 -0
  217. package/src/lib/format/statusbar-checks.d.ts +8 -0
  218. package/src/lib/format/statusbar-checks.js +35 -0
  219. package/src/lib/format/statusbar-checks.js.map +1 -0
  220. package/src/lib/format/statusbar-checks.mjs +32 -0
  221. package/src/lib/format/statusbar-checks.mjs.map +1 -0
  222. package/src/lib/format/table-checks.d.mts +21 -0
  223. package/src/lib/format/table-checks.d.ts +21 -0
  224. package/src/lib/format/table-checks.js +86 -0
  225. package/src/lib/format/table-checks.js.map +1 -0
  226. package/src/lib/format/table-checks.mjs +82 -0
  227. package/src/lib/format/table-checks.mjs.map +1 -0
  228. package/src/lib/format/translate.js +2 -2
  229. package/src/lib/format/translate.js.map +1 -1
  230. package/src/lib/format/translate.mjs +2 -2
  231. package/src/lib/format/translate.mjs.map +1 -1
  232. package/src/lib/format/upgrade.d.mts +6 -3
  233. package/src/lib/format/upgrade.d.ts +6 -3
  234. package/src/lib/format/upgrade.js +6 -3
  235. package/src/lib/format/upgrade.js.map +1 -1
  236. package/src/lib/format/upgrade.mjs +6 -3
  237. package/src/lib/format/upgrade.mjs.map +1 -1
  238. package/src/lib/format/validate.d.mts +18 -2
  239. package/src/lib/format/validate.d.ts +18 -2
  240. package/src/lib/format/validate.js +36 -3
  241. package/src/lib/format/validate.js.map +1 -1
  242. package/src/lib/format/validate.mjs +35 -3
  243. package/src/lib/format/validate.mjs.map +1 -1
  244. package/src/lib/format/version.d.mts +2 -2
  245. package/src/lib/format/version.d.ts +2 -2
  246. package/src/lib/format/version.js +2 -2
  247. package/src/lib/format/version.js.map +1 -1
  248. package/src/lib/format/version.mjs +2 -2
  249. package/src/lib/format/version.mjs.map +1 -1
  250. package/src/lib/format/words.d.mts +6 -2
  251. package/src/lib/format/words.d.ts +6 -2
  252. package/src/lib/format/words.js +13 -7
  253. package/src/lib/format/words.js.map +1 -1
  254. package/src/lib/format/words.mjs +13 -7
  255. package/src/lib/format/words.mjs.map +1 -1
  256. package/src/lib/parts.d.mts +45 -0
  257. package/src/lib/parts.d.ts +45 -0
  258. package/src/lib/parts.js +60 -0
  259. package/src/lib/parts.js.map +1 -0
  260. package/src/lib/parts.mjs +53 -0
  261. package/src/lib/parts.mjs.map +1 -0
  262. package/src/lib/record/cells.d.mts +3 -3
  263. package/src/lib/record/cells.d.ts +3 -3
  264. package/src/lib/record/cells.js +16 -15
  265. package/src/lib/record/cells.js.map +1 -1
  266. package/src/lib/record/cells.mjs +16 -15
  267. package/src/lib/record/cells.mjs.map +1 -1
  268. package/src/lib/record/compute.d.mts +16 -5
  269. package/src/lib/record/compute.d.ts +16 -5
  270. package/src/lib/record/compute.js +57 -16
  271. package/src/lib/record/compute.js.map +1 -1
  272. package/src/lib/record/compute.mjs +57 -16
  273. package/src/lib/record/compute.mjs.map +1 -1
  274. package/src/lib/record/data-source.d.mts +140 -3
  275. package/src/lib/record/data-source.d.ts +140 -3
  276. package/src/lib/record/data-source.js.map +1 -1
  277. package/src/lib/record/data-source.mjs.map +1 -1
  278. package/src/lib/record/drafts.d.mts +19 -0
  279. package/src/lib/record/drafts.d.ts +19 -0
  280. package/src/lib/record/drafts.js +35 -0
  281. package/src/lib/record/drafts.js.map +1 -0
  282. package/src/lib/record/drafts.mjs +31 -0
  283. package/src/lib/record/drafts.mjs.map +1 -0
  284. package/src/lib/record/env.d.mts +5 -1
  285. package/src/lib/record/env.d.ts +5 -1
  286. package/src/lib/record/env.js +9 -1
  287. package/src/lib/record/env.js.map +1 -1
  288. package/src/lib/record/env.mjs +8 -1
  289. package/src/lib/record/env.mjs.map +1 -1
  290. package/src/lib/record/form-limits.d.mts +81 -0
  291. package/src/lib/record/form-limits.d.ts +81 -0
  292. package/src/lib/record/form-limits.js +231 -0
  293. package/src/lib/record/form-limits.js.map +1 -0
  294. package/src/lib/record/form-limits.mjs +224 -0
  295. package/src/lib/record/form-limits.mjs.map +1 -0
  296. package/src/lib/record/form.d.mts +173 -4
  297. package/src/lib/record/form.d.ts +173 -4
  298. package/src/lib/record/form.js +792 -88
  299. package/src/lib/record/form.js.map +1 -1
  300. package/src/lib/record/form.mjs +792 -88
  301. package/src/lib/record/form.mjs.map +1 -1
  302. package/src/lib/record/limits-part.d.mts +6 -0
  303. package/src/lib/record/limits-part.d.ts +6 -0
  304. package/src/lib/record/limits-part.js +12 -0
  305. package/src/lib/record/limits-part.js.map +1 -0
  306. package/src/lib/record/limits-part.mjs +7 -0
  307. package/src/lib/record/limits-part.mjs.map +1 -0
  308. package/src/lib/record/line-choices.d.mts +30 -0
  309. package/src/lib/record/line-choices.d.ts +30 -0
  310. package/src/lib/record/line-choices.js +42 -0
  311. package/src/lib/record/line-choices.js.map +1 -0
  312. package/src/lib/record/line-choices.mjs +36 -0
  313. package/src/lib/record/line-choices.mjs.map +1 -0
  314. package/src/lib/record/locales/ar.js +10 -0
  315. package/src/lib/record/locales/ar.js.map +1 -1
  316. package/src/lib/record/locales/ar.mjs +10 -0
  317. package/src/lib/record/locales/ar.mjs.map +1 -1
  318. package/src/lib/record/locales/de.js +10 -0
  319. package/src/lib/record/locales/de.js.map +1 -1
  320. package/src/lib/record/locales/de.mjs +10 -0
  321. package/src/lib/record/locales/de.mjs.map +1 -1
  322. package/src/lib/record/locales/fr.js +10 -0
  323. package/src/lib/record/locales/fr.js.map +1 -1
  324. package/src/lib/record/locales/fr.mjs +10 -0
  325. package/src/lib/record/locales/fr.mjs.map +1 -1
  326. package/src/lib/record/memory-data-source.d.mts +44 -3
  327. package/src/lib/record/memory-data-source.d.ts +44 -3
  328. package/src/lib/record/memory-data-source.js +102 -40
  329. package/src/lib/record/memory-data-source.js.map +1 -1
  330. package/src/lib/record/memory-data-source.mjs +101 -40
  331. package/src/lib/record/memory-data-source.mjs.map +1 -1
  332. package/src/lib/record/memory-kept.d.mts +20 -0
  333. package/src/lib/record/memory-kept.d.ts +20 -0
  334. package/src/lib/record/memory-kept.js +46 -0
  335. package/src/lib/record/memory-kept.js.map +1 -0
  336. package/src/lib/record/memory-kept.mjs +43 -0
  337. package/src/lib/record/memory-kept.mjs.map +1 -0
  338. package/src/lib/record/memory-later.d.mts +19 -0
  339. package/src/lib/record/memory-later.d.ts +19 -0
  340. package/src/lib/record/memory-later.js +39 -0
  341. package/src/lib/record/memory-later.js.map +1 -0
  342. package/src/lib/record/memory-later.mjs +35 -0
  343. package/src/lib/record/memory-later.mjs.map +1 -0
  344. package/src/lib/record/memory-part.d.mts +7 -0
  345. package/src/lib/record/memory-part.d.ts +7 -0
  346. package/src/lib/record/memory-part.js +13 -0
  347. package/src/lib/record/memory-part.js.map +1 -0
  348. package/src/lib/record/memory-part.mjs +8 -0
  349. package/src/lib/record/memory-part.mjs.map +1 -0
  350. package/src/lib/record/messages.d.mts +17 -0
  351. package/src/lib/record/messages.d.ts +17 -0
  352. package/src/lib/record/messages.js +10 -0
  353. package/src/lib/record/messages.js.map +1 -1
  354. package/src/lib/record/messages.mjs +10 -0
  355. package/src/lib/record/messages.mjs.map +1 -1
  356. package/src/lib/record/option-rules.d.mts +50 -0
  357. package/src/lib/record/option-rules.d.ts +50 -0
  358. package/src/lib/record/option-rules.js +137 -0
  359. package/src/lib/record/option-rules.js.map +1 -0
  360. package/src/lib/record/option-rules.mjs +134 -0
  361. package/src/lib/record/option-rules.mjs.map +1 -0
  362. package/src/lib/record/rules.d.mts +2 -2
  363. package/src/lib/record/rules.d.ts +2 -2
  364. package/src/lib/record/rules.js +3 -3
  365. package/src/lib/record/rules.js.map +1 -1
  366. package/src/lib/record/rules.mjs +3 -3
  367. package/src/lib/record/rules.mjs.map +1 -1
  368. package/src/lib/record/run.js +9 -7
  369. package/src/lib/record/run.js.map +1 -1
  370. package/src/lib/record/run.mjs +9 -7
  371. package/src/lib/record/run.mjs.map +1 -1
  372. package/src/lib/record/search.d.mts +12 -2
  373. package/src/lib/record/search.d.ts +12 -2
  374. package/src/lib/record/search.js +29 -4
  375. package/src/lib/record/search.js.map +1 -1
  376. package/src/lib/record/search.mjs +29 -4
  377. package/src/lib/record/search.mjs.map +1 -1
  378. package/src/lib/record/set-when.d.mts +3 -3
  379. package/src/lib/record/set-when.d.ts +3 -3
  380. package/src/lib/record/set-when.js +9 -9
  381. package/src/lib/record/set-when.js.map +1 -1
  382. package/src/lib/record/set-when.mjs +9 -9
  383. package/src/lib/record/set-when.mjs.map +1 -1
  384. package/src/lib/record/signed.d.mts +21 -0
  385. package/src/lib/record/signed.d.ts +21 -0
  386. package/src/lib/record/signed.js +62 -0
  387. package/src/lib/record/signed.js.map +1 -0
  388. package/src/lib/record/signed.mjs +57 -0
  389. package/src/lib/record/signed.mjs.map +1 -0
  390. package/src/lib/record/taking.d.mts +60 -0
  391. package/src/lib/record/taking.d.ts +60 -0
  392. package/src/lib/record/taking.js +58 -0
  393. package/src/lib/record/taking.js.map +1 -0
  394. package/src/lib/record/taking.mjs +50 -0
  395. package/src/lib/record/taking.mjs.map +1 -0
  396. package/src/lib/record/value-map.js +11 -4
  397. package/src/lib/record/value-map.js.map +1 -1
  398. package/src/lib/record/value-map.mjs +11 -4
  399. package/src/lib/record/value-map.mjs.map +1 -1
  400. package/src/lib/record/values.d.mts +20 -0
  401. package/src/lib/record/values.d.ts +20 -0
  402. package/src/lib/record/values.js.map +1 -1
  403. package/src/lib/record/values.mjs.map +1 -1
  404. package/src/lib/record/waits.d.mts +6 -1
  405. package/src/lib/record/waits.d.ts +6 -1
  406. package/src/lib/record/waits.js +2 -2
  407. package/src/lib/record/waits.js.map +1 -1
  408. package/src/lib/record/waits.mjs +2 -2
  409. package/src/lib/record/waits.mjs.map +1 -1
  410. package/src/runtime.d.mts +16 -5
  411. package/src/runtime.d.ts +16 -5
  412. package/src/runtime.js +41 -3
  413. package/src/runtime.js.map +1 -1
  414. package/src/runtime.mjs +13 -0
  415. package/src/runtime.mjs.map +1 -1
@@ -1,34 +1,32 @@
1
+ import { changesOptions, optionsList } from './choice-options.mjs';
1
2
  import { BUILT_IN_NAMES, USER_PARTS } from '../expression/built-ins.mjs';
2
3
  import { compileModifier } from '../expression/modifier.mjs';
3
4
  import { compileExpression } from '../expression/expression.mjs';
5
+ import { functionTable } from '../expression/app-functions.mjs';
4
6
  import { dependencyOrder } from '../expression/order.mjs';
5
7
  import { valuesIn } from './words.mjs';
6
8
  import { BROWSER_HOTKEYS, hotkeyWords } from './hotkeys.mjs';
7
9
  import { checkButtonParts, checkHead } from './head-checks.mjs';
8
10
  import { checkSectionTracks } from './section-checks.mjs';
9
11
  import { fieldPattern } from './pattern.mjs';
10
- import { BULK_KINDS, bulkSettable } from './bulk-edit.mjs';
11
12
  import { DUPLICATE_KINDS } from './duplicate-kinds.mjs';
12
- import { IMPORT_KINDS, importable } from './import-fields.mjs';
13
13
  import { isTimeZone } from '../record/zones.mjs';
14
14
  import { fill, MESSAGES } from '../record/messages.mjs';
15
15
  import { autofillGroupProblem } from './autofill-group.mjs';
16
+ import { checkLimits } from './limits-checks.mjs';
17
+ import { checkSeats } from './seats-checks.mjs';
18
+ import { checkList, checkRelated } from './list-checks.mjs';
19
+ import { checkSlots } from './slots-checks.mjs';
20
+ import { checkSignature } from './signature-checks.mjs';
21
+ import { checkEndings } from './endings-checks.mjs';
22
+ import { checkAddressSearch, checkMap, checkPlace } from './map-checks.mjs';
23
+ import { checkLineKinds, checkTable } from './table-checks.mjs';
24
+ import { checkStatusbar } from './statusbar-checks.mjs';
25
+ import { checkOptionRules } from './option-checks.mjs';
16
26
  const has = (record, key) => Object.prototype.hasOwnProperty.call(record, key);
17
27
  /** The kinds of field an expression can give a value to: the rest hold records, lines or files. */
18
28
  const EXPRESSIBLE = new Set(['char', 'text', 'html', 'integer', 'float', 'monetary', 'boolean', 'date', 'datetime', 'selection', 'json']);
19
29
  const EXPRESSIBLE_WORDS = 'text, numbers, yes or no, dates, choices and json can';
20
- /** The kinds of field a list's filter bar can list the values of. */
21
- const LISTS_ITS_VALUES = new Set(['selection', 'many2one', 'many2many', 'boolean', 'char', 'integer']);
22
- /** Kinds of field a list's card shows as its picture: a picture held, or text holding a picture's address. */
23
- const PICTURES = new Set(['image', 'binary', 'char']);
24
- /** What a calendar's start and end can be: a day, or a day and a time. */
25
- const CALENDAR_TIMES = new Set(['date', 'datetime']);
26
- /** What a record's words on a calendar can be. */
27
- const CALENDAR_WORDS = new Set(['char', 'text', 'selection', 'many2one', 'integer', 'float', 'monetary', 'date', 'datetime']);
28
- /** What a calendar's columns come from: a value that repeats. */
29
- const CALENDAR_COLUMNS = new Set(['many2one', 'selection', 'char', 'integer']);
30
- /** What a record's colour comes from. */
31
- const CALENDAR_COLOURS = new Set(['many2one', 'selection', 'char', 'integer', 'boolean']);
32
30
  /** Names quoted and joined as a person lists them: "a", "b" and "c". */
33
31
  const listed = (names) => {
34
32
  const quoted = names.map((name) => `"${name}"`);
@@ -38,7 +36,12 @@ const listed = (names) => {
38
36
  export class ReferenceCheck {
39
37
  page;
40
38
  line;
39
+ options;
41
40
  issues = [];
41
+ /** What does not stop the page, said beside it: the kinds an expression hands its functions (`CheckOptions.kinds`). */
42
+ warnings = [];
43
+ /** The parts whose checks are owed, not come yet (the one-tag script's: later-checks.ts): run once they have. */
44
+ later = new Set();
42
45
  ids = new Map();
43
46
  /** Where the answers of each saved form placed here go, by name: the path of the part that took it first. */
44
47
  formNames = new Map();
@@ -47,11 +50,23 @@ export class ReferenceCheck {
47
50
  goingTo = [];
48
51
  /** The names of the slots the layout places. */
49
52
  slots = new Set();
50
- constructor(page, line) {
53
+ /** The app's functions, as the page's expressions call them; or why the names given cannot be. */
54
+ table;
55
+ tableProblem;
56
+ constructor(page, line, options = {}) {
51
57
  this.page = page;
52
58
  this.line = line;
59
+ this.options = options;
60
+ try {
61
+ this.table = functionTable(options.functions);
62
+ }
63
+ catch (error) {
64
+ this.tableProblem = error.message;
65
+ }
53
66
  }
54
67
  run() {
68
+ if (this.tableProblem)
69
+ this.report('(functions)', this.tableProblem);
55
70
  if (this.page.timeZone !== undefined)
56
71
  this.checkZone(this.page.timeZone, 'timeZone');
57
72
  this.checkFields(this.page.fields, 'fields', this.line?.name, this.line?.parent);
@@ -63,9 +78,10 @@ export class ReferenceCheck {
63
78
  walkNode: (node, path) => this.walkNode(node, path),
64
79
  checkModifiers: (owner, path, keys) => this.checkModifiers(owner, path, keys),
65
80
  checkSteps: (steps, path) => this.checkSteps(steps, path),
66
- checkPress: (button, path) => this.checkPress(button, path),
81
+ checkPress: (button, path) => this.checkPress(button, path, true),
67
82
  isPart: (id) => this.ids.has(id),
68
83
  isPlace: (id) => this.places.has(id),
84
+ owe: (part) => this.later.add(part),
69
85
  });
70
86
  if (this.page.on)
71
87
  this.checkEvents(this.page.on, 'on');
@@ -75,6 +91,16 @@ export class ReferenceCheck {
75
91
  this.checkDuplicates(this.page);
76
92
  if (this.page.records !== undefined && !this.line)
77
93
  this.checkRecords(this.page.records);
94
+ if (this.page.endings !== undefined && !this.line) {
95
+ if (this.page.data.kind !== 'responses')
96
+ this.report('endings', 'endings are for a page of responses, shown once it is sent');
97
+ // Each ending: in the one-tag script, its checks come with the answers part, which a page with any asks for.
98
+ if (Array.isArray(this.page.endings) && this.page.endings.length)
99
+ checkEndings(this.page, { ...this.fieldTools(), checkModifiers: (owner, path, keys) => this.checkModifiers(owner, path, keys), checkWords: (owner, path, keys, sent) => this.checkWords(owner, path, keys, sent) });
100
+ }
101
+ // A page's limits and Continue later: checked where it has either (in the one-tag script, by the limits part it asks for).
102
+ if (!this.line && (this.page.limits !== undefined || this.page.continueLater !== undefined))
103
+ checkLimits(this.page, { report: (path, message) => this.report(path, message), owe: (part) => this.later.add(part) });
78
104
  this.checkGoingTo();
79
105
  return this.issues;
80
106
  }
@@ -139,7 +165,7 @@ export class ReferenceCheck {
139
165
  if (!Array.isArray(children))
140
166
  return this.report(`${path}.lineForm`, 'a line’s form holds its parts: { "children": [...] }');
141
167
  const page = { ...this.page, fields: def.fields, layout: { type: 'sections', id: `${node.id}#line`, children }, on: undefined };
142
- const inside = new ReferenceCheck(page, { name: node.field, parent: this.page.fields });
168
+ const inside = new ReferenceCheck(page, { name: node.field, parent: this.page.fields }, this.options);
143
169
  inside.walkChildren(children, `${path}.lineForm`);
144
170
  inside.checkGoingTo();
145
171
  this.issues.push(...inside.issues);
@@ -161,8 +187,10 @@ export class ReferenceCheck {
161
187
  this.checkSteps(steps, `${path}.show.${target}`);
162
188
  }
163
189
  }
164
- /** A press: steps, an app action, or both — and its steps checked. */
165
- checkPress(button, path) {
190
+ /** A press: steps, an app action, or both — and its steps checked; on a button drawn on the record (`drawn`, not a list's row's), the `{field}`s its words show. */
191
+ checkPress(button, path, drawn = false) {
192
+ if (drawn && 'type' in button)
193
+ this.checkWords(button, path, ['label', 'pressedLabel']);
166
194
  // A button with a menu of its own and nothing else is a menu button.
167
195
  const menu = 'menu' in button && !!button.menu?.length;
168
196
  if (!button.steps && button.action === undefined && !menu)
@@ -229,7 +257,7 @@ export class ReferenceCheck {
229
257
  for (const [name, value] of Object.entries(step.into ?? {})) {
230
258
  this.need(name, `${at}.into.${name}`);
231
259
  try {
232
- compileExpression(value);
260
+ compileExpression(value, this.table);
233
261
  }
234
262
  catch (error) {
235
263
  this.report(`${at}.into.${name}`, `cannot read "${value}": ${error.message}`);
@@ -246,10 +274,22 @@ export class ReferenceCheck {
246
274
  }
247
275
  });
248
276
  }
249
- /** Words that show fields' values: each `{name}` a field of this page. */
250
- checkValuesIn(words, path) {
277
+ /**
278
+ * Words that show fields' values: each `{name}` a field of this page — in an ending's (`sent`), or `id`, the id the
279
+ * app's source gave the response.
280
+ */
281
+ checkValuesIn(words, path, sent = false) {
251
282
  for (const name of valuesIn(words))
252
- this.need(name, path);
283
+ if (!(sent && name === 'id' && !has(this.page.fields, 'id')))
284
+ this.need(name, path);
285
+ }
286
+ /** The words of a part that may show the answers given (`keys` of `owner`): each `{name}` a field of the page. */
287
+ checkWords(owner, path, keys, sent = false) {
288
+ for (const key of keys) {
289
+ const words = owner[key];
290
+ if (typeof words === 'string')
291
+ this.checkValuesIn(words, `${path}.${key}`, sent);
292
+ }
253
293
  }
254
294
  report(path, message) {
255
295
  this.issues.push({ path, message });
@@ -307,13 +347,14 @@ export class ReferenceCheck {
307
347
  continue;
308
348
  let compiled;
309
349
  try {
310
- compiled = compileModifier(value);
350
+ compiled = compileModifier(value, this.table);
311
351
  }
312
352
  catch (error) {
313
353
  this.report(`${path}.${key}`, `cannot read "${value}": ${error.message}`);
314
354
  continue;
315
355
  }
316
356
  this.checkReads(value, compiled, `${path}.${key}`, scope);
357
+ this.checkKinds(value, `${path}.${key}`, scope);
317
358
  }
318
359
  }
319
360
  /**
@@ -329,7 +370,7 @@ export class ReferenceCheck {
329
370
  continue;
330
371
  let fields;
331
372
  try {
332
- fields = compileModifier(where.condition).fields;
373
+ fields = compileModifier(where.condition, this.table).fields;
333
374
  }
334
375
  catch (error) {
335
376
  this.report(path, `cannot read the condition "${where.condition}": ${error.message}`);
@@ -360,6 +401,14 @@ export class ReferenceCheck {
360
401
  this.report(path, `"${source}" reads "${name}", which is not a field ${where}`);
361
402
  }
362
403
  }
404
+ /** The kinds of value an expression hands its functions, where the check reads them (`CheckOptions.kinds`). */
405
+ checkKinds(source, path, scope) {
406
+ if (!this.options.kinds)
407
+ return;
408
+ const parent = scope.lines === undefined ? undefined : (scope.parent ?? this.page.fields);
409
+ for (const words of this.options.kinds(source, { fields: scope.fields, ...(parent ? { parent } : {}) }))
410
+ this.warnings.push({ path, message: `"${source}": ${words}` });
411
+ }
363
412
  /**
364
413
  * An expression for a value must read, read only fields where it is worked
365
414
  * out, and add up only line fields that exist. Returns the fields it reads,
@@ -368,13 +417,14 @@ export class ReferenceCheck {
368
417
  checkExpression(source, path, scope) {
369
418
  let compiled;
370
419
  try {
371
- compiled = compileExpression(source);
420
+ compiled = compileExpression(source, this.table);
372
421
  }
373
422
  catch (error) {
374
423
  this.report(path, `cannot read "${source}": ${error.message}`);
375
424
  return null;
376
425
  }
377
426
  this.checkReads(source, compiled, path, scope);
427
+ this.checkKinds(source, path, scope);
378
428
  for (const sum of compiled.sums) {
379
429
  if (!has(scope.fields, sum.lines))
380
430
  continue;
@@ -418,11 +468,30 @@ export class ReferenceCheck {
418
468
  this.report(`${base}.${cycle[0]}.compute`, message);
419
469
  }
420
470
  }
471
+ /**
472
+ * A choice's options, a page's own and a table line's alike: each condition on one reads the record — a line's, its
473
+ * line and `parent.` (`options[].invisible`); its list's `dependsOn` names fields (`checkDependsOn`); an earlier
474
+ * question it carries them from (`optionsFrom.field`) is another choice of the page, never one carrying its own from
475
+ * this one — and never for a line, whose choices are its own or one of the app's lists.
476
+ */
477
+ checkChoices(name, def, path, fields, scope) {
478
+ optionsList(def.optionsFrom)?.dependsOn?.forEach((other, i) => this.checkDependsOn(name, other, `${path}.optionsFrom.dependsOn[${i}]`, fields, scope.lines, scope.parent));
479
+ // Options by condition, or carried from an earlier question: in the one-tag script, checked by the options part a
480
+ // page with any asks for.
481
+ if (changesOptions(def))
482
+ checkOptionRules(name, def, path, fields, scope, { ...this.fieldTools(), checkModifiers: (owner, at, keys, inside) => this.checkModifiers(owner, at, keys, inside) });
483
+ }
421
484
  checkFields(fields, base, lines, parent) {
422
485
  const scope = { fields, lines, ...(parent ? { parent } : {}) };
423
486
  this.checkWorkedOut(fields, base, scope);
424
487
  for (const [name, def] of Object.entries(fields)) {
425
488
  const path = `${base}.${name}`;
489
+ // A field's label, its help and its choices' words may show the answers given — the page's own fields', not a line's.
490
+ if (lines === undefined) {
491
+ this.checkWords(def, path, ['label', 'help']);
492
+ if (def.type === 'selection' && Array.isArray(def.options))
493
+ def.options.forEach((option, i) => this.checkWords(option ?? {}, `${path}.options[${i}]`, ['label']));
494
+ }
426
495
  if (def.type === 'monetary' && def.currencyField !== undefined)
427
496
  this.checkCurrency(def.currencyField, `${path}.currencyField`, fields, lines, parent);
428
497
  // What an amount is for, by a condition on the record it is on: "/month" on a rent.
@@ -462,7 +531,8 @@ export class ReferenceCheck {
462
531
  this.checkZone(linked.timeZone, `${path}.fields.${column}.timeZone`);
463
532
  }
464
533
  if (def.type === 'selection')
465
- def.optionsFrom?.dependsOn?.forEach((other, i) => this.need(other, `${path}.optionsFrom.dependsOn[${i}]`, fields));
534
+ this.checkChoices(name, def, path, fields, scope);
535
+ checkSeats(def, path, lines ?? this.line?.name, (at, message) => this.report(at, message));
466
536
  if (def.type === 'properties')
467
537
  def.definitionsFrom?.dependsOn?.forEach((other, i) => this.need(other, `${path}.definitionsFrom.dependsOn[${i}]`, fields));
468
538
  if (def.type === 'properties' && def.definitions) {
@@ -480,7 +550,7 @@ export class ReferenceCheck {
480
550
  // Its lines read the record they are on — on a line, that line — as parent.
481
551
  this.checkFields(def.fields, `${path}.fields`, name, fields);
482
552
  if ('lineKinds' in def && def.lineKinds)
483
- this.checkLineKinds(name, def.lineKinds, def.fields, `${path}.lineKinds`);
553
+ checkLineKinds(name, def.lineKinds, def.fields, `${path}.lineKinds`, this.fieldTools());
484
554
  if (def.sequenceField !== undefined) {
485
555
  const sequence = def.fields[def.sequenceField];
486
556
  if (!sequence)
@@ -492,6 +562,21 @@ export class ReferenceCheck {
492
562
  }
493
563
  }
494
564
  }
565
+ /**
566
+ * A field a choice's list changes with: one beside it, or on a line `parent.` and a field of the record it is on. A
567
+ * line's list never changes with its own column: choosing would ask for its choices again.
568
+ */
569
+ checkDependsOn(own, name, path, fields, lines, parent = this.page.fields) {
570
+ if (!name.startsWith('parent.')) {
571
+ if (lines !== undefined && name === own)
572
+ return this.report(path, `"${own}" cannot change with itself`);
573
+ return void this.need(name, path, fields);
574
+ }
575
+ if (lines === undefined)
576
+ return this.report(path, `"${name}": only a line’s choices read parent, the record it is on`);
577
+ if (!has(parent, name.slice('parent.'.length)))
578
+ this.report(path, `"${name}": "${name.slice('parent.'.length)}" is not a field of this page`);
579
+ }
495
580
  /** A money field's currency: a field beside it, or on a line `parent.` and a field of the record — one that holds a currency. */
496
581
  checkCurrency(name, path, fields, lines, parent = this.page.fields) {
497
582
  let currency;
@@ -507,33 +592,55 @@ export class ReferenceCheck {
507
592
  this.report(path, `"${name}" is a ${currency.type}; a currency field must be a many2one, selection or char`);
508
593
  }
509
594
  }
510
- checkLineKinds(field, kinds, lineFields, path) {
511
- const kindField = lineFields[kinds.field];
512
- if (!kindField)
513
- this.report(`${path}.field`, `"${kinds.field}" is not a field of the lines of "${field}"`);
514
- else if (kindField.type !== 'selection' && kindField.type !== 'char') {
515
- this.report(`${path}.field`, `"${kinds.field}" is a ${kindField.type}; the field that says what a line is must be a selection or char`);
516
- }
517
- else if (kindField.type === 'selection') {
518
- for (const which of ['section', 'note']) {
519
- const value = kinds[which] ?? which;
520
- if (!kindField.options.some((o) => o.value === value))
521
- this.report(`${path}.${which}`, `"${kinds.field}" has no option "${value}"`);
522
- }
523
- }
524
- const text = lineFields[kinds.text];
525
- if (!text)
526
- this.report(`${path}.text`, `"${kinds.text}" is not a field of the lines of "${field}"`);
527
- else if (text.type !== 'char' && text.type !== 'text') {
528
- this.report(`${path}.text`, `"${kinds.text}" is a ${text.type}; a section's or note's text must be a char or text field`);
529
- }
595
+ /** The tools a table of lines' checks are handed (table-checks.ts). */
596
+ tableTools() {
597
+ return {
598
+ ...this.fieldTools(),
599
+ claim: (id, path) => this.claim(id, path),
600
+ checkModifiers: (owner, path, keys, scope) => this.checkModifiers(owner, path, keys, scope),
601
+ checkPress: (button, path) => this.checkPress(button, path),
602
+ };
603
+ }
604
+ /** The tools a part's checks of a field are handed (part-checks.ts). */
605
+ fieldTools() {
606
+ return { page: this.page, need: (name, path, scope) => this.need(name, path, scope), report: (path, message) => this.report(path, message), owe: (part) => this.later.add(part) };
607
+ }
608
+ /** The tools a map's checks are handed (map-checks.ts). */
609
+ mapTools() {
610
+ return {
611
+ page: this.page,
612
+ need: (name, path, scope) => this.need(name, path, scope),
613
+ report: (path, message) => this.report(path, message),
614
+ checkModifiers: (owner, path, keys, scope) => this.checkModifiers(owner, path, keys, scope),
615
+ checkSteps: (steps, path) => this.checkSteps(steps, path),
616
+ here: () => this.here(),
617
+ owe: (part) => this.later.add(part),
618
+ };
619
+ }
620
+ /** The tools a list page's own checks are handed (list-checks.ts). */
621
+ listTools() {
622
+ return {
623
+ page: this.page,
624
+ need: (name, path, scope) => this.need(name, path, scope),
625
+ report: (path, message) => this.report(path, message),
626
+ claim: (id, path) => this.claim(id, path),
627
+ checkModifiers: (owner, path, keys, scope) => this.checkModifiers(owner, path, keys, scope),
628
+ checkPress: (button, path) => this.checkPress(button, path),
629
+ checkPlace: (at, path, scope, pin, card) => checkPlace(at, path, scope, pin, card, this.mapTools()),
630
+ here: () => this.here(),
631
+ onLine: !!this.line,
632
+ checkCurrency: (name, path, fields, lines, parent) => this.checkCurrency(name, path, fields, lines, parent),
633
+ checkZone: (zone, path) => this.checkZone(zone, path),
634
+ checkFilterItem: (item, at, scope) => this.checkFilterItem(item, at, scope),
635
+ owe: (part) => this.later.add(part),
636
+ };
530
637
  }
531
638
  /** A page's own buttons at its foot: buttons as any. */
532
639
  walkFooter(footer, path) {
533
640
  footer?.forEach((button, i) => {
534
641
  this.claim(button.id, `${path}.footer[${i}]`);
535
642
  this.checkModifiers(button, `${path}.footer[${i}]`);
536
- this.checkPress(button, `${path}.footer[${i}]`);
643
+ this.checkPress(button, `${path}.footer[${i}]`, true);
537
644
  });
538
645
  }
539
646
  walkRoot(root, path) {
@@ -547,405 +654,18 @@ export class ReferenceCheck {
547
654
  if (root.type === 'sections')
548
655
  return this.walkChildren(root.children, path);
549
656
  if (root.type === 'list')
550
- return this.walkList(root, path);
657
+ return checkList(root, path, this.listTools());
658
+ // A wizard's own buttons' words, and each step's title and words, may show the answers given: "Your visit to {branch}".
659
+ this.checkWords(root, path, ['nextLabel', 'backLabel', 'finishLabel']);
551
660
  root.children.forEach((step, i) => {
552
661
  const stepPath = `${path}.children[${i}]`;
553
662
  this.places.add(step.id);
554
663
  this.claim(step.id, stepPath);
555
664
  this.checkModifiers(step, stepPath);
665
+ this.checkWords(step, stepPath, ['label', 'description']);
556
666
  this.walkChildren(step.children, stepPath);
557
667
  });
558
668
  }
559
- walkList(list, path) {
560
- if (this.page.data.kind !== 'record')
561
- this.report('data', 'a list shows records: its data needs kind "record" and a model');
562
- this.checkListed(list, this.page.fields, path);
563
- list.groupBy?.forEach((name, i) => this.need(name, `${path}.groupBy[${i}]`));
564
- // A list's filters are about its own records: their fields are the page's, and there is no record to take a value from.
565
- const walk = (item, at) => {
566
- if ('any' in item)
567
- return item.any.forEach((inner, i) => walk(inner, `${at}.any[${i}]`));
568
- if ('all' in item)
569
- return item.all.forEach((inner, i) => walk(inner, `${at}.all[${i}]`));
570
- this.need(item.field, `${at}.field`);
571
- if (item.valueFrom !== undefined)
572
- this.report(`${at}.valueFrom`, "a list's filter compares with values, not with another field");
573
- };
574
- const named = new Set();
575
- list.filters?.forEach((filter, i) => {
576
- this.claim(filter.id, `${path}.filters[${i}]`);
577
- named.add(filter.id);
578
- filter.filter?.forEach((item, j) => walk(item, `${path}.filters[${i}].filter[${j}]`));
579
- // A period is of a date, or a date and time.
580
- const dated = filter.period ? this.need(filter.period.field, `${path}.filters[${i}].period.field`) : undefined;
581
- if (dated && dated.type !== 'date' && dated.type !== 'datetime')
582
- this.report(`${path}.filters[${i}].period.field`, `"${filter.period?.field}" is a ${dated.type}; a period is of a date, or a date and time`);
583
- });
584
- list.defaultFilters?.forEach((id, i) => {
585
- if (!named.has(id))
586
- this.report(`${path}.defaultFilters[${i}]`, `no filter "${id}" in this list`);
587
- });
588
- list.actions?.forEach((button, i) => {
589
- this.claim(button.id, `${path}.actions[${i}]`);
590
- this.checkPress(button, `${path}.actions[${i}]`);
591
- });
592
- if (list.map)
593
- this.walkListMap(list.map, `${path}.map`);
594
- if (list.calendar)
595
- this.walkListCalendar(list, `${path}.calendar`);
596
- this.checkListCells(list, path);
597
- this.checkListRows(list, path);
598
- }
599
- /**
600
- * A list's cards, gallery, columns people choose and rows' own rules: a
601
- * card's fields the page's, its picture one that can hold a picture; the
602
- * optional columns among the columns; a row's tones, bold and buttons
603
- * reading the list's record (its fields, `id`, `user`); and the list
604
- * opening on one thing.
605
- */
606
- checkListRows(list, path) {
607
- if (list.card)
608
- this.checkListCard(list.card, `${path}.card`, this.page.fields);
609
- for (const column of Object.keys(list.optionalColumns ?? {})) {
610
- if (!list.columns.includes(column))
611
- this.report(`${path}.optionalColumns.${column}`, `"${column}" is no column of this list: a person shows or hides its columns`);
612
- }
613
- // A column a person may hide, every one hidden at first: the list would open with no column but its own.
614
- if (list.optionalColumns && list.columns.every((column) => list.optionalColumns?.[column] === 'hide'))
615
- this.report(`${path}.optionalColumns`, 'every column starts hidden: show one at least, as the list opens');
616
- if (list.gallery?.opens === 'gallery') {
617
- if (list.map?.view && list.map.view !== 'list')
618
- this.report(`${path}.gallery.opens`, 'the list opens on its map (map.view) and on its gallery: say one');
619
- if (list.calendar?.opens === 'calendar')
620
- this.report(`${path}.gallery.opens`, 'the list opens on its calendar and on its gallery: say one');
621
- }
622
- list.rowTones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.rowTones[${i}]`, ['when']));
623
- this.checkModifiers(list, path, ['rowBold']);
624
- list.rowButtons?.forEach((button, i) => {
625
- this.claim(button.id, `${path}.rowButtons[${i}]`);
626
- this.checkPress(button, `${path}.rowButtons[${i}]`);
627
- // Shown by a condition on each row's record.
628
- this.checkModifiers(button, `${path}.rowButtons[${i}]`, ['invisible']);
629
- });
630
- }
631
- /**
632
- * What a list's card shows — a list page's, or a related list's of its own records' fields: its title, subtitle,
633
- * status and lines each a field, its picture one that can hold a picture.
634
- */
635
- checkListCard(card, at, fields) {
636
- for (const key of ['title', 'subtitle', 'status'])
637
- if (card[key] !== undefined)
638
- this.need(card[key], `${at}.${key}`, fields);
639
- if (card.image !== undefined) {
640
- const def = this.need(card.image, `${at}.image`, fields);
641
- if (def && !PICTURES.has(def.type))
642
- this.report(`${at}.image`, `"${card.image}" is a ${def.type}; a card's picture is an image or binary field, or text holding a picture's address`);
643
- }
644
- card.fields?.forEach((line, i) => (Array.isArray(line) ? line : [line]).forEach((part, j) => this.need(typeof part === 'string' ? part : part.field, `${at}.fields[${i}]${Array.isArray(line) ? `[${j}]` : ''}${typeof part === 'string' ? '' : '.field'}`, fields)));
645
- }
646
- /**
647
- * A list's calendar: when each record starts (a date, or a date and time)
648
- * and ends (the same kind), its words, its colour and its columns, each
649
- * from a field that can; the view it opens on is one it offers, its hours
650
- * run forward, and the list opens on one thing.
651
- */
652
- walkListCalendar(list, path) {
653
- const calendar = list.calendar;
654
- const typed = (name, key, ok, says) => {
655
- if (name === undefined)
656
- return undefined;
657
- const def = this.need(name, `${path}.${key}`);
658
- if (def && !ok.has(def.type))
659
- this.report(`${path}.${key}`, `"${name}" is a ${def.type}; ${says}`);
660
- return def;
661
- };
662
- const start = typed(calendar.start, 'start', CALENDAR_TIMES, "a calendar's start is a date, or a date and time");
663
- const end = typed(calendar.end, 'end', CALENDAR_TIMES, "a calendar's end is a date, or a date and time");
664
- if (start && end && CALENDAR_TIMES.has(start.type) && CALENDAR_TIMES.has(end.type) && start.type !== end.type) {
665
- this.report(`${path}.end`, `"${calendar.end}" is a ${end.type}; an end is the same kind of field as its start, a ${start.type}`);
666
- }
667
- typed(calendar.title, 'title', CALENDAR_WORDS, "a record's words on a calendar are a field that can be said");
668
- typed(calendar.colour, 'colour', CALENDAR_COLOURS, 'a colour comes from a link, a choice, text, a whole number or a yes or no');
669
- typed(calendar.resource, 'resource', CALENDAR_COLUMNS, 'a column for each comes from a link, a choice, text or a whole number');
670
- const views = calendar.views ?? ['day', 'week', 'month', 'agenda'];
671
- views.forEach((view, i) => {
672
- if (views.indexOf(view) !== i)
673
- this.report(`${path}.views[${i}]`, `"${view}" is offered twice`);
674
- });
675
- if (calendar.view && !views.includes(calendar.view))
676
- this.report(`${path}.view`, `"${calendar.view}" is not one of its views (${views.join(', ')})`);
677
- if (calendar.hours && calendar.hours.end <= calendar.hours.start)
678
- this.report(`${path}.hours`, `the hours end at ${calendar.hours.end}, before they start at ${calendar.hours.start}`);
679
- if (calendar.opens === 'calendar' && list.map?.view && list.map.view !== 'list')
680
- this.report(`${path}.opens`, 'the list opens on its map (map.view) and on its calendar: say one');
681
- }
682
- /**
683
- * A list drawn as a backlog: each column's cells are a column's, a lead's
684
- * icon is by a field's values; the filter bar's fields can list their
685
- * values; rows nest under a link to the list's own records; and buttons for
686
- * the rows chosen need a box to choose one.
687
- */
688
- checkListCells(list, path) {
689
- if (list.filterFields && list.search !== 'fields')
690
- this.report(`${path}.filterFields`, 'the filter fields are for the "fields" search: add "search": "fields"');
691
- list.filterFields?.forEach((name, i) => {
692
- const def = this.need(name, `${path}.filterFields[${i}]`);
693
- if (def && !LISTS_ITS_VALUES.has(def.type))
694
- this.report(`${path}.filterFields[${i}]`, `"${name}" is a ${def.type}; a filter lists the values of a choice, a link, links, a yes or no, text or a whole number`);
695
- });
696
- // A filter's own name is for a filter the bar offers.
697
- for (const name of Object.keys(list.filterLabels ?? {})) {
698
- if (!list.filterFields?.includes(name))
699
- this.report(`${path}.filterLabels.${name}`, `"${name}" is no field of filterFields: its name is for a filter the bar offers`);
700
- }
701
- if (list.parentField !== undefined) {
702
- const def = this.need(list.parentField, `${path}.parentField`);
703
- const model = this.page.data.kind === 'record' ? this.page.data.model : '';
704
- if (def && (def.type !== 'many2one' || def.relation !== model)) {
705
- this.report(`${path}.parentField`, `"${list.parentField}" ${def.type === 'many2one' ? `links to ${def.relation}` : `is a ${def.type}`}; a parent is a many2one to the list’s own model, ${model}`);
706
- }
707
- }
708
- if (list.choose === false && list.actions?.length)
709
- this.report(`${path}.actions`, 'a list’s buttons act on the rows chosen: with "choose": false there is no way to choose one');
710
- this.checkBulkEdit(list, path);
711
- this.checkImport(list, path);
712
- }
713
- /**
714
- * What a list shows of its records — a list page's, or a related list's of its own records' fields: its columns, its
715
- * order and the fields its search looks in, each a field; each column's cells for a column of it, a sum, an average,
716
- * the lowest and the highest of a number column (any column's count), a lead's icon by a field's values.
717
- */
718
- checkListed(list, fields, path) {
719
- list.columns.forEach((name, i) => this.need(name, `${path}.columns[${i}]`, fields));
720
- list.sort?.forEach((order, i) => this.need(order.field, `${path}.sort[${i}].field`, fields));
721
- list.searchFields?.forEach((name, i) => this.need(name, `${path}.searchFields[${i}]`, fields));
722
- for (const [column, cell] of Object.entries(list.cells ?? {})) {
723
- const at = `${path}.cells.${column}`;
724
- if (!list.columns.includes(column)) {
725
- this.report(at, `"${column}" is no column of this list: its cells are for its columns`);
726
- continue;
727
- }
728
- const kind = has(fields, column) ? fields[column].type : undefined;
729
- if (cell.total && cell.total !== 'count' && kind && kind !== 'integer' && kind !== 'float' && kind !== 'monetary') {
730
- this.report(`${at}.total`, `"${column}" is a ${kind}; its total "${cell.total}" is of numbers — an integer, a float or an amount — and any column's is "count"`);
731
- }
732
- if (!cell.lead)
733
- continue;
734
- const def = this.need(cell.lead.field, `${at}.lead.field`, fields);
735
- if (def?.type !== 'selection')
736
- continue;
737
- const values = new Set(def.options.map((option) => String(option.value)));
738
- for (const value of Object.keys(cell.lead.looks))
739
- if (!values.has(value))
740
- this.report(`${at}.lead.looks.${value}`, `"${value}" is no value of "${cell.lead.field}"`);
741
- }
742
- }
743
- /** A list's Edit: each field it names one value can set in many records, and rows to choose to set it on. */
744
- checkBulkEdit(list, path) {
745
- if (!list.bulkEdit)
746
- return;
747
- if (list.choose === false)
748
- this.report(`${path}.bulkEdit`, 'Edit changes the rows chosen: with "choose": false there is no way to choose one');
749
- if (list.bulkEdit === true) {
750
- if (!list.columns.some((name) => bulkSettable(this.page.fields[name])))
751
- this.report(`${path}.bulkEdit`, 'Edit offers the columns one value can set, and none of these can: name the fields it offers');
752
- return;
753
- }
754
- const named = list.bulkEdit;
755
- named.forEach((name, i) => {
756
- const at = `${path}.bulkEdit[${i}]`;
757
- if (named.indexOf(name) !== i)
758
- this.report(at, `"${name}" is offered twice`);
759
- const def = this.need(name, at);
760
- if (!def)
761
- return;
762
- if (!BULK_KINDS.has(def.type))
763
- this.report(at, `"${name}" is a ${def.type}; one value set in many records is words, a number, money, a yes or no, a date, a choice or a link`);
764
- else if (def.readonly === true || def.compute)
765
- this.report(at, `"${name}" is ${def.compute ? 'worked out from other fields' : 'read-only'}; Edit sets a field people can change`);
766
- });
767
- }
768
- /** A list's Import: each field it names one a file's cell can fill, and at least one such field on the page. */
769
- checkImport(list, path) {
770
- if (!list.import)
771
- return;
772
- if (list.import === true) {
773
- if (!Object.values(this.page.fields).some((def) => importable(def)))
774
- this.report(`${path}.import`, 'Import fills the fields a file’s cell can, and this page has none: words, a number, money, yes or no, a date, a choice or a link people can change');
775
- return;
776
- }
777
- const named = list.import;
778
- named.forEach((name, i) => {
779
- const at = `${path}.import[${i}]`;
780
- if (named.indexOf(name) !== i)
781
- this.report(at, `"${name}" is offered twice`);
782
- const def = this.need(name, at);
783
- if (!def)
784
- return;
785
- if (!IMPORT_KINDS.has(def.type))
786
- this.report(at, `"${name}" is a ${def.type}; a file's cell fills words, a number, money, a yes or no, a date, a choice, or a link or links by name`);
787
- else if (def.readonly === true || def.compute)
788
- this.report(at, `"${name}" is ${def.compute ? 'worked out from other fields' : 'read-only'}; Import fills a field people can change`);
789
- });
790
- }
791
- /** A list's map: where each record is, and the fields its pin and its card show, each of a type that can. */
792
- walkListMap(map, path) {
793
- const pair = map.latitude !== undefined || map.longitude !== undefined;
794
- if (map.point !== undefined && pair)
795
- this.report(path, 'a map takes point, or latitude and longitude, not both');
796
- else if (map.point === undefined && (map.latitude === undefined || map.longitude === undefined))
797
- this.report(path, 'a map needs where each record is: point, or latitude and longitude');
798
- this.checkPlace(map, path, this.here(), map.pin, map.card);
799
- if (map.regions)
800
- this.need(map.regions.field, `${path}.regions.field`);
801
- if (typeof map.heat === 'object' && map.heat.weight !== undefined) {
802
- const def = this.need(map.heat.weight, `${path}.heat.weight`);
803
- if (def && def.type !== 'float' && def.type !== 'integer' && def.type !== 'monetary')
804
- this.report(`${path}.heat.weight`, `"${map.heat.weight}" is a ${def.type}; a weight is a number field`);
805
- }
806
- }
807
- /**
808
- * Where a place is kept, and the fields its pin and its card show, each of a
809
- * type that can: a page's fields, or a line's when the place is a line's.
810
- */
811
- checkPlace(at, path, scope, pin, card) {
812
- const typed = (name, where, ok, says) => {
813
- if (name === undefined)
814
- return;
815
- const def = this.need(name, where, scope.fields);
816
- if (def && !ok(def.type))
817
- this.report(where, `"${name}" is a ${def.type}; ${says}`);
818
- };
819
- const number = (type) => type === 'float' || type === 'integer';
820
- const picture = (type) => type === 'image' || type === 'binary';
821
- typed(at.point, `${path}.point`, (type) => type === 'json', 'a point is kept in a json field');
822
- typed(at.latitude, `${path}.latitude`, number, 'a latitude is a number field (float or integer)');
823
- typed(at.longitude, `${path}.longitude`, number, 'a longitude is a number field (float or integer)');
824
- if (pin) {
825
- typed(pin.image, `${path}.pin.image`, picture, "a pin's photo is an image or binary field");
826
- typed(pin.turn, `${path}.pin.turn`, number, 'a turn is a number field, in degrees');
827
- if (pin.iconField !== undefined)
828
- this.need(pin.iconField, `${path}.pin.iconField`, scope.fields);
829
- if (pin.label !== undefined)
830
- this.need(pin.label, `${path}.pin.label`, scope.fields);
831
- pin.tones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.pin.tones[${i}]`, ['when'], scope));
832
- pin.money?.per?.forEach((item, i) => this.checkModifiers(item, `${path}.pin.money.per[${i}]`, ['when'], scope));
833
- }
834
- if (card) {
835
- if (card.title !== undefined)
836
- this.need(card.title, `${path}.card.title`, scope.fields);
837
- typed(card.image, `${path}.card.image`, picture, "a card's picture is an image or binary field");
838
- // A line is a part, or several on one line; a part a field's name, or a field with its words.
839
- card.fields?.forEach((line, i) => (Array.isArray(line) ? line : [line]).forEach((part, j) => {
840
- const at = `${path}.card.fields[${i}]${Array.isArray(line) ? `[${j}]` : ''}${typeof part === 'string' ? '' : '.field'}`;
841
- this.need(typeof part === 'string' ? part : part.field, at, scope.fields);
842
- }));
843
- typed(card.href, `${path}.card.href`, (type) => type === 'char' || type === 'text', "a card's link is a text field holding a web address");
844
- }
845
- }
846
- /**
847
- * A form's map: each layer one kind — the record's place, a place for each
848
- * of its lines, a route between layers, or the reader's own place — its
849
- * fields where that kind keeps them, and a route's ends layers it can join.
850
- */
851
- walkMap(node, path) {
852
- const layers = new Map();
853
- node.layers.forEach((layer, i) => {
854
- const at = `${path}.layers[${i}]`;
855
- if (layers.has(layer.id))
856
- this.report(`${at}.id`, `two layers are called "${layer.id}"`);
857
- const pair = layer.latitude !== undefined || layer.longitude !== undefined;
858
- const placed = layer.point !== undefined || pair;
859
- const kinds = [placed, !!layer.route, !!layer.myLocation, !!layer.regions, !!layer.reachable].filter(Boolean).length;
860
- if (kinds !== 1)
861
- this.report(at, 'a layer is one of: a place (point, or latitude and longitude, for the record or for each of its lines), a route, myLocation, regions, or reachable');
862
- if (layer.regions?.choose !== undefined)
863
- this.need(layer.regions.choose, `${at}.regions.choose`);
864
- if (layer.point !== undefined && pair)
865
- this.report(at, 'a layer takes point, or latitude and longitude, not both');
866
- else if (layer.point === undefined && pair && (layer.latitude === undefined || layer.longitude === undefined))
867
- this.report(at, 'a layer needs both latitude and longitude');
868
- if (layer.lines !== undefined && !placed)
869
- this.report(`${at}.lines`, "a layer of lines needs where each line is: point, or latitude and longitude, of the line's fields");
870
- if (layer.live && (!placed || layer.lines !== undefined))
871
- this.report(`${at}.live`, "live is for a layer of the record's own place: it moves as its fields change");
872
- this.checkModifiers(layer, at, ['invisible']);
873
- layers.set(layer.id, { lines: layer.lines !== undefined, place: placed || !!layer.myLocation });
874
- if (!placed)
875
- return;
876
- if (layer.lines === undefined)
877
- return this.checkPlace(layer, at, this.here(), layer.pin, layer.card);
878
- const def = this.need(layer.lines, `${at}.lines`);
879
- if (!def)
880
- return;
881
- if (def.type !== 'one2many')
882
- return this.report(`${at}.lines`, `"${layer.lines}" is a ${def.type}; a layer of lines is a one2many field`);
883
- this.checkPlace(layer, at, { fields: def.fields, lines: layer.lines, parent: this.page.fields }, layer.pin, layer.card);
884
- });
885
- node.layers.forEach((layer, i) => {
886
- const route = layer.route;
887
- if (!route)
888
- return;
889
- const at = `${path}.layers[${i}].route`;
890
- const end = (id, key) => {
891
- if (id === undefined)
892
- return;
893
- const found = layers.get(id);
894
- if (!found)
895
- this.report(`${at}.${key}`, `no layer "${id}" on this map`);
896
- else if (!found.place || found.lines)
897
- this.report(`${at}.${key}`, `a route's ${key} is a layer of one place, or the reader's own; "${id}" is not`);
898
- };
899
- end(route.from, 'from');
900
- end(route.to, 'to');
901
- if (route.through !== undefined) {
902
- const found = layers.get(route.through);
903
- if (!found)
904
- this.report(`${at}.through`, `no layer "${route.through}" on this map`);
905
- else if (!found.lines)
906
- this.report(`${at}.through`, `a route goes through a layer of lines; "${route.through}" is not one`);
907
- }
908
- if (route.to === undefined && route.through === undefined)
909
- this.report(at, 'a route needs where it goes: to, or through');
910
- const written = (name, key, ok, says) => {
911
- if (name === undefined)
912
- return;
913
- const def = this.need(name, `${at}.write.${key}`);
914
- if (def && !ok(def.type))
915
- this.report(`${at}.write.${key}`, `"${name}" is a ${def.type}; ${says}`);
916
- };
917
- const number = (type) => type === 'float' || type === 'integer';
918
- written(route.write?.seconds, 'seconds', number, 'seconds are a number field');
919
- written(route.write?.metres, 'metres', number, 'metres are a number field');
920
- written(route.write?.line, 'line', (type) => type === 'json', 'a line is kept in a json field');
921
- });
922
- node.layers.forEach((layer, i) => {
923
- if (!layer.reachable)
924
- return;
925
- const found = layers.get(layer.reachable.from);
926
- const at = `${path}.layers[${i}].reachable.from`;
927
- if (!found)
928
- this.report(at, `no layer "${layer.reachable.from}" on this map`);
929
- else if (!found.place || found.lines)
930
- this.report(at, `a reachable area is worked out from a layer of one place, or the reader's own; "${layer.reachable.from}" is not`);
931
- });
932
- node.fences?.forEach((fence, i) => {
933
- const at = `${path}.fences[${i}]`;
934
- const watched = node.layers.find((layer) => layer.id === fence.layer);
935
- if (!watched)
936
- this.report(`${at}.layer`, `no layer "${fence.layer}" on this map`);
937
- else if (!watched.live && !watched.myLocation)
938
- this.report(`${at}.layer`, `a fence watches a place that moves: a live layer, or the reader's own; "${fence.layer}" is not`);
939
- if (fence.area !== undefined) {
940
- const def = this.need(fence.area, `${at}.area`);
941
- if (def && def.type !== 'json')
942
- this.report(`${at}.area`, `"${fence.area}" is a ${def.type}; an area is kept in a json field`);
943
- }
944
- if (fence.within !== undefined && !node.layers.some((layer) => layer.id === fence.within && layer.reachable))
945
- this.report(`${at}.within`, `a fence is within a reachable area of this map; "${fence.within}" is not one`);
946
- this.checkSteps(fence.steps, `${at}.steps`);
947
- });
948
- }
949
669
  walkChildren(children, path) {
950
670
  children.forEach((child, i) => this.walkNode(child, `${path}.children[${i}]`));
951
671
  }
@@ -977,13 +697,13 @@ export class ReferenceCheck {
977
697
  case 'form':
978
698
  return this.checkFormNode(node, path);
979
699
  case 'button':
980
- return this.checkPress(node, path);
700
+ return this.checkPress(node, path, true);
981
701
  case 'text':
982
702
  return this.checkValuesIn(node.text, `${path}.text`);
983
703
  case 'map':
984
- return this.walkMap(node, path);
704
+ return checkMap(node, path, this.mapTools());
985
705
  case 'related':
986
- return this.checkRelated(node, path);
706
+ return checkRelated(node, path, this.listTools());
987
707
  case 'slot':
988
708
  this.slots.add(node.name);
989
709
  return;
@@ -1008,48 +728,6 @@ export class ReferenceCheck {
1008
728
  else
1009
729
  this.formNames.set(node.name, path);
1010
730
  }
1011
- /**
1012
- * A related list: on a record's page, its columns, order, search, cells, card and rows' rules of its own records'
1013
- * fields, its link to this record a link of theirs to this model where its fields name it, and its filter's
1014
- * `valueFrom` this record's.
1015
- */
1016
- checkRelated(node, path) {
1017
- // The quick check reads no shapes: a part with nothing to go by is said here.
1018
- if (typeof node.model !== 'string' || typeof node.field !== 'string' || !Array.isArray(node.columns) || !node.fields || typeof node.fields !== 'object') {
1019
- return this.report(path, 'a related list needs the model of its records, their link to this record (field), their fields and its columns');
1020
- }
1021
- const model = this.page.data.kind === 'record' ? this.page.data.model : null;
1022
- if (!model || this.line)
1023
- this.report(path, 'a related list shows the records that point at a record: it belongs on a record’s page');
1024
- const fields = node.fields;
1025
- this.checkListed(node, fields, path);
1026
- // On a phone its rows are cards, as a list's: of its own records' fields.
1027
- if (node.card)
1028
- this.checkListCard(node.card, `${path}.card`, fields);
1029
- // Its rows' own rules, as a list's, each read on the row's record: its own fields, its id, the person, the context.
1030
- const row = { fields };
1031
- node.rowTones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.rowTones[${i}]`, ['when'], row));
1032
- this.checkModifiers(node, path, ['rowBold'], row);
1033
- node.rowButtons?.forEach((button, i) => {
1034
- this.claim(button.id, `${path}.rowButtons[${i}]`);
1035
- this.checkPress(button, `${path}.rowButtons[${i}]`);
1036
- this.checkModifiers(button, `${path}.rowButtons[${i}]`, ['invisible'], row);
1037
- });
1038
- const link = has(fields, node.field) ? fields[node.field] : undefined;
1039
- if (link && model && (link.type !== 'many2one' || link.relation !== model)) {
1040
- this.report(`${path}.field`, `"${node.field}" ${link.type === 'many2one' ? `links to ${link.relation}` : `is a ${link.type}`}; the records listed point at this one by a many2one to ${model}`);
1041
- }
1042
- for (const [name, def] of Object.entries(fields)) {
1043
- if (def.type === 'monetary' && def.currencyField !== undefined)
1044
- this.checkCurrency(def.currencyField, `${path}.fields.${name}.currencyField`, fields, undefined, fields);
1045
- if (def.type === 'datetime' && def.timeZone !== undefined)
1046
- this.checkZone(def.timeZone, `${path}.fields.${name}.timeZone`);
1047
- }
1048
- // Its filter is about its records, by their fields; what it compares with may be read from this record.
1049
- node.filter?.forEach((item, i) => this.checkFilterItem(item, `${path}.filter[${i}]`, this.here()));
1050
- if (node.viewAll && node.viewAll.page === this.page.id)
1051
- this.report(`${path}.viewAll.page`, 'View all opens a list of the records related: this page is the record they point at');
1052
- }
1053
731
  walkTabs(node, path) {
1054
732
  node.children.forEach((tab, i) => {
1055
733
  const tabPath = `${path}.children[${i}]`;
@@ -1068,36 +746,13 @@ export class ReferenceCheck {
1068
746
  this.need(sheet.title.avatarField, `${path}.title.avatarField`);
1069
747
  sheet.title.placeholderWhen?.forEach((item, i) => this.checkModifiers(item, `${path}.title.placeholderWhen[${i}]`, ['when']));
1070
748
  }
1071
- if (sheet.statusbar) {
1072
- const { field, visibleStates, durationsField, fold, saves, clickable } = sheet.statusbar;
1073
- const at = `${path}.statusbar`;
1074
- this.checkModifiers(sheet.statusbar, at);
1075
- if (durationsField !== undefined) {
1076
- const times = this.need(durationsField, `${at}.durationsField`);
1077
- if (times && times.type !== 'json')
1078
- this.report(`${at}.durationsField`, `"${durationsField}" is a ${times.type}; time per step comes from a json field`);
1079
- }
1080
- if (saves && !clickable)
1081
- this.report(`${at}.saves`, 'a click saves only where a step can be clicked (clickable)');
1082
- const def = this.need(field, `${path}.statusbar.field`);
1083
- if (fold && def && def.type !== 'many2one')
1084
- this.report(`${at}.fold`, 'steps fold by their record (a stage), so only a link’s steps fold; a choice keeps the steps it shows with visibleStates');
1085
- if (def && def.type !== 'selection' && def.type !== 'many2one') {
1086
- this.report(`${path}.statusbar.field`, `"${field}" is a ${def.type}; a statusbar needs a selection or many2one`);
1087
- }
1088
- if (def?.type === 'selection' && visibleStates) {
1089
- const values = new Set(def.options.map((o) => o.value));
1090
- visibleStates.forEach((state, i) => {
1091
- if (!values.has(state)) {
1092
- this.report(`${path}.statusbar.visibleStates[${i}]`, `"${state}" is not an option of "${field}"`);
1093
- }
1094
- });
1095
- }
1096
- }
749
+ // A statusbar, drawn by the business part, whose checks come with it in the one-tag script.
750
+ if (sheet.statusbar)
751
+ checkStatusbar(sheet.statusbar, path, { ...this.fieldTools(), checkModifiers: (owner, at, keys) => this.checkModifiers(owner, at, keys) });
1097
752
  sheet.buttons?.forEach((button, i) => {
1098
753
  this.claim(button.id, `${path}.buttons[${i}]`);
1099
754
  this.checkModifiers(button, `${path}.buttons[${i}]`);
1100
- this.checkPress(button, `${path}.buttons[${i}]`);
755
+ this.checkPress(button, `${path}.buttons[${i}]`, true);
1101
756
  });
1102
757
  sheet.statButtons?.forEach((stat, i) => {
1103
758
  this.claim(stat.id, `${path}.statButtons[${i}]`);
@@ -1135,7 +790,7 @@ export class ReferenceCheck {
1135
790
  alert.buttons?.forEach((button, j) => {
1136
791
  this.claim(button.id, `${at}.buttons[${j}]`);
1137
792
  this.checkModifiers(button, `${at}.buttons[${j}]`);
1138
- this.checkPress(button, `${at}.buttons[${j}]`);
793
+ this.checkPress(button, `${at}.buttons[${j}]`, true);
1139
794
  });
1140
795
  });
1141
796
  // The gear menu: buttons of the record, each doing something — its steps, an action, or a built-in — and named.
@@ -1163,46 +818,6 @@ export class ReferenceCheck {
1163
818
  this.checkModifiers(sheet.sidePanel, `${path}.sidePanel`);
1164
819
  }
1165
820
  }
1166
- /** An address with suggestions: a char field, its place kept as a place field keeps one, its parts in a json field. */
1167
- checkAddressSearch(node, def, path) {
1168
- const options = node.options ?? {};
1169
- const kind = (key) => {
1170
- const name = options[key];
1171
- return typeof name === 'string' && has(this.page.fields, name) ? this.page.fields[name].type : undefined;
1172
- };
1173
- if (def && def.type !== 'char')
1174
- this.report(`${path}.widget`, `address-search is for a char field; "${node.field}" is a ${def.type}`);
1175
- const place = kind('placeField');
1176
- if (place && place !== 'json' && place !== 'float')
1177
- this.report(`${path}.options.placeField`, `"${options['placeField']}" is a ${place}; a place is kept in a json field, or in a float field (its latitude) with longitudeField`);
1178
- if (place === 'float' && options['longitudeField'] === undefined)
1179
- this.report(`${path}.options.placeField`, `"${options['placeField']}" keeps a latitude: name the float field for its longitude in longitudeField`);
1180
- if (options['longitudeField'] !== undefined && place !== 'float')
1181
- this.report(`${path}.options.longitudeField`, 'longitudeField goes with a placeField that is a float field, its latitude');
1182
- const longitude = kind('longitudeField');
1183
- if (longitude && longitude !== 'float')
1184
- this.report(`${path}.options.longitudeField`, `"${options['longitudeField']}" is a ${longitude}; a longitude is kept in a float field`);
1185
- const parts = kind('partsField');
1186
- if (parts && parts !== 'json')
1187
- this.report(`${path}.options.partsField`, `"${options['partsField']}" is a ${parts}; an address's parts are kept in a json field`);
1188
- }
1189
- /** Booking slots: on a date and time, its end written in another, asked again as the fields it depends on change. */
1190
- checkSlots(node, def, path) {
1191
- const options = node.options ?? {};
1192
- if (def && def.type !== 'datetime')
1193
- this.report(`${path}.widget`, `slots is for a datetime field; "${node.field}" is a ${def.type}`);
1194
- const end = options['endField'];
1195
- if (typeof end === 'string' && has(this.page.fields, end)) {
1196
- const type = this.page.fields[end].type;
1197
- if (end === node.field)
1198
- this.report(`${path}.options.endField`, 'a booking ends in another field than the one it starts in');
1199
- else if (type !== 'datetime')
1200
- this.report(`${path}.options.endField`, `"${end}" is a ${type}; a booking's end is kept in a datetime field`);
1201
- }
1202
- const on = options['dependsOn'];
1203
- if (Array.isArray(on))
1204
- on.forEach((name, i) => typeof name === 'string' && this.need(name, `${path}.options.dependsOn[${i}]`));
1205
- }
1206
821
  /** A picker's own settings, on the kind of field each is for: a path on a link, Fieldia's own list on a choice, a button adding tags, a colour for each state. */
1207
822
  checkPicker(node, def, path) {
1208
823
  const options = node.options ?? {};
@@ -1224,6 +839,8 @@ export class ReferenceCheck {
1224
839
  }
1225
840
  checkFieldNode(node, path) {
1226
841
  const def = this.need(node.field, `${path}.field`);
842
+ // Its own label and help, as the field's: words that may show the answers given.
843
+ this.checkWords(node, path, ['label', 'help']);
1227
844
  // Answer rules: each condition and rule across fields reads, each pattern is a regular expression.
1228
845
  node.validate?.forEach((rule, i) => {
1229
846
  const at = `${path}.validate[${i}]`;
@@ -1255,39 +872,31 @@ export class ReferenceCheck {
1255
872
  if (problem)
1256
873
  this.report(`${path}.autocomplete`, problem);
1257
874
  }
1258
- if (def && node.totals) {
1259
- if (def.type !== 'one2many') {
1260
- this.report(`${path}.totals`, `totals only apply to one2many fields; "${node.field}" is a ${def.type}`);
1261
- }
1262
- else {
1263
- node.totals.forEach((column, i) => {
1264
- const sub = has(def.fields, column) ? def.fields[column] : undefined;
1265
- if (!sub)
1266
- this.report(`${path}.totals[${i}]`, `"${column}" is not a field of the lines of "${node.field}"`);
1267
- else if (!['integer', 'float', 'monetary'].includes(sub.type)) {
1268
- this.report(`${path}.totals[${i}]`, `"${column}" is a ${sub.type}; only integer, float and monetary columns add up`);
1269
- }
1270
- });
1271
- }
1272
- }
875
+ if (def && node.totals && def.type !== 'one2many')
876
+ this.report(`${path}.totals`, `totals only apply to one2many fields; "${node.field}" is a ${def.type}`);
1273
877
  // Options that point at another field: by convention their names end in "Field".
1274
878
  for (const [key, value] of Object.entries(node.options ?? {})) {
1275
879
  if (key.endsWith('Field') && typeof value === 'string')
1276
880
  this.need(value, `${path}.options.${key}`);
1277
881
  }
882
+ // An address with suggestions, drawn by the app's maps: its checks come with a map's in the one-tag script.
1278
883
  if (node.widget === 'address-search')
1279
- this.checkAddressSearch(node, def, path);
1280
- if (node.widget === 'slots')
1281
- this.checkSlots(node, def, path);
884
+ checkAddressSearch(node, def, path, this.mapTools());
885
+ // Booking slots on a date and time, a signature on a file: drawn by their parts, whose checks of their settings come
886
+ // with them in the one-tag script.
887
+ if (node.widget === 'slots' && def && def.type !== 'datetime')
888
+ this.report(`${path}.widget`, `slots is for a datetime field; "${node.field}" is a ${def.type}`);
889
+ else if (node.widget === 'slots')
890
+ checkSlots(node, path, this.fieldTools());
891
+ if (node.widget === 'signature' && def && def.type !== 'binary')
892
+ this.report(`${path}.widget`, `signature is for a binary field; "${node.field}" is a ${def.type}`);
893
+ else if (node.widget === 'signature')
894
+ checkSignature(node, path, this.fieldTools());
1282
895
  if (def)
1283
896
  this.checkPicker(node, def, path);
1284
897
  // The order lines load in: fields of its lines.
1285
- if (def && node.order) {
1286
- if (def.type !== 'one2many')
1287
- this.report(`${path}.order`, `order only applies to one2many fields; "${node.field}" is a ${def.type}`);
1288
- else
1289
- node.order.forEach((item, i) => !has(def.fields, item.field) && this.report(`${path}.order[${i}].field`, `"${item.field}" is not a field of the lines of "${node.field}"`));
1290
- }
898
+ if (def && node.order && def.type !== 'one2many')
899
+ this.report(`${path}.order`, `order only applies to one2many fields; "${node.field}" is a ${def.type}`);
1291
900
  // A line's own form: its parts read the line's fields, the record as parent.
1292
901
  if (def && node.lineForm) {
1293
902
  if (def.type !== 'one2many')
@@ -1305,36 +914,8 @@ export class ReferenceCheck {
1305
914
  // A table's own rules: a cell's read on its line (with parent), a column's `hidden` on the record.
1306
915
  if (def && node.lineDelete !== undefined && def.type !== 'one2many')
1307
916
  this.report(`${path}.lineDelete`, `lineDelete only applies to one2many fields; "${node.field}" is a ${def.type}`);
1308
- if (def && (node.cells || node.rowTones || node.rowBold !== undefined || node.rowButtons || node.lineDelete !== undefined)) {
1309
- if (def.type !== 'one2many') {
1310
- if (node.cells || node.rowTones || node.rowBold !== undefined || node.rowButtons)
1311
- this.report(path, `cells, rowTones, rowBold and rowButtons only apply to one2many fields; "${node.field}" is a ${def.type}`);
1312
- }
1313
- else {
1314
- const line = { fields: def.fields, lines: node.field };
1315
- for (const [column, rules] of Object.entries(node.cells ?? {})) {
1316
- if (!has(def.fields, column)) {
1317
- this.report(`${path}.cells.${column}`, `"${column}" is not a field of the lines of "${node.field}"`);
1318
- continue;
1319
- }
1320
- this.checkModifiers(rules, `${path}.cells.${column}`, ['invisible', 'readonly', 'required', 'bold'], line);
1321
- this.checkModifiers(rules, `${path}.cells.${column}`, ['hidden']);
1322
- rules.tones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.cells.${column}.tones[${i}]`, ['when'], line));
1323
- // Its widget's options that point at a field: one of the line's.
1324
- for (const [key, value] of Object.entries(rules.options ?? {})) {
1325
- if (key.endsWith('Field') && typeof value === 'string')
1326
- this.need(value, `${path}.cells.${column}.options.${key}`, def.fields);
1327
- }
1328
- }
1329
- node.rowTones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.rowTones[${i}]`, ['when'], line));
1330
- this.checkModifiers(node, path, ['rowBold', 'lineDelete'], line);
1331
- node.rowButtons?.forEach((button, i) => {
1332
- this.claim(button.id, `${path}.rowButtons[${i}]`);
1333
- this.checkModifiers(button, `${path}.rowButtons[${i}]`, ['invisible'], line);
1334
- this.checkPress(button, `${path}.rowButtons[${i}]`);
1335
- });
1336
- }
1337
- }
917
+ if (def && def.type !== 'one2many' && (node.cells || node.rowTones || node.rowBold !== undefined || node.rowButtons))
918
+ this.report(path, `cells, rowTones, rowBold and rowButtons only apply to one2many fields; "${node.field}" is a ${def.type}`);
1338
919
  // Buttons for the chosen lines and in the control row: buttons of the record, as any.
1339
920
  for (const key of ['selectedButtons', 'controlButtons']) {
1340
921
  if (!node[key])
@@ -1347,32 +928,14 @@ export class ReferenceCheck {
1347
928
  this.checkPress(button, `${path}.${key}[${i}]`);
1348
929
  });
1349
930
  }
1350
- if (def && node.optionalColumns) {
1351
- if (def.type !== 'one2many') {
1352
- this.report(`${path}.optionalColumns`, `optional columns only apply to one2many fields; "${node.field}" is a ${def.type}`);
1353
- }
1354
- else {
1355
- // The columns the table shows: those named, or every line field but the structural ones.
1356
- const shown = node.columns ?? Object.keys(def.fields).filter((c) => c !== ('lineKinds' in def ? def.lineKinds?.field : undefined) && c !== def.sequenceField);
1357
- for (const column of Object.keys(node.optionalColumns)) {
1358
- if (!shown.includes(column))
1359
- this.report(`${path}.optionalColumns.${column}`, `"${column}" is not a column of "${node.field}"`);
1360
- }
1361
- }
1362
- }
931
+ if (def && node.optionalColumns && def.type !== 'one2many')
932
+ this.report(`${path}.optionalColumns`, `optional columns only apply to one2many fields; "${node.field}" is a ${def.type}`);
933
+ // A table of lines' own settings, drawn by the lines part: its columns, totals, order, cells and rows' rules.
934
+ if (def?.type === 'one2many')
935
+ return checkTable(node, def, path, this.tableTools());
1363
936
  if (!def || !node.columns)
1364
937
  return;
1365
- if (def.type === 'one2many') {
1366
- node.columns.forEach((column, i) => {
1367
- if (!has(def.fields, column)) {
1368
- this.report(`${path}.columns[${i}]`, `"${column}" is not a field of the lines of "${node.field}"`);
1369
- }
1370
- else if (def.fields[column].type === 'one2many') {
1371
- this.report(`${path}.columns[${i}]`, `"${column}" holds lines of its own: they are edited in the line's form (lineForm), not drawn in its row`);
1372
- }
1373
- });
1374
- }
1375
- else if (def.type === 'many2many') {
938
+ if (def.type === 'many2many') {
1376
939
  // A table of the records linked: its columns are fields of those records.
1377
940
  node.columns.forEach((column, i) => {
1378
941
  if (!def.fields || !has(def.fields, column))