@fieldia/core 0.10.0 → 0.12.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 (205) hide show
  1. package/README.md +204 -0
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +4700 -904
  4. package/src/index.d.mts +2 -1
  5. package/src/index.d.ts +2 -1
  6. package/src/index.js +7 -1
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +2 -1
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/expression/built-ins.d.mts +4 -0
  11. package/src/lib/expression/built-ins.d.ts +4 -0
  12. package/src/lib/expression/built-ins.js +8 -0
  13. package/src/lib/expression/built-ins.js.map +1 -0
  14. package/src/lib/expression/built-ins.mjs +5 -0
  15. package/src/lib/expression/built-ins.mjs.map +1 -0
  16. package/src/lib/expression/evaluator.js +17 -7
  17. package/src/lib/expression/evaluator.js.map +1 -1
  18. package/src/lib/expression/evaluator.mjs +17 -7
  19. package/src/lib/expression/evaluator.mjs.map +1 -1
  20. package/src/lib/expression/expression.d.mts +7 -0
  21. package/src/lib/expression/expression.d.ts +7 -0
  22. package/src/lib/expression/expression.js +2 -0
  23. package/src/lib/expression/expression.js.map +1 -1
  24. package/src/lib/expression/expression.mjs +3 -1
  25. package/src/lib/expression/expression.mjs.map +1 -1
  26. package/src/lib/expression/functions.d.mts +2 -0
  27. package/src/lib/expression/functions.d.ts +2 -0
  28. package/src/lib/expression/functions.js +38 -5
  29. package/src/lib/expression/functions.js.map +1 -1
  30. package/src/lib/expression/functions.mjs +38 -5
  31. package/src/lib/expression/functions.mjs.map +1 -1
  32. package/src/lib/expression/modifier.d.mts +7 -0
  33. package/src/lib/expression/modifier.d.ts +7 -0
  34. package/src/lib/expression/modifier.js +3 -1
  35. package/src/lib/expression/modifier.js.map +1 -1
  36. package/src/lib/expression/modifier.mjs +4 -2
  37. package/src/lib/expression/modifier.mjs.map +1 -1
  38. package/src/lib/expression/parser.d.mts +8 -1
  39. package/src/lib/expression/parser.d.ts +8 -1
  40. package/src/lib/expression/parser.js +30 -2
  41. package/src/lib/expression/parser.js.map +1 -1
  42. package/src/lib/expression/parser.mjs +30 -2
  43. package/src/lib/expression/parser.mjs.map +1 -1
  44. package/src/lib/expression/reads.d.mts +7 -0
  45. package/src/lib/expression/reads.d.ts +7 -0
  46. package/src/lib/expression/reads.js +23 -0
  47. package/src/lib/expression/reads.js.map +1 -1
  48. package/src/lib/expression/reads.mjs +21 -0
  49. package/src/lib/expression/reads.mjs.map +1 -1
  50. package/src/lib/format/actions.d.mts +1176 -0
  51. package/src/lib/format/actions.d.ts +1176 -0
  52. package/src/lib/format/actions.js +62 -0
  53. package/src/lib/format/actions.js.map +1 -0
  54. package/src/lib/format/actions.mjs +59 -0
  55. package/src/lib/format/actions.mjs.map +1 -0
  56. package/src/lib/format/check-page.d.mts +7 -2
  57. package/src/lib/format/check-page.d.ts +7 -2
  58. package/src/lib/format/check-page.js +6 -3
  59. package/src/lib/format/check-page.js.map +1 -1
  60. package/src/lib/format/check-page.mjs +6 -3
  61. package/src/lib/format/check-page.mjs.map +1 -1
  62. package/src/lib/format/field.d.mts +3285 -214
  63. package/src/lib/format/field.d.ts +3285 -214
  64. package/src/lib/format/field.js +105 -16
  65. package/src/lib/format/field.js.map +1 -1
  66. package/src/lib/format/field.mjs +104 -15
  67. package/src/lib/format/field.mjs.map +1 -1
  68. package/src/lib/format/hotkeys.d.mts +6 -0
  69. package/src/lib/format/hotkeys.d.ts +6 -0
  70. package/src/lib/format/hotkeys.js +11 -0
  71. package/src/lib/format/hotkeys.js.map +1 -0
  72. package/src/lib/format/hotkeys.mjs +7 -0
  73. package/src/lib/format/hotkeys.mjs.map +1 -0
  74. package/src/lib/format/layout.d.mts +4773 -211
  75. package/src/lib/format/layout.d.ts +4773 -211
  76. package/src/lib/format/layout.js +147 -21
  77. package/src/lib/format/layout.js.map +1 -1
  78. package/src/lib/format/layout.mjs +146 -20
  79. package/src/lib/format/layout.mjs.map +1 -1
  80. package/src/lib/format/layout.types.d.mts +2 -1
  81. package/src/lib/format/layout.types.d.ts +2 -1
  82. package/src/lib/format/page.d.mts +3563 -222
  83. package/src/lib/format/page.d.ts +3563 -222
  84. package/src/lib/format/page.js +19 -2
  85. package/src/lib/format/page.js.map +1 -1
  86. package/src/lib/format/page.mjs +20 -3
  87. package/src/lib/format/page.mjs.map +1 -1
  88. package/src/lib/format/part-look.d.mts +36 -0
  89. package/src/lib/format/part-look.d.ts +36 -0
  90. package/src/lib/format/part-look.js +23 -0
  91. package/src/lib/format/part-look.js.map +1 -0
  92. package/src/lib/format/part-look.mjs +20 -0
  93. package/src/lib/format/part-look.mjs.map +1 -0
  94. package/src/lib/format/references.d.mts +54 -2
  95. package/src/lib/format/references.d.ts +54 -2
  96. package/src/lib/format/references.js +450 -43
  97. package/src/lib/format/references.js.map +1 -1
  98. package/src/lib/format/references.mjs +450 -43
  99. package/src/lib/format/references.mjs.map +1 -1
  100. package/src/lib/format/translate.js +5 -3
  101. package/src/lib/format/translate.js.map +1 -1
  102. package/src/lib/format/translate.mjs +5 -3
  103. package/src/lib/format/translate.mjs.map +1 -1
  104. package/src/lib/format/words.d.mts +9 -0
  105. package/src/lib/format/words.d.ts +9 -0
  106. package/src/lib/format/words.js +35 -0
  107. package/src/lib/format/words.js.map +1 -0
  108. package/src/lib/format/words.mjs +30 -0
  109. package/src/lib/format/words.mjs.map +1 -0
  110. package/src/lib/record/cells.d.mts +37 -0
  111. package/src/lib/record/cells.d.ts +37 -0
  112. package/src/lib/record/cells.js +59 -0
  113. package/src/lib/record/cells.js.map +1 -0
  114. package/src/lib/record/cells.mjs +54 -0
  115. package/src/lib/record/cells.mjs.map +1 -0
  116. package/src/lib/record/compute.d.mts +5 -4
  117. package/src/lib/record/compute.d.ts +5 -4
  118. package/src/lib/record/compute.js +11 -10
  119. package/src/lib/record/compute.js.map +1 -1
  120. package/src/lib/record/compute.mjs +12 -11
  121. package/src/lib/record/compute.mjs.map +1 -1
  122. package/src/lib/record/data-source.d.mts +37 -3
  123. package/src/lib/record/data-source.d.ts +37 -3
  124. package/src/lib/record/data-source.js.map +1 -1
  125. package/src/lib/record/data-source.mjs.map +1 -1
  126. package/src/lib/record/env.d.mts +38 -1
  127. package/src/lib/record/env.d.ts +38 -1
  128. package/src/lib/record/env.js +41 -3
  129. package/src/lib/record/env.js.map +1 -1
  130. package/src/lib/record/env.mjs +38 -3
  131. package/src/lib/record/env.mjs.map +1 -1
  132. package/src/lib/record/filter.d.mts +10 -0
  133. package/src/lib/record/filter.d.ts +10 -0
  134. package/src/lib/record/filter.js +49 -0
  135. package/src/lib/record/filter.js.map +1 -1
  136. package/src/lib/record/filter.mjs +48 -0
  137. package/src/lib/record/filter.mjs.map +1 -1
  138. package/src/lib/record/form.d.mts +226 -25
  139. package/src/lib/record/form.d.ts +226 -25
  140. package/src/lib/record/form.js +785 -83
  141. package/src/lib/record/form.js.map +1 -1
  142. package/src/lib/record/form.mjs +787 -85
  143. package/src/lib/record/form.mjs.map +1 -1
  144. package/src/lib/record/locales/ar.js +3 -0
  145. package/src/lib/record/locales/ar.js.map +1 -1
  146. package/src/lib/record/locales/ar.mjs +3 -0
  147. package/src/lib/record/locales/ar.mjs.map +1 -1
  148. package/src/lib/record/locales/de.js +3 -0
  149. package/src/lib/record/locales/de.js.map +1 -1
  150. package/src/lib/record/locales/de.mjs +3 -0
  151. package/src/lib/record/locales/de.mjs.map +1 -1
  152. package/src/lib/record/locales/fr.js +3 -0
  153. package/src/lib/record/locales/fr.js.map +1 -1
  154. package/src/lib/record/locales/fr.mjs +3 -0
  155. package/src/lib/record/locales/fr.mjs.map +1 -1
  156. package/src/lib/record/memory-data-source.d.mts +26 -2
  157. package/src/lib/record/memory-data-source.d.ts +26 -2
  158. package/src/lib/record/memory-data-source.js +98 -7
  159. package/src/lib/record/memory-data-source.js.map +1 -1
  160. package/src/lib/record/memory-data-source.mjs +98 -7
  161. package/src/lib/record/memory-data-source.mjs.map +1 -1
  162. package/src/lib/record/messages.d.mts +5 -0
  163. package/src/lib/record/messages.d.ts +5 -0
  164. package/src/lib/record/messages.js +3 -0
  165. package/src/lib/record/messages.js.map +1 -1
  166. package/src/lib/record/messages.mjs +3 -0
  167. package/src/lib/record/messages.mjs.map +1 -1
  168. package/src/lib/record/roles.d.mts +10 -0
  169. package/src/lib/record/roles.d.ts +10 -0
  170. package/src/lib/record/roles.js +21 -0
  171. package/src/lib/record/roles.js.map +1 -0
  172. package/src/lib/record/roles.mjs +18 -0
  173. package/src/lib/record/roles.mjs.map +1 -0
  174. package/src/lib/record/rules.js +30 -0
  175. package/src/lib/record/rules.js.map +1 -1
  176. package/src/lib/record/rules.mjs +31 -1
  177. package/src/lib/record/rules.mjs.map +1 -1
  178. package/src/lib/record/run.d.mts +248 -0
  179. package/src/lib/record/run.d.ts +248 -0
  180. package/src/lib/record/run.js +455 -0
  181. package/src/lib/record/run.js.map +1 -0
  182. package/src/lib/record/run.mjs +450 -0
  183. package/src/lib/record/run.mjs.map +1 -0
  184. package/src/lib/record/set-when.d.mts +5 -2
  185. package/src/lib/record/set-when.d.ts +5 -2
  186. package/src/lib/record/set-when.js +28 -12
  187. package/src/lib/record/set-when.js.map +1 -1
  188. package/src/lib/record/set-when.mjs +30 -14
  189. package/src/lib/record/set-when.mjs.map +1 -1
  190. package/src/lib/record/value-map.d.mts +41 -0
  191. package/src/lib/record/value-map.d.ts +41 -0
  192. package/src/lib/record/value-map.js +80 -0
  193. package/src/lib/record/value-map.js.map +1 -0
  194. package/src/lib/record/value-map.mjs +73 -0
  195. package/src/lib/record/value-map.mjs.map +1 -0
  196. package/src/lib/record/values.d.mts +10 -0
  197. package/src/lib/record/values.d.ts +10 -0
  198. package/src/lib/record/values.js.map +1 -1
  199. package/src/lib/record/values.mjs.map +1 -1
  200. package/src/runtime.d.mts +11 -4
  201. package/src/runtime.d.ts +11 -4
  202. package/src/runtime.js +13 -1
  203. package/src/runtime.js.map +1 -1
  204. package/src/runtime.mjs +4 -0
  205. package/src/runtime.mjs.map +1 -1
