nexus-shared 1.1.21 → 3.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/CHANGELOG.md +262 -0
- package/LICENSE +21 -0
- package/README.md +1000 -0
- package/dist/Client.Index.d.ts +55 -0
- package/dist/Client.Index.js +60 -0
- package/dist/Components/Chats/Chat.d.ts +28 -0
- package/dist/Components/Chats/Chat.js +19 -0
- package/dist/Components/Chats/ChatButton.d.ts +26 -0
- package/dist/Components/Chats/ChatButton.js +41 -0
- package/dist/Components/Chats/ChatComposer.d.ts +41 -0
- package/dist/Components/Chats/ChatComposer.js +177 -0
- package/dist/Components/Chats/ChatConversations.d.ts +35 -0
- package/dist/Components/Chats/ChatConversations.js +38 -0
- package/dist/Components/Chats/ChatPanel.d.ts +66 -0
- package/dist/Components/Chats/ChatPanel.js +94 -0
- package/dist/Components/Chats/ChatParts.d.ts +87 -0
- package/dist/Components/Chats/ChatParts.js +100 -0
- package/dist/Components/Chats/ChatThread.d.ts +22 -0
- package/dist/Components/Chats/ChatThread.js +96 -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 +242 -0
- package/dist/Components/Documents/SplitButton.d.ts +64 -0
- package/dist/Components/Documents/SplitButton.js +129 -0
- package/dist/Components/Documents/TabButtons.d.ts +80 -0
- package/dist/Components/Documents/TabButtons.js +38 -0
- package/dist/Components/Forms/ApiForm.d.ts +23 -0
- package/dist/Components/Forms/ApiForm.js +100 -0
- package/dist/Components/Forms/Crud.d.ts +17 -0
- package/dist/Components/Forms/Crud.js +708 -0
- package/dist/Components/Forms/CrudParts.d.ts +15 -0
- package/dist/Components/Forms/CrudParts.js +36 -0
- package/dist/Components/Forms/ExcelImport.d.ts +42 -0
- package/dist/Components/Forms/ExcelImport.js +190 -0
- package/dist/Components/Forms/Form.d.ts +52 -0
- package/dist/Components/Forms/Form.js +323 -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 +400 -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 +172 -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 +37 -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 +117 -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 +115 -0
- package/dist/Components/Inputs/RowsInput.js +308 -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 +27 -0
- package/dist/Components/Inputs/TabularForm.js +348 -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 +240 -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 +49 -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 +596 -0
- package/dist/Components/Viewers/DataTableColumns.d.ts +28 -0
- package/dist/Components/Viewers/DataTableColumns.js +243 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +138 -0
- package/dist/Components/Viewers/DataTableParts.js +157 -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 +96 -0
- package/dist/Helpers/ApiClient.js +200 -0
- package/dist/Helpers/ApiFormHelpers.d.ts +49 -0
- package/dist/Helpers/ApiFormHelpers.js +120 -0
- package/dist/Helpers/ApiModules.d.ts +131 -0
- package/dist/Helpers/ApiModules.js +195 -0
- package/dist/Helpers/ApiResponses.d.ts +51 -0
- package/dist/Helpers/ApiResponses.js +250 -0
- package/dist/Helpers/ApiRoutes.d.ts +140 -0
- package/dist/Helpers/ApiRoutes.js +183 -0
- package/dist/Helpers/ApiToasts.d.ts +21 -0
- package/dist/Helpers/ApiToasts.js +22 -0
- package/dist/Helpers/ChatBackend.d.ts +134 -0
- package/dist/Helpers/ChatBackend.js +332 -0
- package/dist/Helpers/ChatHelpers.d.ts +189 -0
- package/dist/Helpers/ChatHelpers.js +486 -0
- package/dist/Helpers/ChatHooks.d.ts +81 -0
- package/dist/Helpers/ChatHooks.js +175 -0
- package/dist/Helpers/ChatStore.d.ts +132 -0
- package/dist/Helpers/ChatStore.js +394 -0
- package/dist/Helpers/ChatSummaries.d.ts +39 -0
- package/dist/Helpers/ChatSummaries.js +118 -0
- package/dist/Helpers/ClassHelpers.d.ts +3 -0
- package/dist/Helpers/ClassHelpers.js +4 -0
- package/dist/Helpers/CrudBackend.d.ts +98 -0
- package/dist/Helpers/CrudBackend.js +214 -0
- package/dist/Helpers/CrudHelpers.d.ts +111 -0
- package/dist/Helpers/CrudHelpers.js +267 -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/ExcelBackend.d.ts +106 -0
- package/dist/Helpers/ExcelBackend.js +290 -0
- package/dist/Helpers/ExcelHelpers.d.ts +98 -0
- package/dist/Helpers/ExcelHelpers.js +302 -0
- package/dist/Helpers/FormDrafts.d.ts +18 -0
- package/dist/Helpers/FormDrafts.js +76 -0
- package/dist/Helpers/FormStore.d.ts +233 -0
- package/dist/Helpers/FormStore.js +752 -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 +252 -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 +68 -0
- package/dist/Helpers/PopoverHelpers.js +195 -0
- package/dist/Helpers/RichTextHelpers.d.ts +77 -0
- package/dist/Helpers/RichTextHelpers.js +1072 -0
- package/dist/Helpers/RowsStore.d.ts +166 -0
- package/dist/Helpers/RowsStore.js +606 -0
- package/dist/Helpers/TableColumns.d.ts +47 -0
- package/dist/Helpers/TableColumns.js +282 -0
- package/dist/Helpers/TableExport.d.ts +30 -0
- package/dist/Helpers/TableExport.js +97 -0
- package/dist/Helpers/TableHelpers.d.ts +110 -0
- package/dist/Helpers/TableHelpers.js +301 -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 +174 -0
- package/dist/Interfaces/ApiInterfaces.js +30 -0
- package/dist/Interfaces/ChatInterfaces.d.ts +240 -0
- package/{src/sso-client.tsx → dist/Interfaces/ChatInterfaces.js} +1 -1
- package/dist/Interfaces/CrudInterfaces.d.ts +232 -0
- package/dist/Interfaces/CrudInterfaces.js +1 -0
- package/dist/Interfaces/DateInterfaces.d.ts +154 -0
- package/dist/Interfaces/DateInterfaces.js +29 -0
- package/dist/Interfaces/ExcelInterfaces.d.ts +215 -0
- package/dist/Interfaces/ExcelInterfaces.js +45 -0
- package/dist/Interfaces/FormInterfaces.d.ts +402 -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 +351 -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 +95 -0
- package/dist/Services/BrowserApi.js +129 -0
- package/dist/Services/ChatApi.d.ts +37 -0
- package/dist/Services/ChatApi.js +98 -0
- package/dist/Services/ExcelApi.d.ts +46 -0
- package/dist/Services/ExcelApi.js +196 -0
- package/dist/Services/ServerApi.d.ts +54 -0
- package/dist/Services/ServerApi.js +78 -0
- package/dist/Services/ThemeService.d.ts +17 -0
- package/dist/Services/ThemeService.js +83 -0
- package/dist/Shared.Index.d.ts +51 -0
- package/dist/Shared.Index.js +55 -0
- package/package.json +85 -33
- package/src/Styles/Nexus.Base.css +70 -0
- package/src/Styles/Nexus.Button.css +153 -0
- package/src/Styles/Nexus.Calendar.css +275 -0
- package/src/Styles/Nexus.Chat.css +1489 -0
- package/src/Styles/Nexus.Choice.css +345 -0
- package/src/Styles/Nexus.Crud.css +161 -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.Excel.css +370 -0
- package/src/Styles/Nexus.Form.css +321 -0
- package/src/Styles/Nexus.Index.css +36 -0
- package/src/Styles/Nexus.Input.css +637 -0
- package/src/Styles/Nexus.Markdown.css +265 -0
- package/src/Styles/Nexus.Menu.css +259 -0
- package/src/Styles/Nexus.Popup.css +731 -0
- package/src/Styles/Nexus.RichText.css +463 -0
- package/src/Styles/Nexus.Rows.css +596 -0
- package/src/Styles/Nexus.Slider.css +278 -0
- package/src/Styles/Nexus.Tab.Buttons.css +419 -0
- package/src/Styles/Nexus.Table.css +1296 -0
- package/src/Styles/Nexus.Theme.Picker.css +232 -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 -29
- package/src/client.ts +0 -31
- package/src/components/documents/button.tsx +0 -133
- 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 -64
- 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 -123
- 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 -53
- 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 -163
- 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.grid-view.css +0 -42
- 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,302 @@
|
|
|
1
|
+
import { ExcelDataType, ExcelRowKind } from "../Interfaces/ExcelInterfaces.js";
|
|
2
|
+
/*
|
|
3
|
+
* Turning a sheet's columns into inputs, a verify's rows into editable rows and back, and handing the person a file.
|
|
4
|
+
* Nothing here calls anything: the calls are in Services/ExcelApi.ts.
|
|
5
|
+
*
|
|
6
|
+
* Two rules decide every reading below, and both come from the backend:
|
|
7
|
+
* - every value travels as **text**, the way it reads in the sheet, because one wire shape serves a verify and an
|
|
8
|
+
* import and the import reads it with the same parser that read the file;
|
|
9
|
+
* - a choice travels as its **key** and never as an id, so a reference cell is shown as the words the person picked
|
|
10
|
+
* and the key beside it is carried through untouched.
|
|
11
|
+
*/
|
|
12
|
+
/** What a spreadsheet file is called in a file picker. */
|
|
13
|
+
export const EXCEL_FILE_ACCEPT = ".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
14
|
+
/** The field of an editor row that holds the sheet row it came from. Not a column, so no input is drawn for it. */
|
|
15
|
+
export const EXCEL_ROW_NUMBER = "__excelRow";
|
|
16
|
+
/** The field of an editor row that holds each reference column's key, which the person never sees or types. */
|
|
17
|
+
export const EXCEL_ROW_KEYS = "__excelKeys";
|
|
18
|
+
/** The field of an editor row that holds what the verify answered for it, so an edit can be told from an untouched row. */
|
|
19
|
+
export const EXCEL_ROW_SENT = "__excelSent";
|
|
20
|
+
/* Texts of the spreadsheet side. One table, so a word is changed or translated in one place. */
|
|
21
|
+
const DEFAULT_TEXT = {
|
|
22
|
+
template: "Download template",
|
|
23
|
+
templateHint: "The file to fill in: every list is a dropdown, and the ids beside a choice fill themselves.",
|
|
24
|
+
choose: "Choose file",
|
|
25
|
+
change: "Choose another file",
|
|
26
|
+
checking: "Checking",
|
|
27
|
+
nothingPicked: "No file picked yet.",
|
|
28
|
+
verifyFailed: "The file could not be read.",
|
|
29
|
+
newRows: "New rows",
|
|
30
|
+
duplicates: "Already there",
|
|
31
|
+
duplicateHint: "Nothing here is updated. Edit a row to add it as a new record instead.",
|
|
32
|
+
toFix: "What needs fixing",
|
|
33
|
+
whyDuplicate: "Why these are already there",
|
|
34
|
+
referenceLocked: "Picked in the spreadsheet: this list belongs to the server.",
|
|
35
|
+
generated: "The file fills this in.",
|
|
36
|
+
importLabel: "Import",
|
|
37
|
+
importNothing: "There is no row to import.",
|
|
38
|
+
importedNone: "Nothing was written.",
|
|
39
|
+
refused: "Not written",
|
|
40
|
+
exportWarning: "Export is cut short",
|
|
41
|
+
close: "Close",
|
|
42
|
+
startOver: "Start over",
|
|
43
|
+
};
|
|
44
|
+
let texts = { ...DEFAULT_TEXT };
|
|
45
|
+
/** Rewords or translates the spreadsheet side's own texts. Names only what changes. */
|
|
46
|
+
export function configureExcelText(changes) {
|
|
47
|
+
texts = { ...texts, ...changes };
|
|
48
|
+
}
|
|
49
|
+
/** One of the spreadsheet side's texts. */
|
|
50
|
+
export function excelText(key) {
|
|
51
|
+
return texts[key];
|
|
52
|
+
}
|
|
53
|
+
/* Columns as inputs */
|
|
54
|
+
/** How wide a column is drawn, by what it holds. The template sizes its own columns the same way. */
|
|
55
|
+
const COLUMN_WIDTH = {
|
|
56
|
+
[ExcelDataType.Text]: "12rem",
|
|
57
|
+
[ExcelDataType.Note]: "20rem",
|
|
58
|
+
[ExcelDataType.WholeNumber]: "8rem",
|
|
59
|
+
[ExcelDataType.Decimal]: "8rem",
|
|
60
|
+
[ExcelDataType.Amount]: "9rem",
|
|
61
|
+
[ExcelDataType.Date]: "9.5rem",
|
|
62
|
+
[ExcelDataType.DateTime]: "12rem",
|
|
63
|
+
[ExcelDataType.Time]: "7.5rem",
|
|
64
|
+
[ExcelDataType.YesNo]: "7rem",
|
|
65
|
+
[ExcelDataType.List]: "16rem",
|
|
66
|
+
};
|
|
67
|
+
/** The shapes a zone-less moment may be written in, as the backend reads them. */
|
|
68
|
+
const MOMENT_PATTERN = {
|
|
69
|
+
source: "[0-9]{4}[-/][0-9]{2}[-/][0-9]{2}([ T][0-9]{2}:[0-9]{2}(:[0-9]{2})?)?",
|
|
70
|
+
message: "Write a date and time as 2026-09-29 14:30.",
|
|
71
|
+
};
|
|
72
|
+
const YES_NO = [
|
|
73
|
+
{ value: "Yes", label: "Yes" },
|
|
74
|
+
{ value: "No", label: "No" },
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* One column of a sheet as an input of the review editors. The rules are the ones the sheet told the person about and
|
|
78
|
+
* no others, so a fix made here is checked the way the file was.
|
|
79
|
+
*
|
|
80
|
+
* Three columns are read-only on purpose:
|
|
81
|
+
* - a **reference**, because its list is the backend's and is picked in the spreadsheet; the cell shows the words the
|
|
82
|
+
* person picked and the key travels back untouched (a key read out of an answer is never typed, and never an id);
|
|
83
|
+
* - a column the **file** fills;
|
|
84
|
+
* - and a **date and time** is a text box rather than a picker, because the sheet's moment is a zone-less wall clock
|
|
85
|
+
* while the picker's value is a UTC moment: a picker here would shift every value by the person's own offset.
|
|
86
|
+
*/
|
|
87
|
+
export function excelColumnInput(column) {
|
|
88
|
+
const shared = {
|
|
89
|
+
name: column.field,
|
|
90
|
+
label: column.header,
|
|
91
|
+
width: COLUMN_WIDTH[column.dataType] ?? "12rem",
|
|
92
|
+
...(column.note ? { hint: column.note } : {}),
|
|
93
|
+
};
|
|
94
|
+
const required = column.isRequired === true;
|
|
95
|
+
const lengths = {
|
|
96
|
+
...(column.minLength && column.minLength > 0 ? { minLength: column.minLength } : {}),
|
|
97
|
+
...(column.maxLength && column.maxLength > 0 ? { maxLength: column.maxLength } : {}),
|
|
98
|
+
};
|
|
99
|
+
const limits = {
|
|
100
|
+
...(column.minimum === null || column.minimum === undefined ? {} : { min: column.minimum }),
|
|
101
|
+
...(column.maximum === null || column.maximum === undefined ? {} : { max: column.maximum }),
|
|
102
|
+
};
|
|
103
|
+
if (column.isReference)
|
|
104
|
+
return { ...shared, type: "text", readOnly: true, readOnlyMessage: excelText("referenceLocked") };
|
|
105
|
+
if (column.isReadOnly)
|
|
106
|
+
return { ...shared, type: "text", readOnly: true, readOnlyMessage: excelText("generated") };
|
|
107
|
+
switch (column.dataType) {
|
|
108
|
+
case ExcelDataType.Note:
|
|
109
|
+
return { ...shared, type: "textarea", rows: 2, required, ...lengths };
|
|
110
|
+
case ExcelDataType.WholeNumber:
|
|
111
|
+
return { ...shared, type: "number", required, decimals: 0, ...limits };
|
|
112
|
+
case ExcelDataType.Decimal:
|
|
113
|
+
return { ...shared, type: "number", required, ...limits };
|
|
114
|
+
case ExcelDataType.Amount:
|
|
115
|
+
return { ...shared, type: "number", required, decimals: 2, fixedDecimals: true, grouping: true, ...limits };
|
|
116
|
+
case ExcelDataType.Date:
|
|
117
|
+
return { ...shared, type: "date", required };
|
|
118
|
+
case ExcelDataType.DateTime:
|
|
119
|
+
return { ...shared, type: "text", required, pattern: MOMENT_PATTERN, placeholder: "2026-09-29 14:30" };
|
|
120
|
+
case ExcelDataType.Time:
|
|
121
|
+
return { ...shared, type: "time", required };
|
|
122
|
+
case ExcelDataType.YesNo:
|
|
123
|
+
return { ...shared, type: "dropdown", required, options: YES_NO, searchable: false };
|
|
124
|
+
default:
|
|
125
|
+
return { ...shared, type: "text", required, ...lengths };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Every column of a sheet as the inputs of one editor row, in the order they read. */
|
|
129
|
+
export function excelColumnInputs(columns) {
|
|
130
|
+
return columns.map(excelColumnInput);
|
|
131
|
+
}
|
|
132
|
+
/* Rows, both ways */
|
|
133
|
+
/** Whether the person edits this column's cell at all, or only reads it. */
|
|
134
|
+
export function isExcelColumnEditable(column) {
|
|
135
|
+
return column.isReference !== true && column.isReadOnly !== true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A verify's rows as the editors' rows: a value per column, the sheet's row number, and - held beside them, never
|
|
139
|
+
* drawn - each reference column's key and the values the verify answered with.
|
|
140
|
+
*
|
|
141
|
+
* A reference cell shows the **words** the person picked, because its key is not something anybody reads or types.
|
|
142
|
+
*/
|
|
143
|
+
export function excelEditorRows(rows, columns) {
|
|
144
|
+
return rows.map(row => {
|
|
145
|
+
const keys = {};
|
|
146
|
+
const editor = { [EXCEL_ROW_NUMBER]: row.number, [EXCEL_ROW_KEYS]: keys, [EXCEL_ROW_SENT]: { ...row.values } };
|
|
147
|
+
for (const column of columns) {
|
|
148
|
+
const value = row.values[column.field] ?? null;
|
|
149
|
+
if (column.isReference) {
|
|
150
|
+
keys[column.field] = value;
|
|
151
|
+
editor[column.field] = row.labels?.[column.field] ?? "";
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
editor[column.field] = toInputValue(column, value);
|
|
155
|
+
}
|
|
156
|
+
return editor;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** Which types are drawn by a number box, whose empty value is `null`. */
|
|
160
|
+
const NUMBER_TYPES = new Set([ExcelDataType.WholeNumber, ExcelDataType.Decimal, ExcelDataType.Amount]);
|
|
161
|
+
/**
|
|
162
|
+
* A cell's text as the input that draws it wants it: a number as a number, a choice unset rather than empty, and
|
|
163
|
+
* everything else as it reads.
|
|
164
|
+
*
|
|
165
|
+
* The three empties are not interchangeable. A number box is empty at `null`; a **dropdown** given `""` treats it as a
|
|
166
|
+
* value and reports it as one its list no longer offers ("Not found"), so an empty choice has to be left unset; a text
|
|
167
|
+
* or date box is empty at `""`.
|
|
168
|
+
*/
|
|
169
|
+
function toInputValue(column, value) {
|
|
170
|
+
if (value === null || value === "")
|
|
171
|
+
return NUMBER_TYPES.has(column.dataType) ? null : column.dataType === ExcelDataType.YesNo ? undefined : "";
|
|
172
|
+
switch (column.dataType) {
|
|
173
|
+
case ExcelDataType.WholeNumber:
|
|
174
|
+
case ExcelDataType.Decimal:
|
|
175
|
+
case ExcelDataType.Amount: {
|
|
176
|
+
const number = Number(value.replace(/,/g, ""));
|
|
177
|
+
return Number.isFinite(number) ? number : null;
|
|
178
|
+
}
|
|
179
|
+
case ExcelDataType.YesNo: {
|
|
180
|
+
const upper = value.trim().toUpperCase();
|
|
181
|
+
if (upper === "YES" || upper === "Y" || upper === "TRUE" || upper === "1")
|
|
182
|
+
return "Yes";
|
|
183
|
+
if (upper === "NO" || upper === "N" || upper === "FALSE" || upper === "0")
|
|
184
|
+
return "No";
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
default:
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** A cell as it travels: text, the way it reads in the sheet. A value the editors could not hold is sent back as it came. */
|
|
192
|
+
function toSheetValue(value) {
|
|
193
|
+
if (value === undefined || value === null)
|
|
194
|
+
return null;
|
|
195
|
+
if (typeof value === "boolean")
|
|
196
|
+
return value ? "Yes" : "No";
|
|
197
|
+
const written = String(value).trim();
|
|
198
|
+
return written.length ? written : null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Whether a cell still holds what the verify answered for it.
|
|
202
|
+
*
|
|
203
|
+
* It is not a comparison of texts, because a cell goes through an input on the way: a sheet that read `38450.50` comes
|
|
204
|
+
* back from a number box as `38450.5`, and `TRUE` comes back from the Yes/No choice as `Yes`. Both are the same value
|
|
205
|
+
* and neither is an edit, so the two are compared the way the input holds them.
|
|
206
|
+
*/
|
|
207
|
+
function isSameCell(column, current, sent) {
|
|
208
|
+
const asSheet = toSheetValue(current);
|
|
209
|
+
if (asSheet === (sent ?? null))
|
|
210
|
+
return true;
|
|
211
|
+
return toInputValue(column, asSheet) === toInputValue(column, sent ?? null);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The editors' rows as rows to send: every value as text, every reference as the key it came with, and the sheet's own
|
|
215
|
+
* row number kept so a problem can still name where it is.
|
|
216
|
+
*
|
|
217
|
+
* A cell the person did not touch goes back **exactly** as it came, which is what makes the round trip faithful: the
|
|
218
|
+
* backend reads its own text again, not a number an input rewrote on the way through.
|
|
219
|
+
*/
|
|
220
|
+
export function excelSheetRows(rows, columns) {
|
|
221
|
+
return rows.map((row, index) => {
|
|
222
|
+
const keys = (row[EXCEL_ROW_KEYS] ?? {});
|
|
223
|
+
const sent = (row[EXCEL_ROW_SENT] ?? {});
|
|
224
|
+
const values = {};
|
|
225
|
+
const labels = {};
|
|
226
|
+
for (const column of columns) {
|
|
227
|
+
if (column.isReference) {
|
|
228
|
+
values[column.field] = keys[column.field] ?? null;
|
|
229
|
+
labels[column.field] = toSheetValue(row[column.field]);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
values[column.field] = isSameCell(column, row[column.field], sent[column.field]) ? (sent[column.field] ?? null) : toSheetValue(row[column.field]);
|
|
233
|
+
}
|
|
234
|
+
const number = Number(row[EXCEL_ROW_NUMBER]);
|
|
235
|
+
return { number: Number.isFinite(number) && number > 0 ? number : index + 1, values, labels };
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Whether the person changed anything in a row since the verify answered it. */
|
|
239
|
+
export function isExcelRowEdited(row, columns) {
|
|
240
|
+
const sent = (row[EXCEL_ROW_SENT] ?? {});
|
|
241
|
+
for (const column of columns) {
|
|
242
|
+
if (!isExcelColumnEditable(column))
|
|
243
|
+
continue;
|
|
244
|
+
if (!isSameCell(column, row[column.field], sent[column.field]))
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
/* What is wrong, in the person's words */
|
|
250
|
+
/** Where a problem is, as the person reads it: the cell, a middle dot, and the column's heading ("B7 - City name"). */
|
|
251
|
+
export function excelProblemPlace(problem) {
|
|
252
|
+
return problem.cell ? `${problem.cell} ${String.fromCharCode(183)} ${problem.header}` : problem.header;
|
|
253
|
+
}
|
|
254
|
+
/** Every problem of these rows, in the order the rows read. */
|
|
255
|
+
export function excelProblems(rows) {
|
|
256
|
+
return rows.flatMap(row => row.problems ?? []);
|
|
257
|
+
}
|
|
258
|
+
/** The rows of one kind. */
|
|
259
|
+
export function excelRowsOfKind(rows, ...kinds) {
|
|
260
|
+
return rows.filter(row => kinds.includes(row.kind));
|
|
261
|
+
}
|
|
262
|
+
/** The rows an import would try to write: the new ones, and the ones still waiting on a fix. */
|
|
263
|
+
export function excelWritableRows(rows) {
|
|
264
|
+
return excelRowsOfKind(rows, ExcelRowKind.New, ExcelRowKind.Invalid);
|
|
265
|
+
}
|
|
266
|
+
/** More than one of the subject: "cities", "boxes", "rows". An English ending, which a text change can replace. */
|
|
267
|
+
export function excelPlural(subject, plural) {
|
|
268
|
+
if (plural)
|
|
269
|
+
return plural;
|
|
270
|
+
if (/(s|x|z|ch|sh)$/i.test(subject))
|
|
271
|
+
return `${subject}es`;
|
|
272
|
+
if (/[^aeiou]y$/i.test(subject))
|
|
273
|
+
return `${subject.slice(0, -1)}ies`;
|
|
274
|
+
return `${subject}s`;
|
|
275
|
+
}
|
|
276
|
+
/** A count said in the subject's own words: "1 city", "12 cities". */
|
|
277
|
+
export function excelCount(rows, subject, plural) {
|
|
278
|
+
if (rows === 1)
|
|
279
|
+
return `1 ${subject}`;
|
|
280
|
+
return `${rows.toLocaleString("en-US")} ${excelPlural(subject, plural)}`;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* What to tell the person before an export that will be cut short, as the owner asked: what is on screen, what will be
|
|
284
|
+
* written, and that filtering is how they get the rest.
|
|
285
|
+
*/
|
|
286
|
+
export function excelExportWarning(rows, limit, subject) {
|
|
287
|
+
return `The search on screen leaves ${excelCount(rows, subject)}, and one export writes at most ${limit.toLocaleString("en-US")}. The first ${limit.toLocaleString("en-US")} are written and the rest are left out. Search or filter for the rows you need to get them all.`;
|
|
288
|
+
}
|
|
289
|
+
/* The file itself */
|
|
290
|
+
/** Hands the person a file to save, under the name the answer gave it. */
|
|
291
|
+
export function saveExcelFile(download) {
|
|
292
|
+
const url = URL.createObjectURL(download.blob);
|
|
293
|
+
const link = document.createElement("a");
|
|
294
|
+
link.href = url;
|
|
295
|
+
link.download = download.fileName;
|
|
296
|
+
link.rel = "noopener";
|
|
297
|
+
document.body.append(link);
|
|
298
|
+
link.click();
|
|
299
|
+
link.remove();
|
|
300
|
+
// Firefox needs the object URL a moment longer than the click.
|
|
301
|
+
setTimeout(() => URL.revokeObjectURL(url), 10_000);
|
|
302
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FormValues } from "../Interfaces/FormInterfaces.ts";
|
|
2
|
+
/** A form's unsaved changes. */
|
|
3
|
+
export interface FormDraft {
|
|
4
|
+
/** The changed values, by input name. */
|
|
5
|
+
values: FormValues;
|
|
6
|
+
/** The same inputs' values when the changes were made, to tell whether someone else saved them since. */
|
|
7
|
+
base: FormValues;
|
|
8
|
+
/** When the draft was saved (milliseconds since 1970). */
|
|
9
|
+
savedAt: number;
|
|
10
|
+
}
|
|
11
|
+
/** Drafts older than this are dropped when read: 30 days. */
|
|
12
|
+
export declare const FORM_DRAFT_MAX_AGE: number;
|
|
13
|
+
/** The draft kept under `key`, or `null`. A draft older than `maxAge` is dropped. */
|
|
14
|
+
export declare function readFormDraft(key: string, maxAge?: number): Promise<FormDraft | null>;
|
|
15
|
+
/** Keeps a draft under `key`. Resolves `true` once it is stored. */
|
|
16
|
+
export declare function writeFormDraft(key: string, draft: FormDraft): Promise<boolean>;
|
|
17
|
+
/** Removes the draft kept under `key`, if any. */
|
|
18
|
+
export declare function deleteFormDraft(key: string): Promise<void>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const DB_NAME = "nexus-forms";
|
|
2
|
+
const STORE_NAME = "drafts";
|
|
3
|
+
/** Drafts older than this are dropped when read: 30 days. */
|
|
4
|
+
export const FORM_DRAFT_MAX_AGE = 30 * 24 * 60 * 60 * 1000;
|
|
5
|
+
let database = null;
|
|
6
|
+
function openDatabase() {
|
|
7
|
+
if (typeof indexedDB === "undefined")
|
|
8
|
+
return Promise.resolve(null);
|
|
9
|
+
database ??= new Promise(resolve => {
|
|
10
|
+
try {
|
|
11
|
+
const request = indexedDB.open(DB_NAME, 1);
|
|
12
|
+
request.onupgradeneeded = () => request.result.createObjectStore(STORE_NAME);
|
|
13
|
+
request.onsuccess = () => {
|
|
14
|
+
const db = request.result;
|
|
15
|
+
// Another tab upgrading the database closes this connection; the next call opens a new one.
|
|
16
|
+
db.onversionchange = () => {
|
|
17
|
+
db.close();
|
|
18
|
+
database = null;
|
|
19
|
+
};
|
|
20
|
+
resolve(db);
|
|
21
|
+
};
|
|
22
|
+
request.onerror = () => resolve(null);
|
|
23
|
+
request.onblocked = () => resolve(null);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
resolve(null);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return database;
|
|
30
|
+
}
|
|
31
|
+
function run(mode, work) {
|
|
32
|
+
return openDatabase().then(db => new Promise(resolve => {
|
|
33
|
+
if (!db)
|
|
34
|
+
return resolve(null);
|
|
35
|
+
try {
|
|
36
|
+
const transaction = db.transaction(STORE_NAME, mode);
|
|
37
|
+
const request = work(transaction.objectStore(STORE_NAME));
|
|
38
|
+
transaction.oncomplete = () => resolve(request.result ?? null);
|
|
39
|
+
transaction.onerror = () => resolve(null);
|
|
40
|
+
transaction.onabort = () => resolve(null);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
resolve(null);
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
function isDraft(value) {
|
|
48
|
+
const draft = value;
|
|
49
|
+
return Boolean(draft && typeof draft === "object" && draft.values && typeof draft.values === "object" && typeof draft.savedAt === "number");
|
|
50
|
+
}
|
|
51
|
+
/** The draft kept under `key`, or `null`. A draft older than `maxAge` is dropped. */
|
|
52
|
+
export async function readFormDraft(key, maxAge = FORM_DRAFT_MAX_AGE) {
|
|
53
|
+
const found = await run("readonly", store => store.get(key));
|
|
54
|
+
if (!isDraft(found))
|
|
55
|
+
return null;
|
|
56
|
+
if (Date.now() - found.savedAt > maxAge) {
|
|
57
|
+
void deleteFormDraft(key);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return { values: found.values, base: found.base ?? {}, savedAt: found.savedAt };
|
|
61
|
+
}
|
|
62
|
+
/** Keeps a draft under `key`. Resolves `true` once it is stored. */
|
|
63
|
+
export async function writeFormDraft(key, draft) {
|
|
64
|
+
try {
|
|
65
|
+
const done = await run("readwrite", store => store.put(draft, key));
|
|
66
|
+
return done !== null;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Values the browser cannot store (a function in a value) keep no draft.
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Removes the draft kept under `key`, if any. */
|
|
74
|
+
export async function deleteFormDraft(key) {
|
|
75
|
+
await run("readwrite", store => store.delete(key));
|
|
76
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { FormErrors, FormRuleResult, FormValues, InputParams, InputValidationContext } from "../Interfaces/FormInterfaces.ts";
|
|
2
|
+
export interface FormStoreOptions<TValues extends FormValues = FormValues> {
|
|
3
|
+
inputs: readonly InputParams[];
|
|
4
|
+
validate?(values: TValues): FormRuleResult;
|
|
5
|
+
onFieldChange?(name: string, value: unknown, values: TValues): Partial<TValues> | null | undefined | void;
|
|
6
|
+
/** The user's date settings, so messages write dates the way the fields show them. */
|
|
7
|
+
context?: InputValidationContext;
|
|
8
|
+
/** The whole form is read-only or disabled: nothing is checked. */
|
|
9
|
+
locked?: boolean;
|
|
10
|
+
/** Unsaved changes are kept on this device under this key. */
|
|
11
|
+
draftKey?: string;
|
|
12
|
+
}
|
|
13
|
+
/** A draft brought back when the form opened. */
|
|
14
|
+
export interface FormRestoredDraft {
|
|
15
|
+
/** When the draft was saved (milliseconds since 1970). */
|
|
16
|
+
savedAt: number;
|
|
17
|
+
/** The inputs it brought back, in form order. */
|
|
18
|
+
names: readonly string[];
|
|
19
|
+
/** Of those, the ones whose saved value changed since the draft was made: someone else saved them. */
|
|
20
|
+
conflicts: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/** What the form's frame shows: the status line, the buttons, and the restored-draft notice. */
|
|
23
|
+
export interface FormStatus {
|
|
24
|
+
/** Some value differs from the ones the form opened with or saved last. */
|
|
25
|
+
changed: boolean;
|
|
26
|
+
/** A submit is running. */
|
|
27
|
+
submitting: boolean;
|
|
28
|
+
/** The values are loading: the inputs show placeholders. */
|
|
29
|
+
loading: boolean;
|
|
30
|
+
/** A submit ran and found problems: every message shows. */
|
|
31
|
+
showAll: boolean;
|
|
32
|
+
/** Inputs that need attention after a submit or a server's answer, in form order. */
|
|
33
|
+
problems: readonly string[];
|
|
34
|
+
/** A message about the whole form: the form's own rule after a submit, or a server's. */
|
|
35
|
+
message: string | null;
|
|
36
|
+
/** When the values were saved last. */
|
|
37
|
+
savedAt: number | null;
|
|
38
|
+
/** When the unsaved changes were last kept as a draft on this device. */
|
|
39
|
+
draftSavedAt: number | null;
|
|
40
|
+
/** A draft brought back when the form opened, until it is discarded, dismissed, or saved. */
|
|
41
|
+
restored: FormRestoredDraft | null;
|
|
42
|
+
/** The id of the `<form>` element, for Save buttons outside it. */
|
|
43
|
+
formId: string | null;
|
|
44
|
+
/** How many `FormButtons` show this form's status. Without any, the form shows its own message. */
|
|
45
|
+
buttons: number;
|
|
46
|
+
}
|
|
47
|
+
/** The form on the page, attached by the `Form` component. */
|
|
48
|
+
export interface FormView {
|
|
49
|
+
/** Checks the inputs on the page as a submit does: every message shows, and the first problem takes focus. Returns the inputs with problems. */
|
|
50
|
+
validate: () => string[];
|
|
51
|
+
/** Focuses an input by name, or the first one. */
|
|
52
|
+
focus: (name?: string) => void;
|
|
53
|
+
}
|
|
54
|
+
/** A store of any values, for code that works with every form (a hook, a bar of buttons). */
|
|
55
|
+
export type AnyFormStore = FormStore<any>;
|
|
56
|
+
export declare class FormStore<TValues extends FormValues = FormValues> {
|
|
57
|
+
private options;
|
|
58
|
+
private inputByName;
|
|
59
|
+
private values;
|
|
60
|
+
private initial;
|
|
61
|
+
private isStarted;
|
|
62
|
+
private ruleErrors;
|
|
63
|
+
/** Rows inputs are checked when asked: 10,000 rows on every keystroke would be slow, and the rows check themselves. */
|
|
64
|
+
private staleRules;
|
|
65
|
+
private crossErrors;
|
|
66
|
+
private crossMessage;
|
|
67
|
+
private serverErrors;
|
|
68
|
+
private serverMessage;
|
|
69
|
+
private forced;
|
|
70
|
+
private touched;
|
|
71
|
+
private showAll;
|
|
72
|
+
/** Inputs the page found problems in on the last submit. */
|
|
73
|
+
private attention;
|
|
74
|
+
private changedNames;
|
|
75
|
+
private revisions;
|
|
76
|
+
private generation;
|
|
77
|
+
private versions;
|
|
78
|
+
private fieldListeners;
|
|
79
|
+
private statusListeners;
|
|
80
|
+
private valueListeners;
|
|
81
|
+
private status;
|
|
82
|
+
private draftTimer;
|
|
83
|
+
/** The draft key already looked up. Drafts are written only after, so an old draft is never overwritten unread. */
|
|
84
|
+
private draftChecked;
|
|
85
|
+
private draftSeq;
|
|
86
|
+
/** The user cancelled: no draft is written until they change a value again. */
|
|
87
|
+
private draftDropped;
|
|
88
|
+
/** A load during a render tells nobody; `announce` does, once the form has mounted. */
|
|
89
|
+
private quiet;
|
|
90
|
+
private unannounced;
|
|
91
|
+
/** The form on the page. Set by `Form`. */
|
|
92
|
+
view: FormView | null;
|
|
93
|
+
/** Submits the `<form>`, as its Save button does. Set by `SubmitForm`. */
|
|
94
|
+
submitter: (() => void) | null;
|
|
95
|
+
/** Whether the form has loaded its first values. */
|
|
96
|
+
get started(): boolean;
|
|
97
|
+
get inputs(): readonly InputParams[];
|
|
98
|
+
/** Every value, plus the fields the record came with. The same object until something changes; do not change it. */
|
|
99
|
+
getValue(): TValues;
|
|
100
|
+
get(name: string): unknown;
|
|
101
|
+
/** The values the form opened with, or saved last. */
|
|
102
|
+
getInitial(): TValues;
|
|
103
|
+
/** Only the values that differ from the ones the form opened with or saved last. */
|
|
104
|
+
getChanges(): Partial<TValues>;
|
|
105
|
+
/** The inputs whose values changed, in form order. */
|
|
106
|
+
changedInputs(): string[];
|
|
107
|
+
isChanged(): boolean;
|
|
108
|
+
getStatus: () => FormStatus;
|
|
109
|
+
/** The message an input shows for the form: a server's, the form's rule once the input was left or a submit ran, or its own rule after a submit its input missed. */
|
|
110
|
+
shownError(name: string): string | undefined;
|
|
111
|
+
/** The shown message, with the form's rule messages given, such as the ones before a change. */
|
|
112
|
+
private shownWith;
|
|
113
|
+
/** The input's own rule message for its value, whether it shows or not. */
|
|
114
|
+
ruleError(name: string): string | null;
|
|
115
|
+
/** The form's own rule message for an input, whether it shows or not. */
|
|
116
|
+
formRuleError(name: string): string | null;
|
|
117
|
+
/** The form's own rule message about the whole form. */
|
|
118
|
+
get formRuleMessage(): string | null;
|
|
119
|
+
/** Inputs whose values break a rule, their own or the form's, in form order. Inputs on the page may find more, such as text that is not a date yet. */
|
|
120
|
+
problems(): string[];
|
|
121
|
+
/** Whether every value passes, as far as the values tell. `validate()` also asks the inputs on the page. */
|
|
122
|
+
isValid(): boolean;
|
|
123
|
+
/** Changes whenever the input must render again: a new value from code, or a message it shows. */
|
|
124
|
+
fieldVersion(name: string): number;
|
|
125
|
+
/** The input's render stamp: it mounts again when this changes, to show a value that did not come from typing. */
|
|
126
|
+
stamp(name: string): string;
|
|
127
|
+
subscribeField(name: string, listener: () => void): () => void;
|
|
128
|
+
subscribeStatus: (listener: () => void) => (() => void);
|
|
129
|
+
/** Every value after each change. `name` is the input the user changed, or `null` for a load, a reset, or several at once. */
|
|
130
|
+
subscribeValue: (listener: (values: TValues, name: string | null) => void) => (() => void);
|
|
131
|
+
/**
|
|
132
|
+
* New inputs or rules. `recheck` checks every value again (the form does after a change of inputs or settings);
|
|
133
|
+
* without it, only the callbacks are swapped, which a parent's render does every time.
|
|
134
|
+
*/
|
|
135
|
+
configure(options: FormStoreOptions<TValues>, recheck?: boolean): void;
|
|
136
|
+
/**
|
|
137
|
+
* Loads values, such as a record from a server. They become the values the form compares with, and every input shows
|
|
138
|
+
* them. With `keepChanged`, the values the user changed stay (a refresh that merges): only the others change, and the
|
|
139
|
+
* names returned are the changed values whose loaded ones also changed, so the page can say so. `draft: false` leaves
|
|
140
|
+
* a saved draft for a later load (the form's placeholders show while the real values load). `quiet` tells no listener
|
|
141
|
+
* until `announce()`, for a load during a render.
|
|
142
|
+
*/
|
|
143
|
+
load(values?: Partial<TValues>, { keepChanged, draft, quiet }?: {
|
|
144
|
+
keepChanged?: boolean;
|
|
145
|
+
draft?: boolean;
|
|
146
|
+
quiet?: boolean;
|
|
147
|
+
}): string[];
|
|
148
|
+
/** Tells listeners about a quiet load. */
|
|
149
|
+
announce(): void;
|
|
150
|
+
private loadValues;
|
|
151
|
+
/** Brings back the values the form opened with or saved last, and clears every message. The draft goes too. */
|
|
152
|
+
reset(): void;
|
|
153
|
+
/** Sets a value from code. Its input shows it at once, and it counts as a change. */
|
|
154
|
+
set(name: string, value: unknown): void;
|
|
155
|
+
/** Sets several values from code, as one change. */
|
|
156
|
+
setValues(values: Partial<TValues>): void;
|
|
157
|
+
/**
|
|
158
|
+
* A value the user typed or picked, from the input rendered with `stamp`. An input that was replaced since (by a value
|
|
159
|
+
* from code, a load, or a reset) can still report its last value as it leaves; that value is ignored.
|
|
160
|
+
*/
|
|
161
|
+
input(name: string, value: unknown, stamp: string): void;
|
|
162
|
+
/** Marks an input as left by the user: the form's rule messages on it show from now on. */
|
|
163
|
+
touch(name: string): void;
|
|
164
|
+
/**
|
|
165
|
+
* Shows messages from outside, such as a server's, on their inputs, with a message for the whole form. They replace
|
|
166
|
+
* the ones shown before; an input's message clears once its value changes. Names that are not inputs join the form's message.
|
|
167
|
+
*/
|
|
168
|
+
setErrors(errors?: FormErrors, message?: string | null): void;
|
|
169
|
+
/** Clears the messages set with `setErrors`. */
|
|
170
|
+
clearErrors(): void;
|
|
171
|
+
/** Checks every value as a submit does: every message shows, and the first problem takes focus. Returns whether all is valid. */
|
|
172
|
+
validate(): boolean;
|
|
173
|
+
/** Focuses an input by name, or the form's first input. */
|
|
174
|
+
focus(name?: string): void;
|
|
175
|
+
/** Submits the form as its Save button does. Only a `SubmitForm` or an `ApiForm` can. */
|
|
176
|
+
submit(): void;
|
|
177
|
+
/** Writes the draft now, e.g. when the page is hidden. With nothing changed, the draft is removed. */
|
|
178
|
+
flushDraft(): void;
|
|
179
|
+
/** Puts back the values of the draft brought back when the form opened, and removes the draft. */
|
|
180
|
+
discardDraft(): void;
|
|
181
|
+
/** Hides the notice of a restored draft and keeps its values. */
|
|
182
|
+
dismissDraft(): void;
|
|
183
|
+
/** Removes the draft and leaves the values as they are, e.g. when the user cancels. Drafts start again with the next change. */
|
|
184
|
+
dropDraft(): void;
|
|
185
|
+
/** @internal Shows every message, as a submit does. `forced` are inputs whose own messages missed a problem their values have. */
|
|
186
|
+
reveal(problems: string[], forced?: readonly string[]): string[];
|
|
187
|
+
/** @internal The inputs the page finds problems in, as values change after a submit. */
|
|
188
|
+
setAttention(names: string[]): void;
|
|
189
|
+
/** @internal A submit starts: messages from the last answer go. */
|
|
190
|
+
beginSubmit(): void;
|
|
191
|
+
/** @internal A submit ended without a save. */
|
|
192
|
+
endSubmit(): void;
|
|
193
|
+
/**
|
|
194
|
+
* @internal A save: the values sent become the ones the form compares with; values typed while it ran stay changes.
|
|
195
|
+
* `extra` (from the server, such as a new id) is set on top.
|
|
196
|
+
*/
|
|
197
|
+
commit(saved: TValues, extra?: Partial<TValues>): void;
|
|
198
|
+
/** @internal A save after which the form went back to its opening values (`resetOnSuccess`). */
|
|
199
|
+
markSaved(): void;
|
|
200
|
+
/** @internal */
|
|
201
|
+
setLoading(loading: boolean): void;
|
|
202
|
+
/** @internal */
|
|
203
|
+
setFormId(formId: string | null): void;
|
|
204
|
+
/** @internal A `FormButtons` shows the status; returns its removal. */
|
|
205
|
+
addButtons(): () => void;
|
|
206
|
+
private indexInputs;
|
|
207
|
+
/** Each input's value in the type the input reports; a missing one is the input's default. Other fields stay as they came. */
|
|
208
|
+
private normalize;
|
|
209
|
+
/** Inputs that were not there, or were hidden, get their value in the input's type, as a load gives it. */
|
|
210
|
+
private adoptNewInputs;
|
|
211
|
+
private change;
|
|
212
|
+
/** The inputs whose `dependsOn` names any of `names`. */
|
|
213
|
+
private followersOf;
|
|
214
|
+
private checkRule;
|
|
215
|
+
private checkAllRules;
|
|
216
|
+
/** Runs the form's own rule; returns the inputs whose message from it changed. */
|
|
217
|
+
private checkCross;
|
|
218
|
+
private clearMessages;
|
|
219
|
+
/** The form's message: a server's, or the form's own rule once a submit ran. */
|
|
220
|
+
private formMessage;
|
|
221
|
+
/** Inputs to point at: the ones the page found on the last submit, and those with a server's message, in form order. */
|
|
222
|
+
private problemList;
|
|
223
|
+
private nextRevision;
|
|
224
|
+
private bump;
|
|
225
|
+
private bumpAll;
|
|
226
|
+
private shownSnapshot;
|
|
227
|
+
private bumpShownChanges;
|
|
228
|
+
private patchStatus;
|
|
229
|
+
private emit;
|
|
230
|
+
private scheduleDraft;
|
|
231
|
+
/** Looks for a draft under the form's key and brings back its values, except those the user changed since the form opened. */
|
|
232
|
+
private restoreDraft;
|
|
233
|
+
}
|