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,85 @@
|
|
|
1
|
+
import { isAbortError } from "./ApiResponses.js";
|
|
2
|
+
import { itemValue, optionKey } from "./DropdownHelpers.js";
|
|
3
|
+
/** The items each picker has seen, by key, kept beside its params so the params stay a dropdown's alone. */
|
|
4
|
+
const seenBy = new WeakMap();
|
|
5
|
+
/**
|
|
6
|
+
* A dropdown over a server's search, paged (`load` takes the dropdown's own request), that names every value it holds:
|
|
7
|
+
* from the pages it has shown, the items it was handed, the server's lookup, or the server's first pages.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const people = pagedPicker(request => mail.people(request), { isInactive: person => person.isActive === false });
|
|
11
|
+
* const inputs = [{ type: EInputTypes.Dropdown, name: "to", label: "To", multiple: true, required: true, ...people }];
|
|
12
|
+
*/
|
|
13
|
+
export function pagedPicker(load, options = {}) {
|
|
14
|
+
const { valueKey = "id", labelKey = "name", descriptionKey, isInactive, find, findPages = 4, findPageSize = 50 } = options;
|
|
15
|
+
const seen = new Map();
|
|
16
|
+
const hold = (items) => {
|
|
17
|
+
for (const item of items) {
|
|
18
|
+
const value = itemValue(item, valueKey);
|
|
19
|
+
if (value !== undefined)
|
|
20
|
+
seen.set(optionKey(value), item);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** Searches the server's first pages for keys nothing has named, stopping at the list's end or when all are found. */
|
|
24
|
+
async function searchPages(missing) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
for (let page = 1; page <= findPages && missing.size > 0; page++) {
|
|
27
|
+
const answer = await load({ search: "", page, pageSize: findPageSize, skip: (page - 1) * findPageSize, signal: controller.signal });
|
|
28
|
+
hold(answer.items);
|
|
29
|
+
for (const item of answer.items)
|
|
30
|
+
missing.delete(optionKey(itemValue(item, valueKey)));
|
|
31
|
+
const ended = answer.hasMore === false || answer.items.length === 0 || (answer.hasMore === undefined && answer.items.length < findPageSize);
|
|
32
|
+
if (ended)
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const params = {
|
|
37
|
+
async loadOptions(request) {
|
|
38
|
+
const answer = await load(request);
|
|
39
|
+
hold(answer.items);
|
|
40
|
+
return answer;
|
|
41
|
+
},
|
|
42
|
+
async resolveOptions(values) {
|
|
43
|
+
const keys = values.map(optionKey);
|
|
44
|
+
const missing = new Set(keys.filter(key => !seen.has(key)));
|
|
45
|
+
try {
|
|
46
|
+
if (missing.size > 0 && find) {
|
|
47
|
+
const found = await find([...missing]);
|
|
48
|
+
hold(found);
|
|
49
|
+
for (const item of found)
|
|
50
|
+
missing.delete(optionKey(itemValue(item, valueKey)));
|
|
51
|
+
}
|
|
52
|
+
if (missing.size > 0)
|
|
53
|
+
await searchPages(missing);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// A value the dropdown cannot name shows as "Not found"; a failed lookup is not worth more than that, but the
|
|
57
|
+
// dropdown's own abort still ends the call.
|
|
58
|
+
if (isAbortError(error))
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
return keys.map(key => seen.get(key)).filter((item) => item !== undefined);
|
|
62
|
+
},
|
|
63
|
+
peekOption: key => seen.get(key),
|
|
64
|
+
valueKey,
|
|
65
|
+
labelKey,
|
|
66
|
+
...(descriptionKey && { descriptionKey }),
|
|
67
|
+
isOptionInactive: item => isInactive?.(item) === true,
|
|
68
|
+
};
|
|
69
|
+
seenBy.set(params, seen);
|
|
70
|
+
return params;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Hands a picker items the page already holds, so a form that opens with their values names them at once, with no call:
|
|
74
|
+
* the people a new message goes to by default.
|
|
75
|
+
*/
|
|
76
|
+
export function holdPickerItems(picker, items) {
|
|
77
|
+
const seen = seenBy.get(picker);
|
|
78
|
+
if (!seen)
|
|
79
|
+
return;
|
|
80
|
+
for (const item of items) {
|
|
81
|
+
const value = itemValue(item, picker.valueKey);
|
|
82
|
+
if (value !== undefined)
|
|
83
|
+
seen.set(optionKey(value), item);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -108,6 +108,19 @@ export declare class RowsStore<TRow extends RowValue = RowValue> {
|
|
|
108
108
|
configure(options: RowsStoreOptions<TRow>, recheck?: boolean): void;
|
|
109
109
|
/** Sets a value in a row. `input` is a change the user made in the row's input: it runs `onRowChange`. */
|
|
110
110
|
set(key: string, name: string, value: unknown, source?: "input" | "code"): void;
|
|
111
|
+
/**
|
|
112
|
+
* Values a person set in a row through another of its inputs - one worked out from them and typed in (`compute.solve`)
|
|
113
|
+
* - as one change. Their cells show them at once, as values from code do, and they are taken as the person's own:
|
|
114
|
+
* `onRowChange` runs for each, the row's inputs narrowed by them lose what they picked, and the rows' value listeners
|
|
115
|
+
* hear of it (a parent form's unsaved-changes state, its Save and its leave guard). A name worked out itself is not
|
|
116
|
+
* taken. Answers whether anything changed.
|
|
117
|
+
*/
|
|
118
|
+
setFromPerson(key: string, values: Partial<TRow>): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Draws every row again, values untouched: the form around the rows changed a value their cells follow
|
|
121
|
+
* (`narrowedBy` `from: "form"`), so each cell is narrowed afresh as it draws.
|
|
122
|
+
*/
|
|
123
|
+
redrawRows(): void;
|
|
111
124
|
/** Marks a value as left by the user: a repeated value shows its message from now on. */
|
|
112
125
|
touch(key: string, name: string): void;
|
|
113
126
|
/**
|
|
@@ -175,7 +188,18 @@ export declare class RowsStore<TRow extends RowValue = RowValue> {
|
|
|
175
188
|
private askPickers;
|
|
176
189
|
/** Drops what the store knew about a removed row. */
|
|
177
190
|
private forget;
|
|
191
|
+
/**
|
|
192
|
+
* Writes one value. `input` was typed in its own cell; `code` comes from code (its cell mounts again with it); `person`
|
|
193
|
+
* is a person's change made through another cell (`setFromPerson`): written as code writes, followed as typing is.
|
|
194
|
+
*/
|
|
178
195
|
private write;
|
|
196
|
+
/**
|
|
197
|
+
* After a person changed a row's value: the row's inputs narrowed by it (`narrowedBy`, a line's Tags by its Item) lose
|
|
198
|
+
* what they picked among its old options, down the row's chain, and the row draws again so each is narrowed afresh;
|
|
199
|
+
* a row input that `dependsOn` it (a Tags box whose most picks are the row's quantity, through `cellParams`) draws
|
|
200
|
+
* again too, its rule asked again.
|
|
201
|
+
*/
|
|
202
|
+
private follow;
|
|
179
203
|
/** Turns a radio off in every row but this one. */
|
|
180
204
|
private radioOn;
|
|
181
205
|
/** Checks one value against its input's rules. */
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { EInputTypes } from "../Interfaces/FormInterfaces.js";
|
|
2
|
+
import { isComputedInput, withComputeSources } from "./InputConditions.js";
|
|
2
3
|
import { findRememberedOpening, forgetInputMemory, isEmptyInputValue, isPickerInput, lastAddedRowKey, readInputMemory, rememberDependencies, rememberedOpeningNow, writeInputMemory, } from "./InputMemory.js";
|
|
4
|
+
import { isEmptyNarrowed, narrowedFollowers } from "./InputNarrowing.js";
|
|
3
5
|
import { duplicateMessageFor, holdsValue, initialInputValue, inputValuesEqual, normalizeInputValue, rowsItemNames, rowsMessage, uniqueKeyOf, validateInput, } from "./InputParamsHelpers.js";
|
|
6
|
+
/** The options with each worked-out input following the inputs it is worked out from (`compute.from` in `dependsOn`). */
|
|
7
|
+
function withSources(options) {
|
|
8
|
+
const inputs = withComputeSources(options.inputs);
|
|
9
|
+
return inputs === options.inputs ? options : { ...options, inputs };
|
|
10
|
+
}
|
|
4
11
|
export class RowsStore {
|
|
5
12
|
options;
|
|
6
13
|
inputByName = new Map();
|
|
@@ -38,7 +45,7 @@ export class RowsStore {
|
|
|
38
45
|
/** The array last given to value listeners. A controlled parent that passes it back changes nothing. */
|
|
39
46
|
lastValue = null;
|
|
40
47
|
constructor(options, rows = []) {
|
|
41
|
-
this.options = options;
|
|
48
|
+
this.options = withSources(options);
|
|
42
49
|
this.indexInputs();
|
|
43
50
|
this.load(rows);
|
|
44
51
|
this.initial = this.getValue();
|
|
@@ -161,7 +168,7 @@ export class RowsStore {
|
|
|
161
168
|
* callbacks (`validate`, `onRowChange`, `newRow`) are swapped, which a parent's render does every time.
|
|
162
169
|
*/
|
|
163
170
|
configure(options, recheck = true) {
|
|
164
|
-
this.options = options;
|
|
171
|
+
this.options = withSources(options);
|
|
165
172
|
if (!recheck)
|
|
166
173
|
return;
|
|
167
174
|
this.indexInputs();
|
|
@@ -179,6 +186,30 @@ export class RowsStore {
|
|
|
179
186
|
return;
|
|
180
187
|
this.settle(changed);
|
|
181
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Values a person set in a row through another of its inputs - one worked out from them and typed in (`compute.solve`)
|
|
191
|
+
* - as one change. Their cells show them at once, as values from code do, and they are taken as the person's own:
|
|
192
|
+
* `onRowChange` runs for each, the row's inputs narrowed by them lose what they picked, and the rows' value listeners
|
|
193
|
+
* hear of it (a parent form's unsaved-changes state, its Save and its leave guard). A name worked out itself is not
|
|
194
|
+
* taken. Answers whether anything changed.
|
|
195
|
+
*/
|
|
196
|
+
setFromPerson(key, values) {
|
|
197
|
+
const before = this.getValue();
|
|
198
|
+
const changed = new Set();
|
|
199
|
+
for (const [name, value] of Object.entries(values))
|
|
200
|
+
this.write(key, name, value, "person", changed, 0);
|
|
201
|
+
if (this.getValue() === before)
|
|
202
|
+
return false;
|
|
203
|
+
this.settle(changed);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Draws every row again, values untouched: the form around the rows changed a value their cells follow
|
|
208
|
+
* (`narrowedBy` `from: "form"`), so each cell is narrowed afresh as it draws.
|
|
209
|
+
*/
|
|
210
|
+
redrawRows() {
|
|
211
|
+
this.touchAllRows();
|
|
212
|
+
}
|
|
182
213
|
/** Marks a value as left by the user: a repeated value shows its message from now on. */
|
|
183
214
|
touch(key, name) {
|
|
184
215
|
let names = this.touched.get(key);
|
|
@@ -200,7 +231,7 @@ export class RowsStore {
|
|
|
200
231
|
const wasEmpty = this.entries.length === 0;
|
|
201
232
|
const row = {};
|
|
202
233
|
for (const params of this.options.inputs)
|
|
203
|
-
if (holdsValue(params))
|
|
234
|
+
if (holdsValue(params) && !isComputedInput(params))
|
|
204
235
|
row[params.name] = initialInputValue(params);
|
|
205
236
|
const { newRow } = this.options;
|
|
206
237
|
const named = { ...(typeof newRow === "function" ? newRow(position) : newRow), ...values };
|
|
@@ -250,7 +281,7 @@ export class RowsStore {
|
|
|
250
281
|
return;
|
|
251
282
|
const read = (name) => row[name];
|
|
252
283
|
for (const params of this.options.inputs)
|
|
253
|
-
if (params.remember && !params.hidden)
|
|
284
|
+
if (params.remember && !params.hidden && !isComputedInput(params))
|
|
254
285
|
writeInputMemory(this.memory, params, row[params.name], read);
|
|
255
286
|
}
|
|
256
287
|
/** Adds a copy of a row after it, without its id and with its radios off. Returns the copy's key. */
|
|
@@ -387,6 +418,11 @@ export class RowsStore {
|
|
|
387
418
|
normalizeRow(row) {
|
|
388
419
|
const values = { ...row };
|
|
389
420
|
for (const params of this.options.inputs) {
|
|
421
|
+
// A worked-out input holds no value: a field the row came with under its name is not the row's to send.
|
|
422
|
+
if (isComputedInput(params)) {
|
|
423
|
+
delete values[params.name];
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
390
426
|
// A hidden input's value stays in the data as it was.
|
|
391
427
|
if (params.hidden)
|
|
392
428
|
continue;
|
|
@@ -437,7 +473,7 @@ export class RowsStore {
|
|
|
437
473
|
const read = (name) => row[name];
|
|
438
474
|
for (const params of this.options.inputs) {
|
|
439
475
|
const { name } = params;
|
|
440
|
-
if (!params.remember || params.hidden || !holdsValue(params))
|
|
476
|
+
if (!params.remember || params.hidden || !holdsValue(params) || isComputedInput(params))
|
|
441
477
|
continue;
|
|
442
478
|
if (name in named && !isEmptyInputValue(params, named[name]))
|
|
443
479
|
continue;
|
|
@@ -507,22 +543,31 @@ export class RowsStore {
|
|
|
507
543
|
this.revisions.delete(key);
|
|
508
544
|
this.asking.delete(key);
|
|
509
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Writes one value. `input` was typed in its own cell; `code` comes from code (its cell mounts again with it); `person`
|
|
548
|
+
* is a person's change made through another cell (`setFromPerson`): written as code writes, followed as typing is.
|
|
549
|
+
*/
|
|
510
550
|
write(key, name, value, source, changed, depth) {
|
|
511
551
|
const index = this.positions.get(key);
|
|
512
552
|
const entry = index === undefined ? undefined : this.entries[index];
|
|
513
553
|
if (!entry || index === undefined)
|
|
514
554
|
return;
|
|
515
555
|
const params = this.inputByName.get(name);
|
|
516
|
-
|
|
556
|
+
// A worked-out input holds no value: one set from code (an `onRowChange` or `deriveRows` patch) is not taken.
|
|
557
|
+
if (isComputedInput(params))
|
|
558
|
+
return;
|
|
559
|
+
const asCode = source !== "input";
|
|
560
|
+
const byPerson = source !== "code";
|
|
561
|
+
const next = params && asCode ? normalizeInputValue(params, value) : value;
|
|
517
562
|
// The person's own pick: a picker's answer about a remembered value no longer replaces it.
|
|
518
|
-
if (
|
|
563
|
+
if (byPerson)
|
|
519
564
|
this.asking.get(key)?.delete(name);
|
|
520
565
|
if (inputValuesEqual(entry.values[name], next))
|
|
521
566
|
return;
|
|
522
567
|
const shownBefore = this.shownError(key, name);
|
|
523
568
|
entry.values = { ...entry.values, [name]: next };
|
|
524
569
|
this.valueCache = null;
|
|
525
|
-
if (
|
|
570
|
+
if (asCode) {
|
|
526
571
|
// The input shows its value from when it mounted; a new revision mounts it again with this one.
|
|
527
572
|
let names = this.revisions.get(key);
|
|
528
573
|
if (!names)
|
|
@@ -544,7 +589,7 @@ export class RowsStore {
|
|
|
544
589
|
if (this.shownError(key, name) !== shownBefore)
|
|
545
590
|
changed.add(key);
|
|
546
591
|
// Values the row derives from the one the user changed, such as an amount from a quantity and a rate.
|
|
547
|
-
if (
|
|
592
|
+
if (byPerson && depth === 0 && this.options.onRowChange) {
|
|
548
593
|
const patch = this.options.onRowChange(entry.values, name, index);
|
|
549
594
|
if (patch) {
|
|
550
595
|
for (const [field, fieldValue] of Object.entries(patch)) {
|
|
@@ -553,6 +598,30 @@ export class RowsStore {
|
|
|
553
598
|
}
|
|
554
599
|
}
|
|
555
600
|
}
|
|
601
|
+
if (byPerson && depth === 0)
|
|
602
|
+
this.follow(key, name, changed);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* After a person changed a row's value: the row's inputs narrowed by it (`narrowedBy`, a line's Tags by its Item) lose
|
|
606
|
+
* what they picked among its old options, down the row's chain, and the row draws again so each is narrowed afresh;
|
|
607
|
+
* a row input that `dependsOn` it (a Tags box whose most picks are the row's quantity, through `cellParams`) draws
|
|
608
|
+
* again too, its rule asked again.
|
|
609
|
+
*/
|
|
610
|
+
follow(key, name, changed) {
|
|
611
|
+
const index = this.positions.get(key);
|
|
612
|
+
const entry = index === undefined ? undefined : this.entries[index];
|
|
613
|
+
if (!entry)
|
|
614
|
+
return;
|
|
615
|
+
const followers = narrowedFollowers(this.options.inputs, [name], "row");
|
|
616
|
+
for (const follower of followers) {
|
|
617
|
+
if (!isEmptyNarrowed(entry.values[follower]))
|
|
618
|
+
this.write(key, follower, null, "code", changed, 1);
|
|
619
|
+
}
|
|
620
|
+
const dependents = this.options.inputs.filter(params => params.dependsOn?.includes(name));
|
|
621
|
+
for (const params of dependents)
|
|
622
|
+
this.checkCell(entry, params);
|
|
623
|
+
if (followers.length || dependents.length)
|
|
624
|
+
changed.add(key);
|
|
556
625
|
}
|
|
557
626
|
/** Turns a radio off in every row but this one. */
|
|
558
627
|
radioOn(entry, name, changed) {
|
|
@@ -563,7 +632,7 @@ export class RowsStore {
|
|
|
563
632
|
}
|
|
564
633
|
/** Checks one value against its input's rules. */
|
|
565
634
|
checkCell(entry, params) {
|
|
566
|
-
const message = params.hidden ? null : validateInput(params, entry.values[params.name], this.options.context);
|
|
635
|
+
const message = params.hidden || isComputedInput(params) ? null : validateInput(params, entry.values[params.name], this.options.context);
|
|
567
636
|
this.setError(this.ruleErrors, entry.key, params.name, message);
|
|
568
637
|
}
|
|
569
638
|
setError(map, key, name, message) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatePreferences } from "../Interfaces/DateInterfaces.ts";
|
|
2
2
|
import type { InputParams } from "../Interfaces/FormInterfaces.ts";
|
|
3
|
-
import type { TableColumn, TableColumnOption, TableHeadBand, TableKey, TablePage, TablePinSide, TableQuery, TableRow, TableSort, TableViewColumn } from "../Interfaces/TableInterfaces.ts";
|
|
3
|
+
import type { TableColumn, TableColumnOption, TableHeadBand, TableKey, TablePage, TablePinSide, TablePins, TableQuery, TableRow, TableSort, TableViewColumn } from "../Interfaces/TableInterfaces.ts";
|
|
4
4
|
/** Rows per page when nothing says otherwise. */
|
|
5
5
|
export declare const DEFAULT_PAGE_SIZE = 10;
|
|
6
6
|
/** Choices of rows per page. */
|
|
@@ -81,6 +81,19 @@ export declare const TABLE_ACTIONS_COLUMN = "#actions";
|
|
|
81
81
|
export declare const MIN_COLUMN_WIDTH = 56;
|
|
82
82
|
/** Whether a key names one of the table's own columns, not a column of the page's. */
|
|
83
83
|
export declare const isOwnColumn: (key: string) => boolean;
|
|
84
|
+
/**
|
|
85
|
+
* The columns as the table arranges them before the user does: every `lead` column (a picture, a logo) first, the
|
|
86
|
+
* others in the order given. When the page pins other columns on the left, a lead column without a side of its own is
|
|
87
|
+
* pinned there too, so it still comes first. The same array when there is nothing to move.
|
|
88
|
+
*/
|
|
89
|
+
export declare function leadColumns<TRow extends TableRow>(columns: readonly TableColumn<TRow>[]): readonly TableColumn<TRow>[];
|
|
90
|
+
/**
|
|
91
|
+
* The column whose text names a row when no `rowLabel` is given ("Delete Kathmandu?"): the first that is not a `lead`
|
|
92
|
+
* column, whose picture has no words.
|
|
93
|
+
*/
|
|
94
|
+
export declare function namingColumn<TRow extends TableRow>(columns: readonly TableColumn<TRow>[]): TableColumn<TRow> | undefined;
|
|
95
|
+
/** The side a column is pinned to: the user's choice (`null` when they unpinned it), else its own `pinned`. */
|
|
96
|
+
export declare function columnPin(column: Pick<TableColumn, "key" | "pinned">, pinned: TablePins): TablePinSide | null;
|
|
84
97
|
/**
|
|
85
98
|
* The columns in the order the user put them: the keys of `order` first, in that order, then any column it does not
|
|
86
99
|
* name, where the columns were given. A key of a column that is gone is left out.
|
|
@@ -96,7 +109,7 @@ export interface ColumnRights {
|
|
|
96
109
|
}
|
|
97
110
|
export interface ViewColumnsOptions extends ColumnRights {
|
|
98
111
|
order?: readonly string[];
|
|
99
|
-
pinned?:
|
|
112
|
+
pinned?: TablePins;
|
|
100
113
|
widths?: Readonly<Record<string, number>>;
|
|
101
114
|
/** Pixels from the pinned side, measured from the columns before it. */
|
|
102
115
|
offsets?: Readonly<Record<string, number>>;
|
|
@@ -81,6 +81,9 @@ export function formatCellText(column, value, options = {}, row) {
|
|
|
81
81
|
return option.label;
|
|
82
82
|
if (isEmpty(value))
|
|
83
83
|
return "";
|
|
84
|
+
// A list of rows a record's details draw as a table (`column.table`): elsewhere, how many it holds.
|
|
85
|
+
if (column.table && Array.isArray(value))
|
|
86
|
+
return String(value.length);
|
|
84
87
|
const { prefix = "", suffix = "" } = column;
|
|
85
88
|
switch (column.format) {
|
|
86
89
|
case "number":
|
|
@@ -232,6 +235,30 @@ export const TABLE_ACTIONS_COLUMN = "#actions";
|
|
|
232
235
|
export const MIN_COLUMN_WIDTH = 56;
|
|
233
236
|
/** Whether a key names one of the table's own columns, not a column of the page's. */
|
|
234
237
|
export const isOwnColumn = (key) => key.startsWith("#");
|
|
238
|
+
/**
|
|
239
|
+
* The columns as the table arranges them before the user does: every `lead` column (a picture, a logo) first, the
|
|
240
|
+
* others in the order given. When the page pins other columns on the left, a lead column without a side of its own is
|
|
241
|
+
* pinned there too, so it still comes first. The same array when there is nothing to move.
|
|
242
|
+
*/
|
|
243
|
+
export function leadColumns(columns) {
|
|
244
|
+
if (!columns.some(column => column.lead))
|
|
245
|
+
return columns;
|
|
246
|
+
const pinsLeft = columns.some(column => !column.lead && column.pinned === "left");
|
|
247
|
+
const leads = columns.filter(column => column.lead).map(column => (pinsLeft && !column.pinned ? { ...column, pinned: "left" } : column));
|
|
248
|
+
const arranged = [...leads, ...columns.filter(column => !column.lead)];
|
|
249
|
+
return arranged.every((column, index) => column === columns[index]) ? columns : arranged;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The column whose text names a row when no `rowLabel` is given ("Delete Kathmandu?"): the first that is not a `lead`
|
|
253
|
+
* column, whose picture has no words.
|
|
254
|
+
*/
|
|
255
|
+
export function namingColumn(columns) {
|
|
256
|
+
return columns.find(column => !column.lead) ?? columns[0];
|
|
257
|
+
}
|
|
258
|
+
/** The side a column is pinned to: the user's choice (`null` when they unpinned it), else its own `pinned`. */
|
|
259
|
+
export function columnPin(column, pinned) {
|
|
260
|
+
return Object.hasOwn(pinned, column.key) ? (pinned[column.key] ?? null) : (column.pinned ?? null);
|
|
261
|
+
}
|
|
235
262
|
/**
|
|
236
263
|
* The columns in the order the user put them: the keys of `order` first, in that order, then any column it does not
|
|
237
264
|
* name, where the columns were given. A key of a column that is gone is left out.
|
|
@@ -303,7 +330,7 @@ export function viewColumns(columns, options = {}) {
|
|
|
303
330
|
const { order, pinned = {}, widths = {}, offsets = {}, released, reorderable = true, resizable = true } = options;
|
|
304
331
|
const groups = { left: [], middle: [], right: [] };
|
|
305
332
|
for (const column of orderColumns(columns, order)) {
|
|
306
|
-
const side =
|
|
333
|
+
const side = columnPin(column, pinned);
|
|
307
334
|
groups[side === "left" ? "left" : side === "right" ? "right" : "middle"].push({
|
|
308
335
|
column,
|
|
309
336
|
// A pinned column the table is too narrow for keeps its side and scrolls with the rest.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputParams } from "../Interfaces/FormInterfaces.ts";
|
|
2
|
-
import type { TableColumn, TableKey, TableLoader, TablePaging, TablePinSide, TableRow, TableSort, TableState } from "../Interfaces/TableInterfaces.ts";
|
|
2
|
+
import type { TableColumn, TableKey, TableLoader, TablePaging, TablePinSide, TablePins, TableRow, TableSort, TableState } from "../Interfaces/TableInterfaces.ts";
|
|
3
3
|
/** What the table component tells its store on every render. */
|
|
4
4
|
export interface TableStoreOptions<TRow extends TableRow = TableRow> {
|
|
5
5
|
columns: readonly TableColumn<TRow>[];
|
|
@@ -75,7 +75,7 @@ export declare class TableStore<TRow extends TableRow = TableRow> {
|
|
|
75
75
|
hidden?: readonly string[];
|
|
76
76
|
order?: readonly string[];
|
|
77
77
|
widths?: Readonly<Record<string, number>>;
|
|
78
|
-
pinned?:
|
|
78
|
+
pinned?: TablePins;
|
|
79
79
|
}, options?: {
|
|
80
80
|
notify?: boolean;
|
|
81
81
|
}): void;
|
|
@@ -159,7 +159,11 @@ export declare class TableStore<TRow extends TableRow = TableRow> {
|
|
|
159
159
|
setWidths(widths: Readonly<Record<string, number>>): void;
|
|
160
160
|
/** Back to widths that follow the content. */
|
|
161
161
|
clearWidths(): void;
|
|
162
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* Keeps a column on the left or the right while the others scroll, or lets it scroll again (`null`). A choice equal
|
|
164
|
+
* to the column's own `pinned` is no change to remember; unpinning a column its params pin is remembered as `null`,
|
|
165
|
+
* which wins over them.
|
|
166
|
+
*/
|
|
163
167
|
setPinned(key: string, side: TablePinSide | null): void;
|
|
164
168
|
/** Every column back as its params say: the given order, widths from the content, no pins, and the hidden ones. */
|
|
165
169
|
resetColumns(): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { errorMessage, isAbortError } from "./ApiResponses.js";
|
|
2
2
|
import { activeFilters } from "./TableFilters.js";
|
|
3
|
-
import { DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, MIN_COLUMN_WIDTH, clampPage, moveColumnKey, nextSort, queryRows, readTablePage, rowKeyText } from "./TableHelpers.js";
|
|
3
|
+
import { DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, MIN_COLUMN_WIDTH, clampPage, columnPin, moveColumnKey, nextSort, queryRows, readTablePage, rowKeyText } from "./TableHelpers.js";
|
|
4
4
|
/** How long a changed or added row stays highlighted. */
|
|
5
5
|
export const TABLE_FLASH_MS = 1600;
|
|
6
6
|
const EMPTY_MAP = new Map();
|
|
@@ -562,12 +562,17 @@ export class TableStore {
|
|
|
562
562
|
return;
|
|
563
563
|
this.set({ widths: NO_WIDTHS });
|
|
564
564
|
}
|
|
565
|
-
/**
|
|
565
|
+
/**
|
|
566
|
+
* Keeps a column on the left or the right while the others scroll, or lets it scroll again (`null`). A choice equal
|
|
567
|
+
* to the column's own `pinned` is no change to remember; unpinning a column its params pin is remembered as `null`,
|
|
568
|
+
* which wins over them.
|
|
569
|
+
*/
|
|
566
570
|
setPinned(key, side) {
|
|
567
|
-
|
|
571
|
+
const own = this.options.columns.find(column => column.key === key)?.pinned ?? null;
|
|
572
|
+
if (columnPin({ key, pinned: own ?? undefined }, this.state.pinned) === side)
|
|
568
573
|
return;
|
|
569
574
|
const pinned = { ...this.state.pinned };
|
|
570
|
-
if (side ===
|
|
575
|
+
if (side === own)
|
|
571
576
|
delete pinned[key];
|
|
572
577
|
else
|
|
573
578
|
pinned[key] = side;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The sizes a toolbar is laid out with, in pixels. */
|
|
2
|
+
export interface ToolbarSizes {
|
|
3
|
+
/** A button's width. */
|
|
4
|
+
tool: number;
|
|
5
|
+
/** A divider's width with its margins. */
|
|
6
|
+
divider: number;
|
|
7
|
+
/** The gap between two items. */
|
|
8
|
+
gap: number;
|
|
9
|
+
/** The "More" button's width. Default `tool`. */
|
|
10
|
+
more?: number;
|
|
11
|
+
}
|
|
12
|
+
/** What a toolbar of a width shows: its items in order, dividers among them, and the tools left for the menu. */
|
|
13
|
+
export interface ToolbarFit<T extends string> {
|
|
14
|
+
shown: (T | "|")[];
|
|
15
|
+
hidden: T[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The items of a toolbar that fit `width`, in order, and the rest for its "More" menu. All of them when they fit; else
|
|
19
|
+
* as many as fit beside the "More" button, with no divider left at the end of the row and none in the menu.
|
|
20
|
+
*
|
|
21
|
+
* @example fitToolbar(["bold", "italic", "|", "link"], 70, { tool: 28, divider: 11, gap: 2 }) // { shown: ["bold"], hidden: ["italic", "link"] }
|
|
22
|
+
*/
|
|
23
|
+
export declare function fitToolbar<T extends string>(items: readonly (T | "|")[], width: number, sizes: ToolbarSizes): ToolbarFit<T>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A toolbar that keeps to one row: which of its buttons fit the room it has, and which go into its "More" menu. Pure,
|
|
3
|
+
* so it is checked without a browser; an editor measures its row and its sizes and draws what this answers.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The items of a toolbar that fit `width`, in order, and the rest for its "More" menu. All of them when they fit; else
|
|
7
|
+
* as many as fit beside the "More" button, with no divider left at the end of the row and none in the menu.
|
|
8
|
+
*
|
|
9
|
+
* @example fitToolbar(["bold", "italic", "|", "link"], 70, { tool: 28, divider: 11, gap: 2 }) // { shown: ["bold"], hidden: ["italic", "link"] }
|
|
10
|
+
*/
|
|
11
|
+
export function fitToolbar(items, width, sizes) {
|
|
12
|
+
const size = (item) => (item === "|" ? sizes.divider : sizes.tool);
|
|
13
|
+
const all = items.reduce((total, item, index) => total + size(item) + (index > 0 ? sizes.gap : 0), 0);
|
|
14
|
+
if (all <= width)
|
|
15
|
+
return { shown: [...items], hidden: [] };
|
|
16
|
+
const room = width - (sizes.more ?? sizes.tool) - sizes.gap;
|
|
17
|
+
let used = 0;
|
|
18
|
+
let count = 0;
|
|
19
|
+
for (const item of items) {
|
|
20
|
+
const next = size(item) + (count > 0 ? sizes.gap : 0);
|
|
21
|
+
if (used + next > room)
|
|
22
|
+
break;
|
|
23
|
+
used += next;
|
|
24
|
+
count++;
|
|
25
|
+
}
|
|
26
|
+
const shown = items.slice(0, count);
|
|
27
|
+
while (shown.length > 0 && shown[shown.length - 1] === "|")
|
|
28
|
+
shown.pop();
|
|
29
|
+
return { shown, hidden: items.slice(count).filter((item) => item !== "|") };
|
|
30
|
+
}
|
|
@@ -38,7 +38,34 @@ export interface TreeStoreOptions<TItem extends TreeItem = TreeItem> {
|
|
|
38
38
|
* than 0). Without it, or when the field is empty, a node counts as having children until its first page answers.
|
|
39
39
|
*/
|
|
40
40
|
hasChildrenKey?: string;
|
|
41
|
+
/** The leaves shown at the start: see `TreeStore.setFilter`. Default: all. */
|
|
42
|
+
filter?: TreeFilterMatch<TItem> | null;
|
|
41
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Which leaves a filtered tree shows (`TreeStore.setFilter`): asked of every item without children. A parent shows when it
|
|
46
|
+
* holds a leaf the filter shows; one whose children are still to load (a lazy tree) shows too, since they may.
|
|
47
|
+
*/
|
|
48
|
+
export type TreeFilterMatch<TItem extends TreeItem = TreeItem> = (item: TItem) => boolean;
|
|
49
|
+
/** One tab of a tree's filters (TreeView `filters`): what it is called, and the leaves it shows. */
|
|
50
|
+
export interface TreeFilter<TItem extends TreeItem = TreeItem> {
|
|
51
|
+
/** Unique among the filters: what `defaultFilter`, the kept choice and `onFilterChange` name. */
|
|
52
|
+
id: string;
|
|
53
|
+
/** The tab's text, e.g. "View only". */
|
|
54
|
+
label: string;
|
|
55
|
+
/** The tab's tooltip. */
|
|
56
|
+
title?: string;
|
|
57
|
+
/** The leaves it shows, asked of every item without children. Left out: all of them (an "All" tab). */
|
|
58
|
+
match?: TreeFilterMatch<TItem>;
|
|
59
|
+
}
|
|
60
|
+
/** Where a tree keeps the filter chosen in this browser: per `key` and `viewer`, nothing without a viewer. */
|
|
61
|
+
export declare function treeFilterStorageKey(remember: {
|
|
62
|
+
key: string;
|
|
63
|
+
viewer?: string | null;
|
|
64
|
+
} | undefined): string | null;
|
|
65
|
+
/** The filter on: the one chosen, else the default, else the first; `undefined` without filters. An id no filter has is passed over. */
|
|
66
|
+
export declare function shownTreeFilter<TFilter extends {
|
|
67
|
+
id: string;
|
|
68
|
+
}>(filters: readonly TFilter[] | undefined, chosen: string | null | undefined, defaultId?: string): TFilter | undefined;
|
|
42
69
|
/** A row that is not an item, under a parent of a lazy tree: its page is on its way, failed (Retry), or more can be loaded. */
|
|
43
70
|
export type TreeStatusKind = "loading" | "error" | "more";
|
|
44
71
|
/** Where a lazy parent's children stand: what was asked of the loader, and what it said. */
|
|
@@ -189,6 +216,21 @@ export interface TreeNode<TItem extends TreeItem = TreeItem> {
|
|
|
189
216
|
* cannot be checked or unchecked as a whole (see `TreeStore.toggle`), and never shows checked unless the value names it.
|
|
190
217
|
*/
|
|
191
218
|
partial: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* A row of the filtered tree (`TreeStore.setFilter`): a leaf the filter shows, a parent holding one, a draft, or a lazy
|
|
221
|
+
* item whose children are still to load. Always true without a filter.
|
|
222
|
+
*/
|
|
223
|
+
shown: boolean;
|
|
224
|
+
/** Children that are `shown`: all of them without a filter. */
|
|
225
|
+
shownChildren: number;
|
|
226
|
+
/**
|
|
227
|
+
* `leaves`, `checkedLeaves`, `openLeaves` and `openChecked` over the leaves the filter shows - what a box, a count and a
|
|
228
|
+
* check of a parent work on while a filter is on. The same numbers without a filter. The value never reads them.
|
|
229
|
+
*/
|
|
230
|
+
shownLeaves: number;
|
|
231
|
+
shownChecked: number;
|
|
232
|
+
shownOpenLeaves: number;
|
|
233
|
+
shownOpenChecked: number;
|
|
192
234
|
/**
|
|
193
235
|
* Set only on a status row, a row that is not an item: under its `parent` (`null`: the top level) a page is on its
|
|
194
236
|
* way, failed, or more can be loaded. A status row is in `rows` only - never in `children`, the value, or the items -
|
|
@@ -252,7 +294,23 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
|
|
|
252
294
|
private openNodes;
|
|
253
295
|
private openCheckedNodes;
|
|
254
296
|
private indexCache;
|
|
297
|
+
/** The leaves shown (`setFilter`); `null`: all. */
|
|
298
|
+
private match;
|
|
299
|
+
/** Goes up whenever a node's counts are worked out again, so what is kept from them knows it is old. */
|
|
300
|
+
private counted;
|
|
301
|
+
private shownRoots;
|
|
255
302
|
constructor(items?: readonly TItem[], options?: TreeStoreOptions<TItem>, value?: readonly TreeValue[]);
|
|
303
|
+
/** Whether a filter is on (`setFilter`). */
|
|
304
|
+
get filtered(): boolean;
|
|
305
|
+
/**
|
|
306
|
+
* Shows only the leaves `match` takes, and the parents that hold one (`null`: every item). Nothing is checked or
|
|
307
|
+
* unchecked by it, and the value is the same whatever filter is on. While one is on, a parent's box, its count, and a
|
|
308
|
+
* check of it - or of "select all" - work on the leaves shown only: checking a parent checks its shown leaves, and a
|
|
309
|
+
* leaf the filter hides keeps its check exactly as it was. Expanded items stay expanded.
|
|
310
|
+
*/
|
|
311
|
+
setFilter(match: TreeFilterMatch<TItem> | null): void;
|
|
312
|
+
/** Children of a parent (`null`: the top level) that are rows of the filtered tree: all of them without a filter. */
|
|
313
|
+
shownChildCount(parent: TreeNode<TItem> | null): number;
|
|
256
314
|
/** Items in the tree, drafts included. */
|
|
257
315
|
get size(): number;
|
|
258
316
|
/** The node of a value. */
|
|
@@ -280,11 +338,19 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
|
|
|
280
338
|
* unloaded items are not in the value - unless the value names it (`valueMode: "checked"`, where it stands for all).
|
|
281
339
|
*/
|
|
282
340
|
state(node: TreeNode<TItem>): TreeCheckState;
|
|
283
|
-
/**
|
|
341
|
+
/** The state of a parent over all of its leaves, whatever the filter shows. */
|
|
342
|
+
private wholeState;
|
|
343
|
+
private stateOver;
|
|
344
|
+
/** Checked boxes and all boxes: leaves with `cascade`, every item without it. Drafts count. The filter does not narrow it. */
|
|
284
345
|
counts(): {
|
|
285
346
|
checked: number;
|
|
286
347
|
total: number;
|
|
287
348
|
};
|
|
349
|
+
/** `counts` over what the filter shows: the leaves shown with `cascade`, the rows of items without it. */
|
|
350
|
+
shownCounts(): {
|
|
351
|
+
checked: number;
|
|
352
|
+
total: number;
|
|
353
|
+
};
|
|
288
354
|
/**
|
|
289
355
|
* The checked values, in tree order, following `valueMode`. In a lazy tree, values whose items are not loaded yet
|
|
290
356
|
* (or not settled) follow the others, so a saved value survives until its items come.
|
|
@@ -306,16 +372,19 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
|
|
|
306
372
|
* silently leave the rest out. The items under it whose own items are all loaded can still be checked one by one.
|
|
307
373
|
*/
|
|
308
374
|
toggle(node: TreeNode<TItem>): boolean;
|
|
309
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* Checks or unchecks a node; with `cascade`, every item under it that is not locked - and, while a filter is on, that
|
|
377
|
+
* the filter shows: the hidden ones keep their checks. Returns whether anything changed.
|
|
378
|
+
*/
|
|
310
379
|
setChecked(node: TreeNode<TItem>, checked: boolean): boolean;
|
|
311
|
-
/** The state of a "select all" box, over the items that are not locked; `null` when every
|
|
380
|
+
/** The state of a "select all" box, over the items that are not locked (and shown); `null` when every one is locked. */
|
|
312
381
|
allState(): TreeCheckState | null;
|
|
313
382
|
/** What a click on a "select all" box does: checks every item that is not locked, or unchecks them all when all are checked. */
|
|
314
383
|
toggleAll(): boolean;
|
|
315
384
|
/**
|
|
316
|
-
* Checks or unchecks every item that is not locked. Returns whether anything changed. In a
|
|
317
|
-
* a node whose items are not all loaded is passed over and the loaded parts under it are
|
|
318
|
-
* items are not loaded stay as they are.
|
|
385
|
+
* Checks or unchecks every item that is not locked, and that the filter shows. Returns whether anything changed. In a
|
|
386
|
+
* lazy tree with `cascade`, a node whose items are not all loaded is passed over and the loaded parts under it are
|
|
387
|
+
* changed; values whose items are not loaded stay as they are.
|
|
319
388
|
*/
|
|
320
389
|
checkAll(checked: boolean): boolean;
|
|
321
390
|
/** Whether every parent of the node is expanded, so the node is a row. */
|