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,348 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { iconArrowDown, iconArrowUp, iconCopy, iconDotsVertical, iconLock, iconRowInsertBottom, iconRowInsertTop, iconTrash, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
5
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
|
+
import { Menu } from "../Documents/Menu.js";
|
|
7
|
+
import { firstFocusable, labelText, RowCell, RowsFooter, RowsLegend, useRowsFrame, useRowsInput, useRowVersion } from "./RowsInput.js";
|
|
8
|
+
/** Height of a row: the input's box and the line under it. Measured from the first row once it renders. */
|
|
9
|
+
const ROW_HEIGHT = { sm: 29, md: 35, lg: 41 };
|
|
10
|
+
/** Rows rendered above and below the ones in view, so Tab and Enter always find the next row on the page. */
|
|
11
|
+
const OVERSCAN = 8;
|
|
12
|
+
const DEFAULT_MAX_HEIGHT = 440;
|
|
13
|
+
/** A column's width by input type, when its params give none. */
|
|
14
|
+
const COLUMN_WIDTH = {
|
|
15
|
+
text: "10rem",
|
|
16
|
+
email: "13rem",
|
|
17
|
+
password: "10rem",
|
|
18
|
+
search: "10rem",
|
|
19
|
+
tel: "9rem",
|
|
20
|
+
url: "13rem",
|
|
21
|
+
textarea: "14rem",
|
|
22
|
+
number: "7.5rem",
|
|
23
|
+
checkbox: "6rem",
|
|
24
|
+
switch: "6rem",
|
|
25
|
+
radio: "6rem",
|
|
26
|
+
"checkbox-group": "16rem",
|
|
27
|
+
"radio-group": "16rem",
|
|
28
|
+
dropdown: "10rem",
|
|
29
|
+
date: "10rem",
|
|
30
|
+
"date-range": "15rem",
|
|
31
|
+
time: "8rem",
|
|
32
|
+
"date-time": "14rem",
|
|
33
|
+
slider: "10rem",
|
|
34
|
+
"range-slider": "12rem",
|
|
35
|
+
};
|
|
36
|
+
/** Columns that keep their width instead of sharing the spare room. */
|
|
37
|
+
const FIXED_TYPES = new Set(["checkbox", "switch", "radio"]);
|
|
38
|
+
const ENTER_MOVES = new Set(["text", "email", "tel", "url", "search", "password", "number"]);
|
|
39
|
+
const cssLength = (value) => (typeof value === "number" ? `${value}px` : value);
|
|
40
|
+
/** How a cell lines up: numbers at the end, checkboxes and radios in the middle, the rest at the start. */
|
|
41
|
+
function cellKind(params) {
|
|
42
|
+
if (params.type === "number")
|
|
43
|
+
return "number";
|
|
44
|
+
if (FIXED_TYPES.has(params.type))
|
|
45
|
+
return "check";
|
|
46
|
+
return "text";
|
|
47
|
+
}
|
|
48
|
+
function columnWidth(params) {
|
|
49
|
+
return cssLength(params.width ?? COLUMN_WIDTH[params.type] ?? "12rem");
|
|
50
|
+
}
|
|
51
|
+
/** One row of inputs. It renders again only when its position, its messages, or a value set from code change. */
|
|
52
|
+
const TabularRow = memo(function TabularRow({ store, rowKey, index, count, columns, context, onAction }) {
|
|
53
|
+
useRowVersion(store, rowKey);
|
|
54
|
+
const title = `${store.names.title} ${index + 1}`;
|
|
55
|
+
const invalid = context.showAll && store.rowProblems(rowKey) > 0;
|
|
56
|
+
const items = [];
|
|
57
|
+
// A row nobody may change (an inherited field) has nothing to offer: no copy of it, no move, no removal.
|
|
58
|
+
const fixed = Boolean(context.rowLock(rowKey, index));
|
|
59
|
+
if (context.canAdd && !fixed) {
|
|
60
|
+
items.push({ value: "insert-above", label: "Insert above", icon: _jsx(InlineIcon, { icon: iconRowInsertTop }) }, { value: "insert-below", label: "Insert below", icon: _jsx(InlineIcon, { icon: iconRowInsertBottom }) }, { value: "duplicate", label: "Duplicate", icon: _jsx(InlineIcon, { icon: iconCopy }) });
|
|
61
|
+
}
|
|
62
|
+
if (context.sortable && !fixed) {
|
|
63
|
+
if (items.length)
|
|
64
|
+
items.push({ separator: true });
|
|
65
|
+
items.push({ value: "move-up", label: "Move up", icon: _jsx(InlineIcon, { icon: iconArrowUp }), disabled: index === 0 || Boolean(context.rowLock(store.keyAt(index - 1), index - 1)) }, { value: "move-down", label: "Move down", icon: _jsx(InlineIcon, { icon: iconArrowDown }), disabled: index === count - 1 });
|
|
66
|
+
}
|
|
67
|
+
if (context.removable && !fixed) {
|
|
68
|
+
if (items.length)
|
|
69
|
+
items.push({ separator: true });
|
|
70
|
+
items.push({ value: "remove", label: "Remove", icon: _jsx(InlineIcon, { icon: iconTrash }), tone: "danger" });
|
|
71
|
+
}
|
|
72
|
+
// Leaving an input counts as using it: a repeated value shows its message from then on.
|
|
73
|
+
function onBlur(event) {
|
|
74
|
+
const cell = event.target.closest("[data-nx-cell]");
|
|
75
|
+
if (!cell || (event.relatedTarget instanceof Node && cell.contains(event.relatedTarget)))
|
|
76
|
+
return;
|
|
77
|
+
store.touch(rowKey, cell.dataset.nxCell ?? "");
|
|
78
|
+
}
|
|
79
|
+
return (_jsxs("div", { role: "row", className: "nx-tabular__row", "aria-rowindex": index + 2, "data-nx-row": rowKey, "data-problem": invalid || undefined, onBlur: onBlur, children: [context.numbered && (_jsxs("div", { role: "cell", className: "nx-tabular__num", children: [_jsx("span", { className: "nx-tabular__num-text", children: index + 1 }), items.length > 0 && (_jsx(Menu, { items: items, onSelect: value => onAction(value, rowKey), "aria-label": `${title} actions`, children: _jsx("button", { type: "button", className: "nx-tabular__num-button", "aria-label": `${title} actions`, title: `${title} actions`, "data-nx-row-actions": "", children: _jsx(InlineIcon, { icon: iconDotsVertical }) }) }))] })), columns.map(params => (_jsx("div", { role: "cell", className: cx("nx-tabular__cell", `nx-tabular__cell--${cellKind(params)}`), "data-nx-cell": params.name, children: _jsx(RowCell, { store: store, rowKey: rowKey, params: context.cellParams(rowKey, params, index), index: index, error: store.shownError(rowKey, params.name), layout: "tabular", idPrefix: context.idPrefix, size: context.size, readOnly: context.readOnly, disabled: context.disabled, lock: context.cellLock(rowKey, params.name, index) }, store.revision(rowKey, params.name)) }, params.name)))] }));
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Rows of inputs in a table, one column per input: a person's emails or addresses, invoice lines, a salary sheet. The
|
|
83
|
+
* value is an array of row objects, one value per input, with any other fields the rows came with (such as ids). Inputs
|
|
84
|
+
* come as params, the same objects a form takes, so the same list can show as sections in a `GroupForm`.
|
|
85
|
+
*
|
|
86
|
+
* Only the rows in view are on the page, so a sheet of thousands of rows opens and types as fast as a short one; the
|
|
87
|
+
* values live in a store outside React, and typing renders nothing. The header and the numbered column stay in place
|
|
88
|
+
* while the table scrolls. Each row has one control, beside its number and never in its place: a menu of insert,
|
|
89
|
+
* duplicate, move and remove. Enter in a text or number cell moves down the column, and on the last row adds a row.
|
|
90
|
+
*
|
|
91
|
+
* Every row is one line high, whatever it holds, so nothing shifts as the table is used; a column generated for every
|
|
92
|
+
* row (`cellReadOnly`) says so once on its header rather than on each of its cells; and `maxHeight` is brought down
|
|
93
|
+
* to whole rows, because a scroller that cuts the last one in half reads as broken rather than as scrollable.
|
|
94
|
+
*
|
|
95
|
+
* Inside a `<form>`, the rows are sent as JSON under `name`; a row with a problem blocks submit, and the first one
|
|
96
|
+
* scrolls into view with focus.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* <TabularForm name="emails" label="Emails" itemLabel="Email" max={5} unique={["email"]} inputs={[
|
|
100
|
+
* { type: "email", name: "email", label: "Email", required: true, pattern: TEXT_PATTERNS.email },
|
|
101
|
+
* { type: "dropdown", name: "type", label: "Type", options: EMAIL_TYPES },
|
|
102
|
+
* { type: "checkbox", name: "isVerified", label: "Verified" },
|
|
103
|
+
* { type: "radio", name: "isPrimary", label: "Primary", required: true },
|
|
104
|
+
* ]} />
|
|
105
|
+
*/
|
|
106
|
+
export function TabularForm(props) {
|
|
107
|
+
const { inputs, label, hint, error, size = "md", maxHeight = DEFAULT_MAX_HEIGHT, numbered = true, readOnly = false, disabled = false, required, max, className, style } = props;
|
|
108
|
+
const addable = props.addable !== false && !readOnly;
|
|
109
|
+
const removable = props.removable !== false && !readOnly && !disabled;
|
|
110
|
+
const sortable = Boolean(props.sortable) && !readOnly && !disabled;
|
|
111
|
+
const layoutRef = useRef(null);
|
|
112
|
+
const rows = useRowsInput(props, "Row", layoutRef);
|
|
113
|
+
const store = rows.store;
|
|
114
|
+
const { snapshot } = rows;
|
|
115
|
+
const frame = useRowsFrame(props, store);
|
|
116
|
+
const columns = useMemo(() => inputs.filter(params => !params.hidden), [inputs]);
|
|
117
|
+
const count = snapshot.keys.length;
|
|
118
|
+
const viewportRef = useRef(null);
|
|
119
|
+
const headRef = useRef(null);
|
|
120
|
+
const bodyRef = useRef(null);
|
|
121
|
+
const [rowHeight, setRowHeight] = useState(ROW_HEIGHT[size]);
|
|
122
|
+
const [viewportMax, setViewportMax] = useState(null);
|
|
123
|
+
const pageRows = Math.ceil((typeof maxHeight === "number" ? maxHeight : 600) / rowHeight);
|
|
124
|
+
const [range, setRange] = useState({ start: 0, end: pageRows + OVERSCAN });
|
|
125
|
+
const [status, setStatus] = useState(null);
|
|
126
|
+
const pendingRef = useRef(null);
|
|
127
|
+
const start = Math.min(range.start, count);
|
|
128
|
+
const end = Math.min(count, Math.max(range.end, start + 1));
|
|
129
|
+
const shownKeys = snapshot.keys.slice(start, end);
|
|
130
|
+
/** The rows in view, from the scroll position, and a margin around them. */
|
|
131
|
+
const updateRange = useCallback(() => {
|
|
132
|
+
const viewport = viewportRef.current;
|
|
133
|
+
if (!viewport)
|
|
134
|
+
return;
|
|
135
|
+
const head = headRef.current?.offsetHeight ?? 0;
|
|
136
|
+
const top = Math.max(0, viewport.scrollTop - head);
|
|
137
|
+
const first = Math.max(0, Math.floor(top / rowHeight) - OVERSCAN);
|
|
138
|
+
const last = Math.ceil((top + Math.max(viewport.clientHeight - head, rowHeight)) / rowHeight) + OVERSCAN;
|
|
139
|
+
// Focus in a row that is about to leave the page moves to the table, so the keyboard keeps its place.
|
|
140
|
+
const active = document.activeElement;
|
|
141
|
+
const activeRow = active instanceof Element ? active.closest("[data-nx-row]") : null;
|
|
142
|
+
if (activeRow && bodyRef.current?.contains(activeRow)) {
|
|
143
|
+
const index = store.indexOf(activeRow.dataset.nxRow ?? "");
|
|
144
|
+
if (index >= 0 && (index < first || index >= last))
|
|
145
|
+
viewport.focus({ preventScroll: true });
|
|
146
|
+
}
|
|
147
|
+
setRange(current => (current.start === first && current.end === last ? current : { start: first, end: last }));
|
|
148
|
+
}, [rowHeight, store]);
|
|
149
|
+
// The real row height, once a row is on the page (custom styles may change it).
|
|
150
|
+
useLayoutEffect(() => {
|
|
151
|
+
const row = bodyRef.current?.querySelector(":scope > .nx-tabular__row");
|
|
152
|
+
const height = row?.offsetHeight;
|
|
153
|
+
if (height && height !== rowHeight)
|
|
154
|
+
setRowHeight(height);
|
|
155
|
+
});
|
|
156
|
+
/**
|
|
157
|
+
* `maxHeight`, brought down to whole rows. A scroller that cuts the last row in half reads as broken rather than as
|
|
158
|
+
* scrollable, and the header is not a row, so the arithmetic has to leave it out — as it does the frame and the
|
|
159
|
+
* horizontal scroll bar, neither of which is a row either.
|
|
160
|
+
*/
|
|
161
|
+
useLayoutEffect(() => {
|
|
162
|
+
const viewport = viewportRef.current;
|
|
163
|
+
const wanted = typeof maxHeight === "number" ? maxHeight : null;
|
|
164
|
+
if (!viewport || wanted === null || count === 0) {
|
|
165
|
+
if (viewportMax !== null)
|
|
166
|
+
setViewportMax(null);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const head = headRef.current?.offsetHeight ?? 0;
|
|
170
|
+
const chrome = viewport.offsetHeight - viewport.clientHeight;
|
|
171
|
+
const room = wanted - chrome - head;
|
|
172
|
+
// Not even one row fits: leave what was asked for rather than showing a header and a sliver.
|
|
173
|
+
const next = room < rowHeight ? null : chrome + head + Math.floor(room / rowHeight) * rowHeight;
|
|
174
|
+
if (next !== viewportMax)
|
|
175
|
+
setViewportMax(next);
|
|
176
|
+
});
|
|
177
|
+
// A resize, a new row height, or fewer rows change which rows are in view.
|
|
178
|
+
useLayoutEffect(() => {
|
|
179
|
+
const viewport = viewportRef.current;
|
|
180
|
+
if (!viewport)
|
|
181
|
+
return;
|
|
182
|
+
updateRange();
|
|
183
|
+
const observer = new ResizeObserver(updateRange);
|
|
184
|
+
observer.observe(viewport);
|
|
185
|
+
return () => observer.disconnect();
|
|
186
|
+
}, [updateRange, count]);
|
|
187
|
+
function tryFocus() {
|
|
188
|
+
const pending = pendingRef.current;
|
|
189
|
+
const body = bodyRef.current;
|
|
190
|
+
if (!pending || !body)
|
|
191
|
+
return;
|
|
192
|
+
const row = body.querySelector(`:scope > [data-nx-row="${CSS.escape(pending.key)}"]`);
|
|
193
|
+
if (!row)
|
|
194
|
+
return;
|
|
195
|
+
pendingRef.current = null;
|
|
196
|
+
const { target } = pending;
|
|
197
|
+
let element;
|
|
198
|
+
if (typeof target === "string")
|
|
199
|
+
element = firstFocusable(row.querySelector(`[data-nx-cell="${CSS.escape(target)}"]`));
|
|
200
|
+
else if ("actions" in target)
|
|
201
|
+
element = row.querySelector("[data-nx-row-actions]") ?? firstFocusable(row);
|
|
202
|
+
else
|
|
203
|
+
element = firstFocusable(row.querySelector("[data-nx-cell]")) ?? firstFocusable(row);
|
|
204
|
+
element?.focus();
|
|
205
|
+
}
|
|
206
|
+
layoutRef.current = {
|
|
207
|
+
focusRow(key, target) {
|
|
208
|
+
const index = store.indexOf(key);
|
|
209
|
+
const viewport = viewportRef.current;
|
|
210
|
+
if (index < 0 || !viewport)
|
|
211
|
+
return;
|
|
212
|
+
pendingRef.current = { key, target };
|
|
213
|
+
// Scroll the row into view below the header, and render the rows around it before the scroll event comes.
|
|
214
|
+
const head = headRef.current?.offsetHeight ?? 0;
|
|
215
|
+
const top = index * rowHeight;
|
|
216
|
+
if (top < viewport.scrollTop)
|
|
217
|
+
viewport.scrollTop = top;
|
|
218
|
+
else if (head + top + rowHeight > viewport.scrollTop + viewport.clientHeight)
|
|
219
|
+
viewport.scrollTop = head + top + rowHeight - viewport.clientHeight;
|
|
220
|
+
setRange(current => (index >= current.start && index < current.end ? current : { start: Math.max(0, index - OVERSCAN), end: index + pageRows + OVERSCAN }));
|
|
221
|
+
// After the render that brings the row (a layout effect), or in the next frame when nothing renders.
|
|
222
|
+
requestAnimationFrame(tryFocus);
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
useLayoutEffect(tryFocus);
|
|
226
|
+
const actionsRef = useRef(rows.actions);
|
|
227
|
+
actionsRef.current = rows.actions;
|
|
228
|
+
const onAction = useCallback((action, key) => {
|
|
229
|
+
const index = store.indexOf(key);
|
|
230
|
+
const run = actionsRef.current;
|
|
231
|
+
if (action === "insert-above")
|
|
232
|
+
run.add(undefined, index);
|
|
233
|
+
else if (action === "insert-below")
|
|
234
|
+
run.add(undefined, index + 1);
|
|
235
|
+
else if (action === "duplicate")
|
|
236
|
+
run.duplicate(key);
|
|
237
|
+
else if (action === "move-up")
|
|
238
|
+
run.move(key, -1);
|
|
239
|
+
else if (action === "move-down")
|
|
240
|
+
run.move(key, 1);
|
|
241
|
+
else
|
|
242
|
+
run.remove(key);
|
|
243
|
+
}, [store]);
|
|
244
|
+
/** The message or hint of the input with focus, shown under the table: cells have no room for their own. */
|
|
245
|
+
function readStatus() {
|
|
246
|
+
const active = document.activeElement;
|
|
247
|
+
const cell = active instanceof Element ? active.closest("[data-nx-cell]") : null;
|
|
248
|
+
const row = cell?.closest("[data-nx-row]");
|
|
249
|
+
let next = null;
|
|
250
|
+
if (cell && row && bodyRef.current?.contains(cell)) {
|
|
251
|
+
const message = cell.querySelector(".nx-field__message");
|
|
252
|
+
const text = message?.textContent?.trim() ?? "";
|
|
253
|
+
const isError = Boolean(message?.classList.contains("nx-field__message--error"));
|
|
254
|
+
const params = columns.find(item => item.name === cell.dataset.nxCell);
|
|
255
|
+
if (text)
|
|
256
|
+
next = { text: isError ? `${store.names.title} ${store.indexOf(row.dataset.nxRow ?? "") + 1}: ${text}` : `${params ? labelText(params) : ""}: ${text}`, error: isError };
|
|
257
|
+
}
|
|
258
|
+
setStatus(current => (current?.text === next?.text && current?.error === next?.error ? current : next));
|
|
259
|
+
}
|
|
260
|
+
const readStatusSoon = () => requestAnimationFrame(readStatus);
|
|
261
|
+
useLayoutEffect(() => {
|
|
262
|
+
if (status)
|
|
263
|
+
readStatus();
|
|
264
|
+
});
|
|
265
|
+
/** Enter moves down the column, Shift+Enter up; on the last row Enter adds a row. */
|
|
266
|
+
function onBodyKeyDown(event) {
|
|
267
|
+
if (event.key !== "Enter" || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
268
|
+
return;
|
|
269
|
+
const target = event.target;
|
|
270
|
+
if (!(target instanceof HTMLInputElement) || !ENTER_MOVES.has(target.type) || target.hasAttribute("aria-haspopup") || target.getAttribute("role") === "combobox")
|
|
271
|
+
return;
|
|
272
|
+
const cell = target.closest("[data-nx-cell]");
|
|
273
|
+
const row = target.closest("[data-nx-row]");
|
|
274
|
+
if (!cell || !row)
|
|
275
|
+
return;
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
const name = cell.dataset.nxCell ?? "";
|
|
278
|
+
const index = store.indexOf(row.dataset.nxRow ?? "") + (event.shiftKey ? -1 : 1);
|
|
279
|
+
if (index < 0)
|
|
280
|
+
return;
|
|
281
|
+
const next = store.keyAt(index);
|
|
282
|
+
if (next)
|
|
283
|
+
layoutRef.current?.focusRow(next, name);
|
|
284
|
+
else if (!event.shiftKey && rows.canAdd) {
|
|
285
|
+
const key = store.add();
|
|
286
|
+
layoutRef.current?.focusRow(key, name);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const context = useMemo(() => ({ idPrefix: frame.id, size, readOnly, disabled, numbered, removable, sortable, canAdd: rows.canAdd, showAll: snapshot.showAll, cellLock: rows.cellLock, cellParams: rows.cellParams, rowLock: rows.rowLock }), [frame.id, size, readOnly, disabled, numbered, removable, sortable, rows.canAdd, snapshot.showAll, rows.cellLock, rows.cellParams, rows.rowLock]);
|
|
290
|
+
/**
|
|
291
|
+
* Which columns are generated for **every** row on the page. The lock is then said once, on the column header,
|
|
292
|
+
* instead of on each of its cells: eighteen locks down one column is eighteen times the ink for one fact. The cells
|
|
293
|
+
* keep their read-only tint and their reason, which shows under the table when one is used.
|
|
294
|
+
*
|
|
295
|
+
* Only the rows in view are asked, which is what a column-wide rule (`cellReadOnly` on a name) answers the same way
|
|
296
|
+
* however the table is scrolled. A lock that really is per row leaves the column unlocked, which is what it means.
|
|
297
|
+
*/
|
|
298
|
+
const lockedColumns = useMemo(() => {
|
|
299
|
+
const locked = new Map();
|
|
300
|
+
for (const params of columns) {
|
|
301
|
+
if (readOnly || params.readOnly === true) {
|
|
302
|
+
locked.set(params.name, true);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (!shownKeys.length)
|
|
306
|
+
continue;
|
|
307
|
+
let reason;
|
|
308
|
+
for (const [offset, key] of shownKeys.entries()) {
|
|
309
|
+
// A locked row says nothing about its columns: the rows around it decide whether a column is generated.
|
|
310
|
+
if (rows.rowLock(key, start + offset))
|
|
311
|
+
continue;
|
|
312
|
+
const lock = rows.cellLock(key, params.name, start + offset);
|
|
313
|
+
if (!lock) {
|
|
314
|
+
reason = undefined;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
if (reason === undefined)
|
|
318
|
+
reason = typeof lock === "string" ? lock : true;
|
|
319
|
+
}
|
|
320
|
+
if (reason !== undefined)
|
|
321
|
+
locked.set(params.name, reason);
|
|
322
|
+
}
|
|
323
|
+
return locked;
|
|
324
|
+
// `shownKeys` is a fresh array on every render; the work is a call per visible cell, and nothing memoised is
|
|
325
|
+
// handed to the rows, so recomputing it costs a map and never a re-render of a row.
|
|
326
|
+
}, [columns, readOnly, rows.cellLock, rows.rowLock, shownKeys, start]);
|
|
327
|
+
// The number and the row's menu, side by side, plus the frame around them. Both are always there, so nothing moves.
|
|
328
|
+
const numberWidth = `calc(${Math.max(2, String(count).length)}ch + 18px + var(--nx-tabular-menu))`;
|
|
329
|
+
const widths = columns.map(columnWidth);
|
|
330
|
+
const template = [
|
|
331
|
+
numbered ? numberWidth : null,
|
|
332
|
+
...columns.map((params, index) => {
|
|
333
|
+
const grow = params.grow ?? (FIXED_TYPES.has(params.type) ? 0 : 1);
|
|
334
|
+
return grow > 0 ? `minmax(${widths[index]}, ${grow}fr)` : widths[index];
|
|
335
|
+
}),
|
|
336
|
+
]
|
|
337
|
+
.filter(Boolean)
|
|
338
|
+
.join(" ");
|
|
339
|
+
const minWidth = `calc(${[numbered ? numberWidth : "0px", ...widths].join(" + ")})`;
|
|
340
|
+
const tableStyle = { "--nx-tabular-columns": template, "--nx-tabular-num": numbered ? numberWidth : "0px", minWidth };
|
|
341
|
+
const invalid = Boolean(error || snapshot.message || snapshot.invalidRows);
|
|
342
|
+
return (_jsxs("fieldset", { id: frame.id, className: cx("nx-rows", "nx-tabular", `nx-tabular--${size}`, className), style: style, disabled: disabled, "data-problem": invalid || undefined, "data-readonly": readOnly || undefined, "aria-label": props["aria-label"], "aria-describedby": frame.messageId, children: [_jsx(RowsLegend, { id: frame.legendId, label: label, required: required || (props.min ?? 0) > 0, lock: frame.lock }), _jsx("input", { ref: rows.proxyRef, className: "nx-visually-hidden", tabIndex: -1, "aria-hidden": "true", form: props.form, onInvalid: rows.onInvalid }), _jsx("div", { ref: viewportRef, className: "nx-tabular__viewport", tabIndex: -1, style: { maxHeight: viewportMax ?? cssLength(maxHeight) }, onScroll: updateRange, children: _jsxs("div", { role: "table", className: "nx-tabular__table", style: tableStyle, "aria-labelledby": label ? frame.legendId : undefined, "aria-label": label ? undefined : props["aria-label"], "aria-rowcount": count + 1, children: [_jsx("div", { ref: headRef, role: "rowgroup", className: "nx-tabular__head", children: _jsxs("div", { role: "row", className: "nx-tabular__row", "aria-rowindex": 1, children: [numbered && (_jsxs("div", { role: "columnheader", className: "nx-tabular__num", children: [_jsx("span", { "aria-hidden": "true", children: "#" }), _jsx("span", { className: "nx-visually-hidden", children: store.names.title })] })), columns.map(params => {
|
|
343
|
+
const columnHint = params.hint;
|
|
344
|
+
const locked = lockedColumns.get(params.name);
|
|
345
|
+
const reason = typeof locked === "string" ? locked : locked ? "Read-only" : undefined;
|
|
346
|
+
return (_jsxs("div", { role: "columnheader", className: cx("nx-tabular__head-cell", `nx-tabular__cell--${cellKind(params)}`), title: reason ?? (typeof columnHint === "string" ? columnHint : undefined), "data-nx-locked": locked ? "" : undefined, children: [_jsx("span", { className: "nx-tabular__head-label", children: params.label ?? params.name }), params.required && !locked && (_jsx("span", { className: "nx-field__required", "aria-hidden": "true", children: "*" })), locked && _jsx(InlineIcon, { icon: iconLock, className: "nx-tabular__head-lock", "aria-hidden": "true" })] }, params.name));
|
|
347
|
+
})] }) }), _jsx("div", { ref: bodyRef, role: "rowgroup", className: "nx-tabular__body", style: { paddingTop: start * rowHeight, paddingBottom: Math.max(0, count - end) * rowHeight }, onKeyDown: onBodyKeyDown, onFocus: readStatus, onBlur: readStatusSoon, onInput: readStatusSoon, children: count === 0 ? (_jsx("div", { role: "row", className: "nx-tabular__row nx-tabular__row--empty", children: _jsx("div", { role: "cell", className: "nx-tabular__empty", children: frame.emptyMessage }) })) : (shownKeys.map((key, offset) => (_jsx(TabularRow, { store: store, rowKey: key, index: start + offset, count: count, columns: columns, context: context, onAction: onAction }, `${snapshot.generation}:${key}`)))) })] }) }), _jsx(RowsFooter, { messageId: frame.messageId, store: store, snapshot: snapshot, error: error, hint: hint, status: status, addLabel: frame.addLabel, addable: addable, canAdd: rows.canAdd, max: max, maxMessage: frame.maxMessage, size: size, onAdd: () => rows.actions.add(), onUndo: () => rows.actions.undo() })] }));
|
|
348
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ChangeEvent, ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
import type { InputDesign, TextRules, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
|
|
3
|
+
import type { Size } from "../../Interfaces/TypeInterfaces.ts";
|
|
4
|
+
type NativeTextAreaProps = Omit<ComponentPropsWithRef<"textarea">, "children" | keyof TextRules>;
|
|
5
|
+
export interface TextAreaProps extends NativeTextAreaProps, TextRules {
|
|
6
|
+
label?: ReactNode;
|
|
7
|
+
/** Help text under the box. */
|
|
8
|
+
hint?: ReactNode;
|
|
9
|
+
/** An error from outside, such as a server message. Shown before the field's own message. */
|
|
10
|
+
error?: string | null;
|
|
11
|
+
/** Default `standard`. */
|
|
12
|
+
design?: InputDesign;
|
|
13
|
+
/** Default `md`. */
|
|
14
|
+
size?: Size;
|
|
15
|
+
/** Decorative icon at the top left. */
|
|
16
|
+
icon?: ReactNode;
|
|
17
|
+
/** Visible lines. Default 3. */
|
|
18
|
+
rows?: number;
|
|
19
|
+
/** Grows with its text, from `rows` up to `maxRows`, then scrolls. Where the browser cannot, it keeps `rows` and a resize handle. */
|
|
20
|
+
autoResize?: boolean;
|
|
21
|
+
/** Tallest size with `autoResize`, in lines. Default 12. */
|
|
22
|
+
maxRows?: number;
|
|
23
|
+
/** Shows the character count, and the limit when `maxLength` is set. */
|
|
24
|
+
showCount?: boolean;
|
|
25
|
+
/** What a read-only field says when someone tries to change it. Default "Read-only: this field cannot be changed." */
|
|
26
|
+
readOnlyMessage?: string;
|
|
27
|
+
/** Name used in messages. Default: the label, when it is text. */
|
|
28
|
+
fieldName?: string;
|
|
29
|
+
/** When the field shows its own message. Default `blur`; submitting a form always shows it. */
|
|
30
|
+
validateOn?: ValidateOn;
|
|
31
|
+
/** The new text after each change. */
|
|
32
|
+
onValueChange?: (value: string, event: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
33
|
+
/** Class of the outer field. `textAreaClassName` goes on the text area. */
|
|
34
|
+
className?: string;
|
|
35
|
+
textAreaClassName?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A multi-line text input with the same label, hint, validation, and designs as `TextBox`.
|
|
39
|
+
*
|
|
40
|
+
* @example <TextArea label="Description" maxLength={500} showCount autoResize />
|
|
41
|
+
*/
|
|
42
|
+
export declare function TextArea(props: TextAreaProps): import("react").JSX.Element;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
4
|
+
import { Field } from "./Field.js";
|
|
5
|
+
import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
|
|
6
|
+
import { useTextField } from "./TextField.js";
|
|
7
|
+
/**
|
|
8
|
+
* A multi-line text input with the same label, hint, validation, and designs as `TextBox`.
|
|
9
|
+
*
|
|
10
|
+
* @example <TextArea label="Description" maxLength={500} showCount autoResize />
|
|
11
|
+
*/
|
|
12
|
+
export function TextArea(props) {
|
|
13
|
+
const { label, hint, error, design = "standard", size = "md", icon, rows = 3, autoResize = false, maxRows = 12, showCount = false, readOnlyMessage = "Read-only: this field cannot be changed.", fieldName, validateOn, onValueChange, className, textAreaClassName, style, required, minLength, maxLength, pattern, patternMessage, validate, placeholder, disabled, readOnly, ref, onChange, onBlur, onInvalid, onKeyDown, onClick, ...textAreaProps } = props;
|
|
14
|
+
const field = useTextField(props);
|
|
15
|
+
const rowsStyle = { "--nx-field-rows": rows, "--nx-field-max-rows": Math.max(rows, maxRows) };
|
|
16
|
+
// A lock at the top end of the box, and a note under it when someone tries to change the text, say why nothing happens.
|
|
17
|
+
const readOnlyNotice = useReadOnlyNotice(Boolean(readOnly) && !disabled, field.elementRef, readOnlyMessage, { keys: ["Enter"] });
|
|
18
|
+
function handleKeyDown(event) {
|
|
19
|
+
readOnlyNotice.onKeyDown(event);
|
|
20
|
+
onKeyDown?.(event);
|
|
21
|
+
}
|
|
22
|
+
function handleClick(event) {
|
|
23
|
+
readOnlyNotice.onClick(event);
|
|
24
|
+
onClick?.(event);
|
|
25
|
+
}
|
|
26
|
+
function handleBlur(event) {
|
|
27
|
+
readOnlyNotice.hide();
|
|
28
|
+
field.controlProps.onBlur(event);
|
|
29
|
+
}
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_jsx(Field, { controlId: field.controlId, messageId: field.messageId, label: label, required: required, design: design, size: size, hint: hint, error: field.error, icon: icon, actions: readOnlyNotice.lock, aside: field.count, disabled: disabled, readOnly: readOnly, raiseLabel: Boolean(placeholder), multiline: true, className: className, style: { ...rowsStyle, ...style }, children: _jsx("textarea", { ...textAreaProps, ...field.controlProps, className: cx("nx-field__control", autoResize && "nx-field__control--auto", textAreaClassName), rows: rows, placeholder: placeholder || " ", disabled: disabled, readOnly: readOnly, onClick: handleClick, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), readOnlyNotice.notice] }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ChangeEvent, type ComponentPropsWithRef, type ReactNode } from "react";
|
|
2
|
+
import type { InputDesign, TextRules, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
|
|
3
|
+
import type { Size } from "../../Interfaces/TypeInterfaces.ts";
|
|
4
|
+
export type TextBoxType = "text" | "email" | "password" | "search" | "tel" | "url";
|
|
5
|
+
type NativeInputProps = Omit<ComponentPropsWithRef<"input">, "type" | "size" | "pattern" | "prefix" | "children" | keyof TextRules>;
|
|
6
|
+
export interface TextBoxProps extends NativeInputProps, TextRules {
|
|
7
|
+
/** Default `text`. `password` adds a show-password button. */
|
|
8
|
+
type?: TextBoxType;
|
|
9
|
+
label?: ReactNode;
|
|
10
|
+
/** Help text under the box. */
|
|
11
|
+
hint?: ReactNode;
|
|
12
|
+
/** An error from outside, such as the server's "Email is already registered". Shown before the field's own message. */
|
|
13
|
+
error?: string | null;
|
|
14
|
+
/** Default `standard`. */
|
|
15
|
+
design?: InputDesign;
|
|
16
|
+
/** Default `md`. */
|
|
17
|
+
size?: Size;
|
|
18
|
+
/** Decorative icon at the start, e.g. `<InlineIcon icon={iconMail} />`. */
|
|
19
|
+
icon?: ReactNode;
|
|
20
|
+
/** Decorative icon at the end. */
|
|
21
|
+
iconEnd?: ReactNode;
|
|
22
|
+
/** Text before the value inside the box, e.g. "https://". Read after the label by screen readers. */
|
|
23
|
+
prefix?: ReactNode;
|
|
24
|
+
/** Text after the value inside the box, e.g. "@nexus.com". */
|
|
25
|
+
suffix?: ReactNode;
|
|
26
|
+
/** Buttons inside the box before the value, such as the rows per page in a table search box. */
|
|
27
|
+
actionsStart?: ReactNode;
|
|
28
|
+
/** Shows a clear button while there is text. Esc clears too. */
|
|
29
|
+
clearable?: boolean;
|
|
30
|
+
/** Shows the character count, and the limit when `maxLength` is set. */
|
|
31
|
+
showCount?: boolean;
|
|
32
|
+
/** What a read-only field says when someone tries to change it. Default "Read-only: this field cannot be changed." */
|
|
33
|
+
readOnlyMessage?: string;
|
|
34
|
+
/** Name used in messages, e.g. "Email is required." Default: the label, when it is text. */
|
|
35
|
+
fieldName?: string;
|
|
36
|
+
/** When the field shows its own message. Default `blur`; submitting a form always shows it. */
|
|
37
|
+
validateOn?: ValidateOn;
|
|
38
|
+
/** The new text after each change. */
|
|
39
|
+
onValueChange?: (value: string, event: ChangeEvent<HTMLInputElement>) => void;
|
|
40
|
+
/** Class of the outer field. `inputClassName` goes on the input. */
|
|
41
|
+
className?: string;
|
|
42
|
+
inputClassName?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A single-line text input with a label, hint, validation message, icons, and optional clear and show-password buttons.
|
|
46
|
+
* Uncontrolled by default (`defaultValue`), so typing does not re-render it; `value` with `onValueChange` also works.
|
|
47
|
+
* Inside a `<form>`, invalid fields block submit and show their messages.
|
|
48
|
+
*
|
|
49
|
+
* @example <TextBox label="Email" type="email" required pattern={TEXT_PATTERNS.email} icon={<InlineIcon icon={iconMail} />} />
|
|
50
|
+
*/
|
|
51
|
+
export declare function TextBox(props: TextBoxProps): import("react").JSX.Element;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { InlineIcon, iconEye, iconEyeOff, iconX } from "nexus-icons";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
|
+
import { Field, hasContent } from "./Field.js";
|
|
7
|
+
import { setInputValue } from "./InputField.js";
|
|
8
|
+
import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
|
|
9
|
+
import { useTextField } from "./TextField.js";
|
|
10
|
+
// Values that are not prose: no red spell-check underline, no capital first letter on phones.
|
|
11
|
+
const PLAIN_TYPES = new Set(["email", "password", "tel", "url"]);
|
|
12
|
+
/**
|
|
13
|
+
* A single-line text input with a label, hint, validation message, icons, and optional clear and show-password buttons.
|
|
14
|
+
* Uncontrolled by default (`defaultValue`), so typing does not re-render it; `value` with `onValueChange` also works.
|
|
15
|
+
* Inside a `<form>`, invalid fields block submit and show their messages.
|
|
16
|
+
*
|
|
17
|
+
* @example <TextBox label="Email" type="email" required pattern={TEXT_PATTERNS.email} icon={<InlineIcon icon={iconMail} />} />
|
|
18
|
+
*/
|
|
19
|
+
export function TextBox(props) {
|
|
20
|
+
const { type = "text", label, hint, error, design = "standard", size = "md", icon, iconEnd, prefix, suffix, actionsStart, clearable = false, showCount = false, readOnlyMessage = "Read-only: this field cannot be changed.", fieldName, validateOn, onValueChange, className, inputClassName, style, required, minLength, maxLength, pattern, patternMessage, validate, placeholder, disabled, readOnly, ref, onChange, onBlur, onInvalid, onKeyDown, onClick, ...inputProps } = props;
|
|
21
|
+
const field = useTextField(props);
|
|
22
|
+
const [revealed, setRevealed] = useState(false);
|
|
23
|
+
const editable = !disabled && !readOnly;
|
|
24
|
+
const plain = PLAIN_TYPES.has(type);
|
|
25
|
+
// A lock at the end of the box, and a note under it when someone tries to change the text, say why nothing happens.
|
|
26
|
+
const readOnlyNotice = useReadOnlyNotice(Boolean(readOnly) && !disabled, field.elementRef, readOnlyMessage);
|
|
27
|
+
function clear() {
|
|
28
|
+
const input = field.elementRef.current;
|
|
29
|
+
if (!input)
|
|
30
|
+
return;
|
|
31
|
+
setInputValue(input, "");
|
|
32
|
+
input.focus();
|
|
33
|
+
}
|
|
34
|
+
function handleKeyDown(event) {
|
|
35
|
+
readOnlyNotice.onKeyDown(event);
|
|
36
|
+
// Esc clears first; a second Esc reaches the page, e.g. to close a dialog.
|
|
37
|
+
if (clearable && editable && event.key === "Escape" && event.currentTarget.value) {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
clear();
|
|
41
|
+
}
|
|
42
|
+
onKeyDown?.(event);
|
|
43
|
+
}
|
|
44
|
+
function handleClick(event) {
|
|
45
|
+
readOnlyNotice.onClick(event);
|
|
46
|
+
onClick?.(event);
|
|
47
|
+
}
|
|
48
|
+
function handleBlur(event) {
|
|
49
|
+
readOnlyNotice.hide();
|
|
50
|
+
field.controlProps.onBlur(event);
|
|
51
|
+
}
|
|
52
|
+
const actions = (clearable && editable) || type === "password" || readOnlyNotice.lock ? (_jsxs(_Fragment, { children: [clearable && editable && (_jsx("button", { type: "button", className: "nx-field__action nx-field__action--clear", tabIndex: -1, "aria-label": "Clear", title: "Clear", onMouseDown: event => event.preventDefault(), onClick: clear, children: _jsx(InlineIcon, { icon: iconX }) })), type === "password" && (_jsx("button", { type: "button", className: "nx-field__action", "aria-label": "Show password", "aria-controls": field.controlId, "aria-pressed": revealed, title: revealed ? "Hide password" : "Show password", disabled: disabled, onMouseDown: event => event.preventDefault(), onClick: () => setRevealed(current => !current), children: _jsx(InlineIcon, { icon: revealed ? iconEyeOff : iconEye }) })), readOnlyNotice.lock] })) : null;
|
|
53
|
+
return (_jsxs(_Fragment, { children: [_jsx(Field, { controlId: field.controlId, messageId: field.messageId, label: label, required: required, design: design, size: size, hint: hint, error: field.error, icon: icon, iconEnd: iconEnd, prefix: prefix, suffix: suffix, actionsStart: actionsStart, actions: actions, aside: field.count, disabled: disabled, readOnly: readOnly,
|
|
54
|
+
// A prefix sits where the resting label would, so the label stays raised.
|
|
55
|
+
raiseLabel: Boolean(placeholder) || hasContent(prefix), className: className, style: style, children: _jsx("input", { spellCheck: plain ? false : undefined, autoCapitalize: plain ? "none" : undefined, ...inputProps, ...field.controlProps, type: type === "password" && revealed ? "text" : type, className: cx("nx-field__control", inputClassName),
|
|
56
|
+
// Never empty, so CSS :placeholder-shown can tell an empty field (hides the clear button, lowers the label).
|
|
57
|
+
placeholder: placeholder || " ", disabled: disabled, readOnly: readOnly, onClick: handleClick, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), readOnlyNotice.notice] }));
|
|
58
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ChangeEvent } from "react";
|
|
2
|
+
import type { TextRules } from "../../Interfaces/InputInterfaces.ts";
|
|
3
|
+
import { type InputFieldOptions, type TextElement } from "./InputField.tsx";
|
|
4
|
+
export interface TextFieldOptions<E extends TextElement> extends InputFieldOptions<E>, TextRules {
|
|
5
|
+
value?: string | number | readonly string[];
|
|
6
|
+
defaultValue?: string | number | readonly string[];
|
|
7
|
+
showCount?: boolean;
|
|
8
|
+
onValueChange?: (value: string, event: ChangeEvent<E>) => void;
|
|
9
|
+
}
|
|
10
|
+
/** Behavior shared by TextBox and TextArea: text rules, and a character count written straight to the DOM. */
|
|
11
|
+
export declare function useTextField<E extends TextElement>(options: TextFieldOptions<E>): {
|
|
12
|
+
controlProps: {
|
|
13
|
+
maxLength: number | undefined;
|
|
14
|
+
id: string;
|
|
15
|
+
ref: (node: E | null) => void;
|
|
16
|
+
"aria-invalid": boolean | undefined;
|
|
17
|
+
"aria-required": true | undefined;
|
|
18
|
+
"aria-describedby": string | undefined;
|
|
19
|
+
onChange: (event: ChangeEvent<E, Element>) => void;
|
|
20
|
+
onBlur: (event: import("react").FocusEvent<E, Element>) => void;
|
|
21
|
+
onInvalid: (event: import("react").FormEvent<E>) => void;
|
|
22
|
+
};
|
|
23
|
+
/** Character count for the field's `aside`, when `showCount` is on. */
|
|
24
|
+
count: import("react").JSX.Element | null;
|
|
25
|
+
controlId: string;
|
|
26
|
+
messageId: string;
|
|
27
|
+
elementRef: import("react").RefObject<E | null>;
|
|
28
|
+
error: string | null;
|
|
29
|
+
touch: () => void;
|
|
30
|
+
change: () => void;
|
|
31
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import { validateText } from "../../Helpers/ValidationHelpers.js";
|
|
5
|
+
import { useInputField } from "./InputField.js";
|
|
6
|
+
/** Behavior shared by TextBox and TextArea: text rules, and a character count written straight to the DOM. */
|
|
7
|
+
export function useTextField(options) {
|
|
8
|
+
const { value, defaultValue, showCount = false, required, minLength, maxLength, pattern, patternMessage, validate } = options;
|
|
9
|
+
const countRef = useRef(null);
|
|
10
|
+
const rules = { required, minLength, maxLength, pattern, patternMessage, validate };
|
|
11
|
+
const patternKey = pattern instanceof RegExp ? pattern.toString() : typeof pattern === "object" ? pattern.source : pattern;
|
|
12
|
+
function updateCount(element) {
|
|
13
|
+
if (countRef.current)
|
|
14
|
+
countRef.current.textContent = String(element.value.length);
|
|
15
|
+
}
|
|
16
|
+
const field = useInputField({
|
|
17
|
+
...options,
|
|
18
|
+
onChange: event => {
|
|
19
|
+
updateCount(event.currentTarget);
|
|
20
|
+
options.onChange?.(event);
|
|
21
|
+
options.onValueChange?.(event.currentTarget.value, event);
|
|
22
|
+
},
|
|
23
|
+
}, {
|
|
24
|
+
check: (element, name) => validateText(element.value, rules, name),
|
|
25
|
+
checkKeys: [value, required, minLength, maxLength, patternKey, patternMessage, validate],
|
|
26
|
+
onReset: updateCount,
|
|
27
|
+
});
|
|
28
|
+
const initialLength = String(value ?? defaultValue ?? "").length;
|
|
29
|
+
return {
|
|
30
|
+
...field,
|
|
31
|
+
controlProps: { ...field.controlProps, maxLength },
|
|
32
|
+
/** Character count for the field's `aside`, when `showCount` is on. */
|
|
33
|
+
count: showCount ? (_jsxs("span", { className: "nx-field__count", children: [_jsx("span", { ref: countRef, children: initialLength }), maxLength ? `/${maxLength}` : null] })) : null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DateScript, TimeInput } from "../../Interfaces/DateInterfaces.ts";
|
|
2
|
+
/** `columns`: hours, minutes (seconds), and AM/PM side by side, for any time. `list`: one list of slots, for bookings. */
|
|
3
|
+
export type TimePanelView = "columns" | "list";
|
|
4
|
+
export interface TimePanelProps {
|
|
5
|
+
/** The chosen time, as an ISO time, or `null` for none. */
|
|
6
|
+
value: string | null;
|
|
7
|
+
/** Columns: after each pick in a column, with the whole new time. List: when a slot is picked, before `onDone`. */
|
|
8
|
+
onValueChange: (value: string) => void;
|
|
9
|
+
/** The pick is finished: Enter, Done, Now, or a slot of the list. A popover closes on this. */
|
|
10
|
+
onDone: () => void;
|
|
11
|
+
/** Default `columns`. */
|
|
12
|
+
view?: TimePanelView;
|
|
13
|
+
/** The field's time format, with seconds already added or removed. Decides a 12- or 24-hour clock. */
|
|
14
|
+
format: string;
|
|
15
|
+
script?: DateScript;
|
|
16
|
+
/** Whether values have seconds ("HH:mm:ss"). */
|
|
17
|
+
seconds?: boolean;
|
|
18
|
+
/** Seconds between allowed times, from midnight. A list offers a slot per step, or one every 30 minutes for steps under 5 minutes. */
|
|
19
|
+
step?: number;
|
|
20
|
+
min?: TimeInput;
|
|
21
|
+
max?: TimeInput;
|
|
22
|
+
isTimeDisabled?: (iso: string) => boolean;
|
|
23
|
+
/** Moves focus into the panel when it mounts, as after ↓ in the box. */
|
|
24
|
+
autoFocus?: boolean;
|
|
25
|
+
/** Now and Done buttons under the columns. Default true; a panel that has its own buttons, as in the date and time picker, turns them off. */
|
|
26
|
+
footer?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The panel of a time picker: columns of hours, minutes, seconds, and AM/PM whose chosen items line up on the middle row, or
|
|
31
|
+
* a list of slots. Items outside `min` and `max`, off the `step`, or refused by `isTimeDisabled` are dimmed and cannot
|
|
32
|
+
* be picked. It holds no value of its own: the picker passes the value in and writes each pick to its box.
|
|
33
|
+
*/
|
|
34
|
+
export declare function TimePanel(props: TimePanelProps): import("react").JSX.Element;
|