nexus-shared 4.0.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 +795 -0
- package/README.md +220 -14
- package/dist/Client.Index.d.ts +13 -0
- package/dist/Client.Index.js +13 -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/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +5 -1
- package/dist/Components/Forms/Crud.js +72 -24
- package/dist/Components/Forms/CrudActions.d.ts +5 -4
- package/dist/Components/Forms/CrudActions.js +42 -12
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudParts.d.ts +11 -4
- package/dist/Components/Forms/CrudParts.js +76 -10
- package/dist/Components/Forms/CrudRowForms.js +65 -5
- package/dist/Components/Forms/CrudTree.js +7 -7
- package/dist/Components/Forms/CrudWorkspace.d.ts +15 -5
- package/dist/Components/Forms/CrudWorkspace.js +19 -11
- package/dist/Components/Forms/Form.d.ts +3 -2
- package/dist/Components/Forms/Form.js +33 -26
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/SubmitForm.js +2 -15
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.js +4 -2
- package/dist/Components/Inputs/GroupForm.js +3 -2
- package/dist/Components/Inputs/InputRenderer.d.ts +5 -1
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +14 -1
- package/dist/Components/Inputs/RowsInput.js +78 -5
- package/dist/Components/Inputs/TabularForm.js +16 -3
- package/dist/Components/Inputs/TreeView.d.ts +24 -1
- package/dist/Components/Inputs/TreeView.js +84 -7
- package/dist/Components/Layouts/Popup.d.ts +22 -2
- package/dist/Components/Layouts/Popup.js +108 -6
- 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 +7 -5
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableParts.d.ts +3 -1
- package/dist/Components/Viewers/DataTableParts.js +2 -2
- package/dist/Components/Viewers/OwnerFiles.d.ts +57 -5
- package/dist/Components/Viewers/OwnerFiles.js +142 -31
- 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/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/CrudColumns.d.ts +44 -6
- package/dist/Helpers/CrudColumns.js +63 -13
- package/dist/Helpers/CrudHelpers.d.ts +36 -1
- package/dist/Helpers/CrudHelpers.js +61 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +18 -1
- package/dist/Helpers/CrudRowForms.js +35 -2
- 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/FileAdapter.d.ts +82 -2
- package/dist/Helpers/FileAdapter.js +72 -1
- package/dist/Helpers/FormStore.d.ts +11 -1
- package/dist/Helpers/FormStore.js +66 -19
- package/dist/Helpers/InputConditions.d.ts +6 -0
- package/dist/Helpers/InputConditions.js +24 -0
- package/dist/Helpers/InputMemory.js +40 -30
- package/dist/Helpers/InputNarrowing.d.ts +38 -6
- package/dist/Helpers/InputNarrowing.js +102 -21
- 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 +17 -1
- package/dist/Helpers/NexusDriveAdapter.js +32 -3
- package/dist/Helpers/NumberHelpers.d.ts +61 -0
- package/dist/Helpers/NumberHelpers.js +124 -8
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/RowsStore.d.ts +24 -0
- package/dist/Helpers/RowsStore.js +79 -10
- package/dist/Helpers/TableHelpers.d.ts +15 -2
- package/dist/Helpers/TableHelpers.js +28 -1
- package/dist/Helpers/TableStore.d.ts +7 -3
- package/dist/Helpers/TableStore.js +9 -4
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +75 -6
- package/dist/Helpers/TreeStore.js +182 -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 +78 -5
- 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 +21 -0
- package/dist/Interfaces/FileInterfaces.js +2 -0
- package/dist/Interfaces/FormInterfaces.d.ts +78 -11
- package/dist/Interfaces/InputInterfaces.d.ts +12 -5
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +28 -2
- package/dist/Services/JoinSources.d.ts +47 -6
- package/dist/Services/JoinSources.js +68 -33
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Shared.Index.d.ts +12 -0
- package/dist/Shared.Index.js +12 -0
- package/package.json +7 -1
- package/src/Styles/Nexus.Button.css +29 -0
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Crud.css +119 -28
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +27 -0
- package/src/Styles/Nexus.Index.css +3 -0
- package/src/Styles/Nexus.Input.css +31 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Popup.css +58 -0
- package/src/Styles/Nexus.Table.css +1458 -1449
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Tree.css +10 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { getChatDomains } from "./ChatBackend.js";
|
|
2
|
+
import { formatDate } from "./DateHelpers.js";
|
|
3
|
+
import { formatDateTime } from "./DateTimeHelpers.js";
|
|
4
|
+
import { formatDays, formatMoney, formatNumber } from "./NumberHelpers.js";
|
|
5
|
+
/*
|
|
6
|
+
* A record's card outside React: which card a kind of record has, the reads every card on the page shares, one card's
|
|
7
|
+
* read from its first answer through reading it again, and the words and values it is drawn with. `DomainCard` draws
|
|
8
|
+
* a `DomainCardRun`'s snapshot; nothing here touches the DOM, so it is checked without a browser.
|
|
9
|
+
*
|
|
10
|
+
* The reads keep the owner's caching rules (as `RequestCache` does): one read of a record at a time however many cards
|
|
11
|
+
* draw it, an answer kept for a short life (30 seconds: a card is live, so less than the lists' five minutes), a
|
|
12
|
+
* failure never kept. A card read again because the record changed takes only a read started after the change.
|
|
13
|
+
*/
|
|
14
|
+
/** How long a card's answer is kept for every card of the page that draws the same record, in milliseconds. */
|
|
15
|
+
export const DOMAIN_CARD_LIFETIME = 30_000;
|
|
16
|
+
/**
|
|
17
|
+
* A kind's card, with `view` typed by what `load` answers.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* defineChatDomains([{ domain: EDomainTypes.LeaveRequest, key: "leave-request", label: "Leave request", card: domainCard({
|
|
21
|
+
* load: (id, signal) => api.get<LeaveRequestCard>(REQUEST_CARD.leave(id), { signal }),
|
|
22
|
+
* view: leave => ({ title: `${leave.leaveTypeName} · ${formatDays(leave.days)}`, code: leave.requestCode, ... }),
|
|
23
|
+
* }) }]);
|
|
24
|
+
*/
|
|
25
|
+
export function domainCard(definition) {
|
|
26
|
+
return definition;
|
|
27
|
+
}
|
|
28
|
+
/** A kind's registered row and its card, or `undefined` when the app gave it none. */
|
|
29
|
+
export function domainCardOf(domainType) {
|
|
30
|
+
const info = getChatDomains().find(row => String(row.domain) === String(domainType));
|
|
31
|
+
return info?.card ? { info, card: info.card } : undefined;
|
|
32
|
+
}
|
|
33
|
+
/** The key a record's answer is kept under: its kind, its id, and who reads it (the same record reads otherwise for another). */
|
|
34
|
+
export function domainCardKey(domainType, id, viewer) {
|
|
35
|
+
return [domainType, id, viewer ?? ""].map(part => encodeURIComponent(String(part))).join("|");
|
|
36
|
+
}
|
|
37
|
+
/** The start of every key of one record (every reader's), or of every record of a kind without an id. */
|
|
38
|
+
export function domainCardRecordPrefix(domainType, id) {
|
|
39
|
+
const kind = `${encodeURIComponent(String(domainType))}|`;
|
|
40
|
+
return id === undefined ? kind : `${kind}${encodeURIComponent(String(id))}|`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Reads shared by every card on the page: one read of a record at a time, its answer kept for a lifetime, a failure not
|
|
44
|
+
* kept. A read nobody waits for any more is aborted.
|
|
45
|
+
*/
|
|
46
|
+
export class DomainCardReads {
|
|
47
|
+
entries = new Map();
|
|
48
|
+
now;
|
|
49
|
+
constructor(now = Date.now) {
|
|
50
|
+
this.now = now;
|
|
51
|
+
}
|
|
52
|
+
/** The answer kept under `key` while it is younger than `lifetime` and was read from a read started at or after `since`. */
|
|
53
|
+
peek(key, lifetime = DOMAIN_CARD_LIFETIME, since = 0) {
|
|
54
|
+
const entry = this.entries.get(key);
|
|
55
|
+
if (entry?.state !== "ready")
|
|
56
|
+
return undefined;
|
|
57
|
+
if (this.now() - entry.at >= lifetime) {
|
|
58
|
+
this.entries.delete(key);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return entry.startedAt >= since ? entry.value : undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The answer under `key`: a read on its way that started at or after `since`, else a kept fresh one, else a new read by
|
|
65
|
+
* `load`. `since` is the moment the record was seen to change: what was read before it does not answer.
|
|
66
|
+
*/
|
|
67
|
+
read(key, load, options = {}) {
|
|
68
|
+
const { lifetime = DOMAIN_CARD_LIFETIME, since = 0 } = options;
|
|
69
|
+
const entry = this.entries.get(key);
|
|
70
|
+
if (entry?.state === "loading" && entry.startedAt >= since)
|
|
71
|
+
return this.wait(key, entry);
|
|
72
|
+
const kept = this.peek(key, lifetime, since);
|
|
73
|
+
if (kept !== undefined)
|
|
74
|
+
return { promise: Promise.resolve(kept), leave: () => { } };
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const startedAt = this.now();
|
|
77
|
+
let answer;
|
|
78
|
+
try {
|
|
79
|
+
answer = Promise.resolve(load(controller.signal));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
answer = Promise.reject(error);
|
|
83
|
+
}
|
|
84
|
+
const loading = { state: "loading", startedAt, controller, waiters: 0, promise: answer };
|
|
85
|
+
loading.promise = answer.then(value => {
|
|
86
|
+
// A read dropped or overtaken on its way answers whoever waits for it, and is not kept.
|
|
87
|
+
if (this.entries.get(key) === loading) {
|
|
88
|
+
if (lifetime > 0)
|
|
89
|
+
this.entries.set(key, { state: "ready", value, startedAt, at: this.now() });
|
|
90
|
+
else
|
|
91
|
+
this.entries.delete(key);
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}, (error) => {
|
|
95
|
+
if (this.entries.get(key) === loading)
|
|
96
|
+
this.entries.delete(key);
|
|
97
|
+
throw error;
|
|
98
|
+
});
|
|
99
|
+
// Nobody may be waiting when it fails (every card left): that is not an unhandled rejection.
|
|
100
|
+
loading.promise.catch(() => { });
|
|
101
|
+
this.entries.set(key, loading);
|
|
102
|
+
return this.wait(key, loading);
|
|
103
|
+
}
|
|
104
|
+
wait(key, loading) {
|
|
105
|
+
loading.waiters++;
|
|
106
|
+
let waiting = true;
|
|
107
|
+
return {
|
|
108
|
+
promise: loading.promise,
|
|
109
|
+
leave: () => {
|
|
110
|
+
if (!waiting)
|
|
111
|
+
return;
|
|
112
|
+
waiting = false;
|
|
113
|
+
loading.waiters--;
|
|
114
|
+
// A card that leaves and comes straight back (a remount) keeps the read: the abort waits a turn.
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
if (loading.waiters > 0)
|
|
117
|
+
return;
|
|
118
|
+
if (this.entries.get(key) === loading)
|
|
119
|
+
this.entries.delete(key);
|
|
120
|
+
loading.controller.abort();
|
|
121
|
+
}, 0);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Drops what is kept or on its way under keys that start with `prefix` (every key when none); a read on its way still
|
|
127
|
+
* answers its waiters. `keptOnly` leaves the reads on their way, so cards reading again for the same change still share one.
|
|
128
|
+
*/
|
|
129
|
+
drop(prefix = "", keptOnly = false) {
|
|
130
|
+
let dropped = 0;
|
|
131
|
+
for (const [key, entry] of [...this.entries]) {
|
|
132
|
+
if (!key.startsWith(prefix) || (keptOnly && entry.state !== "ready"))
|
|
133
|
+
continue;
|
|
134
|
+
this.entries.delete(key);
|
|
135
|
+
dropped++;
|
|
136
|
+
}
|
|
137
|
+
return dropped;
|
|
138
|
+
}
|
|
139
|
+
/** Answers kept and reads on their way. */
|
|
140
|
+
get size() {
|
|
141
|
+
return this.entries.size;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** The reads every card of the page shares. */
|
|
145
|
+
export const domainCardReads = new DomainCardReads();
|
|
146
|
+
// Failures
|
|
147
|
+
const statusOf = (error) => {
|
|
148
|
+
const status = error?.status;
|
|
149
|
+
return typeof status === "number" ? status : undefined;
|
|
150
|
+
};
|
|
151
|
+
/** A read the page called off (the card closed): not a failure to show. */
|
|
152
|
+
export function isDomainCardAbort(error) {
|
|
153
|
+
return error?.name === "AbortError";
|
|
154
|
+
}
|
|
155
|
+
/** Whether a failure means the reader may not open the record, or it is not there for them: 403 and 404 alike. */
|
|
156
|
+
export function isDomainCardRefusal(error) {
|
|
157
|
+
const status = statusOf(error);
|
|
158
|
+
return status === 403 || status === 404;
|
|
159
|
+
}
|
|
160
|
+
/** Every run that is drawn, for `refreshDomainCards`. */
|
|
161
|
+
const drawn = new Set();
|
|
162
|
+
/** Two answers of the same record: the same when their JSON is. */
|
|
163
|
+
const sameData = (a, b) => {
|
|
164
|
+
try {
|
|
165
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return a === b;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* One card's read: loading, then the record, a refusal or a failure; read again when the record changes (the card stays
|
|
173
|
+
* on screen meanwhile, and says so when the record turned out different); tried again on Retry. Subscribe to draw it.
|
|
174
|
+
*/
|
|
175
|
+
export class DomainCardRun {
|
|
176
|
+
domainType;
|
|
177
|
+
id;
|
|
178
|
+
key;
|
|
179
|
+
options;
|
|
180
|
+
reads;
|
|
181
|
+
now;
|
|
182
|
+
snapshot;
|
|
183
|
+
listeners = new Set();
|
|
184
|
+
reading = null;
|
|
185
|
+
round = 0;
|
|
186
|
+
constructor(options) {
|
|
187
|
+
this.options = options;
|
|
188
|
+
this.domainType = options.domainType;
|
|
189
|
+
this.id = options.id;
|
|
190
|
+
this.key = domainCardKey(options.domainType, options.id, options.viewer);
|
|
191
|
+
this.reads = options.reads ?? domainCardReads;
|
|
192
|
+
this.now = options.now ?? Date.now;
|
|
193
|
+
// A record read a moment ago by another card is drawn at once, without a placeholder.
|
|
194
|
+
const kept = this.reads.peek(this.key, this.lifetime);
|
|
195
|
+
this.snapshot = kept === undefined ? { status: "loading", refreshing: false, refreshFailed: false } : { status: "ready", data: kept, refreshing: false, refreshFailed: false };
|
|
196
|
+
}
|
|
197
|
+
get lifetime() {
|
|
198
|
+
return this.options.lifetime ?? DOMAIN_CARD_LIFETIME;
|
|
199
|
+
}
|
|
200
|
+
subscribe = (listener) => {
|
|
201
|
+
this.listeners.add(listener);
|
|
202
|
+
return () => this.listeners.delete(listener);
|
|
203
|
+
};
|
|
204
|
+
getSnapshot = () => this.snapshot;
|
|
205
|
+
set(next) {
|
|
206
|
+
this.snapshot = { ...this.snapshot, ...next };
|
|
207
|
+
for (const listener of this.listeners)
|
|
208
|
+
listener();
|
|
209
|
+
}
|
|
210
|
+
/** The card is drawn: reads, unless it already shows a kept answer. */
|
|
211
|
+
start() {
|
|
212
|
+
drawn.add(this);
|
|
213
|
+
if (this.snapshot.status === "ready" || this.reading)
|
|
214
|
+
return;
|
|
215
|
+
this.read(0);
|
|
216
|
+
}
|
|
217
|
+
/** The card is gone: stops waiting (the read is aborted when no other card waits for it). */
|
|
218
|
+
stop() {
|
|
219
|
+
drawn.delete(this);
|
|
220
|
+
this.round++;
|
|
221
|
+
this.reading?.leave();
|
|
222
|
+
this.reading = null;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The record changed: reads it again, from a read started now. The card on screen stays until the answer lands, and
|
|
226
|
+
* what other readers kept of the record is forgotten, since it changed for them too.
|
|
227
|
+
*/
|
|
228
|
+
reload() {
|
|
229
|
+
this.reads.drop(domainCardRecordPrefix(this.domainType, this.id), true);
|
|
230
|
+
this.read(this.now());
|
|
231
|
+
}
|
|
232
|
+
/** After a failure: reads once more. */
|
|
233
|
+
retry() {
|
|
234
|
+
this.read(this.now());
|
|
235
|
+
}
|
|
236
|
+
read(since) {
|
|
237
|
+
const round = ++this.round;
|
|
238
|
+
this.reading?.leave();
|
|
239
|
+
const shown = this.snapshot.status === "ready" || this.snapshot.status === "refused";
|
|
240
|
+
if (shown)
|
|
241
|
+
this.set({ refreshing: true });
|
|
242
|
+
else
|
|
243
|
+
this.set({ status: "loading", refreshing: false, refreshFailed: false, error: undefined });
|
|
244
|
+
const reading = this.reads.read(this.key, this.options.load, { lifetime: this.lifetime, since });
|
|
245
|
+
this.reading = reading;
|
|
246
|
+
reading.promise.then(data => {
|
|
247
|
+
if (round !== this.round)
|
|
248
|
+
return;
|
|
249
|
+
this.reading = null;
|
|
250
|
+
const before = this.snapshot;
|
|
251
|
+
const changed = before.status === "ready" && before.data !== undefined && !sameData(before.data, data);
|
|
252
|
+
this.set({ status: "ready", data, refreshing: false, refreshFailed: false, error: undefined, ...(changed && { changedAt: this.now() }) });
|
|
253
|
+
}, (error) => {
|
|
254
|
+
if (round !== this.round)
|
|
255
|
+
return;
|
|
256
|
+
this.reading = null;
|
|
257
|
+
if (isDomainCardAbort(error)) {
|
|
258
|
+
this.set({ refreshing: false });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (isDomainCardRefusal(error)) {
|
|
262
|
+
// The reader lost the record, or it is gone: nothing of it stays on screen.
|
|
263
|
+
this.set({ status: "refused", data: undefined, refreshing: false, refreshFailed: false, changedAt: undefined, error });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (this.snapshot.status === "ready")
|
|
267
|
+
this.set({ refreshing: false, refreshFailed: true, error });
|
|
268
|
+
else
|
|
269
|
+
this.set({ status: "failed", refreshing: false, error });
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* A record changed (the page approved it, a message said so): every card drawing it reads it again, and no answer kept
|
|
275
|
+
* from before is handed out. Without an id, every record of the kind.
|
|
276
|
+
*/
|
|
277
|
+
export function refreshDomainCards(domainType, id, reads = domainCardReads) {
|
|
278
|
+
reads.drop(domainCardRecordPrefix(domainType, id));
|
|
279
|
+
let count = 0;
|
|
280
|
+
for (const run of drawn) {
|
|
281
|
+
if (String(run.domainType) !== String(domainType) || (id !== undefined && String(run.id) !== String(id)))
|
|
282
|
+
continue;
|
|
283
|
+
run.reload();
|
|
284
|
+
count++;
|
|
285
|
+
}
|
|
286
|
+
return count;
|
|
287
|
+
}
|
|
288
|
+
// Words
|
|
289
|
+
/** The kind's name inside a sentence: "Leave request" reads "leave request"; "HR letter" keeps its capitals. */
|
|
290
|
+
export function domainCardNoun(kind) {
|
|
291
|
+
const name = kind?.trim();
|
|
292
|
+
if (!name)
|
|
293
|
+
return "record";
|
|
294
|
+
return /^[A-Z][a-z]/.test(name) ? name[0].toLowerCase() + name.slice(1) : name;
|
|
295
|
+
}
|
|
296
|
+
/** What a reader the record's module refused reads: the same for "not yours" and "not there", so a stranger learns nothing. */
|
|
297
|
+
export function domainCardRefusedText(kind) {
|
|
298
|
+
return `You cannot open this ${domainCardNoun(kind)}.`;
|
|
299
|
+
}
|
|
300
|
+
/** What a failed read says, beside Try again. */
|
|
301
|
+
export function domainCardFailedText(kind) {
|
|
302
|
+
return `Could not load this ${domainCardNoun(kind)}.`;
|
|
303
|
+
}
|
|
304
|
+
const isMissing = (value) => value === null || value === undefined || (typeof value === "string" && value.trim() === "") || (typeof value === "number" && Number.isNaN(value));
|
|
305
|
+
const numberOf = (value) => {
|
|
306
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
307
|
+
return Number.isFinite(number) ? number : null;
|
|
308
|
+
};
|
|
309
|
+
/** A value written by its kind, or "" when it is missing. A value its kind cannot read is written as it is. */
|
|
310
|
+
export function domainCardValueText(value, kind = "text", options = {}) {
|
|
311
|
+
if (isMissing(value))
|
|
312
|
+
return "";
|
|
313
|
+
const held = value;
|
|
314
|
+
const { preferences, timeZone, locale, currency } = options;
|
|
315
|
+
switch (kind) {
|
|
316
|
+
case "number": {
|
|
317
|
+
const number = numberOf(held);
|
|
318
|
+
return number === null ? String(held) : formatNumber(number, { decimals: 2, grouping: true, locale });
|
|
319
|
+
}
|
|
320
|
+
case "days": {
|
|
321
|
+
const number = numberOf(held);
|
|
322
|
+
return number === null ? String(held) : formatDays(number, { locale });
|
|
323
|
+
}
|
|
324
|
+
case "money": {
|
|
325
|
+
const number = numberOf(held);
|
|
326
|
+
return number === null ? String(held) : formatMoney(number, { currency, locale });
|
|
327
|
+
}
|
|
328
|
+
case "date":
|
|
329
|
+
return formatDate(String(held), preferences) || String(held);
|
|
330
|
+
case "datetime":
|
|
331
|
+
return formatDateTime(String(held), { ...preferences, ...(timeZone && { timeZone }) }) || String(held);
|
|
332
|
+
default:
|
|
333
|
+
return String(held);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** The fields a card draws: the ones with a value, and the ones that say what stands in for a missing one. */
|
|
337
|
+
export function domainCardFields(params) {
|
|
338
|
+
return (params.fields ?? []).filter(field => !isMissing(field.value) || Boolean(field.empty));
|
|
339
|
+
}
|
|
340
|
+
// Shapes
|
|
341
|
+
/** What a placeholder holds when a kind gave nothing and none of its cards has loaded yet: a title, a state, four fields. */
|
|
342
|
+
export const DEFAULT_DOMAIN_CARD_SHAPE = { subtitle: false, state: true, fields: 4, figures: 0, hints: [], lines: 0, columns: 0, more: false, link: false };
|
|
343
|
+
/** How many of each part a card drew. */
|
|
344
|
+
export function domainCardShape(params) {
|
|
345
|
+
const lines = params.lines;
|
|
346
|
+
return {
|
|
347
|
+
subtitle: Boolean(params.subtitle || params.code || params.link),
|
|
348
|
+
state: Boolean(params.state),
|
|
349
|
+
fields: domainCardFields(params).length,
|
|
350
|
+
figures: params.figures?.length ?? 0,
|
|
351
|
+
hints: (params.figures ?? []).flatMap((figure, index) => (figure.hint ? [index] : [])),
|
|
352
|
+
lines: lines ? lines.rows.length : 0,
|
|
353
|
+
columns: lines && lines.rows.length > 0 ? lines.columns.length : 0,
|
|
354
|
+
more: Boolean(lines && (lines.more ?? 0) > 0),
|
|
355
|
+
link: Boolean(params.link),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const learned = new Map();
|
|
359
|
+
/** Keeps a kind's last drawn shape, so its next placeholder holds the same parts. */
|
|
360
|
+
export function rememberDomainCardShape(domainType, shape) {
|
|
361
|
+
learned.set(String(domainType), shape);
|
|
362
|
+
}
|
|
363
|
+
/** The shape a kind's placeholder holds: its last card's, else what its card says, else the default. */
|
|
364
|
+
export function heldDomainCardShape(domainType, card) {
|
|
365
|
+
return learned.get(String(domainType)) ?? { ...DEFAULT_DOMAIN_CARD_SHAPE, ...card?.held };
|
|
366
|
+
}
|
|
367
|
+
/** What `load`, `view` and `extra` are told about a record. */
|
|
368
|
+
export function domainCardContext(domainType, id, extra = {}) {
|
|
369
|
+
return { domainType, id, ...extra };
|
|
370
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApiModuleValue, ApiResult } from "../Interfaces/ApiInterfaces.ts";
|
|
2
|
-
import type { FileCallOptions, FileCategoryInfo, FileCopy, FileOwner, FileUpload, OwnerFiles, PictureType, StoredFile } from "../Interfaces/FileInterfaces.ts";
|
|
2
|
+
import type { FileCallOptions, FileCategoryInfo, FileChange, FileCopy, FileOwner, FileUpload, OwnerFiles, PictureType, StoredFile } from "../Interfaces/FileInterfaces.ts";
|
|
3
3
|
export declare abstract class FileAdapter {
|
|
4
4
|
/** The owner's files and what their category takes, `canWrite` included. */
|
|
5
5
|
abstract list(owner: FileOwner<ApiModuleValue>, options?: FileCallOptions): Promise<ApiResult<OwnerFiles>>;
|
|
@@ -14,8 +14,18 @@ export declare abstract class FileAdapter {
|
|
|
14
14
|
abstract setPrimary(owner: FileOwner<ApiModuleValue>, fileId: string | null, options?: FileCallOptions): Promise<ApiResult<unknown>>;
|
|
15
15
|
/** A new name for one of the owner's files; the backend keeps the extension of what the file really is. */
|
|
16
16
|
abstract rename(owner: FileOwner<ApiModuleValue>, fileId: string, name: string, options?: FileCallOptions): Promise<ApiResult<unknown>>;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Deletes one of the owner's files. In a category that holds removals for a review (`FileCategoryInfo.holdsRemovals`)
|
|
19
|
+
* the file stays: the answer is the file as it is now, `pendingRemoval: true` (at least `{ pendingRemoval: true }`),
|
|
20
|
+
* which `removalHeld` reads.
|
|
21
|
+
*/
|
|
18
22
|
abstract remove(owner: FileOwner<ApiModuleValue>, fileId: string, options?: FileCallOptions): Promise<ApiResult<unknown>>;
|
|
23
|
+
/**
|
|
24
|
+
* Keeps a file whose removal waits for a review (`StoredFile.pendingRemoval`): the removal is cancelled and the file
|
|
25
|
+
* answered as it is now (null when the backend answers nothing). A backend without held removals leaves it out, and no
|
|
26
|
+
* Keep is offered.
|
|
27
|
+
*/
|
|
28
|
+
keep?(owner: FileOwner<ApiModuleValue>, fileId: string, options?: FileCallOptions): Promise<ApiResult<StoredFile | null>>;
|
|
19
29
|
/** The address an `<img>`, a link or the viewer opens the file by. */
|
|
20
30
|
abstract fileUrl(file: StoredFile, copy: FileCopy): string;
|
|
21
31
|
/**
|
|
@@ -25,7 +35,77 @@ export declare abstract class FileAdapter {
|
|
|
25
35
|
abstract editorModuleUrl(signal?: AbortSignal): Promise<string>;
|
|
26
36
|
/** The most owners one `primaries` call takes. */
|
|
27
37
|
get maxOwners(): number;
|
|
38
|
+
/**
|
|
39
|
+
* The changes still allowed when a refused change says the owner's files may now be changed only some ways - a
|
|
40
|
+
* receipt posted since the page was read, whose documents are only added to - else `null` (any other refusal). The
|
|
41
|
+
* panel then offers only those for that owner, under the refusal's own words. Default: no refusal says so.
|
|
42
|
+
*/
|
|
43
|
+
changesLeft(answer: Pick<ApiResult, "messageCode" | "errorCode" | "status">): readonly FileChange[] | null;
|
|
28
44
|
}
|
|
45
|
+
/** Whether a Delete's answer says the file stays until a review (`FileAdapter.remove` in a category holding removals). */
|
|
46
|
+
export declare const removalHeld: (result: unknown) => boolean;
|
|
47
|
+
/** The words on a file whose removal waits for a review. */
|
|
48
|
+
export declare const PENDING_REMOVAL_TAG = "Removal waiting for review";
|
|
49
|
+
/** Who reviews a held removal when a page does not say (`OwnerFilesParams.removalReviewer`). */
|
|
50
|
+
export declare const REMOVAL_REVIEWER = "Nexus support";
|
|
51
|
+
/** "The picture stays until Nexus support approves its removal.": what a Delete held for a review says once sent. */
|
|
52
|
+
export declare const removalHeldSentence: (noun: string, reviewer?: string | null) => string;
|
|
53
|
+
/**
|
|
54
|
+
* "If the public sees this picture, it stays until Nexus support approves its removal.": what a Delete asks in a category
|
|
55
|
+
* that holds removals, where only a file the public sees (approved) is held and the answer says which.
|
|
56
|
+
*/
|
|
57
|
+
export declare const removalHeldQuestion: (noun: string, reviewer?: string | null) => string;
|
|
58
|
+
/** Whether a change is allowed: every change when no list is given, else the ones it names. */
|
|
59
|
+
export declare const allowsFileChange: (changes: readonly FileChange[] | null | undefined, change: FileChange) => boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The changes allowed when both the page and a refusal narrowed them: those both allow. `null` from both: every change.
|
|
62
|
+
*/
|
|
63
|
+
export declare function narrowFileChanges(page: readonly FileChange[] | null | undefined, learned: readonly FileChange[] | null | undefined): readonly FileChange[] | null;
|
|
64
|
+
/**
|
|
65
|
+
* What a list says under its files while some changes are left out: the page's reason when its own `changes` leave them
|
|
66
|
+
* out, else the words of the refusal that narrowed them (`FileAdapter.changesLeft`); nothing while every change is allowed.
|
|
67
|
+
*/
|
|
68
|
+
export declare function fileChangesNote(page: {
|
|
69
|
+
changes?: readonly FileChange[] | null;
|
|
70
|
+
reason?: string | null;
|
|
71
|
+
}, learned: {
|
|
72
|
+
changes: readonly FileChange[];
|
|
73
|
+
reason: string | null;
|
|
74
|
+
} | null | undefined): string | null;
|
|
75
|
+
/** What an owner's files offer a person: each button a gallery, a document list or the picture input may show. */
|
|
76
|
+
export interface OwnerFileActions {
|
|
77
|
+
/** Upload (or Replace, in a category that keeps one file and has one: that also deletes the one kept). */
|
|
78
|
+
upload: boolean;
|
|
79
|
+
/** Rename a document. */
|
|
80
|
+
rename: boolean;
|
|
81
|
+
/** Delete a file. */
|
|
82
|
+
remove: boolean;
|
|
83
|
+
/** Make this file the owner's picture (the star); never on the one that already is. */
|
|
84
|
+
makePrimary: boolean;
|
|
85
|
+
/** Show no picture, the pictures kept (Remove, beside Upload). */
|
|
86
|
+
clearPrimary: boolean;
|
|
87
|
+
/** Edit a picture: saved as a new one - the owner's picture when this one was, in place of it in a one-file category. */
|
|
88
|
+
edit: boolean;
|
|
89
|
+
/** Keep a file whose removal waits for a review: the removal cancelled. Offered in place of Delete. */
|
|
90
|
+
keep: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The buttons offered for an owner's files, and for one file when given: none without `canWrite` (the backend's, a page's
|
|
94
|
+
* `readOnly` or `lockReason`), and each only when the changes allowed hold every change it makes - Replace and Edit in a
|
|
95
|
+
* one-file category delete the file kept; Edit of the owner's picture makes the new one the picture.
|
|
96
|
+
*
|
|
97
|
+
* Held removals (`holdsRemovals`): a file whose removal waits for a review offers only Keep (`keeps`: the backend can), never
|
|
98
|
+
* Rename, Delete, Make main or Edit; and a one-file category offers no Replace or in-place Edit, since its Delete frees no
|
|
99
|
+
* room until the review.
|
|
100
|
+
*/
|
|
101
|
+
export declare function ownerFileActions({ canWrite, changes, maxCount, kept, holdsRemovals, keeps }: {
|
|
102
|
+
canWrite: boolean;
|
|
103
|
+
changes?: readonly FileChange[] | null;
|
|
104
|
+
maxCount: number;
|
|
105
|
+
kept: number;
|
|
106
|
+
holdsRemovals?: boolean;
|
|
107
|
+
keeps?: boolean;
|
|
108
|
+
}, file?: Pick<StoredFile, "isPrimary" | "pendingRemoval"> | null): OwnerFileActions;
|
|
29
109
|
/** One owner's key, for caches and listeners: the same module, category and id are the same owner. */
|
|
30
110
|
export declare const fileOwnerKey: (owner: FileOwner<ApiModuleValue>) => string;
|
|
31
111
|
/** Whether a category takes anything that is not a picture: its files are a document list, not a gallery. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isPictureType } from "../Interfaces/FileInterfaces.js";
|
|
1
|
+
import { FILE_CHANGES, isPictureType } from "../Interfaces/FileInterfaces.js";
|
|
2
2
|
import { chatFileSize } from "./ChatHelpers.js";
|
|
3
3
|
/*
|
|
4
4
|
* Where an owner's files are kept. `FileAdapter` is the class an app implements for its own file backend;
|
|
@@ -15,6 +15,77 @@ export class FileAdapter {
|
|
|
15
15
|
get maxOwners() {
|
|
16
16
|
return 200;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The changes still allowed when a refused change says the owner's files may now be changed only some ways - a
|
|
20
|
+
* receipt posted since the page was read, whose documents are only added to - else `null` (any other refusal). The
|
|
21
|
+
* panel then offers only those for that owner, under the refusal's own words. Default: no refusal says so.
|
|
22
|
+
*/
|
|
23
|
+
changesLeft(answer) {
|
|
24
|
+
void answer;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Whether a Delete's answer says the file stays until a review (`FileAdapter.remove` in a category holding removals). */
|
|
29
|
+
export const removalHeld = (result) => typeof result === "object" && result !== null && result.pendingRemoval === true;
|
|
30
|
+
/** The words on a file whose removal waits for a review. */
|
|
31
|
+
export const PENDING_REMOVAL_TAG = "Removal waiting for review";
|
|
32
|
+
/** Who reviews a held removal when a page does not say (`OwnerFilesParams.removalReviewer`). */
|
|
33
|
+
export const REMOVAL_REVIEWER = "Nexus support";
|
|
34
|
+
/** "The picture stays until Nexus support approves its removal.": what a Delete held for a review says once sent. */
|
|
35
|
+
export const removalHeldSentence = (noun, reviewer) => `The ${noun} stays until ${reviewer?.trim() || REMOVAL_REVIEWER} approves its removal.`;
|
|
36
|
+
/**
|
|
37
|
+
* "If the public sees this picture, it stays until Nexus support approves its removal.": what a Delete asks in a category
|
|
38
|
+
* that holds removals, where only a file the public sees (approved) is held and the answer says which.
|
|
39
|
+
*/
|
|
40
|
+
export const removalHeldQuestion = (noun, reviewer) => `If the public sees this ${noun}, it stays until ${reviewer?.trim() || REMOVAL_REVIEWER} approves its removal.`;
|
|
41
|
+
/** Whether a change is allowed: every change when no list is given, else the ones it names. */
|
|
42
|
+
export const allowsFileChange = (changes, change) => !changes || changes.includes(change);
|
|
43
|
+
/**
|
|
44
|
+
* The changes allowed when both the page and a refusal narrowed them: those both allow. `null` from both: every change.
|
|
45
|
+
*/
|
|
46
|
+
export function narrowFileChanges(page, learned) {
|
|
47
|
+
if (!page)
|
|
48
|
+
return learned ?? null;
|
|
49
|
+
if (!learned)
|
|
50
|
+
return page;
|
|
51
|
+
return page.filter(change => learned.includes(change));
|
|
52
|
+
}
|
|
53
|
+
/** Whether a list of changes leaves any out (none given leaves nothing out). */
|
|
54
|
+
const leavesOut = (changes) => Boolean(changes) && FILE_CHANGES.some(change => !changes.includes(change));
|
|
55
|
+
/**
|
|
56
|
+
* What a list says under its files while some changes are left out: the page's reason when its own `changes` leave them
|
|
57
|
+
* out, else the words of the refusal that narrowed them (`FileAdapter.changesLeft`); nothing while every change is allowed.
|
|
58
|
+
*/
|
|
59
|
+
export function fileChangesNote(page, learned) {
|
|
60
|
+
if (!leavesOut(narrowFileChanges(page.changes, learned?.changes)))
|
|
61
|
+
return null;
|
|
62
|
+
const pageReason = leavesOut(page.changes) ? page.reason?.trim() : undefined;
|
|
63
|
+
return pageReason || learned?.reason?.trim() || null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The buttons offered for an owner's files, and for one file when given: none without `canWrite` (the backend's, a page's
|
|
67
|
+
* `readOnly` or `lockReason`), and each only when the changes allowed hold every change it makes - Replace and Edit in a
|
|
68
|
+
* one-file category delete the file kept; Edit of the owner's picture makes the new one the picture.
|
|
69
|
+
*
|
|
70
|
+
* Held removals (`holdsRemovals`): a file whose removal waits for a review offers only Keep (`keeps`: the backend can), never
|
|
71
|
+
* Rename, Delete, Make main or Edit; and a one-file category offers no Replace or in-place Edit, since its Delete frees no
|
|
72
|
+
* room until the review.
|
|
73
|
+
*/
|
|
74
|
+
export function ownerFileActions({ canWrite, changes, maxCount, kept, holdsRemovals = false, keeps = false }, file) {
|
|
75
|
+
const can = (change) => canWrite && allowsFileChange(changes, change);
|
|
76
|
+
const single = maxCount === 1;
|
|
77
|
+
const pending = file?.pendingRemoval === true;
|
|
78
|
+
// Replace and an in-place Edit delete the file kept first: held, it would still take the one place.
|
|
79
|
+
const replaces = can("remove") && !holdsRemovals;
|
|
80
|
+
return {
|
|
81
|
+
upload: can("add") && (!single || kept === 0 || replaces),
|
|
82
|
+
rename: can("rename") && !pending,
|
|
83
|
+
remove: can("remove") && !pending,
|
|
84
|
+
makePrimary: can("primary") && Boolean(file) && !file.isPrimary && !pending,
|
|
85
|
+
clearPrimary: can("primary"),
|
|
86
|
+
edit: can("add") && !pending && (single ? replaces : !file?.isPrimary || can("primary")),
|
|
87
|
+
keep: pending && keeps && can("remove"),
|
|
88
|
+
};
|
|
18
89
|
}
|
|
19
90
|
/** One owner's key, for caches and listeners: the same module, category and id are the same owner. */
|
|
20
91
|
export const fileOwnerKey = (owner) => `${String(owner.module)}|${owner.category}|${owner.ownerId}`;
|
|
@@ -193,6 +193,13 @@ export declare class FormStore<TValues extends FormValues = FormValues> {
|
|
|
193
193
|
set(name: string, value: unknown): void;
|
|
194
194
|
/** Sets several values from code, as one change. */
|
|
195
195
|
setValues(values: Partial<TValues>): void;
|
|
196
|
+
/**
|
|
197
|
+
* Values a person set through another input - one worked out from them and typed in (`compute.solve`) - as one change.
|
|
198
|
+
* Their inputs show them at once, as values from code do, and they are taken as the person's own: the form's
|
|
199
|
+
* `onFieldChange` runs for them, and inputs narrowed by them lose what they picked. Nothing is taken while the values
|
|
200
|
+
* load. Answers whether anything changed.
|
|
201
|
+
*/
|
|
202
|
+
setFromPerson(values: Partial<TValues>): boolean;
|
|
196
203
|
/**
|
|
197
204
|
* A value the user typed or picked, from the input rendered with `stamp`, with what a choice input reported beside it
|
|
198
205
|
* (`item`: a dropdown's option, kept for `pickedItem`). An input that was replaced since (by a value from code, a load,
|
|
@@ -298,7 +305,10 @@ export declare class FormStore<TValues extends FormValues = FormValues> {
|
|
|
298
305
|
/** Inputs that were not there, or were hidden, get their value in the input's type, as a load gives it. */
|
|
299
306
|
private adoptNewInputs;
|
|
300
307
|
private change;
|
|
301
|
-
/**
|
|
308
|
+
/**
|
|
309
|
+
* The inputs whose `dependsOn` (or `narrowedBy`) names any of `names`, and the rows inputs whose row inputs are narrowed
|
|
310
|
+
* by one of them `from: "form"`: their rows draw again with the form's value as it is now.
|
|
311
|
+
*/
|
|
302
312
|
private followersOf;
|
|
303
313
|
private checkRule;
|
|
304
314
|
private checkAllRules;
|