@fieldia/core 0.27.0 → 0.28.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 (97) hide show
  1. package/README.md +24 -5
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +4 -2
  4. package/src/index.d.mts +2 -0
  5. package/src/index.d.ts +2 -0
  6. package/src/index.js +6 -2
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +2 -0
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/format/layout.d.mts +23 -2
  11. package/src/lib/format/layout.d.ts +23 -2
  12. package/src/lib/format/layout.js +1 -1
  13. package/src/lib/format/layout.js.map +1 -1
  14. package/src/lib/format/layout.mjs +1 -1
  15. package/src/lib/format/layout.mjs.map +1 -1
  16. package/src/lib/format/page.d.mts +8 -1
  17. package/src/lib/format/page.d.ts +8 -1
  18. package/src/lib/format/upgrade.d.mts +2 -1
  19. package/src/lib/format/upgrade.d.ts +2 -1
  20. package/src/lib/format/upgrade.js +2 -1
  21. package/src/lib/format/upgrade.js.map +1 -1
  22. package/src/lib/format/upgrade.mjs +2 -1
  23. package/src/lib/format/upgrade.mjs.map +1 -1
  24. package/src/lib/format/version.d.mts +2 -2
  25. package/src/lib/format/version.d.ts +2 -2
  26. package/src/lib/format/version.js +2 -2
  27. package/src/lib/format/version.js.map +1 -1
  28. package/src/lib/format/version.mjs +2 -2
  29. package/src/lib/format/version.mjs.map +1 -1
  30. package/src/lib/record/check.js +3 -1
  31. package/src/lib/record/check.js.map +1 -1
  32. package/src/lib/record/check.mjs +3 -1
  33. package/src/lib/record/check.mjs.map +1 -1
  34. package/src/lib/record/day-words.d.mts +10 -0
  35. package/src/lib/record/day-words.d.ts +10 -0
  36. package/src/lib/record/day-words.js +23 -0
  37. package/src/lib/record/day-words.js.map +1 -0
  38. package/src/lib/record/day-words.mjs +20 -0
  39. package/src/lib/record/day-words.mjs.map +1 -0
  40. package/src/lib/record/form-limits.d.mts +5 -8
  41. package/src/lib/record/form-limits.d.ts +5 -8
  42. package/src/lib/record/form-limits.js +20 -21
  43. package/src/lib/record/form-limits.js.map +1 -1
  44. package/src/lib/record/form-limits.mjs +21 -22
  45. package/src/lib/record/form-limits.mjs.map +1 -1
  46. package/src/lib/record/form.d.mts +9 -7
  47. package/src/lib/record/form.d.ts +9 -7
  48. package/src/lib/record/form.js +70 -6
  49. package/src/lib/record/form.js.map +1 -1
  50. package/src/lib/record/form.mjs +70 -6
  51. package/src/lib/record/form.mjs.map +1 -1
  52. package/src/lib/record/limits.js +4 -2
  53. package/src/lib/record/limits.js.map +1 -1
  54. package/src/lib/record/limits.mjs +4 -2
  55. package/src/lib/record/limits.mjs.map +1 -1
  56. package/src/lib/record/locales/ar.js +1 -0
  57. package/src/lib/record/locales/ar.js.map +1 -1
  58. package/src/lib/record/locales/ar.mjs +1 -0
  59. package/src/lib/record/locales/ar.mjs.map +1 -1
  60. package/src/lib/record/locales/de.js +1 -0
  61. package/src/lib/record/locales/de.js.map +1 -1
  62. package/src/lib/record/locales/de.mjs +1 -0
  63. package/src/lib/record/locales/de.mjs.map +1 -1
  64. package/src/lib/record/locales/fr.js +1 -0
  65. package/src/lib/record/locales/fr.js.map +1 -1
  66. package/src/lib/record/locales/fr.mjs +1 -0
  67. package/src/lib/record/locales/fr.mjs.map +1 -1
  68. package/src/lib/record/messages.d.mts +10 -2
  69. package/src/lib/record/messages.d.ts +10 -2
  70. package/src/lib/record/messages.js +3 -4
  71. package/src/lib/record/messages.js.map +1 -1
  72. package/src/lib/record/messages.mjs +3 -4
  73. package/src/lib/record/messages.mjs.map +1 -1
  74. package/src/lib/record/option-rules.d.mts +7 -1
  75. package/src/lib/record/option-rules.d.ts +7 -1
  76. package/src/lib/record/option-rules.js +9 -3
  77. package/src/lib/record/option-rules.js.map +1 -1
  78. package/src/lib/record/option-rules.mjs +9 -3
  79. package/src/lib/record/option-rules.mjs.map +1 -1
  80. package/src/lib/record/record-words.d.mts +13 -1
  81. package/src/lib/record/record-words.d.ts +13 -1
  82. package/src/lib/record/record-words.js +29 -1
  83. package/src/lib/record/record-words.js.map +1 -1
  84. package/src/lib/record/record-words.mjs +27 -1
  85. package/src/lib/record/record-words.mjs.map +1 -1
  86. package/src/lib/record/waits.d.mts +7 -5
  87. package/src/lib/record/waits.d.ts +7 -5
  88. package/src/lib/record/waits.js +7 -10
  89. package/src/lib/record/waits.js.map +1 -1
  90. package/src/lib/record/waits.mjs +7 -10
  91. package/src/lib/record/waits.mjs.map +1 -1
  92. package/src/runtime.d.mts +1 -1
  93. package/src/runtime.d.ts +1 -1
  94. package/src/runtime.js +7 -3
  95. package/src/runtime.js.map +1 -1
  96. package/src/runtime.mjs +4 -1
  97. package/src/runtime.mjs.map +1 -1