package/README.md CHANGED
@@ -28,5 +28,209 @@ list of records — that your app implements;
28
28
  The page format is also a JSON Schema, for editors and other languages:
29
29
  `@fieldia/core/page.schema.json`.
30
30
 
31
+ A page's `look` sets its accent, font, spacing, corners, labels and light or
32
+ dark scheme, and, under `parts`, a look for each kind of part over it — the
33
+ settings each kind has are `PART_LOOKS`:
34
+
35
+ ```json
36
+ "look": {
37
+ "accent": "#1f7a4d",
38
+ "parts": {
39
+ "inputs": { "background": "#fff7e6", "border": "#c4320a", "corners": "round", "textSize": "large", "accent": "#c4320a" },
40
+ "choices": { "accent": "#1f7a4d", "background": "#f0f7ff", "border": "#1677ff", "corners": "soft", "textSize": "small" },
41
+ "groups": { "background": "#fbfaf7", "border": "#d6cfc2", "corners": "round" },
42
+ "buttons": { "accent": "#6941c6", "corners": "round", "textSize": "large" },
43
+ "tables": { "background": "#eef2f7", "border": "#9aa3ad", "textSize": "small" }
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## A saved form placed in another
49
+
50
+ A block made once — an address, a contact person, a consent — is a page of its
51
+ own, placed in any other by its id with a `form` part:
52
+
53
+ ```json
54
+ { "type": "form", "id": "home", "page": "address", "name": "home_address", "title": "Home address" }
55
+ { "type": "form", "id": "billing", "page": "address", "version": 2, "name": "billing_address" }
56
+ ```
57
+
58
+ - `page` is the saved page's id; `version` keeps to one published version, and
59
+ left out means the latest.
60
+ - `name` is where its answers go: the values hold them under it as an object,
61
+ `{ "home_address": { "street": "12 Nile Street", "city": "Cairo" } }`, so two
62
+ copies of one form never mix. It must not be a field of the page, nor another
63
+ copy's name.
64
+ - `title` is the words over it: the saved page's own title when left out, none
65
+ when empty. `colspan`, `invisible` and `readonly` work as on a section.
66
+
67
+ Only a page of `sections` or `tabs` can be placed; a page cannot be placed in
68
+ itself. The page's own conditions cannot read inside a saved form's answers.
69
+
70
+ A form is given the saved page once the app has found it —
71
+ `form.embed(partId, page)`, as the viewer does — and returns the inner form.
72
+ From then the saved page's own required fields, answer rules, conditions and
73
+ worked-out values hold inside it: the outer form refuses to save or send while
74
+ one fails (the error under `home_address.street`, shown on the inner form beside
75
+ its field), sends only the copies on show, tells `home_address` as changed when
76
+ anything inside changes, and loads, puts back and saves the nested answers. A
77
+ data source sees each copy's answers as one JSON value under its name.
78
+
79
+ ## What a press or a moment does
80
+
81
+ A button — in the body, a record's header, a stat button or a list's actions —
82
+ runs `steps`, in order, each plain data and never code; and the page's `on`
83
+ runs them at its moments:
84
+
85
+ ```json
86
+ { "type": "button", "id": "new-customer", "label": "New customer", "steps": [
87
+ { "do": "open", "page": "customer", "as": "panel", "values": { "name": "customer_name" },
88
+ "into": { "customer_id": "id" }, "then": [{ "do": "say", "message": "Customer added", "tone": "success" }] }
89
+ ] }
90
+
91
+ "on": {
92
+ "change": { "product": [{ "do": "call", "action": "check_stock" }] },
93
+ "beforeSave": [{ "do": "check" }, { "do": "ask", "message": "Send the order?" }],
94
+ "afterSave": [{ "do": "say", "message": "Order sent", "tone": "success" }]
95
+ }
96
+ ```
97
+
98
+ - `open` another page in a `dialog` (the default), a `panel` beside this form,
99
+ or in its `page`; on a `record` (an expression giving its id) or a new one with
100
+ `values`. A panel comes from its `side`: `end` (the default: the end of the
101
+ line, the right or, right to left, the left) or `start`, as the page reads;
102
+ or `left`, `right`, `top` or `bottom`, whatever its direction — a side on a
103
+ dialog or a page in its place is refused. Once it is saved or sent, `into`
104
+ sets this form's fields from its answers (`id` is the record it saved) and
105
+ `then` runs here.
106
+ - `set` a field from an expression, `clear` one, `addLine` to a table of lines.
107
+ - `check` the form or some fields, `save` (a page of responses sends), `reset`.
108
+ - `goTo` a tab or a wizard step, `say` something in a tone, `ask` Yes or No.
109
+ - `call` one of the app's own actions by name, with `params`; `close` the dialog
110
+ or panel the form was opened in.
111
+ - `openUrl` a web address an expression gives, in a new tab unless `newTab` is
112
+ false; `reload` the record from the data source after the app changed it —
113
+ an app's answer may ask for that with `reload: true`. An `open` of a list
114
+ page may give a `filter` — `valueFrom` reading this form — so it shows only
115
+ the records related to this one.
116
+
117
+ Any step may run only `when` a condition holds. A step that fails or is refused
118
+ stops the ones after it: a check that finds problems, No to a question, a save
119
+ that cannot be made, the app saying stop, a page closed without saving. The
120
+ moments are `open`, `change` (by field: a person's change, never a step's or a
121
+ rule's), `beforeSave` (a step that stops keeps it unsaved), `afterSave` and
122
+ `show` (by tab or wizard step id). A button may still name only an `action`,
123
+ handed to the app as before.
124
+
125
+ ### Running steps
126
+
127
+ A press runs with `form.runAction(id)`: the button's `confirm` asked first,
128
+ then its `steps`, then its `action`, when it names one, as a final `call`.
129
+ The app runs its own with `form.run(steps, { id })`. Each run ends with a
130
+ `RunResult`:
131
+
132
+ ```ts
133
+ const result = await form.run([{ do: 'check' }, { do: 'save' }]);
134
+ // { done: true }
135
+ // { done: false, stoppedAt: 0, step: { do: 'check' }, reason: 'check', message: 'Email is required' }
136
+ ```
137
+
138
+ - `reason` is why it stopped: `check` (problems found, and shown), `no` (a
139
+ question answered No), `save` (refused: the first problem, or the backend's
140
+ words), `app` (the app said stop, or threw — `error` holds what), `closed`
141
+ (a page opened and closed without saving), `cannot` (it cannot run here: no
142
+ host to open with, a field the page lacks).
143
+ - `stoppedAt` is its place among the steps run — for a step of an `open`'s
144
+ `then`, the open step's; for a button's `action`, one past its steps; none
145
+ when the `confirm` was answered No — and `step` is the step itself.
146
+
147
+ What the core cannot draw it asks of a host, which the viewer gives:
148
+
149
+ ```ts
150
+ const form = createForm({
151
+ page,
152
+ dataSource,
153
+ host: {
154
+ open: (request) => showPage(request), // { page, version?, as, side?, title?, recordId, values? } → { saved, recordId?, values?, label? }
155
+ say: (message, tone) => toast(message, tone),
156
+ ask: (message) => askYesOrNo(message), // Promise<boolean>
157
+ close: () => panel.close(), // the dialog or panel this form was opened in
158
+ show: (tab) => tabs.pick(tab), // and the viewer calls form.shown(tab), as for a tab picked by hand
159
+ },
160
+ });
161
+ ```
162
+
163
+ Without a host, `say` says nothing, `ask` asks the form's `confirm` option — or
164
+ goes on as though answered Yes, so a form run from code is never held by a
165
+ question — and `open`, `close` and a `goTo` of a tab stop with `cannot`.
166
+
167
+ An `open` step reads `record` and `values` against this form; a field named
168
+ alone goes whole, a link with its name. Once the page is saved, `into` reads
169
+ the opened form's values, `id` being the record it saved; a link set to that
170
+ record is named by the host's `label` (its page's title field), else the
171
+ values' `display_name`, else their `name`, else the id.
172
+
173
+ ### The app's actions
174
+
175
+ `onAction` gets each `call`: `{ id, action, params, recordId, values, recordIds }`.
176
+ `id` is the button's, or the moment's (`on.open`, `on.change.<field>`,
177
+ `on.beforeSave`, `on.afterSave`, `on.show.<id>`), or what `form.run` was
178
+ given (`run` when nothing); `recordIds` are the records chosen in a list, for
179
+ every call of that run. Fieldia waits for the answer, which may be an
180
+ `ActionResult`:
181
+
182
+ ```ts
183
+ onAction: async ({ action, values }) => {
184
+ if (action === 'price') return { values: { price: await priceOf(values.product_id) }, say: 'Priced' };
185
+ if (action === 'reserve' && !(await inStock(values))) return { stop: 'Out of stock' };
186
+ if (action === 'pick') return { open: { page: 'customer', as: 'panel', into: { customer_id: 'id' } } };
187
+ },
188
+ ```
189
+
190
+ `values` are set as a `set` step sets them (a link may be given by its id);
191
+ `say` is said, as words or `{ message, tone }`; `open` runs as an `open` step;
192
+ `stop` stops the steps after it, and its words, if any, are said as a
193
+ warning. Nothing answered means go on: an `onAction` that returns nothing
194
+ works as it always did.
195
+
196
+ ### The moments
197
+
198
+ - `open` runs once the form has its values: as soon as a new one is made, and
199
+ after each `load()` of a record.
200
+ - `change` runs for a person's change only — `setValue(name, value)`, and a
201
+ table's lines added, edited, moved or removed. `setValue(name, value, { by: 'app' })`,
202
+ `setValues(values)`, steps, worked-out values, `setWhen` and the data
203
+ source's onchange never run it, so steps never chase each other round.
204
+ - `beforeSave` runs before the save's own check, so it can fill in what is
205
+ asked for. A stop keeps it unsaved: `save()` gives false; a check's
206
+ problems show as the save's own would; any other stop leaves
207
+ `status: 'error'` and `saveProblem: { kind: 'stopped', reason, message }`.
208
+ - `afterSave` runs once saved or sent.
209
+ - `show` runs as a wizard's step is entered — the first once `open` has run —
210
+ and as the viewer calls `form.shown(tabId)` for a tab.
211
+
212
+ Each moment runs one at a time. Set off again while it runs, `open`, `change`
213
+ and `show` run once more after it, with the values as they are then, however
214
+ often they were set off meanwhile. `beforeSave` and `afterSave` do not run
215
+ again: a save made meanwhile — a step of their own, or the app saving inside a
216
+ call — goes ahead without them, so nothing waits on itself. A moment set off
217
+ by its own run, as a `show` whose `goTo` leads back, does not run.
218
+
219
+ ### Events
220
+
221
+ ```ts
222
+ const off = form.on('change', ({ field, value, values, by }) => {}); // by: 'person' | 'step' | 'app'
223
+ form.on('save', ({ recordId, values }) => {});
224
+ form.on('send', ({ values }) => {}); // a page of responses: the answers sent
225
+ form.on('action', ({ request, result }) => {}); // the app answered a call
226
+ form.on('run', ({ id, steps, result }) => {}); // a run ended
227
+ form.on('step', ({ step }) => {}); // a wizard's step entered
228
+ form.on('open', ({ recordId, values }) => {});
229
+ off();
230
+ ```
231
+
232
+ `subscribe` still hears every change of the form's state; `settled()` now
233
+ waits for runs too, a question still unanswered among them.
234
+
31
235
  To show a page, use [`@fieldia/viewer`](https://www.npmjs.com/package/@fieldia/viewer)
32
236
  or a framework binding. MIT licensed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldia/core",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Headless form model for Fieldia: the page format, modifiers, validation and record state. No DOM.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://fieldia.dev",