nexus-shared 1.1.20 → 2.0.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/LICENSE +21 -0
- package/README.md +903 -0
- package/dist/Client.Index.d.ts +44 -0
- package/dist/Client.Index.js +49 -0
- package/dist/Components/Documents/Button.d.ts +29 -0
- package/dist/Components/Documents/Button.js +11 -0
- package/dist/Components/Documents/Menu.d.ts +102 -0
- package/dist/Components/Documents/Menu.js +238 -0
- package/dist/Components/Documents/SplitButton.d.ts +64 -0
- package/dist/Components/Documents/SplitButton.js +127 -0
- package/dist/Components/Documents/TabButtons.d.ts +62 -0
- package/dist/Components/Documents/TabButtons.js +19 -0
- package/dist/Components/Forms/ApiForm.d.ts +21 -0
- package/dist/Components/Forms/ApiForm.js +99 -0
- package/dist/Components/Forms/Crud.d.ts +17 -0
- package/dist/Components/Forms/Crud.js +556 -0
- package/dist/Components/Forms/CrudParts.d.ts +15 -0
- package/dist/Components/Forms/CrudParts.js +36 -0
- package/dist/Components/Forms/Form.d.ts +52 -0
- package/dist/Components/Forms/Form.js +319 -0
- package/dist/Components/Forms/SubmitForm.d.ts +65 -0
- package/dist/Components/Forms/SubmitForm.js +198 -0
- package/dist/Components/Inputs/Calendar.d.ts +69 -0
- package/dist/Components/Inputs/Calendar.js +258 -0
- package/dist/Components/Inputs/CheckBox.d.ts +53 -0
- package/dist/Components/Inputs/CheckBox.js +87 -0
- package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
- package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
- package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
- package/dist/Components/Inputs/ChoiceField.js +169 -0
- package/dist/Components/Inputs/DatePicker.d.ts +54 -0
- package/dist/Components/Inputs/DatePicker.js +246 -0
- package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
- package/dist/Components/Inputs/DateRangePicker.js +283 -0
- package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
- package/dist/Components/Inputs/DateTimePicker.js +399 -0
- package/dist/Components/Inputs/Dropdown.d.ts +117 -0
- package/dist/Components/Inputs/Dropdown.js +839 -0
- package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
- package/dist/Components/Inputs/DropdownParts.js +102 -0
- package/dist/Components/Inputs/EditorShell.d.ts +45 -0
- package/dist/Components/Inputs/EditorShell.js +214 -0
- package/dist/Components/Inputs/Field.d.ts +53 -0
- package/dist/Components/Inputs/Field.js +16 -0
- package/dist/Components/Inputs/GroupForm.d.ts +20 -0
- package/dist/Components/Inputs/GroupForm.js +170 -0
- package/dist/Components/Inputs/InputField.d.ts +59 -0
- package/dist/Components/Inputs/InputField.js +169 -0
- package/dist/Components/Inputs/InputRenderer.d.ts +35 -0
- package/dist/Components/Inputs/InputRenderer.js +99 -0
- package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
- package/dist/Components/Inputs/MarkdownEditor.js +289 -0
- package/dist/Components/Inputs/NumberBox.d.ts +53 -0
- package/dist/Components/Inputs/NumberBox.js +228 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
- package/dist/Components/Inputs/RadioGroup.js +43 -0
- package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
- package/dist/Components/Inputs/ReadOnlyNotice.js +116 -0
- package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
- package/dist/Components/Inputs/RichTextEditor.js +1170 -0
- package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
- package/dist/Components/Inputs/RichTextParts.js +157 -0
- package/dist/Components/Inputs/RowsInput.d.ts +104 -0
- package/dist/Components/Inputs/RowsInput.js +259 -0
- package/dist/Components/Inputs/Slider.d.ts +87 -0
- package/dist/Components/Inputs/Slider.js +216 -0
- package/dist/Components/Inputs/TabularForm.d.ts +23 -0
- package/dist/Components/Inputs/TabularForm.js +283 -0
- package/dist/Components/Inputs/TextArea.d.ts +43 -0
- package/dist/Components/Inputs/TextArea.js +31 -0
- package/dist/Components/Inputs/TextBox.d.ts +52 -0
- package/dist/Components/Inputs/TextBox.js +58 -0
- package/dist/Components/Inputs/TextField.d.ts +31 -0
- package/dist/Components/Inputs/TextField.js +35 -0
- package/dist/Components/Inputs/TimePanel.d.ts +34 -0
- package/dist/Components/Inputs/TimePanel.js +288 -0
- package/dist/Components/Inputs/TimePicker.d.ts +54 -0
- package/dist/Components/Inputs/TimePicker.js +239 -0
- package/dist/Components/Inputs/TreeView.d.ts +160 -0
- package/dist/Components/Inputs/TreeView.js +947 -0
- package/dist/Components/Layouts/MessageBox.d.ts +71 -0
- package/dist/Components/Layouts/MessageBox.js +104 -0
- package/dist/Components/Layouts/Popup.d.ts +92 -0
- package/dist/Components/Layouts/Popup.js +376 -0
- package/dist/Components/Layouts/PopupContext.d.ts +2 -0
- package/dist/Components/Layouts/PopupContext.js +4 -0
- package/dist/Components/Layouts/PopupDock.d.ts +22 -0
- package/dist/Components/Layouts/PopupDock.js +121 -0
- package/dist/Components/Layouts/PopupHost.d.ts +56 -0
- package/dist/Components/Layouts/PopupHost.js +163 -0
- package/dist/Components/Layouts/PopupRules.d.ts +30 -0
- package/dist/Components/Layouts/PopupRules.js +63 -0
- package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
- package/dist/Components/Layouts/ThemePicker.js +22 -0
- package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
- package/dist/Components/Layouts/ThemeScript.js +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.js +29 -0
- package/dist/Components/Layouts/Toaster.d.ts +76 -0
- package/dist/Components/Layouts/Toaster.js +172 -0
- package/dist/Components/Viewers/DataTable.d.ts +25 -0
- package/dist/Components/Viewers/DataTable.js +542 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +134 -0
- package/dist/Components/Viewers/DataTableParts.js +145 -0
- package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
- package/dist/Components/Viewers/MarkdownView.js +79 -0
- package/dist/Components/Viewers/RichTextView.d.ts +18 -0
- package/dist/Components/Viewers/RichTextView.js +17 -0
- package/dist/Helpers/AnimationHelpers.d.ts +6 -0
- package/dist/Helpers/AnimationHelpers.js +35 -0
- package/dist/Helpers/ApiClient.d.ts +74 -0
- package/dist/Helpers/ApiClient.js +197 -0
- package/dist/Helpers/ApiFormHelpers.d.ts +26 -0
- package/dist/Helpers/ApiFormHelpers.js +56 -0
- package/dist/Helpers/ApiModules.d.ts +135 -0
- package/dist/Helpers/ApiModules.js +194 -0
- package/dist/Helpers/ApiResponses.d.ts +41 -0
- package/dist/Helpers/ApiResponses.js +159 -0
- package/dist/Helpers/ApiRoutes.d.ts +150 -0
- package/dist/Helpers/ApiRoutes.js +191 -0
- package/dist/Helpers/ApiToasts.d.ts +21 -0
- package/dist/Helpers/ApiToasts.js +22 -0
- package/dist/Helpers/ClassHelpers.d.ts +3 -0
- package/dist/Helpers/ClassHelpers.js +4 -0
- package/dist/Helpers/CrudBackend.d.ts +56 -0
- package/dist/Helpers/CrudBackend.js +119 -0
- package/dist/Helpers/CrudHelpers.d.ts +76 -0
- package/dist/Helpers/CrudHelpers.js +202 -0
- package/dist/Helpers/DateHelpers.d.ts +88 -0
- package/dist/Helpers/DateHelpers.js +363 -0
- package/dist/Helpers/DatePreferences.d.ts +14 -0
- package/dist/Helpers/DatePreferences.js +94 -0
- package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
- package/dist/Helpers/DateTimeHelpers.js +286 -0
- package/dist/Helpers/DragHelpers.d.ts +129 -0
- package/dist/Helpers/DragHelpers.js +281 -0
- package/dist/Helpers/DropdownHelpers.d.ts +56 -0
- package/dist/Helpers/DropdownHelpers.js +180 -0
- package/dist/Helpers/FormDrafts.d.ts +18 -0
- package/dist/Helpers/FormDrafts.js +76 -0
- package/dist/Helpers/FormStore.d.ts +231 -0
- package/dist/Helpers/FormStore.js +734 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
- package/dist/Helpers/InputParamsHelpers.js +388 -0
- package/dist/Helpers/MarkdownEditing.d.ts +27 -0
- package/dist/Helpers/MarkdownEditing.js +250 -0
- package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
- package/dist/Helpers/MarkdownHelpers.js +775 -0
- package/dist/Helpers/MessageBuilder.d.ts +74 -0
- package/dist/Helpers/MessageBuilder.js +254 -0
- package/dist/Helpers/NepaliCalendar.d.ts +25 -0
- package/dist/Helpers/NepaliCalendar.js +107 -0
- package/dist/Helpers/NumberHelpers.d.ts +71 -0
- package/dist/Helpers/NumberHelpers.js +202 -0
- package/dist/Helpers/Permissions.d.ts +62 -0
- package/dist/Helpers/Permissions.js +133 -0
- package/dist/Helpers/PopoverHelpers.d.ts +43 -0
- package/dist/Helpers/PopoverHelpers.js +91 -0
- package/dist/Helpers/RichTextHelpers.d.ts +77 -0
- package/dist/Helpers/RichTextHelpers.js +1072 -0
- package/dist/Helpers/RowsStore.d.ts +160 -0
- package/dist/Helpers/RowsStore.js +584 -0
- package/dist/Helpers/TableColumns.d.ts +40 -0
- package/dist/Helpers/TableColumns.js +254 -0
- package/dist/Helpers/TableExport.d.ts +30 -0
- package/dist/Helpers/TableExport.js +97 -0
- package/dist/Helpers/TableHelpers.d.ts +102 -0
- package/dist/Helpers/TableHelpers.js +281 -0
- package/dist/Helpers/TableStore.d.ts +142 -0
- package/dist/Helpers/TableStore.js +530 -0
- package/dist/Helpers/ThemeHelpers.d.ts +15 -0
- package/dist/Helpers/ThemeHelpers.js +16 -0
- package/dist/Helpers/TimeHelpers.d.ts +73 -0
- package/dist/Helpers/TimeHelpers.js +253 -0
- package/dist/Helpers/TreeStore.d.ts +188 -0
- package/dist/Helpers/TreeStore.js +577 -0
- package/dist/Helpers/ValidationHelpers.d.ts +124 -0
- package/dist/Helpers/ValidationHelpers.js +301 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +149 -0
- package/dist/Interfaces/ApiInterfaces.js +24 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +171 -0
- package/{src/sso-client.tsx → dist/Interfaces/CrudInterfaces.js} +1 -1
- package/dist/Interfaces/DateInterfaces.d.ts +154 -0
- package/dist/Interfaces/DateInterfaces.js +29 -0
- package/dist/Interfaces/FormInterfaces.d.ts +352 -0
- package/dist/Interfaces/FormInterfaces.js +1 -0
- package/dist/Interfaces/InputInterfaces.d.ts +141 -0
- package/dist/Interfaces/InputInterfaces.js +1 -0
- package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
- package/dist/Interfaces/MessageInterfaces.js +5 -0
- package/dist/Interfaces/TableInterfaces.d.ts +291 -0
- package/dist/Interfaces/TableInterfaces.js +1 -0
- package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
- package/dist/Interfaces/ThemeInterfaces.js +17 -0
- package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
- package/dist/Interfaces/TypeInterfaces.js +1 -0
- package/dist/Server.Index.d.ts +2 -0
- package/dist/Server.Index.js +5 -0
- package/dist/Services/ApiProxy.d.ts +32 -0
- package/dist/Services/ApiProxy.js +111 -0
- package/dist/Services/BrowserApi.d.ts +78 -0
- package/dist/Services/BrowserApi.js +111 -0
- package/dist/Services/ServerApi.d.ts +49 -0
- package/dist/Services/ServerApi.js +75 -0
- package/dist/Services/ThemeService.d.ts +17 -0
- package/dist/Services/ThemeService.js +83 -0
- package/dist/Shared.Index.d.ts +43 -0
- package/dist/Shared.Index.js +47 -0
- package/package.json +82 -33
- package/src/Styles/Nexus.Base.css +70 -0
- package/src/Styles/Nexus.Button.css +151 -0
- package/src/Styles/Nexus.Calendar.css +275 -0
- package/src/Styles/Nexus.Choice.css +345 -0
- package/src/Styles/Nexus.Crud.css +84 -0
- package/src/Styles/Nexus.DateTime.css +81 -0
- package/src/Styles/Nexus.Dropdown.css +774 -0
- package/src/Styles/Nexus.Editor.css +297 -0
- package/src/Styles/Nexus.Form.css +298 -0
- package/src/Styles/Nexus.Index.css +34 -0
- package/src/Styles/Nexus.Input.css +637 -0
- package/src/Styles/Nexus.Markdown.css +265 -0
- package/src/Styles/Nexus.Menu.css +249 -0
- package/src/Styles/Nexus.Popup.css +731 -0
- package/src/Styles/Nexus.RichText.css +463 -0
- package/src/Styles/Nexus.Rows.css +566 -0
- package/src/Styles/Nexus.Slider.css +278 -0
- package/src/Styles/Nexus.Tab.Buttons.css +364 -0
- package/src/Styles/Nexus.Table.css +1014 -0
- package/src/Styles/Nexus.Theme.Picker.css +159 -0
- package/src/Styles/Nexus.Themes.css +194 -0
- package/src/Styles/Nexus.Time.css +147 -0
- package/src/Styles/Nexus.Toast.css +265 -0
- package/src/Styles/Nexus.Tokens.css +72 -0
- package/src/Styles/Nexus.Tree.css +435 -0
- package/src/api-services/authentication-service.tsx +0 -23
- package/src/api-services/preference-service.tsx +0 -5
- package/src/api-services/system-service.tsx +0 -34
- package/src/client.ts +0 -31
- package/src/components/documents/button.tsx +0 -142
- package/src/components/documents/icon-box.tsx +0 -93
- package/src/components/documents/page-title.tsx +0 -7
- package/src/components/documents/tab-button.tsx +0 -172
- package/src/components/documents/tag.tsx +0 -30
- package/src/components/index.js +0 -0
- package/src/components/inputs/checkbox-input.tsx +0 -66
- package/src/components/inputs/input-box.tsx +0 -48
- package/src/components/inputs/input-element.tsx +0 -65
- package/src/components/inputs/input-form.tsx +0 -164
- package/src/components/inputs/input.tsx +0 -181
- package/src/components/inputs/number-input.tsx +0 -108
- package/src/components/inputs/radiobox-input.tsx +0 -53
- package/src/components/inputs/textarea-input.tsx +0 -47
- package/src/components/inputs/textbox-input.tsx +0 -45
- package/src/components/layouts/global-dialogbox.tsx +0 -431
- package/src/components/layouts/global-layout.tsx +0 -59
- package/src/components/layouts/layout-helpers.tsx +0 -20
- package/src/components/layouts/panels/user-panel.tsx +0 -112
- package/src/components/layouts/utility-menu.tsx +0 -49
- package/src/components/panels/theme-panel.tsx +0 -46
- package/src/helpers/bitwise-helpers.tsx +0 -11
- package/src/helpers/browser-helpers.tsx +0 -156
- package/src/helpers/datasource-helpers.tsx +0 -99
- package/src/helpers/element-helpers.tsx +0 -57
- package/src/helpers/input-helpers.tsx +0 -74
- package/src/helpers/string-helpers.tsx +0 -28
- package/src/helpers/utility-helpers.tsx +0 -45
- package/src/helpers/validation-helpers.tsx +0 -302
- package/src/index.ts +0 -26
- package/src/interface.ts +0 -19
- package/src/interfaces/auth-token-interfaces.tsx +0 -6
- package/src/interfaces/browser-interfaces.tsx +0 -36
- package/src/interfaces/button-interfaces.tsx +0 -63
- package/src/interfaces/datasource-interfaces.tsx +0 -22
- package/src/interfaces/datatable-interfaces.tsx +0 -25
- package/src/interfaces/dialogbox-interfaces.tsx +0 -5
- package/src/interfaces/exception-interfaces.tsx +0 -99
- package/src/interfaces/http-interfaces.tsx +0 -129
- package/src/interfaces/icon-interfaces.tsx +0 -129
- package/src/interfaces/input-interfaces.tsx +0 -410
- package/src/interfaces/layout-interfaces.tsx +0 -191
- package/src/interfaces/menu-interfaces.tsx +0 -49
- package/src/interfaces/message-interfaces.tsx +0 -32
- package/src/interfaces/permission-interfaces.tsx +0 -9
- package/src/interfaces/storage-interfaces.tsx +0 -5
- package/src/interfaces/system-interfaces.tsx +0 -22
- package/src/interfaces/theme-interfaces.tsx +0 -123
- package/src/interfaces/type-interfaces.tsx +0 -28
- package/src/interfaces/user-interfaces.tsx +0 -47
- package/src/nexus-client.tsx +0 -21
- package/src/nexus.environments.tsx +0 -66
- package/src/proxy-api/proxy-backend.tsx +0 -60
- package/src/proxy-api/proxy-constants.tsx +0 -28
- package/src/proxy-api/proxy-helpers.tsx +0 -91
- package/src/proxy-api-client.tsx +0 -1
- package/src/proxy-api-server.tsx +0 -2
- package/src/services/http-client-services.tsx +0 -75
- package/src/services/http-services.tsx +0 -158
- package/src/services/loader-service.tsx +0 -185
- package/src/services/localstorage-service.tsx +0 -119
- package/src/services/message-services.tsx +0 -383
- package/src/services/theme-service.tsx +0 -161
- package/src/services/user-services.tsx +0 -10
- package/src/sso-config/auth-token-validation.ts +0 -16
- package/src/sso-config/callback-route.tsx +0 -35
- package/src/sso-config/cookie-encryption.ts +0 -51
- package/src/sso-config/cookie-helpers.tsx +0 -102
- package/src/sso-config/forgot-password-route.tsx +0 -36
- package/src/sso-config/oauth-callback-state.ts +0 -61
- package/src/sso-config/pkce-helpers.ts +0 -9
- package/src/sso-config/provider-complete-route.tsx +0 -18
- package/src/sso-config/redirect-context-actions.tsx +0 -45
- package/src/sso-config/redirect-context-helpers.tsx +0 -159
- package/src/sso-config/redirect-context-persist.tsx +0 -18
- package/src/sso-config/redirect-context-route.tsx +0 -45
- package/src/sso-config/refresh-route.tsx +0 -23
- package/src/sso-config/sign-in-route.tsx +0 -74
- package/src/sso-config/sign-out-route.tsx +0 -57
- package/src/sso-config/sign-up-route.tsx +0 -57
- package/src/sso-config/sso-backend-flow.ts +0 -38
- package/src/sso-config/sso-complete-route.tsx +0 -50
- package/src/sso-config/sso-config.ts +0 -59
- package/src/sso-config/sso-interfaces.tsx +0 -99
- package/src/sso-config/sso-response-helpers.tsx +0 -20
- package/src/sso-server.tsx +0 -21
- package/src/styles/nexus.animation.css +0 -269
- package/src/styles/nexus.core.css +0 -119
- package/src/styles/nexus.dialog.css +0 -144
- package/src/styles/nexus.icon.css +0 -51
- package/src/styles/nexus.input.css +0 -207
- package/src/styles/nexus.loader.css +0 -11
- package/src/styles/nexus.logic.css +0 -47
- package/src/styles/nexus.utility.css +0 -347
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { duplicateMessageFor, initialInputValue, inputValuesEqual, normalizeInputValue, rowsItemNames, rowsMessage, uniqueKeyOf, validateInput, } from "./InputParamsHelpers.js";
|
|
2
|
+
export class RowsStore {
|
|
3
|
+
options;
|
|
4
|
+
inputByName = new Map();
|
|
5
|
+
entries = [];
|
|
6
|
+
positions = new Map();
|
|
7
|
+
initial = [];
|
|
8
|
+
seq = 0;
|
|
9
|
+
generation = 0;
|
|
10
|
+
// Messages: an input's own rules, and values repeated in `unique` inputs.
|
|
11
|
+
ruleErrors = new Map();
|
|
12
|
+
duplicateErrors = new Map();
|
|
13
|
+
invalidByRow = new Map();
|
|
14
|
+
invalidCells = 0;
|
|
15
|
+
message = null;
|
|
16
|
+
messageShown = false;
|
|
17
|
+
showAll = false;
|
|
18
|
+
touched = new Map();
|
|
19
|
+
removed = null;
|
|
20
|
+
// Rendering: a revision per value set from code (its input mounts again with it), a version per row.
|
|
21
|
+
revisions = new Map();
|
|
22
|
+
versions = new Map();
|
|
23
|
+
rowListeners = new Map();
|
|
24
|
+
listeners = new Set();
|
|
25
|
+
valueListeners = new Set();
|
|
26
|
+
snapshot;
|
|
27
|
+
valueCache = null;
|
|
28
|
+
keysCache = null;
|
|
29
|
+
dirtyUnique = new Set();
|
|
30
|
+
/** The array last given to value listeners. A controlled parent that passes it back changes nothing. */
|
|
31
|
+
lastValue = null;
|
|
32
|
+
constructor(options, rows = []) {
|
|
33
|
+
this.options = options;
|
|
34
|
+
this.indexInputs();
|
|
35
|
+
this.load(rows);
|
|
36
|
+
this.initial = this.getValue();
|
|
37
|
+
this.lastValue = this.initial;
|
|
38
|
+
this.snapshot = this.buildSnapshot();
|
|
39
|
+
}
|
|
40
|
+
/* Reading */
|
|
41
|
+
/** Every row as it is now. The same array until something changes; do not change it. */
|
|
42
|
+
getValue() {
|
|
43
|
+
this.valueCache ??= this.entries.map(entry => entry.values);
|
|
44
|
+
return this.valueCache;
|
|
45
|
+
}
|
|
46
|
+
getSnapshot = () => this.snapshot;
|
|
47
|
+
get count() {
|
|
48
|
+
return this.entries.length;
|
|
49
|
+
}
|
|
50
|
+
get inputs() {
|
|
51
|
+
return this.options.inputs;
|
|
52
|
+
}
|
|
53
|
+
/** The item's names for titles and messages. */
|
|
54
|
+
get names() {
|
|
55
|
+
return rowsItemNames(this.options, this.options.fallbackItem ?? "Row");
|
|
56
|
+
}
|
|
57
|
+
indexOf(key) {
|
|
58
|
+
return this.positions.get(key) ?? -1;
|
|
59
|
+
}
|
|
60
|
+
keyAt(index) {
|
|
61
|
+
return this.entries[index]?.key ?? null;
|
|
62
|
+
}
|
|
63
|
+
row(key) {
|
|
64
|
+
const index = this.positions.get(key);
|
|
65
|
+
return index === undefined ? undefined : this.entries[index]?.values;
|
|
66
|
+
}
|
|
67
|
+
get(key, name) {
|
|
68
|
+
return this.row(key)?.[name];
|
|
69
|
+
}
|
|
70
|
+
/** Grows when a value was set from code; an input renders again from the store with a new one. */
|
|
71
|
+
revision(key, name) {
|
|
72
|
+
return this.revisions.get(key)?.get(name) ?? 0;
|
|
73
|
+
}
|
|
74
|
+
/** Grows whenever what a row shows changes: a message, or a value set from code. */
|
|
75
|
+
version(key) {
|
|
76
|
+
return this.versions.get(key) ?? 0;
|
|
77
|
+
}
|
|
78
|
+
/** The message a value shows now: every message after `validateAll`; before, a repeated value once its input was left. */
|
|
79
|
+
shownError(key, name) {
|
|
80
|
+
const duplicate = this.duplicateErrors.get(key)?.get(name);
|
|
81
|
+
if (this.showAll)
|
|
82
|
+
return this.ruleErrors.get(key)?.get(name) ?? duplicate;
|
|
83
|
+
return duplicate && this.touched.get(key)?.has(name) ? duplicate : undefined;
|
|
84
|
+
}
|
|
85
|
+
/** How many values in a row fail. */
|
|
86
|
+
rowProblems(key) {
|
|
87
|
+
return this.invalidByRow.get(key) ?? 0;
|
|
88
|
+
}
|
|
89
|
+
/** Whether any value or the rows' own rules fail, shown or not. */
|
|
90
|
+
hasProblems() {
|
|
91
|
+
return this.invalidCells > 0 || this.message !== null;
|
|
92
|
+
}
|
|
93
|
+
/** The first problem in row order, then the rows' own message; `null` when all is valid. */
|
|
94
|
+
firstProblem() {
|
|
95
|
+
if (this.invalidCells > 0) {
|
|
96
|
+
for (const entry of this.entries) {
|
|
97
|
+
if (!this.invalidByRow.get(entry.key))
|
|
98
|
+
continue;
|
|
99
|
+
for (const params of this.options.inputs) {
|
|
100
|
+
if (this.ruleErrors.get(entry.key)?.has(params.name) || this.duplicateErrors.get(entry.key)?.has(params.name))
|
|
101
|
+
return { key: entry.key, name: params.name };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return this.message ? { key: null, name: null } : null;
|
|
106
|
+
}
|
|
107
|
+
/** The first problem's message, named with its row: "Address 2: City is required." */
|
|
108
|
+
firstMessage() {
|
|
109
|
+
const place = this.firstProblem();
|
|
110
|
+
if (!place)
|
|
111
|
+
return null;
|
|
112
|
+
if (place.key === null)
|
|
113
|
+
return this.message;
|
|
114
|
+
const text = this.ruleErrors.get(place.key)?.get(place.name) ?? this.duplicateErrors.get(place.key)?.get(place.name) ?? "";
|
|
115
|
+
return `${this.names.title} ${this.indexOf(place.key) + 1}: ${text}`;
|
|
116
|
+
}
|
|
117
|
+
/** Whether the rows differ from the ones loaded last (`replace`) or first. */
|
|
118
|
+
isChanged() {
|
|
119
|
+
const rows = this.getValue();
|
|
120
|
+
if (rows.length !== this.initial.length)
|
|
121
|
+
return true;
|
|
122
|
+
return rows.some((row, index) => {
|
|
123
|
+
const before = this.initial[index];
|
|
124
|
+
return row !== before && this.options.inputs.some(params => !inputValuesEqual(row[params.name], before?.[params.name]));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/* Subscribing */
|
|
128
|
+
/** Structure: the rows, their order, and the rows' own message. */
|
|
129
|
+
subscribe = (listener) => {
|
|
130
|
+
this.listeners.add(listener);
|
|
131
|
+
return () => this.listeners.delete(listener);
|
|
132
|
+
};
|
|
133
|
+
/** One row: its messages and the values set from code. */
|
|
134
|
+
subscribeRow(key, listener) {
|
|
135
|
+
let set = this.rowListeners.get(key);
|
|
136
|
+
if (!set)
|
|
137
|
+
this.rowListeners.set(key, (set = new Set()));
|
|
138
|
+
set.add(listener);
|
|
139
|
+
return () => {
|
|
140
|
+
set.delete(listener);
|
|
141
|
+
if (!set.size && this.rowListeners.get(key) === set)
|
|
142
|
+
this.rowListeners.delete(key);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Every row, after each change of a value or of the rows. */
|
|
146
|
+
subscribeValue(listener) {
|
|
147
|
+
this.valueListeners.add(listener);
|
|
148
|
+
return () => this.valueListeners.delete(listener);
|
|
149
|
+
}
|
|
150
|
+
/* Changing */
|
|
151
|
+
/**
|
|
152
|
+
* New rules or inputs. `recheck` checks every value again (after a change of inputs or rules); without it, only the
|
|
153
|
+
* callbacks (`validate`, `onRowChange`, `newRow`) are swapped, which a parent's render does every time.
|
|
154
|
+
*/
|
|
155
|
+
configure(options, recheck = true) {
|
|
156
|
+
this.options = options;
|
|
157
|
+
if (!recheck)
|
|
158
|
+
return;
|
|
159
|
+
this.indexInputs();
|
|
160
|
+
this.checkEverything();
|
|
161
|
+
this.touchAllRows();
|
|
162
|
+
this.refresh();
|
|
163
|
+
}
|
|
164
|
+
/** Sets a value in a row. `input` is a change the user made in the row's input: it runs `onRowChange`. */
|
|
165
|
+
set(key, name, value, source = "code") {
|
|
166
|
+
const before = this.getValue();
|
|
167
|
+
const changed = new Set();
|
|
168
|
+
this.write(key, name, value, source, changed, 0);
|
|
169
|
+
// Nothing was written: the same value again.
|
|
170
|
+
if (this.getValue() === before)
|
|
171
|
+
return;
|
|
172
|
+
this.settle(changed);
|
|
173
|
+
}
|
|
174
|
+
/** Marks a value as left by the user: a repeated value shows its message from now on. */
|
|
175
|
+
touch(key, name) {
|
|
176
|
+
let names = this.touched.get(key);
|
|
177
|
+
if (names?.has(name))
|
|
178
|
+
return;
|
|
179
|
+
if (!names)
|
|
180
|
+
this.touched.set(key, (names = new Set()));
|
|
181
|
+
names.add(name);
|
|
182
|
+
if (!this.showAll && this.duplicateErrors.get(key)?.has(name))
|
|
183
|
+
this.bump(key);
|
|
184
|
+
}
|
|
185
|
+
/** Adds a row with the inputs' defaults, `newRow`, and `values`, at `index` or at the end. Returns its key. */
|
|
186
|
+
add(values, index = this.entries.length) {
|
|
187
|
+
const position = Math.max(0, Math.min(index, this.entries.length));
|
|
188
|
+
const wasEmpty = this.entries.length === 0;
|
|
189
|
+
const row = {};
|
|
190
|
+
for (const params of this.options.inputs)
|
|
191
|
+
row[params.name] = initialInputValue(params);
|
|
192
|
+
const { newRow } = this.options;
|
|
193
|
+
Object.assign(row, typeof newRow === "function" ? newRow(position) : newRow, values);
|
|
194
|
+
// The first row of a list has each radio on: the first address added is the primary one.
|
|
195
|
+
for (const params of this.options.inputs) {
|
|
196
|
+
if (params.type === "radio" && wasEmpty && values?.[params.name] === undefined)
|
|
197
|
+
row[params.name] = true;
|
|
198
|
+
}
|
|
199
|
+
const entry = { key: this.newKey(), values: this.normalizeRow(row) };
|
|
200
|
+
this.entries.splice(position, 0, entry);
|
|
201
|
+
this.afterStructure();
|
|
202
|
+
const changed = new Set();
|
|
203
|
+
for (const params of this.options.inputs) {
|
|
204
|
+
this.checkCell(entry, params);
|
|
205
|
+
// Only one row keeps a radio on: a new row that has it takes it from the others.
|
|
206
|
+
if (params.type === "radio" && entry.values[params.name] === true)
|
|
207
|
+
this.radioOn(entry, params.name, changed);
|
|
208
|
+
}
|
|
209
|
+
this.messageShown = true;
|
|
210
|
+
this.removed = null;
|
|
211
|
+
this.markUniqueDirty();
|
|
212
|
+
this.settle(changed);
|
|
213
|
+
return entry.key;
|
|
214
|
+
}
|
|
215
|
+
/** Adds a copy of a row after it, without its id and with its radios off. Returns the copy's key. */
|
|
216
|
+
duplicate(key) {
|
|
217
|
+
const index = this.positions.get(key);
|
|
218
|
+
const source = index === undefined ? undefined : this.entries[index];
|
|
219
|
+
if (!source || index === undefined)
|
|
220
|
+
return null;
|
|
221
|
+
const values = { ...source.values };
|
|
222
|
+
if (this.options.rowKey)
|
|
223
|
+
delete values[this.options.rowKey];
|
|
224
|
+
for (const params of this.options.inputs)
|
|
225
|
+
if (params.type === "radio")
|
|
226
|
+
values[params.name] = false;
|
|
227
|
+
return this.add(values, index + 1);
|
|
228
|
+
}
|
|
229
|
+
/** Removes a row. A radio it had on moves to the first row left. `undoRemove` puts it back. */
|
|
230
|
+
remove(key) {
|
|
231
|
+
const index = this.positions.get(key);
|
|
232
|
+
if (index === undefined)
|
|
233
|
+
return;
|
|
234
|
+
const [entry] = this.entries.splice(index, 1);
|
|
235
|
+
if (!entry)
|
|
236
|
+
return;
|
|
237
|
+
this.forget(key);
|
|
238
|
+
this.afterStructure();
|
|
239
|
+
const changed = new Set();
|
|
240
|
+
const moved = [];
|
|
241
|
+
const first = this.entries[0];
|
|
242
|
+
for (const params of this.options.inputs) {
|
|
243
|
+
if (params.type !== "radio" || entry.values[params.name] !== true || !first)
|
|
244
|
+
continue;
|
|
245
|
+
this.write(first.key, params.name, true, "code", changed, 1);
|
|
246
|
+
moved.push({ key: first.key, name: params.name });
|
|
247
|
+
}
|
|
248
|
+
this.removed = { entry, index, moved };
|
|
249
|
+
this.messageShown = true;
|
|
250
|
+
this.markUniqueDirty();
|
|
251
|
+
this.settle(changed);
|
|
252
|
+
}
|
|
253
|
+
/** Puts the last removed row back where it was. Returns its key, or `null` when there is nothing to undo. */
|
|
254
|
+
undoRemove() {
|
|
255
|
+
const removed = this.removed;
|
|
256
|
+
if (!removed)
|
|
257
|
+
return null;
|
|
258
|
+
this.removed = null;
|
|
259
|
+
const { entry, index, moved } = removed;
|
|
260
|
+
// A row with the same id may have been added since; the row comes back under a new key then.
|
|
261
|
+
if (this.positions.has(entry.key))
|
|
262
|
+
entry.key = this.newKey();
|
|
263
|
+
this.entries.splice(Math.min(index, this.entries.length), 0, entry);
|
|
264
|
+
this.afterStructure();
|
|
265
|
+
const changed = new Set();
|
|
266
|
+
for (const { key, name } of moved)
|
|
267
|
+
if (this.positions.has(key))
|
|
268
|
+
this.write(key, name, false, "code", changed, 1);
|
|
269
|
+
for (const params of this.options.inputs)
|
|
270
|
+
this.checkCell(entry, params);
|
|
271
|
+
this.markUniqueDirty();
|
|
272
|
+
this.settle(changed);
|
|
273
|
+
return entry.key;
|
|
274
|
+
}
|
|
275
|
+
/** Moves a row to a new position. */
|
|
276
|
+
move(key, toIndex) {
|
|
277
|
+
const index = this.positions.get(key);
|
|
278
|
+
const target = Math.max(0, Math.min(toIndex, this.entries.length - 1));
|
|
279
|
+
if (index === undefined || index === target)
|
|
280
|
+
return;
|
|
281
|
+
const [entry] = this.entries.splice(index, 1);
|
|
282
|
+
if (!entry)
|
|
283
|
+
return;
|
|
284
|
+
this.entries.splice(target, 0, entry);
|
|
285
|
+
this.afterStructure();
|
|
286
|
+
this.removed = null;
|
|
287
|
+
// "Same as in row 1" names positions, which just changed.
|
|
288
|
+
this.markUniqueDirty();
|
|
289
|
+
this.settle(new Set());
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Loads new rows, e.g. fresh data from a server. With `asInitial` (default), they become the rows `reset` and
|
|
293
|
+
* `isChanged` compare with. `emit` tells value listeners, for a change the parent does not know of yet.
|
|
294
|
+
*/
|
|
295
|
+
replace(rows, { asInitial = true, emit = false } = {}) {
|
|
296
|
+
this.load(rows);
|
|
297
|
+
if (asInitial)
|
|
298
|
+
this.initial = this.getValue();
|
|
299
|
+
this.generation++;
|
|
300
|
+
this.showAll = false;
|
|
301
|
+
this.messageShown = false;
|
|
302
|
+
this.touched.clear();
|
|
303
|
+
this.removed = null;
|
|
304
|
+
this.refresh(true);
|
|
305
|
+
if (emit)
|
|
306
|
+
this.emit();
|
|
307
|
+
else
|
|
308
|
+
this.lastValue = this.getValue();
|
|
309
|
+
}
|
|
310
|
+
/** Back to the rows loaded last, as a form reset does. */
|
|
311
|
+
reset() {
|
|
312
|
+
this.replace(this.initial, { asInitial: false, emit: true });
|
|
313
|
+
}
|
|
314
|
+
/** Shows every message, as a submit does. Returns the first problem, or `null` when all is valid. */
|
|
315
|
+
validateAll() {
|
|
316
|
+
this.showAll = true;
|
|
317
|
+
this.messageShown = true;
|
|
318
|
+
this.checkEverything();
|
|
319
|
+
this.touchAllRows();
|
|
320
|
+
this.refresh();
|
|
321
|
+
return this.firstProblem();
|
|
322
|
+
}
|
|
323
|
+
/* Inside */
|
|
324
|
+
indexInputs() {
|
|
325
|
+
this.inputByName = new Map(this.options.inputs.map(params => [params.name, params]));
|
|
326
|
+
}
|
|
327
|
+
newKey() {
|
|
328
|
+
return "new:" + ++this.seq;
|
|
329
|
+
}
|
|
330
|
+
/** Each input's value in the type the input reports, and the other fields of the row as they came. */
|
|
331
|
+
normalizeRow(row) {
|
|
332
|
+
const values = { ...row };
|
|
333
|
+
for (const params of this.options.inputs) {
|
|
334
|
+
// A hidden input's value stays in the data as it was.
|
|
335
|
+
if (params.hidden)
|
|
336
|
+
continue;
|
|
337
|
+
values[params.name] = normalizeInputValue(params, row[params.name]);
|
|
338
|
+
}
|
|
339
|
+
return values;
|
|
340
|
+
}
|
|
341
|
+
load(rows) {
|
|
342
|
+
const { rowKey } = this.options;
|
|
343
|
+
const used = new Set();
|
|
344
|
+
this.entries = rows.map(row => {
|
|
345
|
+
const id = rowKey ? row[rowKey] : undefined;
|
|
346
|
+
let key = id === undefined || id === null || id === "" ? "" : "id:" + String(id);
|
|
347
|
+
if (!key || used.has(key))
|
|
348
|
+
key = this.newKey();
|
|
349
|
+
used.add(key);
|
|
350
|
+
return { key, values: this.normalizeRow(row) };
|
|
351
|
+
});
|
|
352
|
+
// Saved data with a radio on in more than one row keeps the first.
|
|
353
|
+
for (const params of this.options.inputs) {
|
|
354
|
+
if (params.type !== "radio")
|
|
355
|
+
continue;
|
|
356
|
+
let found = false;
|
|
357
|
+
for (const entry of this.entries) {
|
|
358
|
+
if (entry.values[params.name] !== true)
|
|
359
|
+
continue;
|
|
360
|
+
if (found)
|
|
361
|
+
entry.values = { ...entry.values, [params.name]: false };
|
|
362
|
+
found = true;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
this.revisions.clear();
|
|
366
|
+
this.afterStructure();
|
|
367
|
+
this.checkEverything();
|
|
368
|
+
}
|
|
369
|
+
afterStructure() {
|
|
370
|
+
this.positions = new Map(this.entries.map((entry, index) => [entry.key, index]));
|
|
371
|
+
this.valueCache = null;
|
|
372
|
+
this.keysCache = null;
|
|
373
|
+
}
|
|
374
|
+
/** Drops what the store knew about a removed row. */
|
|
375
|
+
forget(key) {
|
|
376
|
+
this.invalidCells -= this.invalidByRow.get(key) ?? 0;
|
|
377
|
+
this.invalidByRow.delete(key);
|
|
378
|
+
this.ruleErrors.delete(key);
|
|
379
|
+
this.duplicateErrors.delete(key);
|
|
380
|
+
this.touched.delete(key);
|
|
381
|
+
this.revisions.delete(key);
|
|
382
|
+
}
|
|
383
|
+
write(key, name, value, source, changed, depth) {
|
|
384
|
+
const index = this.positions.get(key);
|
|
385
|
+
const entry = index === undefined ? undefined : this.entries[index];
|
|
386
|
+
if (!entry || index === undefined)
|
|
387
|
+
return;
|
|
388
|
+
const params = this.inputByName.get(name);
|
|
389
|
+
const next = params && source === "code" ? normalizeInputValue(params, value) : value;
|
|
390
|
+
if (inputValuesEqual(entry.values[name], next))
|
|
391
|
+
return;
|
|
392
|
+
const shownBefore = this.shownError(key, name);
|
|
393
|
+
entry.values = { ...entry.values, [name]: next };
|
|
394
|
+
this.valueCache = null;
|
|
395
|
+
if (source === "code") {
|
|
396
|
+
// The input shows its value from when it mounted; a new revision mounts it again with this one.
|
|
397
|
+
let names = this.revisions.get(key);
|
|
398
|
+
if (!names)
|
|
399
|
+
this.revisions.set(key, (names = new Map()));
|
|
400
|
+
names.set(name, (names.get(name) ?? 0) + 1);
|
|
401
|
+
changed.add(key);
|
|
402
|
+
}
|
|
403
|
+
if (params) {
|
|
404
|
+
this.checkCell(entry, params);
|
|
405
|
+
if (params.type === "radio") {
|
|
406
|
+
// The row shows which radios it has on (a section's "Primary" tag), so it renders again, typed or not.
|
|
407
|
+
changed.add(key);
|
|
408
|
+
if (next === true)
|
|
409
|
+
this.radioOn(entry, name, changed);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (this.options.unique?.includes(name))
|
|
413
|
+
this.dirtyUnique.add(name);
|
|
414
|
+
if (this.shownError(key, name) !== shownBefore)
|
|
415
|
+
changed.add(key);
|
|
416
|
+
// Values the row derives from the one the user changed, such as an amount from a quantity and a rate.
|
|
417
|
+
if (source === "input" && depth === 0 && this.options.onRowChange) {
|
|
418
|
+
const patch = this.options.onRowChange(entry.values, name, index);
|
|
419
|
+
if (patch) {
|
|
420
|
+
for (const [field, fieldValue] of Object.entries(patch)) {
|
|
421
|
+
if (field !== name)
|
|
422
|
+
this.write(key, field, fieldValue, "code", changed, depth + 1);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/** Turns a radio off in every row but this one. */
|
|
428
|
+
radioOn(entry, name, changed) {
|
|
429
|
+
for (const other of this.entries) {
|
|
430
|
+
if (other !== entry && other.values[name] === true)
|
|
431
|
+
this.write(other.key, name, false, "code", changed, 1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/** Checks one value against its input's rules. */
|
|
435
|
+
checkCell(entry, params) {
|
|
436
|
+
const message = params.hidden ? null : validateInput(params, entry.values[params.name], this.options.context);
|
|
437
|
+
this.setError(this.ruleErrors, entry.key, params.name, message);
|
|
438
|
+
}
|
|
439
|
+
setError(map, key, name, message) {
|
|
440
|
+
const current = map.get(key);
|
|
441
|
+
if ((current?.get(name) ?? null) === message)
|
|
442
|
+
return;
|
|
443
|
+
const had = this.isInvalid(key, name);
|
|
444
|
+
if (message) {
|
|
445
|
+
if (current)
|
|
446
|
+
current.set(name, message);
|
|
447
|
+
else
|
|
448
|
+
map.set(key, new Map([[name, message]]));
|
|
449
|
+
}
|
|
450
|
+
else if (current) {
|
|
451
|
+
current.delete(name);
|
|
452
|
+
if (!current.size)
|
|
453
|
+
map.delete(key);
|
|
454
|
+
}
|
|
455
|
+
const has = this.isInvalid(key, name);
|
|
456
|
+
if (has !== had) {
|
|
457
|
+
const delta = has ? 1 : -1;
|
|
458
|
+
this.invalidCells += delta;
|
|
459
|
+
const count = (this.invalidByRow.get(key) ?? 0) + delta;
|
|
460
|
+
if (count > 0)
|
|
461
|
+
this.invalidByRow.set(key, count);
|
|
462
|
+
else
|
|
463
|
+
this.invalidByRow.delete(key);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
isInvalid(key, name) {
|
|
467
|
+
return Boolean(this.ruleErrors.get(key)?.has(name) || this.duplicateErrors.get(key)?.has(name));
|
|
468
|
+
}
|
|
469
|
+
markUniqueDirty() {
|
|
470
|
+
for (const name of this.options.unique ?? [])
|
|
471
|
+
this.dirtyUnique.add(name);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Finds repeated values in one `unique` input. Every row with the value gets a message, so the one just edited says
|
|
475
|
+
* so: the first row names the second, the others name the first.
|
|
476
|
+
*/
|
|
477
|
+
checkUnique(name, changed) {
|
|
478
|
+
const params = this.inputByName.get(name);
|
|
479
|
+
const found = new Map();
|
|
480
|
+
const groups = new Map();
|
|
481
|
+
this.entries.forEach((entry, index) => {
|
|
482
|
+
const value = uniqueKeyOf(entry.values[name]);
|
|
483
|
+
if (value === null)
|
|
484
|
+
return;
|
|
485
|
+
const group = groups.get(value);
|
|
486
|
+
if (group)
|
|
487
|
+
group.push(index);
|
|
488
|
+
else
|
|
489
|
+
groups.set(value, [index]);
|
|
490
|
+
});
|
|
491
|
+
for (const group of groups.values()) {
|
|
492
|
+
if (group.length < 2)
|
|
493
|
+
continue;
|
|
494
|
+
for (const index of group) {
|
|
495
|
+
const entry = this.entries[index];
|
|
496
|
+
// A value that breaks its own rules says so first.
|
|
497
|
+
if (this.ruleErrors.get(entry.key)?.has(name))
|
|
498
|
+
continue;
|
|
499
|
+
found.set(entry.key, duplicateMessageFor(params, name, index === group[0] ? group[1] : group[0], this.names.item));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const keys = new Set([...found.keys()]);
|
|
503
|
+
for (const [key, names] of this.duplicateErrors)
|
|
504
|
+
if (names.has(name))
|
|
505
|
+
keys.add(key);
|
|
506
|
+
for (const key of keys) {
|
|
507
|
+
const before = this.shownError(key, name);
|
|
508
|
+
this.setError(this.duplicateErrors, key, name, found.get(key) ?? null);
|
|
509
|
+
if (this.shownError(key, name) !== before)
|
|
510
|
+
changed.add(key);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
checkEverything() {
|
|
514
|
+
this.ruleErrors.clear();
|
|
515
|
+
this.duplicateErrors.clear();
|
|
516
|
+
this.invalidByRow.clear();
|
|
517
|
+
this.invalidCells = 0;
|
|
518
|
+
for (const entry of this.entries)
|
|
519
|
+
for (const params of this.options.inputs)
|
|
520
|
+
this.checkCell(entry, params);
|
|
521
|
+
const ignored = new Set();
|
|
522
|
+
for (const name of this.options.unique ?? [])
|
|
523
|
+
this.checkUnique(name, ignored);
|
|
524
|
+
this.dirtyUnique.clear();
|
|
525
|
+
this.message = rowsMessage(this.getValue(), this.options, this.options.fallbackItem);
|
|
526
|
+
}
|
|
527
|
+
/** After a change: repeated values, the rows' own message, the rows to render again, and value listeners. */
|
|
528
|
+
settle(changed) {
|
|
529
|
+
for (const name of this.dirtyUnique)
|
|
530
|
+
this.checkUnique(name, changed);
|
|
531
|
+
this.dirtyUnique.clear();
|
|
532
|
+
this.message = rowsMessage(this.getValue(), this.options, this.options.fallbackItem);
|
|
533
|
+
for (const key of changed)
|
|
534
|
+
this.bump(key);
|
|
535
|
+
this.refresh();
|
|
536
|
+
this.emit();
|
|
537
|
+
}
|
|
538
|
+
bump(key) {
|
|
539
|
+
this.versions.set(key, (this.versions.get(key) ?? 0) + 1);
|
|
540
|
+
const listeners = this.rowListeners.get(key);
|
|
541
|
+
if (listeners)
|
|
542
|
+
for (const listener of [...listeners])
|
|
543
|
+
listener();
|
|
544
|
+
}
|
|
545
|
+
touchAllRows() {
|
|
546
|
+
for (const entry of this.entries)
|
|
547
|
+
this.bump(entry.key);
|
|
548
|
+
}
|
|
549
|
+
emit() {
|
|
550
|
+
const rows = this.getValue();
|
|
551
|
+
this.lastValue = rows;
|
|
552
|
+
for (const listener of [...this.valueListeners])
|
|
553
|
+
listener(rows);
|
|
554
|
+
}
|
|
555
|
+
buildSnapshot() {
|
|
556
|
+
return {
|
|
557
|
+
keys: (this.keysCache ??= this.entries.map(entry => entry.key)),
|
|
558
|
+
generation: this.generation,
|
|
559
|
+
message: this.messageShown || this.showAll ? this.message : null,
|
|
560
|
+
showAll: this.showAll,
|
|
561
|
+
invalidRows: this.showAll ? this.invalidByRow.size : 0,
|
|
562
|
+
removed: this.removed ? { index: this.removed.index } : null,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/** Replaces the snapshot when what it holds changed, and tells structure listeners. */
|
|
566
|
+
refresh(force = false) {
|
|
567
|
+
const next = this.buildSnapshot();
|
|
568
|
+
const current = this.snapshot;
|
|
569
|
+
const sameKeys = current && (current.keys === next.keys || (current.keys.length === next.keys.length && current.keys.every((key, index) => key === next.keys[index])));
|
|
570
|
+
if (!force &&
|
|
571
|
+
current &&
|
|
572
|
+
sameKeys &&
|
|
573
|
+
current.generation === next.generation &&
|
|
574
|
+
current.message === next.message &&
|
|
575
|
+
current.showAll === next.showAll &&
|
|
576
|
+
current.invalidRows === next.invalidRows &&
|
|
577
|
+
current.removed?.index === next.removed?.index) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
this.snapshot = sameKeys && current ? { ...next, keys: current.keys } : next;
|
|
581
|
+
for (const listener of [...this.listeners])
|
|
582
|
+
listener();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface ColumnDragOptions {
|
|
2
|
+
/** The heading pressed. */
|
|
3
|
+
heading: HTMLElement;
|
|
4
|
+
/** The box the columns scroll in, scrolled while the pointer is near its edges. */
|
|
5
|
+
scroller?: HTMLElement | null;
|
|
6
|
+
/** The table, which carries the attribute that changes the cursor while a drag runs. */
|
|
7
|
+
root?: HTMLElement | null;
|
|
8
|
+
/** The move the user made: `key` goes just before `beforeKey`, or to the end when it is null. */
|
|
9
|
+
onDrop: (key: string, beforeKey: string | null) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Drags a column heading to another place among the headings pinned to the same side. A line shows where it will land;
|
|
13
|
+
* Esc puts it back. Returns a function that ends the drag.
|
|
14
|
+
*/
|
|
15
|
+
export declare function startColumnDrag(event: PointerEvent, { heading, scroller, root, onDrop }: ColumnDragOptions): () => void;
|
|
16
|
+
export interface ColumnResizeOptions {
|
|
17
|
+
/** The heading whose edge was pressed. */
|
|
18
|
+
heading: HTMLElement;
|
|
19
|
+
/** The table, which carries the attribute that changes the cursor while the drag runs. */
|
|
20
|
+
root?: HTMLElement | null;
|
|
21
|
+
/** Before the first move: the table writes the width of every column, so only this one changes. */
|
|
22
|
+
onStart?: () => void;
|
|
23
|
+
/** On every move, with the new width in pixels. The table writes it straight to the column, with no render. */
|
|
24
|
+
onResize: (width: number) => void;
|
|
25
|
+
/** The width to keep, once the drag ends. Esc sends the width it started at. */
|
|
26
|
+
onCommit: (width: number) => void;
|
|
27
|
+
/** Narrowest the column gets. Default 56. */
|
|
28
|
+
min?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Drags the edge of a heading to change its width. Returns a function that ends the drag. */
|
|
31
|
+
export declare function startColumnResize(event: PointerEvent, { heading, root, onStart, onResize, onCommit, min }: ColumnResizeOptions): () => void;
|
|
32
|
+
/**
|
|
33
|
+
* The width a column needs for its widest value and its heading, at most `max`. Used by "Fit to content" in a heading's
|
|
34
|
+
* menu and by a double-click on its edge.
|
|
35
|
+
*
|
|
36
|
+
* A cell's value is a block that fills its column and cuts long text with an ellipsis, so its own width says nothing
|
|
37
|
+
* about the text inside it. Each one is measured at `max-content` instead, with the cap taken off: every cell is set,
|
|
38
|
+
* then every cell is read, so the browser lays the table out twice, not once per row.
|
|
39
|
+
*/
|
|
40
|
+
export declare function fitColumnWidth(table: HTMLElement, key: string, max?: number): number;
|