nexus-shared 3.2.0 → 4.1.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/CHANGELOG.md +2359 -0
- package/README.md +1911 -1123
- package/dist/Client.Index.d.ts +27 -0
- package/dist/Client.Index.js +27 -0
- package/dist/Components/Chats/ChatComposer.d.ts +7 -1
- package/dist/Components/Chats/ChatComposer.js +9 -6
- package/dist/Components/Chats/ChatParts.d.ts +15 -2
- package/dist/Components/Chats/ChatParts.js +15 -4
- package/dist/Components/Chats/ChatPopup.d.ts +46 -0
- package/dist/Components/Chats/ChatPopup.js +34 -0
- package/dist/Components/Chats/ChatThread.d.ts +9 -1
- package/dist/Components/Chats/ChatThread.js +54 -6
- package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
- package/dist/Components/Dashboards/DashboardBoard.js +594 -0
- package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
- package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
- package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
- package/dist/Components/Dashboards/WidgetFrame.js +45 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +24 -3
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +628 -247
- package/dist/Components/Forms/CrudActions.d.ts +137 -0
- package/dist/Components/Forms/CrudActions.js +214 -0
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +37 -2
- package/dist/Components/Forms/CrudParts.js +123 -7
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +174 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
- package/dist/Components/Forms/CrudWorkspace.js +177 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +6 -5
- package/dist/Components/Forms/Form.js +134 -21
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +182 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -6
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +12 -12
- package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
- package/dist/Components/Inputs/InputRenderer.js +37 -32
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +75 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +15 -2
- package/dist/Components/Inputs/RowsInput.js +96 -8
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +45 -32
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +11 -2
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TreeView.d.ts +76 -2
- package/dist/Components/Inputs/TreeView.js +227 -16
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +34 -7
- package/dist/Components/Layouts/Popup.js +143 -13
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- package/dist/Components/Messages/MailClient.d.ts +50 -0
- package/dist/Components/Messages/MailClient.js +410 -0
- package/dist/Components/Messages/MailColumns.d.ts +21 -0
- package/dist/Components/Messages/MailColumns.js +92 -0
- package/dist/Components/Messages/MailCompose.d.ts +46 -0
- package/dist/Components/Messages/MailCompose.js +154 -0
- package/dist/Components/Messages/MailReader.d.ts +43 -0
- package/dist/Components/Messages/MailReader.js +207 -0
- package/dist/Components/Messages/MailThread.d.ts +71 -0
- package/dist/Components/Messages/MailThread.js +185 -0
- package/dist/Components/Messages/MessageBell.d.ts +41 -0
- package/dist/Components/Messages/MessageBell.js +76 -0
- package/dist/Components/Viewers/DataTable.js +153 -49
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
- package/dist/Components/Viewers/DataTableParts.js +26 -14
- package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
- package/dist/Components/Viewers/OwnerFiles.js +700 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- package/dist/Helpers/ApiModules.js +4 -0
- package/dist/Helpers/AuditEntries.d.ts +50 -0
- package/dist/Helpers/AuditEntries.js +96 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/ChatBackend.d.ts +8 -1
- package/dist/Helpers/ChatBackend.js +18 -0
- package/dist/Helpers/ChatHelpers.d.ts +2 -0
- package/dist/Helpers/ChatHelpers.js +4 -1
- package/dist/Helpers/CrudBackend.d.ts +20 -2
- package/dist/Helpers/CrudBackend.js +46 -2
- package/dist/Helpers/CrudColumns.d.ts +91 -6
- package/dist/Helpers/CrudColumns.js +148 -7
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +116 -2
- package/dist/Helpers/CrudHelpers.js +256 -0
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +32 -0
- package/dist/Helpers/CrudRowForms.js +76 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- package/dist/Helpers/DashboardHelpers.d.ts +115 -0
- package/dist/Helpers/DashboardHelpers.js +305 -0
- package/dist/Helpers/DomainCards.d.ts +140 -0
- package/dist/Helpers/DomainCards.js +370 -0
- package/dist/Helpers/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +158 -0
- package/dist/Helpers/FileAdapter.js +173 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +100 -5
- package/dist/Helpers/FormStore.js +368 -25
- package/dist/Helpers/InputConditions.d.ts +56 -0
- package/dist/Helpers/InputConditions.js +192 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +395 -0
- package/dist/Helpers/InputNarrowing.d.ts +50 -0
- package/dist/Helpers/InputNarrowing.js +148 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +157 -74
- package/dist/Helpers/MailClientHelpers.d.ts +96 -0
- package/dist/Helpers/MailClientHelpers.js +211 -0
- package/dist/Helpers/MessageBackend.d.ts +152 -0
- package/dist/Helpers/MessageBackend.js +329 -0
- package/dist/Helpers/MessageHelpers.d.ts +160 -0
- package/dist/Helpers/MessageHelpers.js +573 -0
- package/dist/Helpers/MessageUnread.d.ts +95 -0
- package/dist/Helpers/MessageUnread.js +245 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
- package/dist/Helpers/NexusDriveAdapter.js +236 -0
- package/dist/Helpers/NumberHelpers.d.ts +156 -0
- package/dist/Helpers/NumberHelpers.js +242 -0
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +14 -2
- package/dist/Helpers/RequestCache.js +22 -3
- package/dist/Helpers/RowsStore.d.ts +61 -2
- package/dist/Helpers/RowsStore.js +203 -16
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +9 -3
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +38 -4
- package/dist/Helpers/TableHelpers.js +78 -10
- package/dist/Helpers/TableJoins.d.ts +13 -0
- package/dist/Helpers/TableJoins.js +34 -0
- package/dist/Helpers/TableStore.d.ts +28 -4
- package/dist/Helpers/TableStore.js +73 -11
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +156 -6
- package/dist/Helpers/TreeStore.js +547 -18
- package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
- package/dist/Interfaces/ApiInterfaces.js +10 -1
- package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
- package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
- package/dist/Interfaces/DashboardInterfaces.js +1 -0
- package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
- package/dist/Interfaces/DomainInterfaces.js +1 -0
- package/dist/Interfaces/FileInterfaces.d.ts +183 -0
- package/dist/Interfaces/FileInterfaces.js +6 -0
- package/dist/Interfaces/FormInterfaces.d.ts +460 -32
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +22 -3
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +102 -3
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +79 -6
- package/dist/Services/JoinSources.js +132 -21
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Shared.Index.d.ts +28 -0
- package/dist/Shared.Index.js +28 -0
- package/package.json +13 -2
- package/src/Styles/Nexus.Avatar.css +6 -5
- package/src/Styles/Nexus.Button.css +39 -4
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Choice.css +16 -0
- package/src/Styles/Nexus.Crud.css +486 -21
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Dropdown.css +8 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +437 -0
- package/src/Styles/Nexus.Form.css +5 -2
- package/src/Styles/Nexus.Index.css +7 -0
- package/src/Styles/Nexus.Input.css +73 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Menu.css +10 -1
- package/src/Styles/Nexus.Popup.css +135 -1
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Tab.Buttons.css +87 -22
- package/src/Styles/Nexus.Table.css +1458 -1387
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Themes.css +12 -6
- package/src/Styles/Nexus.Tokens.css +8 -3
- package/src/Styles/Nexus.Tree.css +41 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { deleteFormDraft, readFormDraft, writeFormDraft } from "./FormDrafts.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isComputedInput } from "./InputConditions.js";
|
|
3
|
+
import { formNarrowingNames, inputNarrowingsOf, isEmptyNarrowed, narrowedFollowers } from "./InputNarrowing.js";
|
|
4
|
+
import { findRememberedOpening, forgetInputMemory, isEmptyInputValue, isPickerInput, readInputMemory, rememberDependencies, rememberedOpeningNow, writeInputMemory } from "./InputMemory.js";
|
|
5
|
+
import { holdsValue, initialInputValue, inputValuesEqual, isRowsInput, keepsTyping, normalizeInputValue, validateInput } from "./InputParamsHelpers.js";
|
|
3
6
|
const INITIAL_STATUS = {
|
|
4
7
|
changed: false,
|
|
5
8
|
submitting: false,
|
|
@@ -40,6 +43,21 @@ export class FormStore {
|
|
|
40
43
|
inputByName = new Map();
|
|
41
44
|
values = {};
|
|
42
45
|
initial = {};
|
|
46
|
+
/** The values the last load was given, as given: an input they name with a value is the form's own, never remembered. */
|
|
47
|
+
given = {};
|
|
48
|
+
/** Where remembered inputs (`remember`) are read and kept; set by the form that draws this store, in the browser. */
|
|
49
|
+
memory = null;
|
|
50
|
+
/** Pickers asked whether they still offer a remembered value; the next opening (a load, a reset) or a submit stops it. */
|
|
51
|
+
memoryRound = null;
|
|
52
|
+
/** The inputs whose picker is being asked: drawn loading, and a change to one (the person's pick, code) ends its ask. */
|
|
53
|
+
asking = new Set();
|
|
54
|
+
/** The values memory opened inputs on: an opening that finds nothing (a reset after the choice went) takes its own back. */
|
|
55
|
+
fromMemory = new Map();
|
|
56
|
+
/**
|
|
57
|
+
* The values a load (or a reset, a draft) is working out, while it works them out: what `get` answers then, so an
|
|
58
|
+
* input's live `hidden` (`showWhen`) is judged on the values the form opens with, never on the ones it had before.
|
|
59
|
+
*/
|
|
60
|
+
reading = null;
|
|
43
61
|
isStarted = false;
|
|
44
62
|
// Messages: an input's own rules, the form's rule, a server's, and rule messages the form shows for its inputs.
|
|
45
63
|
ruleErrors = new Map();
|
|
@@ -72,6 +90,8 @@ export class FormStore {
|
|
|
72
90
|
draftDropped = false;
|
|
73
91
|
/** An input reported a value while the values loaded and was refused: it is drawn again from the store when loading ends. */
|
|
74
92
|
refusedWhileLoading = false;
|
|
93
|
+
/** What a choice input reported beside the value the user picked last (`pickedItem`): its item, kept with that value. */
|
|
94
|
+
picked = new Map();
|
|
75
95
|
/** A load during a render tells nobody; `announce` does, once the form has mounted. */
|
|
76
96
|
quiet = false;
|
|
77
97
|
unannounced = false;
|
|
@@ -92,7 +112,13 @@ export class FormStore {
|
|
|
92
112
|
return this.values;
|
|
93
113
|
}
|
|
94
114
|
get(name) {
|
|
95
|
-
return this.values[name];
|
|
115
|
+
return this.reading ? this.reading[name] : this.values[name];
|
|
116
|
+
}
|
|
117
|
+
/** The params of the input of this name, as the form was given them; undefined for a name no input has. */
|
|
118
|
+
inputParams(name) {
|
|
119
|
+
if (!this.inputByName.size && this.options.inputs.length)
|
|
120
|
+
this.indexInputs();
|
|
121
|
+
return this.inputByName.get(name);
|
|
96
122
|
}
|
|
97
123
|
/** The values the form opened with, or saved last. */
|
|
98
124
|
getInitial() {
|
|
@@ -112,6 +138,19 @@ export class FormStore {
|
|
|
112
138
|
isChanged() {
|
|
113
139
|
return this.changedNames.size > 0;
|
|
114
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Whether the changes are worth asking about before they are dropped (Cancel, closing a popup, leaving the page): a
|
|
143
|
+
* changed value of any input but a password or a one-time code (`keepsTyping`). A form where only those were typed is
|
|
144
|
+
* changed, and Save and its line say so, but nothing asks before it goes.
|
|
145
|
+
*/
|
|
146
|
+
isWorthKeeping() {
|
|
147
|
+
for (const name of this.changedNames) {
|
|
148
|
+
const params = this.inputParams(name);
|
|
149
|
+
if (!params || keepsTyping(params))
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
115
154
|
getStatus = () => this.status;
|
|
116
155
|
/** The message an input shows for the form: a server's, the form's rule once the input was left or a submit ran, or its own rule after a submit its input missed. */
|
|
117
156
|
shownError(name) {
|
|
@@ -129,12 +168,21 @@ export class FormStore {
|
|
|
129
168
|
return this.ruleError(name) ?? undefined;
|
|
130
169
|
return undefined;
|
|
131
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* The item a choice input reported with the value it holds now, when the user picked that value: a dropdown's option
|
|
173
|
+
* (its options, with `multiple`). `undefined` for a value loaded, set from code, or changed since - so what it answers
|
|
174
|
+
* always goes with the value. A CRUD names what a save joined from it (`crudJoinedNames`).
|
|
175
|
+
*/
|
|
176
|
+
pickedItem(name) {
|
|
177
|
+
const picked = this.picked.get(name);
|
|
178
|
+
return picked && inputValuesEqual(picked.value, this.values[name]) ? picked.item : undefined;
|
|
179
|
+
}
|
|
132
180
|
/** The input's own rule message for its value, whether it shows or not. */
|
|
133
181
|
ruleError(name) {
|
|
134
182
|
if (this.staleRules.has(name)) {
|
|
135
183
|
this.staleRules.delete(name);
|
|
136
184
|
const params = this.inputByName.get(name);
|
|
137
|
-
const message = params && !params.hidden && !this.options.locked ? validateInput(params, this.values[name], this.options.context) : null;
|
|
185
|
+
const message = params && !params.hidden && !isComputedInput(params) && !this.options.locked ? validateInput(params, this.values[name], this.options.context) : null;
|
|
138
186
|
if (message)
|
|
139
187
|
this.ruleErrors.set(name, message);
|
|
140
188
|
else
|
|
@@ -152,7 +200,7 @@ export class FormStore {
|
|
|
152
200
|
}
|
|
153
201
|
/** Inputs whose values break a rule, their own or the form's, in form order. Inputs on the page may find more, such as text that is not a date yet. */
|
|
154
202
|
problems() {
|
|
155
|
-
return this.options.inputs.filter(params => !params.hidden && (this.ruleError(params.name) || this.crossErrors.has(params.name))).map(params => params.name);
|
|
203
|
+
return this.options.inputs.filter(params => !params.hidden && !isComputedInput(params) && (this.ruleError(params.name) || this.crossErrors.has(params.name))).map(params => params.name);
|
|
156
204
|
}
|
|
157
205
|
/** Whether every value passes, as far as the values tell. `validate()` also asks the inputs on the page. */
|
|
158
206
|
isValid() {
|
|
@@ -247,7 +295,10 @@ export class FormStore {
|
|
|
247
295
|
loadValues(values, keepChanged, draft) {
|
|
248
296
|
if (!this.inputByName.size && this.options.inputs.length)
|
|
249
297
|
this.indexInputs();
|
|
250
|
-
|
|
298
|
+
this.given = values;
|
|
299
|
+
// A load opens afresh: what memory opened before belongs to the values it replaces.
|
|
300
|
+
this.fromMemory.clear();
|
|
301
|
+
const next = this.openRemembered(this.normalize(values), values);
|
|
251
302
|
const merge = keepChanged && this.isStarted;
|
|
252
303
|
const conflicts = [];
|
|
253
304
|
let current = next;
|
|
@@ -301,8 +352,12 @@ export class FormStore {
|
|
|
301
352
|
this.scheduleDraft();
|
|
302
353
|
return conflicts;
|
|
303
354
|
}
|
|
304
|
-
/**
|
|
355
|
+
/**
|
|
356
|
+
* Brings back the values the form opened with or saved last, and clears every message. The draft goes too. A
|
|
357
|
+
* remembered input opens on the choice kept now, so a form reset after a save starts on what was just saved.
|
|
358
|
+
*/
|
|
305
359
|
reset() {
|
|
360
|
+
this.initial = this.openRemembered({ ...this.initial }, this.given);
|
|
306
361
|
this.values = { ...this.initial };
|
|
307
362
|
this.changedNames.clear();
|
|
308
363
|
this.generation++;
|
|
@@ -323,10 +378,26 @@ export class FormStore {
|
|
|
323
378
|
this.change(Object.entries(values), "code");
|
|
324
379
|
}
|
|
325
380
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
381
|
+
* Values a person set through another input - one worked out from them and typed in (`compute.solve`) - as one change.
|
|
382
|
+
* Their inputs show them at once, as values from code do, and they are taken as the person's own: the form's
|
|
383
|
+
* `onFieldChange` runs for them, and inputs narrowed by them lose what they picked. Nothing is taken while the values
|
|
384
|
+
* load. Answers whether anything changed.
|
|
385
|
+
*/
|
|
386
|
+
setFromPerson(values) {
|
|
387
|
+
if (this.status.loading) {
|
|
388
|
+
this.refusedWhileLoading = true;
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
const before = this.values;
|
|
392
|
+
this.change(Object.entries(values), "person");
|
|
393
|
+
return this.values !== before;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* A value the user typed or picked, from the input rendered with `stamp`, with what a choice input reported beside it
|
|
397
|
+
* (`item`: a dropdown's option, kept for `pickedItem`). An input that was replaced since (by a value from code, a load,
|
|
398
|
+
* or a reset) can still report its last value as it leaves; that value is ignored.
|
|
328
399
|
*/
|
|
329
|
-
input(name, value, stamp) {
|
|
400
|
+
input(name, value, stamp, item) {
|
|
330
401
|
if (stamp !== this.stamp(name))
|
|
331
402
|
return;
|
|
332
403
|
// Nothing is taken while the values load, not even from an input whose `disabled` was taken off by hand: the
|
|
@@ -335,6 +406,10 @@ export class FormStore {
|
|
|
335
406
|
this.refusedWhileLoading = true;
|
|
336
407
|
return;
|
|
337
408
|
}
|
|
409
|
+
if (item === undefined)
|
|
410
|
+
this.picked.delete(name);
|
|
411
|
+
else
|
|
412
|
+
this.picked.set(name, { value, item });
|
|
338
413
|
this.change([[name, value]], "input");
|
|
339
414
|
}
|
|
340
415
|
/** Marks an input as left by the user: the form's rule messages on it show from now on. */
|
|
@@ -358,7 +433,8 @@ export class FormStore {
|
|
|
358
433
|
if (!text)
|
|
359
434
|
continue;
|
|
360
435
|
const params = this.inputByName.get(name);
|
|
361
|
-
|
|
436
|
+
// A worked-out input is never typed in, so a message naming it is the form's.
|
|
437
|
+
if (params && !params.hidden && !isComputedInput(params))
|
|
362
438
|
this.serverErrors.set(name, text);
|
|
363
439
|
else
|
|
364
440
|
others.push(text);
|
|
@@ -454,6 +530,8 @@ export class FormStore {
|
|
|
454
530
|
}
|
|
455
531
|
/** @internal A submit starts: messages from the last answer go. */
|
|
456
532
|
beginSubmit() {
|
|
533
|
+
// What is in the form is what is saved: a remembered value a picker answers for now would come in after it.
|
|
534
|
+
this.stopAskingPickers();
|
|
457
535
|
const shown = this.shownSnapshot();
|
|
458
536
|
this.serverErrors.clear();
|
|
459
537
|
this.serverMessage = null;
|
|
@@ -474,6 +552,8 @@ export class FormStore {
|
|
|
474
552
|
if (extra) {
|
|
475
553
|
for (const [name, value] of Object.entries(extra)) {
|
|
476
554
|
const params = this.inputByName.get(name);
|
|
555
|
+
if (isComputedInput(params))
|
|
556
|
+
continue;
|
|
477
557
|
const next = params && !params.hidden ? normalizeInputValue(params, value) : value;
|
|
478
558
|
if (inputValuesEqual(this.values[name], next))
|
|
479
559
|
continue;
|
|
@@ -491,6 +571,67 @@ export class FormStore {
|
|
|
491
571
|
this.emit(null);
|
|
492
572
|
this.flushDraft();
|
|
493
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* @internal Whether the input's picker is being asked whether it still offers a remembered value: the input draws
|
|
576
|
+
* loading until it answers, the person picks, or the form opens again.
|
|
577
|
+
*/
|
|
578
|
+
isRemembering(name) {
|
|
579
|
+
return this.asking.has(name);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* @internal The browser storage remembered inputs read and keep (`InputLayoutParams.remember`), set by the form that
|
|
583
|
+
* draws this store once it is in the browser. Inputs the values left without one open on it at once (as the values
|
|
584
|
+
* the form opened with), a picker once it answers that it still offers the value; a form drawn on a server opened
|
|
585
|
+
* without it, so nothing differs while it hydrates.
|
|
586
|
+
*/
|
|
587
|
+
attachMemory(storage) {
|
|
588
|
+
if (this.memory === storage)
|
|
589
|
+
return;
|
|
590
|
+
this.memory = storage;
|
|
591
|
+
if (!storage || !this.isStarted)
|
|
592
|
+
return;
|
|
593
|
+
const opened = this.openRemembered({ ...this.initial }, this.given);
|
|
594
|
+
const changed = [];
|
|
595
|
+
for (const params of this.options.inputs) {
|
|
596
|
+
const { name } = params;
|
|
597
|
+
// Only an input still as the form opened it: one typed in (or restored from a draft) keeps what it has.
|
|
598
|
+
if (!params.remember || isComputedInput(params) || this.changedNames.has(name) || inputValuesEqual(opened[name], this.initial[name]))
|
|
599
|
+
continue;
|
|
600
|
+
this.initial = { ...this.initial, [name]: opened[name] };
|
|
601
|
+
this.values = { ...this.values, [name]: opened[name] };
|
|
602
|
+
this.nextRevision(name);
|
|
603
|
+
this.bump(name);
|
|
604
|
+
changed.push(name);
|
|
605
|
+
}
|
|
606
|
+
if (!changed.length)
|
|
607
|
+
return;
|
|
608
|
+
this.checkAllRules();
|
|
609
|
+
this.checkCross();
|
|
610
|
+
this.bumpFollowers(changed);
|
|
611
|
+
this.emit(null);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Inputs that follow ones memory opened (`dependsOn`: a `compute.from`, `showWhen`, `spanWhen`, `labelWhen`, or a
|
|
615
|
+
* `narrowedBy`) draw again with their params read afresh, as after a change: a cell redraws only on its own bump.
|
|
616
|
+
*/
|
|
617
|
+
bumpFollowers(names) {
|
|
618
|
+
for (const name of this.followersOf(names))
|
|
619
|
+
if (!names.includes(name))
|
|
620
|
+
this.bump(name);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* @internal A save: each remembered input keeps the value saved (an empty one forgets), for the next form that asks -
|
|
624
|
+
* under the saved values' `per` value, and only where its `when` holds on them.
|
|
625
|
+
*/
|
|
626
|
+
rememberSaved(values) {
|
|
627
|
+
if (!this.memory)
|
|
628
|
+
return;
|
|
629
|
+
const read = (name) => values[name];
|
|
630
|
+
for (const params of this.options.inputs) {
|
|
631
|
+
if (params.remember && !params.hidden && !isComputedInput(params))
|
|
632
|
+
writeInputMemory(this.memory, params, read(params.name), read);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
494
635
|
/** @internal A save after which the form went back to its opening values (`resetOnSuccess`). */
|
|
495
636
|
markSaved() {
|
|
496
637
|
this.patchStatus({ submitting: false, savedAt: Date.now() });
|
|
@@ -521,20 +662,160 @@ export class FormStore {
|
|
|
521
662
|
/** Each input's value in the type the input reports; a missing one is the input's default. Other fields stay as they came. */
|
|
522
663
|
normalize(values) {
|
|
523
664
|
const result = { ...values };
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (params
|
|
527
|
-
|
|
665
|
+
// A worked-out input (`compute`) holds no value: a field the data has under its name is not the form's to send.
|
|
666
|
+
for (const params of this.options.inputs)
|
|
667
|
+
if (isComputedInput(params))
|
|
668
|
+
delete result[params.name];
|
|
669
|
+
const inputs = this.options.inputs.filter(params => holdsValue(params) && !isComputedInput(params));
|
|
670
|
+
// Which inputs are hidden is judged on these values: an input shown by another's value (`showWhen`) whose other input
|
|
671
|
+
// gets its value in this very load (its default, the record's). So every input is worked out as shown first...
|
|
672
|
+
for (const params of inputs)
|
|
528
673
|
result[params.name] = params.name in values ? normalizeInputValue(params, values[params.name]) : initialInputValue(params);
|
|
674
|
+
// ...then a hidden input's value goes back to what the data held (a picture has none: the record keeps what it came with).
|
|
675
|
+
const hidden = this.readingFrom(result, () => inputs.filter(params => params.hidden));
|
|
676
|
+
for (const params of hidden) {
|
|
677
|
+
if (params.name in values)
|
|
678
|
+
result[params.name] = values[params.name];
|
|
679
|
+
else
|
|
680
|
+
delete result[params.name];
|
|
681
|
+
}
|
|
682
|
+
return result;
|
|
683
|
+
}
|
|
684
|
+
/** Runs `work` with `get` answering from these values (a load's, a reset's, a draft's), and gives back its answer. */
|
|
685
|
+
readingFrom(values, work) {
|
|
686
|
+
const before = this.reading;
|
|
687
|
+
this.reading = values;
|
|
688
|
+
try {
|
|
689
|
+
return work();
|
|
690
|
+
}
|
|
691
|
+
finally {
|
|
692
|
+
this.reading = before;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* The values with each remembered input the given values leave without one opened on the company's preferred value
|
|
697
|
+
* or the person's last choice (`rememberedOpeningNow`), its `when` and `per` read among these values; without storage
|
|
698
|
+
* (on a server, before the form is drawn) only a preferred value applies. A kept choice no longer offered is forgotten.
|
|
699
|
+
* A picker that must be asked first opens nothing now: it is asked, and its answer opens the input later
|
|
700
|
+
* (`askPickers`) - the round any earlier opening started stops here. An input memory opened before (a reset after a
|
|
701
|
+
* save, the storage arriving) that now opens on nothing takes the value the given values give it back.
|
|
702
|
+
*/
|
|
703
|
+
openRemembered(values, given) {
|
|
704
|
+
let result = values;
|
|
705
|
+
// The values as they open, in form order: a group opened on its kept value names the package kept for it.
|
|
706
|
+
const read = (name) => result[name];
|
|
707
|
+
const asks = [];
|
|
708
|
+
const set = (name, value) => {
|
|
709
|
+
if (result === values)
|
|
710
|
+
result = { ...values };
|
|
711
|
+
result[name] = value;
|
|
712
|
+
};
|
|
713
|
+
// Shown or hidden as the values being opened say, not as the store's previous ones do.
|
|
714
|
+
const shown = this.readingFrom(values, () => this.options.inputs.filter(params => params.remember && !params.hidden && holdsValue(params) && !isComputedInput(params)));
|
|
715
|
+
for (const params of shown) {
|
|
716
|
+
const { name } = params;
|
|
717
|
+
if (name in given && !isEmptyInputValue(params, given[name]))
|
|
718
|
+
continue;
|
|
719
|
+
// A picker is asked in the browser only, once the storage came: a server's draw never calls it or reads its cache.
|
|
720
|
+
if (!this.memory && isPickerInput(params))
|
|
721
|
+
continue;
|
|
722
|
+
const stored = this.memory ? readInputMemory(this.memory, params.remember, read) : undefined;
|
|
723
|
+
const opening = rememberedOpeningNow(params, stored, read);
|
|
724
|
+
if (opening.forget)
|
|
725
|
+
forgetInputMemory(this.memory, params.remember, read);
|
|
726
|
+
if (opening.ask)
|
|
727
|
+
asks.push({ params, stored });
|
|
728
|
+
if (opening.value !== undefined) {
|
|
729
|
+
const value = normalizeInputValue(params, opening.value);
|
|
730
|
+
set(name, value);
|
|
731
|
+
this.fromMemory.set(name, value);
|
|
732
|
+
}
|
|
733
|
+
else if (this.fromMemory.has(name)) {
|
|
734
|
+
// Still the value memory opened it on: the input's own comes back, so a choice no longer offered is not left in.
|
|
735
|
+
if (inputValuesEqual(result[name], this.fromMemory.get(name))) {
|
|
736
|
+
set(name, name in given ? normalizeInputValue(params, given[name]) : initialInputValue(params));
|
|
737
|
+
}
|
|
738
|
+
this.fromMemory.delete(name);
|
|
739
|
+
}
|
|
529
740
|
}
|
|
741
|
+
this.askPickers(asks, read);
|
|
530
742
|
return result;
|
|
531
743
|
}
|
|
744
|
+
/** Stops asking pickers about remembered values: their answers no longer open anything, and their inputs stop loading. */
|
|
745
|
+
stopAskingPickers() {
|
|
746
|
+
this.memoryRound?.abort();
|
|
747
|
+
this.memoryRound = null;
|
|
748
|
+
const asked = [...this.asking];
|
|
749
|
+
this.asking.clear();
|
|
750
|
+
for (const name of asked)
|
|
751
|
+
this.bump(name);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Asks each picker whether it still offers the remembered value (`findRememberedOpening`), the input drawn loading
|
|
755
|
+
* meanwhile. An answer forgets a choice no longer offered, and opens an offered one (`openAsked`) only while the input
|
|
756
|
+
* is still asked about - no pick, no value from code since - and the values it was judged on (`rememberDependencies`:
|
|
757
|
+
* the group that narrows it, the one it is kept per, its `when`) are still the form's: a group changed meanwhile opens
|
|
758
|
+
* nothing.
|
|
759
|
+
*/
|
|
760
|
+
askPickers(asks, read) {
|
|
761
|
+
this.stopAskingPickers();
|
|
762
|
+
if (!asks.length)
|
|
763
|
+
return;
|
|
764
|
+
const round = new AbortController();
|
|
765
|
+
this.memoryRound = round;
|
|
766
|
+
const memory = this.memory;
|
|
767
|
+
for (const { params, stored } of asks) {
|
|
768
|
+
const { name } = params;
|
|
769
|
+
const judgedOn = rememberDependencies(params).map(other => [other, read(other)]);
|
|
770
|
+
this.asking.add(name);
|
|
771
|
+
this.bump(name);
|
|
772
|
+
void findRememberedOpening(params, stored, read, round.signal).then(opening => {
|
|
773
|
+
if (round.signal.aborted)
|
|
774
|
+
return;
|
|
775
|
+
if (opening.forget)
|
|
776
|
+
forgetInputMemory(memory, params.remember, read);
|
|
777
|
+
if (!this.asking.delete(name))
|
|
778
|
+
return;
|
|
779
|
+
const still = judgedOn.every(([other, value]) => inputValuesEqual(this.values[other], value));
|
|
780
|
+
if (opening.value !== undefined && still)
|
|
781
|
+
this.openAsked(name, opening.value);
|
|
782
|
+
else
|
|
783
|
+
this.bump(name);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* A picker answered for a remembered value: the input opens on it, as the value the form opened with (not a change) -
|
|
789
|
+
* only while it is as the form opened it and still on the form. One the person typed in, or a draft gave back, keeps
|
|
790
|
+
* what it has.
|
|
791
|
+
*/
|
|
792
|
+
openAsked(name, value) {
|
|
793
|
+
const params = this.inputByName.get(name);
|
|
794
|
+
if (!params || params.hidden || !holdsValue(params) || isComputedInput(params) || this.changedNames.has(name)) {
|
|
795
|
+
this.bump(name);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const next = normalizeInputValue(params, value);
|
|
799
|
+
this.fromMemory.set(name, next);
|
|
800
|
+
if (inputValuesEqual(this.values[name], next)) {
|
|
801
|
+
this.bump(name);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
this.initial = { ...this.initial, [name]: next };
|
|
805
|
+
this.values = { ...this.values, [name]: next };
|
|
806
|
+
this.nextRevision(name);
|
|
807
|
+
this.bump(name);
|
|
808
|
+
this.checkAllRules();
|
|
809
|
+
this.checkCross();
|
|
810
|
+
this.bumpFollowers([name]);
|
|
811
|
+
this.emit(null);
|
|
812
|
+
}
|
|
532
813
|
/** Inputs that were not there, or were hidden, get their value in the input's type, as a load gives it. */
|
|
533
814
|
adoptNewInputs(previous) {
|
|
534
815
|
const before = new Map(previous.map(params => [params.name, params]));
|
|
535
816
|
for (const params of this.options.inputs) {
|
|
536
817
|
const old = before.get(params.name);
|
|
537
|
-
if (params.hidden || (old && !old.hidden))
|
|
818
|
+
if (params.hidden || !holdsValue(params) || isComputedInput(params) || (old && !old.hidden))
|
|
538
819
|
continue;
|
|
539
820
|
const { name } = params;
|
|
540
821
|
const value = name in this.values ? normalizeInputValue(params, this.values[name]) : initialInputValue(params);
|
|
@@ -547,20 +828,31 @@ export class FormStore {
|
|
|
547
828
|
this.changedNames.add(name);
|
|
548
829
|
}
|
|
549
830
|
}
|
|
550
|
-
change(entries,
|
|
831
|
+
change(entries, changedBy) {
|
|
832
|
+
// A person's change through another input (`setFromPerson`) is written as code writes (its inputs mount again with
|
|
833
|
+
// it) and followed as typing is.
|
|
834
|
+
const source = changedBy === "code" ? "code" : "input";
|
|
835
|
+
const writtenAs = changedBy === "input" ? "input" : "code";
|
|
551
836
|
const crossBefore = this.crossErrors;
|
|
552
837
|
const shown = new Map();
|
|
553
838
|
const remounted = new Set();
|
|
554
839
|
const written = [];
|
|
840
|
+
/** Inputs a picker was asked about for a remembered value: this change is theirs now, and they stop loading. */
|
|
841
|
+
const unasked = [];
|
|
555
842
|
const remember = (name) => {
|
|
556
843
|
if (!shown.has(name))
|
|
557
844
|
shown.set(name, this.shownError(name));
|
|
558
845
|
};
|
|
559
846
|
const write = (name, value, from) => {
|
|
560
847
|
const params = this.inputByName.get(name);
|
|
848
|
+
// A worked-out input holds no value: one set from code (or a draft, an `onFieldChange` patch) is not taken.
|
|
849
|
+
if (isComputedInput(params))
|
|
850
|
+
return;
|
|
561
851
|
const next = params && !params.hidden && from === "code" ? normalizeInputValue(params, value) : value;
|
|
562
852
|
if (inputValuesEqual(this.values[name], next))
|
|
563
853
|
return;
|
|
854
|
+
if (this.asking.delete(name))
|
|
855
|
+
unasked.push(name);
|
|
564
856
|
remember(name);
|
|
565
857
|
this.values = { ...this.values, [name]: next };
|
|
566
858
|
if (from === "code" && params) {
|
|
@@ -578,7 +870,7 @@ export class FormStore {
|
|
|
578
870
|
written.push(name);
|
|
579
871
|
};
|
|
580
872
|
for (const [name, value] of entries)
|
|
581
|
-
write(name, value,
|
|
873
|
+
write(name, value, writtenAs);
|
|
582
874
|
if (!written.length)
|
|
583
875
|
return;
|
|
584
876
|
// Values derived from the ones the user changed, such as an amount from a quantity and a rate.
|
|
@@ -592,6 +884,44 @@ export class FormStore {
|
|
|
592
884
|
write(field, value, "code");
|
|
593
885
|
}
|
|
594
886
|
}
|
|
887
|
+
// An input whose options another narrows (`narrowedBy`) loses the value it picked among the other's old options when
|
|
888
|
+
// a person changes the other one; a value set from code (a load, a patch) is the code's to keep consistent. Down a
|
|
889
|
+
// chain too: a Country change clears the Region, and with it the District picked among that Region's (each input
|
|
890
|
+
// once, whatever order the form lists them in; one the person set in this same change keeps its value).
|
|
891
|
+
if (source === "input") {
|
|
892
|
+
const followers = narrowedFollowers(this.options.inputs, written, "form");
|
|
893
|
+
for (const name of followers) {
|
|
894
|
+
if (!isEmptyNarrowed(this.values[name]))
|
|
895
|
+
write(name, null, "code");
|
|
896
|
+
}
|
|
897
|
+
// Rows whose inputs follow a form value (`from: "form"`, a line's Tags of the form's Store): every row loses what
|
|
898
|
+
// it picked among the old value's options, and down the row's own chain.
|
|
899
|
+
const changedNow = new Set([...written, ...followers]);
|
|
900
|
+
for (const params of this.options.inputs) {
|
|
901
|
+
if (!isRowsInput(params))
|
|
902
|
+
continue;
|
|
903
|
+
const rowInputs = params.inputs ?? [];
|
|
904
|
+
if (!formNarrowingNames(rowInputs).some(name => changedNow.has(name)))
|
|
905
|
+
continue;
|
|
906
|
+
const clear = narrowedFollowers(rowInputs, changedNow, "rows-of-form");
|
|
907
|
+
const rows = this.values[params.name];
|
|
908
|
+
if (!clear.length || !Array.isArray(rows))
|
|
909
|
+
continue;
|
|
910
|
+
let touched = false;
|
|
911
|
+
const next = rows.map(row => {
|
|
912
|
+
if (row === null || typeof row !== "object")
|
|
913
|
+
return row;
|
|
914
|
+
const filled = clear.filter(name => !isEmptyNarrowed(row[name]));
|
|
915
|
+
if (!filled.length)
|
|
916
|
+
return row;
|
|
917
|
+
touched = true;
|
|
918
|
+
// Each cleared as its input holds nothing: an empty list for a picker of several.
|
|
919
|
+
return { ...row, ...Object.fromEntries(filled.map(name => [name, emptyRowValue(rowInputs, name)])) };
|
|
920
|
+
});
|
|
921
|
+
if (touched)
|
|
922
|
+
write(params.name, next, "code");
|
|
923
|
+
}
|
|
924
|
+
}
|
|
595
925
|
// Inputs that follow a changed one (`dependsOn`) draw again with their params read afresh, and their rule is asked
|
|
596
926
|
// again: a getter may have just made one required, read-only or hidden.
|
|
597
927
|
const followers = this.followersOf(written);
|
|
@@ -604,24 +934,30 @@ export class FormStore {
|
|
|
604
934
|
if (!shown.has(name))
|
|
605
935
|
shown.set(name, this.shownWith(name, crossBefore));
|
|
606
936
|
for (const [name, before] of shown)
|
|
607
|
-
if (remounted.has(name) || followers.has(name) || this.shownError(name) !== before)
|
|
937
|
+
if (remounted.has(name) || followers.has(name) || unasked.includes(name) || this.shownError(name) !== before)
|
|
608
938
|
this.bump(name);
|
|
609
939
|
this.patchStatus({ changed: this.changedNames.size > 0, message: this.formMessage(), problems: this.problemList() });
|
|
610
|
-
this.emit(
|
|
940
|
+
this.emit(changedBy === "input" ? written[0] : written.length === 1 ? written[0] : null);
|
|
611
941
|
// A change after a cancel is worth keeping again.
|
|
612
942
|
if (source === "input")
|
|
613
943
|
this.draftDropped = false;
|
|
614
944
|
this.scheduleDraft();
|
|
615
945
|
}
|
|
616
|
-
/**
|
|
946
|
+
/**
|
|
947
|
+
* The inputs whose `dependsOn` (or `narrowedBy`) names any of `names`, and the rows inputs whose row inputs are narrowed
|
|
948
|
+
* by one of them `from: "form"`: their rows draw again with the form's value as it is now.
|
|
949
|
+
*/
|
|
617
950
|
followersOf(names) {
|
|
618
951
|
const found = new Set();
|
|
619
952
|
if (!names.length)
|
|
620
953
|
return found;
|
|
621
954
|
const changed = new Set(names);
|
|
622
|
-
for (const params of this.options.inputs)
|
|
623
|
-
if (params.dependsOn?.some(name => changed.has(name)))
|
|
955
|
+
for (const params of this.options.inputs) {
|
|
956
|
+
if (params.dependsOn?.some(name => changed.has(name)) || inputNarrowingsOf(params).some(narrowing => changed.has(narrowing.name)))
|
|
624
957
|
found.add(params.name);
|
|
958
|
+
else if (isRowsInput(params) && formNarrowingNames(params.inputs ?? []).some(name => changed.has(name)))
|
|
959
|
+
found.add(params.name);
|
|
960
|
+
}
|
|
625
961
|
return found;
|
|
626
962
|
}
|
|
627
963
|
checkRule(name) {
|
|
@@ -631,7 +967,7 @@ export class FormStore {
|
|
|
631
967
|
return;
|
|
632
968
|
}
|
|
633
969
|
this.staleRules.delete(name);
|
|
634
|
-
const message = params && !params.hidden && !this.options.locked ? validateInput(params, this.values[name], this.options.context) : null;
|
|
970
|
+
const message = params && !params.hidden && !isComputedInput(params) && !this.options.locked ? validateInput(params, this.values[name], this.options.context) : null;
|
|
635
971
|
if (message)
|
|
636
972
|
this.ruleErrors.set(name, message);
|
|
637
973
|
else
|
|
@@ -657,7 +993,7 @@ export class FormStore {
|
|
|
657
993
|
if (!message)
|
|
658
994
|
continue;
|
|
659
995
|
const params = this.inputByName.get(name);
|
|
660
|
-
if (params && !params.hidden)
|
|
996
|
+
if (params && !params.hidden && !isComputedInput(params))
|
|
661
997
|
next.set(name, message);
|
|
662
998
|
else
|
|
663
999
|
others.push(message);
|
|
@@ -751,9 +1087,11 @@ export class FormStore {
|
|
|
751
1087
|
}
|
|
752
1088
|
const entries = [];
|
|
753
1089
|
const conflicts = [];
|
|
1090
|
+
// An input the draft's own values show (its other input among them) is restored too.
|
|
1091
|
+
const hidden = this.readingFrom({ ...this.values, ...draft.values }, () => new Set(this.options.inputs.filter(params => params.hidden).map(params => params.name)));
|
|
754
1092
|
for (const params of this.options.inputs) {
|
|
755
1093
|
const { name } = params;
|
|
756
|
-
if (
|
|
1094
|
+
if (hidden.has(name) || isComputedInput(params) || !(name in draft.values) || this.changedNames.has(name))
|
|
757
1095
|
continue;
|
|
758
1096
|
const value = draft.values[name];
|
|
759
1097
|
if (sameData(value, this.values[name]))
|
|
@@ -770,3 +1108,8 @@ export class FormStore {
|
|
|
770
1108
|
this.patchStatus({ restored: { savedAt: draft.savedAt, names: entries.map(([name]) => name), conflicts }, draftSavedAt: draft.savedAt });
|
|
771
1109
|
}
|
|
772
1110
|
}
|
|
1111
|
+
/** A row input's value when nothing is picked: as the rows hold it (an empty list for a picker of several). */
|
|
1112
|
+
function emptyRowValue(rowInputs, name) {
|
|
1113
|
+
const params = rowInputs.find(input => input.name === name);
|
|
1114
|
+
return params ? normalizeInputValue(params, null) : null;
|
|
1115
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type FormValues, type InputCondition, type InputLabelWhen, type InputParams, type InputSpanWhen } from "../Interfaces/FormInterfaces.ts";
|
|
2
|
+
/** No value: null, undefined, an empty or blank text, an empty list. `false` and `0` are values. */
|
|
3
|
+
export declare function isEmptyValue(value: unknown): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether one condition holds for the other input's value. A list value (a multiple choice) holds `in` when any of its
|
|
6
|
+
* items is listed, and `notIn` when none is. Every key the condition has must hold; one with none holds always.
|
|
7
|
+
*/
|
|
8
|
+
export declare function conditionHolds(condition: InputCondition, value: unknown): boolean;
|
|
9
|
+
/** The conditions of a `showWhen` or a `readOnlyWhen`, as a list. */
|
|
10
|
+
export declare function conditionsOf(conditions: InputParams["showWhen"]): readonly InputCondition[];
|
|
11
|
+
/** Whether every condition holds, reading the other inputs' values with `read`. None holds always. */
|
|
12
|
+
export declare function conditionsHold(conditions: InputParams["showWhen"], read: (name: string) => unknown): boolean;
|
|
13
|
+
/** Whether an input with this `showWhen` shows, reading the other inputs' values with `read`: every condition must hold. */
|
|
14
|
+
export declare function inputShown(showWhen: InputParams["showWhen"], read: (name: string) => unknown): boolean;
|
|
15
|
+
/** Whether an input with this `readOnlyWhen` is read-only, reading the other inputs' values with `read`: every condition must hold. */
|
|
16
|
+
export declare function inputReadOnly(readOnlyWhen: InputParams["readOnlyWhen"], read: (name: string) => unknown): boolean;
|
|
17
|
+
/** The text of `texts` for the other input's value (compared as text), or undefined when none is listed for it. */
|
|
18
|
+
export declare function textFor(texts: Readonly<Record<string, string>> | undefined, value: unknown): string | undefined;
|
|
19
|
+
/** The label an input with this `labelWhen` shows for the other input's value: listed, else `otherwise`, else its own. */
|
|
20
|
+
export declare function labelFor(labelWhen: InputLabelWhen, value: unknown, own: InputParams["label"]): InputParams["label"];
|
|
21
|
+
/**
|
|
22
|
+
* The columns an input with this `spanWhen` spans, reading the other inputs' values with `read`: `spanWhen.span` while
|
|
23
|
+
* every condition of `when` holds, else its own `span` (none: undefined, the whole row).
|
|
24
|
+
*/
|
|
25
|
+
export declare function spanFor(spanWhen: InputSpanWhen | undefined, read: (name: string) => unknown, own: InputParams["span"]): InputParams["span"];
|
|
26
|
+
/**
|
|
27
|
+
* The inputs with a live `hidden` for each one that has `showWhen`, a live `readOnly` for each one that has
|
|
28
|
+
* `readOnlyWhen` - its own `hidden` / `readOnly` (a value or a getter), or else the conditions, read through `read` as
|
|
29
|
+
* the values are now - a live `label` (and `placeholder`) for each one that has `labelWhen`, and a live `span` for each
|
|
30
|
+
* one that has `spanWhen` (its own `span`, a value or a getter, while the conditions do not hold); and its `dependsOn`
|
|
31
|
+
* joined by the names they read (and by the inputs a `compute` is worked out from), so the form draws it again (and asks
|
|
32
|
+
* its rule again) when they change. The other params are copied with their getters as they are. The same array when no
|
|
33
|
+
* input has any.
|
|
34
|
+
*
|
|
35
|
+
* @example withInputConditions(inputs, name => store.get(name))
|
|
36
|
+
*/
|
|
37
|
+
export declare function withInputConditions(inputs: readonly InputParams[], read: (name: string) => unknown): readonly InputParams[];
|
|
38
|
+
/**
|
|
39
|
+
* The inputs with each worked-out one's `dependsOn` joined by the inputs it is worked out from (`compute.from`), so what
|
|
40
|
+
* follows `dependsOn` - a row's cells (`RowsStore`), as a form's do through `withInputConditions` - draws it again when
|
|
41
|
+
* one of them changes. The same array when none is worked out from another, and the same copy for the same array.
|
|
42
|
+
*/
|
|
43
|
+
export declare function withComputeSources(inputs: readonly InputParams[]): readonly InputParams[];
|
|
44
|
+
/** Whether the input is worked out from others (`compute`): it holds no value of the form's and is never typed in. */
|
|
45
|
+
export declare function isComputedInput(params: InputParams | undefined): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* What a worked-out input (`compute`) shows for these values and the form's `computeWith`. A page's rule that throws
|
|
48
|
+
* shows nothing, and says so in the console, so one bad figure never takes the form down with it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function computedValue(params: InputParams, values: FormValues, withValue: unknown): unknown;
|
|
51
|
+
/**
|
|
52
|
+
* A worked-out value as its box writes it: a Number input's figure with its `decimals` always written (1,073.50), grouped,
|
|
53
|
+
* in `locale` (the person's, unless the params name their own); any other input's value as text. Null, undefined, "",
|
|
54
|
+
* NaN, or a figure that is not finite, write nothing, so the box is blank rather than "NaN" or "0.00".
|
|
55
|
+
*/
|
|
56
|
+
export declare function computedText(params: InputParams, value: unknown, locale?: string): string;
|