@@ -85,7 +85,9 @@ function innerForm(options, within, place, outer) {
85
85
  const page = zonedPage(options.page);
86
86
  const scheduler = options.scheduler ?? hostScheduler;
87
87
  // 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);
88
+ // …and a day written in the page's order where it says one (`look.dateOrder`): "can't be after Oct 20, 2026".
89
+ const dateOrder = page.look?.dateOrder;
90
+ const messages = page.lowerLabels || dateOrder ? { ...(options.messages ?? MESSAGES.en), lowerLabels: page.lowerLabels, dateOrder } : (options.messages ?? MESSAGES.en);
89
91
  /** One of the form's sentences in the words the page names its records by (`records`), else as the language says it plainly. */
90
92
  const named = (noun, plain) => recordSentence(page.records, noun, plain, messages.locale);
91
93
  /** The app's functions the page's expressions call: a name none may call is refused now. */
@@ -550,6 +552,7 @@ function innerForm(options, within, place, outer) {
550
552
  let edit = false;
551
553
  function set(patch) {
552
554
  const was = state.values;
555
+ const choicesWas = state.choices;
553
556
  const shown = state.errors;
554
557
  const edited = edit;
555
558
  edit = false;
@@ -558,6 +561,12 @@ function innerForm(options, within, place, outer) {
558
561
  // A line's choices go with their line.
559
562
  if (state.values !== was)
560
563
  state = { ...state, choices: choicesNow(state.choices) };
564
+ // What was said taken out is said while it is true: an option offered again, or picked again, is said no more.
565
+ if ((state.values !== was || state.choices !== choicesWas) && Object.keys(state.takenOut).length) {
566
+ const takenOut = takenOutTrue();
567
+ if (takenOut !== state.takenOut)
568
+ state = { ...state, takenOut };
569
+ }
561
570
  // What waits for an app's answer, as the values shown last settled.
562
571
  if (patch.values !== undefined && patch.asked === undefined && !sameAsked(state.asked, askedNow))
563
572
  state = { ...state, asked: Object.fromEntries(askedNow) };
@@ -858,7 +867,9 @@ function innerForm(options, within, place, outer) {
858
867
  const written = line
859
868
  ? (values[field] ?? []).map((l) => (l.key === line.key ? { ...l, values: { ...l.values, [line.column]: without(l.values[line.column]) } } : l))
860
869
  : without(values[field]);
861
- writeValues({ ...values, [field]: written }, {}, false, { [line ? `${field}.${line.key}.${line.column}` : field]: takenOutWords(labels) });
870
+ const path = line ? `${field}.${line.key}.${line.column}` : field;
871
+ takenOutOf.set(path, lost.map((value, i) => ({ value, label: labels[i] })));
872
+ writeValues({ ...values, [field]: written }, {}, false, { [path]: takenOutWords(labels) });
862
873
  afterEdit(field);
863
874
  changed(field, 'step', { keep: true });
864
875
  }
@@ -967,6 +978,38 @@ function innerForm(options, within, place, outer) {
967
978
  * "Garden is no longer offered, so it was taken out".
968
979
  */
969
980
  const takenOutWords = (labels) => fillMessage(messages, labels.length > 1 ? messages.notOfferedNowMany : messages.notOfferedNow, { name: listedLabels(labels.map((label) => (label.includes('{') ? wordsNow(label) : label))) });
981
+ /** The options each note of `takenOut` names, by its path: a note goes once none of them is still out. */
982
+ const takenOutOf = new Map();
983
+ /**
984
+ * The notes of options taken out (`FormState.takenOut`) that are still true for the values and the choices now: one
985
+ * whose options are all offered again — the type that offers them picked again, the treatment ticked again above, the
986
+ * app's list asked again offering it — or picked again goes; one with some still out names those. A list being asked
987
+ * again says nothing yet. The same notes, when none changed.
988
+ */
989
+ function takenOutTrue() {
990
+ let kept = null;
991
+ for (const path of Object.keys(state.takenOut)) {
992
+ const options = takenOutOf.get(path);
993
+ const held = valueAt(state.values, path);
994
+ if (!options || held === gone || state.choices[path]?.loading)
995
+ continue;
996
+ const offered = form.offered(path);
997
+ if (!offered)
998
+ continue;
999
+ const chosen = [held].flat();
1000
+ const left = options.filter((option) => !chosen.includes(option.value) && !offered.some((one) => one.value === option.value));
1001
+ if (left.length === options.length)
1002
+ continue;
1003
+ kept ??= { ...state.takenOut };
1004
+ if (left.length) {
1005
+ takenOutOf.set(path, left);
1006
+ kept[path] = takenOutWords(left.map((option) => option.label));
1007
+ }
1008
+ else
1009
+ delete kept[path];
1010
+ }
1011
+ return kept ?? state.takenOut;
1012
+ }
970
1013
  /** A line's field as its value is checked: its choices from the app's list, those loaded for its line. */
971
1014
  function checkedLine(path, def) {
972
1015
  const loaded = def.type === 'selection' && optionsList(def.optionsFrom) ? state.choices[path]?.options : null;
@@ -1152,6 +1195,23 @@ function innerForm(options, within, place, outer) {
1152
1195
  const name = node.field;
1153
1196
  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
1197
  }
1198
+ /** What a check would refuse now (`collectErrors`), for the state it was found for. */
1199
+ let refusals = null;
1200
+ /**
1201
+ * Whether the form refuses the answer at a path — a field's, or a table line's cell (`field.lineKey.subfield`) — as a
1202
+ * check would now: one it shows that holds a value its rules do not take (under its least, past today, a pattern not
1203
+ * met, an answer rule's error). An empty answer is none, required or not: there is nothing worked out from it to wait.
1204
+ */
1205
+ function refusedAt(path) {
1206
+ if (refusals?.state !== state)
1207
+ refusals = { state, errors: collectErrors() };
1208
+ if (!refusals.errors[path])
1209
+ return false;
1210
+ const parts = path.split('.');
1211
+ const def = page.fields[parts[0]];
1212
+ const held = valueAt(state.values, path);
1213
+ return held !== gone && !isEmpty((parts.length > 2 && def?.type === 'one2many' ? def.fields[parts[parts.length - 1]] : def), held);
1214
+ }
1155
1215
  /** Warnings for the visible fields of the page, without publishing them. */
1156
1216
  function collectWarnings() {
1157
1217
  const warnings = {};
@@ -1420,6 +1480,8 @@ function innerForm(options, within, place, outer) {
1420
1480
  if (out) {
1421
1481
  settled = out.values;
1422
1482
  takenOut = { ...takenOut, ...out.said };
1483
+ for (const [path, gone] of Object.entries(out.gone))
1484
+ takenOutOf.set(path, gone);
1423
1485
  }
1424
1486
  if (Object.keys(takenOut).length || Object.keys(state.takenOut).length)
1425
1487
  extra = { takenOut, ...extra };
@@ -2707,11 +2769,13 @@ function innerForm(options, within, place, outer) {
2707
2769
  return changesOptions(cell ? (lines?.type === 'one2many' ? lines.fields[cell.column] : undefined) : page.fields[name]);
2708
2770
  },
2709
2771
  waitsFor(name) {
2710
- const found = state.asked[name];
2711
- if (found)
2712
- return { field: name, asking: true, ...(found.failed !== undefined ? { failed: found.failed } : {}) };
2772
+ // What it is worked out from first: an answer that does not read, or that the form refuses, is never asked about.
2713
2773
  const from = workedOut.get(name);
2714
- return from ? waitingOn(page, from, state.values, state.unread) : null;
2774
+ const waits = from ? waitingOn(page, from, state.values, state.unread, refusedAt) : null;
2775
+ if (waits)
2776
+ return waits;
2777
+ const found = state.asked[name];
2778
+ return found ? { field: name, asking: true, ...(found.failed !== undefined ? { failed: found.failed } : {}) } : null;
2715
2779
  },
2716
2780
  problem(name) {
2717
2781
  fieldDef(name);