@fieldia/core 0.20.0 → 0.22.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.
- package/README.md +116 -1
- package/package.json +1 -1
- package/schema/page.schema.json +173 -1
- package/src/index.d.mts +2 -2
- package/src/index.d.ts +2 -2
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
- package/src/index.mjs +2 -2
- package/src/index.mjs.map +1 -1
- package/src/lib/expression/parser.js +4 -4
- package/src/lib/expression/parser.js.map +1 -1
- package/src/lib/expression/parser.mjs +5 -5
- package/src/lib/expression/parser.mjs.map +1 -1
- package/src/lib/expression/tokenizer.d.mts +12 -0
- package/src/lib/expression/tokenizer.d.ts +12 -0
- package/src/lib/expression/tokenizer.js +21 -12
- package/src/lib/expression/tokenizer.js.map +1 -1
- package/src/lib/expression/tokenizer.mjs +20 -12
- package/src/lib/expression/tokenizer.mjs.map +1 -1
- package/src/lib/format/check-page.d.mts +3 -2
- package/src/lib/format/check-page.d.ts +3 -2
- package/src/lib/format/check-page.js +9 -4
- package/src/lib/format/check-page.js.map +1 -1
- package/src/lib/format/check-page.mjs +10 -5
- package/src/lib/format/check-page.mjs.map +1 -1
- package/src/lib/format/field.d.mts +101 -0
- package/src/lib/format/field.d.ts +101 -0
- package/src/lib/format/field.js +25 -2
- package/src/lib/format/field.js.map +1 -1
- package/src/lib/format/field.mjs +24 -1
- package/src/lib/format/field.mjs.map +1 -1
- package/src/lib/format/layout.d.mts +142 -17
- package/src/lib/format/layout.d.ts +142 -17
- package/src/lib/format/layout.js +18 -1
- package/src/lib/format/layout.js.map +1 -1
- package/src/lib/format/layout.mjs +17 -0
- package/src/lib/format/layout.mjs.map +1 -1
- package/src/lib/format/layout.types.d.mts +6 -2
- package/src/lib/format/layout.types.d.ts +6 -2
- package/src/lib/format/page.d.mts +87 -10
- package/src/lib/format/page.d.ts +87 -10
- package/src/lib/format/page.js +5 -1
- package/src/lib/format/page.js.map +1 -1
- package/src/lib/format/page.mjs +6 -2
- package/src/lib/format/page.mjs.map +1 -1
- package/src/lib/format/pattern.d.mts +10 -0
- package/src/lib/format/pattern.d.ts +10 -0
- package/src/lib/format/pattern.js +33 -0
- package/src/lib/format/pattern.js.map +1 -0
- package/src/lib/format/pattern.mjs +30 -0
- package/src/lib/format/pattern.mjs.map +1 -0
- package/src/lib/format/references.d.mts +7 -0
- package/src/lib/format/references.d.ts +7 -0
- package/src/lib/format/references.js +54 -0
- package/src/lib/format/references.js.map +1 -1
- package/src/lib/format/references.mjs +54 -0
- package/src/lib/format/references.mjs.map +1 -1
- package/src/lib/format/translate.js +6 -4
- package/src/lib/format/translate.js.map +1 -1
- package/src/lib/format/translate.mjs +6 -4
- package/src/lib/format/translate.mjs.map +1 -1
- package/src/lib/format/upgrade.d.mts +41 -0
- package/src/lib/format/upgrade.d.ts +41 -0
- package/src/lib/format/upgrade.js +41 -0
- package/src/lib/format/upgrade.js.map +1 -0
- package/src/lib/format/upgrade.mjs +37 -0
- package/src/lib/format/upgrade.mjs.map +1 -0
- package/src/lib/format/validate.d.mts +2 -1
- package/src/lib/format/validate.d.ts +2 -1
- package/src/lib/format/validate.js +4 -2
- package/src/lib/format/validate.js.map +1 -1
- package/src/lib/format/validate.mjs +4 -2
- package/src/lib/format/validate.mjs.map +1 -1
- package/src/lib/format/version.d.mts +16 -1
- package/src/lib/format/version.d.ts +16 -1
- package/src/lib/format/version.js +39 -2
- package/src/lib/format/version.js.map +1 -1
- package/src/lib/format/version.mjs +36 -1
- package/src/lib/format/version.mjs.map +1 -1
- package/src/lib/record/check.d.mts +13 -3
- package/src/lib/record/check.d.ts +13 -3
- package/src/lib/record/check.js +87 -17
- package/src/lib/record/check.js.map +1 -1
- package/src/lib/record/check.mjs +85 -17
- package/src/lib/record/check.mjs.map +1 -1
- package/src/lib/record/data-source.d.mts +224 -10
- package/src/lib/record/data-source.d.ts +224 -10
- package/src/lib/record/data-source.js +29 -0
- package/src/lib/record/data-source.js.map +1 -1
- package/src/lib/record/data-source.mjs +25 -0
- package/src/lib/record/data-source.mjs.map +1 -1
- package/src/lib/record/filter.js +14 -2
- package/src/lib/record/filter.js.map +1 -1
- package/src/lib/record/filter.mjs +14 -2
- package/src/lib/record/filter.mjs.map +1 -1
- package/src/lib/record/form.d.mts +186 -3
- package/src/lib/record/form.d.ts +186 -3
- package/src/lib/record/form.js +1036 -57
- package/src/lib/record/form.js.map +1 -1
- package/src/lib/record/form.mjs +1037 -60
- package/src/lib/record/form.mjs.map +1 -1
- package/src/lib/record/input-rules.d.mts +277 -0
- package/src/lib/record/input-rules.d.ts +277 -0
- package/src/lib/record/input-rules.js +724 -0
- package/src/lib/record/input-rules.js.map +1 -0
- package/src/lib/record/input-rules.mjs +705 -0
- package/src/lib/record/input-rules.mjs.map +1 -0
- package/src/lib/record/inputs.d.mts +25 -0
- package/src/lib/record/inputs.d.ts +25 -0
- package/src/lib/record/inputs.js +135 -9
- package/src/lib/record/inputs.js.map +1 -1
- package/src/lib/record/inputs.mjs +131 -9
- package/src/lib/record/inputs.mjs.map +1 -1
- package/src/lib/record/locales/ar.js +23 -2
- package/src/lib/record/locales/ar.js.map +1 -1
- package/src/lib/record/locales/ar.mjs +23 -2
- package/src/lib/record/locales/ar.mjs.map +1 -1
- package/src/lib/record/locales/de.js +23 -2
- package/src/lib/record/locales/de.js.map +1 -1
- package/src/lib/record/locales/de.mjs +23 -2
- package/src/lib/record/locales/de.mjs.map +1 -1
- package/src/lib/record/locales/fr.js +23 -2
- package/src/lib/record/locales/fr.js.map +1 -1
- package/src/lib/record/locales/fr.mjs +23 -2
- package/src/lib/record/locales/fr.mjs.map +1 -1
- package/src/lib/record/memory-data-source.d.mts +40 -3
- package/src/lib/record/memory-data-source.d.ts +40 -3
- package/src/lib/record/memory-data-source.js +171 -23
- package/src/lib/record/memory-data-source.js.map +1 -1
- package/src/lib/record/memory-data-source.mjs +171 -23
- package/src/lib/record/memory-data-source.mjs.map +1 -1
- package/src/lib/record/messages.d.mts +43 -0
- package/src/lib/record/messages.d.ts +43 -0
- package/src/lib/record/messages.js +23 -2
- package/src/lib/record/messages.js.map +1 -1
- package/src/lib/record/messages.mjs +23 -2
- package/src/lib/record/messages.mjs.map +1 -1
- package/src/lib/record/rules.js +3 -1
- package/src/lib/record/rules.js.map +1 -1
- package/src/lib/record/rules.mjs +3 -1
- package/src/lib/record/rules.mjs.map +1 -1
- package/src/lib/record/run.d.mts +53 -9
- package/src/lib/record/run.d.ts +53 -9
- package/src/lib/record/run.js +68 -21
- package/src/lib/record/run.js.map +1 -1
- package/src/lib/record/run.mjs +68 -21
- package/src/lib/record/run.mjs.map +1 -1
- package/src/lib/record/search.d.mts +8 -1
- package/src/lib/record/search.d.ts +8 -1
- package/src/lib/record/search.js +28 -8
- package/src/lib/record/search.js.map +1 -1
- package/src/lib/record/search.mjs +28 -8
- package/src/lib/record/search.mjs.map +1 -1
- package/src/lib/record/structures.d.mts +8 -3
- package/src/lib/record/structures.d.ts +8 -3
- package/src/lib/record/structures.js +51 -7
- package/src/lib/record/structures.js.map +1 -1
- package/src/lib/record/structures.mjs +48 -6
- package/src/lib/record/structures.mjs.map +1 -1
- package/src/lib/record/upload-to.d.mts +50 -0
- package/src/lib/record/upload-to.d.ts +50 -0
- package/src/lib/record/upload-to.js +117 -0
- package/src/lib/record/upload-to.js.map +1 -0
- package/src/lib/record/upload-to.mjs +114 -0
- package/src/lib/record/upload-to.mjs.map +1 -0
- package/src/lib/record/uploads.d.mts +152 -0
- package/src/lib/record/uploads.d.ts +152 -0
- package/src/lib/record/uploads.js +345 -0
- package/src/lib/record/uploads.js.map +1 -0
- package/src/lib/record/uploads.mjs +337 -0
- package/src/lib/record/uploads.mjs.map +1 -0
- package/src/lib/record/values.d.mts +6 -0
- package/src/lib/record/values.d.ts +6 -0
- package/src/lib/record/values.js.map +1 -1
- package/src/lib/record/values.mjs.map +1 -1
- package/src/runtime.d.mts +15 -8
- package/src/runtime.d.ts +15 -8
- package/src/runtime.js +48 -1
- package/src/runtime.js.map +1 -1
- package/src/runtime.mjs +12 -5
- package/src/runtime.mjs.map +1 -1
|
@@ -4,7 +4,8 @@ import type { Page } from '../format/page.mjs';
|
|
|
4
4
|
import type { JsonValue } from '../format/json.mjs';
|
|
5
5
|
import { type FieldTone, type LineState } from './cells.mjs';
|
|
6
6
|
import { type Messages } from './messages.mjs';
|
|
7
|
-
import { type DataSource, type RecordChanges, type SaveProblem } from './data-source.mjs';
|
|
7
|
+
import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type SaveProblem, type UploadFile } from './data-source.mjs';
|
|
8
|
+
import { type FileUpload, type UploadHow } from './uploads.mjs';
|
|
8
9
|
import { type Scheduler } from './scheduler.mjs';
|
|
9
10
|
import { type ActionHost, type ActionRequest, type ActionResult, type OnAction, type PostedMessage, type RunContext, type RunResult } from './run.mjs';
|
|
10
11
|
import { type RecordId, type RelatedRecord, type Value, type Values } from './values.mjs';
|
|
@@ -42,6 +43,89 @@ export interface FormState {
|
|
|
42
43
|
readonly choices: Readonly<Record<string, Choices>>;
|
|
43
44
|
/** The definitions loaded for a properties field with `definitionsFrom`, by field, once asked for. */
|
|
44
45
|
readonly definitions: Readonly<Record<string, Definitions>>;
|
|
46
|
+
/** Files on their way to the data source's store, in the order picked: each field shows its own (`upload`). */
|
|
47
|
+
readonly uploads: readonly FileUpload[];
|
|
48
|
+
/** The record's version, as loaded or last saved, from a source that keeps versions: each save carries it. Null for a new record, or from a source that tells none. */
|
|
49
|
+
readonly version: RecordVersion | null;
|
|
50
|
+
/**
|
|
51
|
+
* What the person may do with the record, as its source told with its load
|
|
52
|
+
* or its last save: the form keeps to it — fields locked or hidden, Save,
|
|
53
|
+
* Archive, Duplicate and Delete not offered. Null when it told nothing, and
|
|
54
|
+
* everything is allowed.
|
|
55
|
+
*/
|
|
56
|
+
readonly can: Readonly<RecordPermissions> | null;
|
|
57
|
+
/** Someone else saved the record after this page opened it, and the last save was refused for it: until Reload, Keep mine or another save. */
|
|
58
|
+
readonly conflict: FormConflict | null;
|
|
59
|
+
/** The person's changes Reload set aside after a conflict, by field, to put back (`putBack`); null when none are. */
|
|
60
|
+
readonly setAside: Readonly<Values> | null;
|
|
61
|
+
/**
|
|
62
|
+
* Fields holding a change of the person's that the last finished save of
|
|
63
|
+
* this record did not send: made while it was on its way, while the run that
|
|
64
|
+
* saved went on (its afterSave run, a copy being made), or since. Worked out
|
|
65
|
+
* from the record as that save left it, so a change back to it is none; and
|
|
66
|
+
* before the record's first save, only a file the store holds and no save
|
|
67
|
+
* sent. Never a value a step, a rule, a compute or the app set: each change
|
|
68
|
+
* is told by its origin. The next record starts with none.
|
|
69
|
+
*/
|
|
70
|
+
readonly unsent: readonly string[];
|
|
71
|
+
/**
|
|
72
|
+
* A way out (`leave()`) waits for the save on its way, or files on theirs,
|
|
73
|
+
* before it checks: the page says what it waits for.
|
|
74
|
+
*/
|
|
75
|
+
readonly leaving: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Text a person left in a box that does not read as its field's value — "12 34" in a number, "31/02/2026" in a
|
|
78
|
+
* date — by its path (`field`, or a line's `field.lineKey.subfield`), with why in words. The field keeps the
|
|
79
|
+
* value it held as the box was entered; validate and save refuse with the words until the text is fixed.
|
|
80
|
+
*/
|
|
81
|
+
readonly unread: Readonly<Record<string, UnreadText>>;
|
|
82
|
+
}
|
|
83
|
+
/** Text left in a box that does not read, and why, as `FormState.unread` keeps it. */
|
|
84
|
+
export interface UnreadText {
|
|
85
|
+
readonly text: string;
|
|
86
|
+
readonly message: string;
|
|
87
|
+
}
|
|
88
|
+
/** A save refused because someone else saved the record first: who and when, and what each of them changed. */
|
|
89
|
+
export interface FormConflict {
|
|
90
|
+
/** The version the record has now, with who saved it (`by`) and when (`at`): Keep mine saves over it. */
|
|
91
|
+
readonly version: RecordVersion;
|
|
92
|
+
/** The record as they left it, when the source told it. */
|
|
93
|
+
readonly theirs?: Readonly<Values>;
|
|
94
|
+
/** The fields they changed since this page opened the record, in the page's order: none known when the source did not tell the record. */
|
|
95
|
+
readonly changed: readonly string[];
|
|
96
|
+
/** Those of them this person changed too, each to something else. */
|
|
97
|
+
readonly both: readonly string[];
|
|
98
|
+
/**
|
|
99
|
+
* Keep mine was pressed, and the record as they left it could not be read:
|
|
100
|
+
* nothing was saved over their work, and the person may try again or Reload.
|
|
101
|
+
*/
|
|
102
|
+
readonly unread?: boolean;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Whether leaving the record now would lose the person's work: changes not
|
|
106
|
+
* saved, files still on their way to the store, or changes Reload set aside
|
|
107
|
+
* and not yet put back or let go, or text left in a box that does not read.
|
|
108
|
+
* Every way out of a record asks this.
|
|
109
|
+
*/
|
|
110
|
+
export declare function unsavedWork(state: FormState): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Whether a way out would lose the person's work without their knowing: a
|
|
113
|
+
* change of theirs the last save did not send (`unsent`), files still on
|
|
114
|
+
* their way, changes Reload set aside — and, `how.changes`, any change not
|
|
115
|
+
* saved. What `form.leave()` — the one check every way out goes through —
|
|
116
|
+
* asks about, once the save on its way and the files on theirs settled.
|
|
117
|
+
* Before a record's first save, a change not saved is not at risk: as in
|
|
118
|
+
* 0.20, each way out decides.
|
|
119
|
+
*/
|
|
120
|
+
export declare function workAtRisk(state: FormState, how?: LeaveHow): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* How a way out asks: `changes`, every change not saved counts too, as a
|
|
123
|
+
* panel's Escape has always asked; `discard`, the person's own Discard: it
|
|
124
|
+
* waits as any way out does, asks nothing, and lets their work go.
|
|
125
|
+
*/
|
|
126
|
+
export interface LeaveHow {
|
|
127
|
+
changes?: boolean;
|
|
128
|
+
discard?: boolean;
|
|
45
129
|
}
|
|
46
130
|
/** A properties field's definitions from the app, as they load. */
|
|
47
131
|
export interface Definitions {
|
|
@@ -225,9 +309,43 @@ export interface Form {
|
|
|
225
309
|
node(id: string): NodeState;
|
|
226
310
|
/** Whether the record is being edited, as conditions read `editing`: a view that locks it to be read says false, and parts follow. */
|
|
227
311
|
setEditing(editing: boolean): void;
|
|
312
|
+
/** Whether a field is read-only whatever the conditions say: by its definition, or for this person by what the record's source said (`can`). */
|
|
228
313
|
fieldReadonly(name: string): boolean;
|
|
229
314
|
/** What a field would be told if the form were checked now, or null when it passes. Nothing is shown. */
|
|
230
315
|
problem(field: string): string | null;
|
|
316
|
+
/**
|
|
317
|
+
* Keep text a person left in a box that does not read (`FormState.unread`), by its path — `field`, or a line's
|
|
318
|
+
* `field.lineKey.subfield` — with why in words; null once it reads, or is let go. Nothing is shown until the box
|
|
319
|
+
* is left (`boxLeft`) or the form is checked.
|
|
320
|
+
*/
|
|
321
|
+
setUnread(path: string, unread: UnreadText | null): void;
|
|
322
|
+
/**
|
|
323
|
+
* A person left a field's box, by its path: its own problem — text that does not read, a format, min or max, a
|
|
324
|
+
* whole number, decimals, a pattern — is shown now, in the words Save would use. That it is required waits for Save.
|
|
325
|
+
*/
|
|
326
|
+
boxLeft(path: string): void;
|
|
327
|
+
/**
|
|
328
|
+
* How a box reads what is typed in it and not yet read — a number, hours, a date, a colour's code, a name looked
|
|
329
|
+
* up — now, as if it were left: every save and send, every check and step forward runs these first (`settle`), so
|
|
330
|
+
* Ctrl+Enter, a button's key, a pager or the app's own save keep what the box shows, or refuse it with its words,
|
|
331
|
+
* and never save a value passed on the way ("1" of "1.5") or the one the box held before. The viewer gives one
|
|
332
|
+
* for the box that has the focus (a change, which each box reads); a box whose reading waits — a name looked up
|
|
333
|
+
* among records — gives its own, which keeps the text unread with why at once, and returns the wait: a save
|
|
334
|
+
* waits for it, while a check or a step forward, which cannot wait, refuses meanwhile. `typing` says whether the
|
|
335
|
+
* box is still being typed in (typed since it last read): autosave, which nobody pressed, waits while it is, and
|
|
336
|
+
* never reads text under the person's keys ("12." of "12.5", "Office " of "Office Park"). Returns what stops it.
|
|
337
|
+
*/
|
|
338
|
+
settling(read: () => void | Promise<unknown>, typing?: () => boolean): () => void;
|
|
339
|
+
/**
|
|
340
|
+
* Read what is typed and not yet read in every box (`settling`), as if each were left: a save does it first.
|
|
341
|
+
* Resolves once every reading that waits (a name looked up) has ended.
|
|
342
|
+
*/
|
|
343
|
+
settle(): Promise<void>;
|
|
344
|
+
/**
|
|
345
|
+
* Whether a box's reading of what was typed still waits (a name being looked up): a check or a step forward made
|
|
346
|
+
* now refuses its text with "still looking", so a caller that can wait for it (`settle`) checks again after.
|
|
347
|
+
*/
|
|
348
|
+
reading(): boolean;
|
|
231
349
|
validate(): boolean;
|
|
232
350
|
save(): Promise<boolean>;
|
|
233
351
|
reset(): void;
|
|
@@ -246,9 +364,32 @@ export interface Form {
|
|
|
246
364
|
/**
|
|
247
365
|
* Show another record of the page's model in this form, loaded from the
|
|
248
366
|
* data source as a pager moves to it, or a new one for null. Changes not
|
|
249
|
-
* saved are dropped: save first. The page's `open` steps run for it.
|
|
367
|
+
* saved are dropped: save first. The page's `open` steps run for it. It
|
|
368
|
+
* goes through `leave()` first: false when the person kept their work.
|
|
369
|
+
*/
|
|
370
|
+
openRecord(id: RecordId | null): Promise<boolean>;
|
|
371
|
+
/**
|
|
372
|
+
* The one check every way out of the form goes through — another record,
|
|
373
|
+
* the record loaded again, the form closed, reset by a step — and the app's
|
|
374
|
+
* own ways out should too, before it navigates or unmounts the form. It
|
|
375
|
+
* first waits for a save on its way and files on theirs (`leaving`
|
|
376
|
+
* meanwhile), then looks at the record as it is now. With the person's
|
|
377
|
+
* work at risk (`workAtRisk`), it asks "Discard your changes?" through the
|
|
378
|
+
* host (`ask(message, { discard: true })`), else `confirm`: true on
|
|
379
|
+
* Discard — files on their way stopped, changes set aside let go — false
|
|
380
|
+
* on Cancel, the form as it was. Without either to ask with, the work is
|
|
381
|
+
* kept: false. A way out taken while one waits or asks joins it. Nothing
|
|
382
|
+
* on its way and nothing at risk: true, as in 0.20.
|
|
383
|
+
*/
|
|
384
|
+
leave(how?: LeaveHow): Promise<boolean>;
|
|
385
|
+
/**
|
|
386
|
+
* Whether a way out goes at once: nothing on its way, no check going, and
|
|
387
|
+
* nothing of the person's at risk (with `how`) — then it goes in the same
|
|
388
|
+
* moment, as in 0.20, and `leave()` would answer true. Else take it through
|
|
389
|
+
* `leave()`. What the viewer's dialogs, pages in place and breadcrumbs ask
|
|
390
|
+
* first, so a way out with nothing to wait for is not put off.
|
|
250
391
|
*/
|
|
251
|
-
|
|
392
|
+
leaveNow(how?: LeaveHow): boolean;
|
|
252
393
|
/**
|
|
253
394
|
* Post words in the record's conversation, as a `post` step does: through
|
|
254
395
|
* the form's host when it has a way to, else told to the form's own
|
|
@@ -352,6 +493,48 @@ export interface Form {
|
|
|
352
493
|
on<E extends keyof FormEvents>(event: E, listener: (event: FormEvents[E]) => void): () => void;
|
|
353
494
|
restoreDraft(): void;
|
|
354
495
|
discardDraft(): void;
|
|
496
|
+
/**
|
|
497
|
+
* Whether a file picked for a file or picture field is stored at once,
|
|
498
|
+
* through the data source's `upload`. False without one: a file then
|
|
499
|
+
* travels inside the record as base64, as it always has.
|
|
500
|
+
*/
|
|
501
|
+
canUpload(): boolean;
|
|
502
|
+
/**
|
|
503
|
+
* Store files picked for a file or picture field — or a field on a table's
|
|
504
|
+
* line (`how.line`) — through the data source's `upload`, each as soon as
|
|
505
|
+
* it is picked: `uploads` in the state shows each on its way, and once
|
|
506
|
+
* stored the field holds its link, a field of several in the order they
|
|
507
|
+
* were picked. Three go at once, the rest wait their turn; `how.prepare`
|
|
508
|
+
* makes each ready first, one at a time (a photo made smaller). A field of
|
|
509
|
+
* one file keeps the last one picked. Gives each upload's id. A save waits
|
|
510
|
+
* for those still going, and stops, under the field, for one that failed.
|
|
511
|
+
*/
|
|
512
|
+
upload(field: string, files: readonly UploadFile[], how?: UploadHow): string[];
|
|
513
|
+
/**
|
|
514
|
+
* A field's files on their way, in the order picked: the uploads in the
|
|
515
|
+
* state that are its own. A table's cell (`lineForm`) gives its line's.
|
|
516
|
+
*/
|
|
517
|
+
uploading(field: string): readonly FileUpload[];
|
|
518
|
+
/** Stop an upload and drop its file, whether it is on its way, waiting, or failed. */
|
|
519
|
+
cancelUpload(id: string): void;
|
|
520
|
+
/** Send a file that failed again. */
|
|
521
|
+
retryUpload(id: string): void;
|
|
522
|
+
/**
|
|
523
|
+
* After a conflict: show the record as the other person left it, loaded
|
|
524
|
+
* again, and set this person's changes aside (`setAside`), to put back.
|
|
525
|
+
* False when it could not be loaded.
|
|
526
|
+
*/
|
|
527
|
+
reloadTheirs(): Promise<boolean>;
|
|
528
|
+
/**
|
|
529
|
+
* After a conflict: save this person's changes over the record as it is
|
|
530
|
+
* now — the other person's values kept wherever this one changed nothing.
|
|
531
|
+
* Refused again as a conflict when a third person saved in between.
|
|
532
|
+
*/
|
|
533
|
+
keepMine(): Promise<boolean>;
|
|
534
|
+
/** Write a change set aside by Reload back, as the person's own: one field's, or all when none is named. */
|
|
535
|
+
putBack(field?: string): void;
|
|
536
|
+
/** Let go of the changes set aside. */
|
|
537
|
+
discardSetAside(): void;
|
|
355
538
|
/** Resolves once every onchange, save, autosave and run of steps started so far has finished — a question still waiting for its answer included. */
|
|
356
539
|
settled(): Promise<void>;
|
|
357
540
|
/**
|
package/src/lib/record/form.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { Page } from '../format/page';
|
|
|
4
4
|
import type { JsonValue } from '../format/json';
|
|
5
5
|
import { type FieldTone, type LineState } from './cells';
|
|
6
6
|
import { type Messages } from './messages';
|
|
7
|
-
import { type DataSource, type RecordChanges, type SaveProblem } from './data-source';
|
|
7
|
+
import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type SaveProblem, type UploadFile } from './data-source';
|
|
8
|
+
import { type FileUpload, type UploadHow } from './uploads';
|
|
8
9
|
import { type Scheduler } from './scheduler';
|
|
9
10
|
import { type ActionHost, type ActionRequest, type ActionResult, type OnAction, type PostedMessage, type RunContext, type RunResult } from './run';
|
|
10
11
|
import { type RecordId, type RelatedRecord, type Value, type Values } from './values';
|
|
@@ -42,6 +43,89 @@ export interface FormState {
|
|
|
42
43
|
readonly choices: Readonly<Record<string, Choices>>;
|
|
43
44
|
/** The definitions loaded for a properties field with `definitionsFrom`, by field, once asked for. */
|
|
44
45
|
readonly definitions: Readonly<Record<string, Definitions>>;
|
|
46
|
+
/** Files on their way to the data source's store, in the order picked: each field shows its own (`upload`). */
|
|
47
|
+
readonly uploads: readonly FileUpload[];
|
|
48
|
+
/** The record's version, as loaded or last saved, from a source that keeps versions: each save carries it. Null for a new record, or from a source that tells none. */
|
|
49
|
+
readonly version: RecordVersion | null;
|
|
50
|
+
/**
|
|
51
|
+
* What the person may do with the record, as its source told with its load
|
|
52
|
+
* or its last save: the form keeps to it — fields locked or hidden, Save,
|
|
53
|
+
* Archive, Duplicate and Delete not offered. Null when it told nothing, and
|
|
54
|
+
* everything is allowed.
|
|
55
|
+
*/
|
|
56
|
+
readonly can: Readonly<RecordPermissions> | null;
|
|
57
|
+
/** Someone else saved the record after this page opened it, and the last save was refused for it: until Reload, Keep mine or another save. */
|
|
58
|
+
readonly conflict: FormConflict | null;
|
|
59
|
+
/** The person's changes Reload set aside after a conflict, by field, to put back (`putBack`); null when none are. */
|
|
60
|
+
readonly setAside: Readonly<Values> | null;
|
|
61
|
+
/**
|
|
62
|
+
* Fields holding a change of the person's that the last finished save of
|
|
63
|
+
* this record did not send: made while it was on its way, while the run that
|
|
64
|
+
* saved went on (its afterSave run, a copy being made), or since. Worked out
|
|
65
|
+
* from the record as that save left it, so a change back to it is none; and
|
|
66
|
+
* before the record's first save, only a file the store holds and no save
|
|
67
|
+
* sent. Never a value a step, a rule, a compute or the app set: each change
|
|
68
|
+
* is told by its origin. The next record starts with none.
|
|
69
|
+
*/
|
|
70
|
+
readonly unsent: readonly string[];
|
|
71
|
+
/**
|
|
72
|
+
* A way out (`leave()`) waits for the save on its way, or files on theirs,
|
|
73
|
+
* before it checks: the page says what it waits for.
|
|
74
|
+
*/
|
|
75
|
+
readonly leaving: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Text a person left in a box that does not read as its field's value — "12 34" in a number, "31/02/2026" in a
|
|
78
|
+
* date — by its path (`field`, or a line's `field.lineKey.subfield`), with why in words. The field keeps the
|
|
79
|
+
* value it held as the box was entered; validate and save refuse with the words until the text is fixed.
|
|
80
|
+
*/
|
|
81
|
+
readonly unread: Readonly<Record<string, UnreadText>>;
|
|
82
|
+
}
|
|
83
|
+
/** Text left in a box that does not read, and why, as `FormState.unread` keeps it. */
|
|
84
|
+
export interface UnreadText {
|
|
85
|
+
readonly text: string;
|
|
86
|
+
readonly message: string;
|
|
87
|
+
}
|
|
88
|
+
/** A save refused because someone else saved the record first: who and when, and what each of them changed. */
|
|
89
|
+
export interface FormConflict {
|
|
90
|
+
/** The version the record has now, with who saved it (`by`) and when (`at`): Keep mine saves over it. */
|
|
91
|
+
readonly version: RecordVersion;
|
|
92
|
+
/** The record as they left it, when the source told it. */
|
|
93
|
+
readonly theirs?: Readonly<Values>;
|
|
94
|
+
/** The fields they changed since this page opened the record, in the page's order: none known when the source did not tell the record. */
|
|
95
|
+
readonly changed: readonly string[];
|
|
96
|
+
/** Those of them this person changed too, each to something else. */
|
|
97
|
+
readonly both: readonly string[];
|
|
98
|
+
/**
|
|
99
|
+
* Keep mine was pressed, and the record as they left it could not be read:
|
|
100
|
+
* nothing was saved over their work, and the person may try again or Reload.
|
|
101
|
+
*/
|
|
102
|
+
readonly unread?: boolean;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Whether leaving the record now would lose the person's work: changes not
|
|
106
|
+
* saved, files still on their way to the store, or changes Reload set aside
|
|
107
|
+
* and not yet put back or let go, or text left in a box that does not read.
|
|
108
|
+
* Every way out of a record asks this.
|
|
109
|
+
*/
|
|
110
|
+
export declare function unsavedWork(state: FormState): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Whether a way out would lose the person's work without their knowing: a
|
|
113
|
+
* change of theirs the last save did not send (`unsent`), files still on
|
|
114
|
+
* their way, changes Reload set aside — and, `how.changes`, any change not
|
|
115
|
+
* saved. What `form.leave()` — the one check every way out goes through —
|
|
116
|
+
* asks about, once the save on its way and the files on theirs settled.
|
|
117
|
+
* Before a record's first save, a change not saved is not at risk: as in
|
|
118
|
+
* 0.20, each way out decides.
|
|
119
|
+
*/
|
|
120
|
+
export declare function workAtRisk(state: FormState, how?: LeaveHow): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* How a way out asks: `changes`, every change not saved counts too, as a
|
|
123
|
+
* panel's Escape has always asked; `discard`, the person's own Discard: it
|
|
124
|
+
* waits as any way out does, asks nothing, and lets their work go.
|
|
125
|
+
*/
|
|
126
|
+
export interface LeaveHow {
|
|
127
|
+
changes?: boolean;
|
|
128
|
+
discard?: boolean;
|
|
45
129
|
}
|
|
46
130
|
/** A properties field's definitions from the app, as they load. */
|
|
47
131
|
export interface Definitions {
|
|
@@ -225,9 +309,43 @@ export interface Form {
|
|
|
225
309
|
node(id: string): NodeState;
|
|
226
310
|
/** Whether the record is being edited, as conditions read `editing`: a view that locks it to be read says false, and parts follow. */
|
|
227
311
|
setEditing(editing: boolean): void;
|
|
312
|
+
/** Whether a field is read-only whatever the conditions say: by its definition, or for this person by what the record's source said (`can`). */
|
|
228
313
|
fieldReadonly(name: string): boolean;
|
|
229
314
|
/** What a field would be told if the form were checked now, or null when it passes. Nothing is shown. */
|
|
230
315
|
problem(field: string): string | null;
|
|
316
|
+
/**
|
|
317
|
+
* Keep text a person left in a box that does not read (`FormState.unread`), by its path — `field`, or a line's
|
|
318
|
+
* `field.lineKey.subfield` — with why in words; null once it reads, or is let go. Nothing is shown until the box
|
|
319
|
+
* is left (`boxLeft`) or the form is checked.
|
|
320
|
+
*/
|
|
321
|
+
setUnread(path: string, unread: UnreadText | null): void;
|
|
322
|
+
/**
|
|
323
|
+
* A person left a field's box, by its path: its own problem — text that does not read, a format, min or max, a
|
|
324
|
+
* whole number, decimals, a pattern — is shown now, in the words Save would use. That it is required waits for Save.
|
|
325
|
+
*/
|
|
326
|
+
boxLeft(path: string): void;
|
|
327
|
+
/**
|
|
328
|
+
* How a box reads what is typed in it and not yet read — a number, hours, a date, a colour's code, a name looked
|
|
329
|
+
* up — now, as if it were left: every save and send, every check and step forward runs these first (`settle`), so
|
|
330
|
+
* Ctrl+Enter, a button's key, a pager or the app's own save keep what the box shows, or refuse it with its words,
|
|
331
|
+
* and never save a value passed on the way ("1" of "1.5") or the one the box held before. The viewer gives one
|
|
332
|
+
* for the box that has the focus (a change, which each box reads); a box whose reading waits — a name looked up
|
|
333
|
+
* among records — gives its own, which keeps the text unread with why at once, and returns the wait: a save
|
|
334
|
+
* waits for it, while a check or a step forward, which cannot wait, refuses meanwhile. `typing` says whether the
|
|
335
|
+
* box is still being typed in (typed since it last read): autosave, which nobody pressed, waits while it is, and
|
|
336
|
+
* never reads text under the person's keys ("12." of "12.5", "Office " of "Office Park"). Returns what stops it.
|
|
337
|
+
*/
|
|
338
|
+
settling(read: () => void | Promise<unknown>, typing?: () => boolean): () => void;
|
|
339
|
+
/**
|
|
340
|
+
* Read what is typed and not yet read in every box (`settling`), as if each were left: a save does it first.
|
|
341
|
+
* Resolves once every reading that waits (a name looked up) has ended.
|
|
342
|
+
*/
|
|
343
|
+
settle(): Promise<void>;
|
|
344
|
+
/**
|
|
345
|
+
* Whether a box's reading of what was typed still waits (a name being looked up): a check or a step forward made
|
|
346
|
+
* now refuses its text with "still looking", so a caller that can wait for it (`settle`) checks again after.
|
|
347
|
+
*/
|
|
348
|
+
reading(): boolean;
|
|
231
349
|
validate(): boolean;
|
|
232
350
|
save(): Promise<boolean>;
|
|
233
351
|
reset(): void;
|
|
@@ -246,9 +364,32 @@ export interface Form {
|
|
|
246
364
|
/**
|
|
247
365
|
* Show another record of the page's model in this form, loaded from the
|
|
248
366
|
* data source as a pager moves to it, or a new one for null. Changes not
|
|
249
|
-
* saved are dropped: save first. The page's `open` steps run for it.
|
|
367
|
+
* saved are dropped: save first. The page's `open` steps run for it. It
|
|
368
|
+
* goes through `leave()` first: false when the person kept their work.
|
|
369
|
+
*/
|
|
370
|
+
openRecord(id: RecordId | null): Promise<boolean>;
|
|
371
|
+
/**
|
|
372
|
+
* The one check every way out of the form goes through — another record,
|
|
373
|
+
* the record loaded again, the form closed, reset by a step — and the app's
|
|
374
|
+
* own ways out should too, before it navigates or unmounts the form. It
|
|
375
|
+
* first waits for a save on its way and files on theirs (`leaving`
|
|
376
|
+
* meanwhile), then looks at the record as it is now. With the person's
|
|
377
|
+
* work at risk (`workAtRisk`), it asks "Discard your changes?" through the
|
|
378
|
+
* host (`ask(message, { discard: true })`), else `confirm`: true on
|
|
379
|
+
* Discard — files on their way stopped, changes set aside let go — false
|
|
380
|
+
* on Cancel, the form as it was. Without either to ask with, the work is
|
|
381
|
+
* kept: false. A way out taken while one waits or asks joins it. Nothing
|
|
382
|
+
* on its way and nothing at risk: true, as in 0.20.
|
|
383
|
+
*/
|
|
384
|
+
leave(how?: LeaveHow): Promise<boolean>;
|
|
385
|
+
/**
|
|
386
|
+
* Whether a way out goes at once: nothing on its way, no check going, and
|
|
387
|
+
* nothing of the person's at risk (with `how`) — then it goes in the same
|
|
388
|
+
* moment, as in 0.20, and `leave()` would answer true. Else take it through
|
|
389
|
+
* `leave()`. What the viewer's dialogs, pages in place and breadcrumbs ask
|
|
390
|
+
* first, so a way out with nothing to wait for is not put off.
|
|
250
391
|
*/
|
|
251
|
-
|
|
392
|
+
leaveNow(how?: LeaveHow): boolean;
|
|
252
393
|
/**
|
|
253
394
|
* Post words in the record's conversation, as a `post` step does: through
|
|
254
395
|
* the form's host when it has a way to, else told to the form's own
|
|
@@ -352,6 +493,48 @@ export interface Form {
|
|
|
352
493
|
on<E extends keyof FormEvents>(event: E, listener: (event: FormEvents[E]) => void): () => void;
|
|
353
494
|
restoreDraft(): void;
|
|
354
495
|
discardDraft(): void;
|
|
496
|
+
/**
|
|
497
|
+
* Whether a file picked for a file or picture field is stored at once,
|
|
498
|
+
* through the data source's `upload`. False without one: a file then
|
|
499
|
+
* travels inside the record as base64, as it always has.
|
|
500
|
+
*/
|
|
501
|
+
canUpload(): boolean;
|
|
502
|
+
/**
|
|
503
|
+
* Store files picked for a file or picture field — or a field on a table's
|
|
504
|
+
* line (`how.line`) — through the data source's `upload`, each as soon as
|
|
505
|
+
* it is picked: `uploads` in the state shows each on its way, and once
|
|
506
|
+
* stored the field holds its link, a field of several in the order they
|
|
507
|
+
* were picked. Three go at once, the rest wait their turn; `how.prepare`
|
|
508
|
+
* makes each ready first, one at a time (a photo made smaller). A field of
|
|
509
|
+
* one file keeps the last one picked. Gives each upload's id. A save waits
|
|
510
|
+
* for those still going, and stops, under the field, for one that failed.
|
|
511
|
+
*/
|
|
512
|
+
upload(field: string, files: readonly UploadFile[], how?: UploadHow): string[];
|
|
513
|
+
/**
|
|
514
|
+
* A field's files on their way, in the order picked: the uploads in the
|
|
515
|
+
* state that are its own. A table's cell (`lineForm`) gives its line's.
|
|
516
|
+
*/
|
|
517
|
+
uploading(field: string): readonly FileUpload[];
|
|
518
|
+
/** Stop an upload and drop its file, whether it is on its way, waiting, or failed. */
|
|
519
|
+
cancelUpload(id: string): void;
|
|
520
|
+
/** Send a file that failed again. */
|
|
521
|
+
retryUpload(id: string): void;
|
|
522
|
+
/**
|
|
523
|
+
* After a conflict: show the record as the other person left it, loaded
|
|
524
|
+
* again, and set this person's changes aside (`setAside`), to put back.
|
|
525
|
+
* False when it could not be loaded.
|
|
526
|
+
*/
|
|
527
|
+
reloadTheirs(): Promise<boolean>;
|
|
528
|
+
/**
|
|
529
|
+
* After a conflict: save this person's changes over the record as it is
|
|
530
|
+
* now — the other person's values kept wherever this one changed nothing.
|
|
531
|
+
* Refused again as a conflict when a third person saved in between.
|
|
532
|
+
*/
|
|
533
|
+
keepMine(): Promise<boolean>;
|
|
534
|
+
/** Write a change set aside by Reload back, as the person's own: one field's, or all when none is named. */
|
|
535
|
+
putBack(field?: string): void;
|
|
536
|
+
/** Let go of the changes set aside. */
|
|
537
|
+
discardSetAside(): void;
|
|
355
538
|
/** Resolves once every onchange, save, autosave and run of steps started so far has finished — a question still waiting for its answer included. */
|
|
356
539
|
settled(): Promise<void>;
|
|
357
540
|
/**
|