nexus-shared 3.2.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2359 -0
- package/README.md +1911 -1123
- package/dist/Client.Index.d.ts +27 -0
- package/dist/Client.Index.js +27 -0
- package/dist/Components/Chats/ChatComposer.d.ts +7 -1
- package/dist/Components/Chats/ChatComposer.js +9 -6
- package/dist/Components/Chats/ChatParts.d.ts +15 -2
- package/dist/Components/Chats/ChatParts.js +15 -4
- package/dist/Components/Chats/ChatPopup.d.ts +46 -0
- package/dist/Components/Chats/ChatPopup.js +34 -0
- package/dist/Components/Chats/ChatThread.d.ts +9 -1
- package/dist/Components/Chats/ChatThread.js +54 -6
- package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
- package/dist/Components/Dashboards/DashboardBoard.js +594 -0
- package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
- package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
- package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
- package/dist/Components/Dashboards/WidgetFrame.js +45 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +24 -3
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +628 -247
- package/dist/Components/Forms/CrudActions.d.ts +137 -0
- package/dist/Components/Forms/CrudActions.js +214 -0
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +37 -2
- package/dist/Components/Forms/CrudParts.js +123 -7
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +174 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
- package/dist/Components/Forms/CrudWorkspace.js +177 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +6 -5
- package/dist/Components/Forms/Form.js +134 -21
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +182 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -6
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +12 -12
- package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
- package/dist/Components/Inputs/InputRenderer.js +37 -32
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +75 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +15 -2
- package/dist/Components/Inputs/RowsInput.js +96 -8
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +45 -32
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +11 -2
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TreeView.d.ts +76 -2
- package/dist/Components/Inputs/TreeView.js +227 -16
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +34 -7
- package/dist/Components/Layouts/Popup.js +143 -13
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- package/dist/Components/Messages/MailClient.d.ts +50 -0
- package/dist/Components/Messages/MailClient.js +410 -0
- package/dist/Components/Messages/MailColumns.d.ts +21 -0
- package/dist/Components/Messages/MailColumns.js +92 -0
- package/dist/Components/Messages/MailCompose.d.ts +46 -0
- package/dist/Components/Messages/MailCompose.js +154 -0
- package/dist/Components/Messages/MailReader.d.ts +43 -0
- package/dist/Components/Messages/MailReader.js +207 -0
- package/dist/Components/Messages/MailThread.d.ts +71 -0
- package/dist/Components/Messages/MailThread.js +185 -0
- package/dist/Components/Messages/MessageBell.d.ts +41 -0
- package/dist/Components/Messages/MessageBell.js +76 -0
- package/dist/Components/Viewers/DataTable.js +153 -49
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
- package/dist/Components/Viewers/DataTableParts.js +26 -14
- package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
- package/dist/Components/Viewers/OwnerFiles.js +700 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- package/dist/Helpers/ApiModules.js +4 -0
- package/dist/Helpers/AuditEntries.d.ts +50 -0
- package/dist/Helpers/AuditEntries.js +96 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/ChatBackend.d.ts +8 -1
- package/dist/Helpers/ChatBackend.js +18 -0
- package/dist/Helpers/ChatHelpers.d.ts +2 -0
- package/dist/Helpers/ChatHelpers.js +4 -1
- package/dist/Helpers/CrudBackend.d.ts +20 -2
- package/dist/Helpers/CrudBackend.js +46 -2
- package/dist/Helpers/CrudColumns.d.ts +91 -6
- package/dist/Helpers/CrudColumns.js +148 -7
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +116 -2
- package/dist/Helpers/CrudHelpers.js +256 -0
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +32 -0
- package/dist/Helpers/CrudRowForms.js +76 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- package/dist/Helpers/DashboardHelpers.d.ts +115 -0
- package/dist/Helpers/DashboardHelpers.js +305 -0
- package/dist/Helpers/DomainCards.d.ts +140 -0
- package/dist/Helpers/DomainCards.js +370 -0
- package/dist/Helpers/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +158 -0
- package/dist/Helpers/FileAdapter.js +173 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +100 -5
- package/dist/Helpers/FormStore.js +368 -25
- package/dist/Helpers/InputConditions.d.ts +56 -0
- package/dist/Helpers/InputConditions.js +192 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +395 -0
- package/dist/Helpers/InputNarrowing.d.ts +50 -0
- package/dist/Helpers/InputNarrowing.js +148 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +157 -74
- package/dist/Helpers/MailClientHelpers.d.ts +96 -0
- package/dist/Helpers/MailClientHelpers.js +211 -0
- package/dist/Helpers/MessageBackend.d.ts +152 -0
- package/dist/Helpers/MessageBackend.js +329 -0
- package/dist/Helpers/MessageHelpers.d.ts +160 -0
- package/dist/Helpers/MessageHelpers.js +573 -0
- package/dist/Helpers/MessageUnread.d.ts +95 -0
- package/dist/Helpers/MessageUnread.js +245 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
- package/dist/Helpers/NexusDriveAdapter.js +236 -0
- package/dist/Helpers/NumberHelpers.d.ts +156 -0
- package/dist/Helpers/NumberHelpers.js +242 -0
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +14 -2
- package/dist/Helpers/RequestCache.js +22 -3
- package/dist/Helpers/RowsStore.d.ts +61 -2
- package/dist/Helpers/RowsStore.js +203 -16
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +9 -3
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +38 -4
- package/dist/Helpers/TableHelpers.js +78 -10
- package/dist/Helpers/TableJoins.d.ts +13 -0
- package/dist/Helpers/TableJoins.js +34 -0
- package/dist/Helpers/TableStore.d.ts +28 -4
- package/dist/Helpers/TableStore.js +73 -11
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +156 -6
- package/dist/Helpers/TreeStore.js +547 -18
- package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
- package/dist/Interfaces/ApiInterfaces.js +10 -1
- package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
- package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
- package/dist/Interfaces/DashboardInterfaces.js +1 -0
- package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
- package/dist/Interfaces/DomainInterfaces.js +1 -0
- package/dist/Interfaces/FileInterfaces.d.ts +183 -0
- package/dist/Interfaces/FileInterfaces.js +6 -0
- package/dist/Interfaces/FormInterfaces.d.ts +460 -32
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +22 -3
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +102 -3
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +79 -6
- package/dist/Services/JoinSources.js +132 -21
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Shared.Index.d.ts +28 -0
- package/dist/Shared.Index.js +28 -0
- package/package.json +13 -2
- package/src/Styles/Nexus.Avatar.css +6 -5
- package/src/Styles/Nexus.Button.css +39 -4
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Choice.css +16 -0
- package/src/Styles/Nexus.Crud.css +486 -21
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Dropdown.css +8 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +437 -0
- package/src/Styles/Nexus.Form.css +5 -2
- package/src/Styles/Nexus.Index.css +7 -0
- package/src/Styles/Nexus.Input.css +73 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Menu.css +10 -1
- package/src/Styles/Nexus.Popup.css +135 -1
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Tab.Buttons.css +87 -22
- package/src/Styles/Nexus.Table.css +1458 -1387
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Themes.css +12 -6
- package/src/Styles/Nexus.Tokens.css +8 -3
- package/src/Styles/Nexus.Tree.css +41 -1
|
@@ -5,16 +5,18 @@ import { useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, us
|
|
|
5
5
|
import { flushSync } from "react-dom";
|
|
6
6
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
7
7
|
import { useBrowserTimeZone, useDatePreferences } from "../../Helpers/DatePreferences.js";
|
|
8
|
+
import { canCall, permissionsVersion, subscribePermissions } from "../../Helpers/Permissions.js";
|
|
8
9
|
import { fitColumnWidth, headLeafCells, startColumnDrag, startColumnResize } from "../../Helpers/TableColumns.js";
|
|
9
10
|
import { exportTableCsv, printTable } from "../../Helpers/TableExport.js";
|
|
10
11
|
import { asFilterInput, filterNames, filterRequest } from "../../Helpers/TableFilters.js";
|
|
11
12
|
import { joinKeys, joinSourceName, tableJoins } from "../../Helpers/TableJoins.js";
|
|
12
|
-
import { canPinColumn, columnAlign, columnValue, cssLength, DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, formatCellText, headBands, MIN_COLUMN_WIDTH, orderColumns, TABLE_ACTIONS_COLUMN, TABLE_INDEX_COLUMN, viewColumns, } from "../../Helpers/TableHelpers.js";
|
|
13
|
+
import { canPinColumn, columnAlign, columnValue, cssLength, DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, formatCellText, headBands, leadColumns, namingColumn, MIN_COLUMN_WIDTH, orderColumns, releasedPins, TABLE_ACTIONS_COLUMN, TABLE_INDEX_COLUMN, viewColumns, } from "../../Helpers/TableHelpers.js";
|
|
13
14
|
import { TableStore } from "../../Helpers/TableStore.js";
|
|
14
15
|
import { readUserPreferences, useNumberLocale, useUserPreferences } from "../../Helpers/UserPreferences.js";
|
|
15
16
|
import { Button } from "../Documents/Button.js";
|
|
16
17
|
import { Menu } from "../Documents/Menu.js";
|
|
17
18
|
import { SplitButton } from "../Documents/SplitButton.js";
|
|
19
|
+
import { useReadOnlyNotice } from "../Inputs/ReadOnlyNotice.js";
|
|
18
20
|
import { TextBox } from "../Inputs/TextBox.js";
|
|
19
21
|
import { toast } from "../Layouts/Toaster.js";
|
|
20
22
|
import { downloadExcelTemplate, exportExcel } from "../../Services/ExcelApi.js";
|
|
@@ -41,8 +43,10 @@ export function useTable() {
|
|
|
41
43
|
const STATE_PREFIX = "nx-table:";
|
|
42
44
|
/** The key of the section `pinnedRows` makes. */
|
|
43
45
|
const PINS_SECTION = "pins";
|
|
44
|
-
/**
|
|
45
|
-
const
|
|
46
|
+
/** A version that never changes: the lookups' for a table with no `join` column, the permissions' for one with no sheet. */
|
|
47
|
+
const NO_VERSION = () => 0;
|
|
48
|
+
/** The page size of a table read whole (`pager="none"`): every row on the first page. */
|
|
49
|
+
const WHOLE_PAGE = Number.MAX_SAFE_INTEGER;
|
|
46
50
|
function readSaved(key) {
|
|
47
51
|
if (!key || typeof localStorage === "undefined")
|
|
48
52
|
return null;
|
|
@@ -77,6 +81,9 @@ function usePerRow(make) {
|
|
|
77
81
|
return value;
|
|
78
82
|
}, [cache, make]);
|
|
79
83
|
}
|
|
84
|
+
/** No pinned column scrolls: the table is wide enough for them all. */
|
|
85
|
+
const NONE_RELEASED = new Set();
|
|
86
|
+
const sameKeys = (a, b) => a.size === b.size && [...a].every(key => b.has(key));
|
|
80
87
|
const sameNumbers = (a, b) => {
|
|
81
88
|
const keys = Object.keys(a);
|
|
82
89
|
return keys.length === Object.keys(b).length && keys.every(key => a[key] === b[key]);
|
|
@@ -99,17 +106,24 @@ const sameNumbers = (a, b) => {
|
|
|
99
106
|
* />
|
|
100
107
|
*/
|
|
101
108
|
export function DataTable(props) {
|
|
102
|
-
const { columns, rows: givenRows, load, loadKey, rowKey = "id", rowLabel, table: givenTable, searchable = true, searchPlaceholder = "Search…", toolbar, actions, add, columnsMenu = true, selectable = false, maxSelection = DEFAULT_MAX_SELECTION, onSelectionChange, massActions, selectBy, rowActions, onRowOpen, openOnClick: ownOpenOnClick, sections: ownSections, rowMarks, pinnedRows, pinnedLoading = false, pinnedLabel = "Pinned", pinnedMax, onReload, reorderable = true, resizable = true, pinnable = true, moreActions, exportable = true, printable = true, exportName, onImport, excel, pageSize: ownPageSize, pageSizes = DEFAULT_PAGE_SIZES, defaultSort = null, stateKey, emptyMessage = "No records yet.", emptyAction, maxHeight, fill = false, size = "md", id, className, style, ref, } = props;
|
|
109
|
+
const { columns: givenColumns, rows: givenRows, load, loadKey, rowKey = "id", rowLabel, table: givenTable, searchable = true, searchPlaceholder = "Search…", caption, toolbar, actions, add, columnsMenu = true, selectable = false, maxSelection = DEFAULT_MAX_SELECTION, onSelectionChange, massActions, selectBy, rowActions, onRowOpen, openOnClick: ownOpenOnClick, onPick, sections: ownSections, rowMarks, rowStrong, pinnedRows, pinnedLoading = false, pinnedLabel = "Pinned", pinnedMax, onReload, reorderable = true, resizable = true, pinnable = true, moreActions, exportable = true, printable = true, exportName, onImport, excel, pageSize: ownPageSize, pageSizes = DEFAULT_PAGE_SIZES, defaultSort = null, stateKey, currency, emptyMessage = "No records yet.", emptyAction, maxHeight, fill = false, size = "md", id, className, style, ref, } = props;
|
|
103
110
|
const paging = givenRows ? "client" : (props.paging ?? "client");
|
|
111
|
+
// A server's pages cannot all be shown at once: such a table keeps its paging in the footer instead.
|
|
112
|
+
const pager = props.pager === "none" && paging === "server" ? "footer" : (props.pager ?? "bar");
|
|
113
|
+
const whole = pager === "none";
|
|
104
114
|
const reloadable = props.reloadable ?? Boolean(load);
|
|
105
115
|
const searchDelay = props.searchDelay ?? (paging === "server" ? 300 : 0);
|
|
106
|
-
// A table with checkboxes numbers its rows: the number becomes the checkbox as soon as the row is under the pointer.
|
|
107
|
-
|
|
116
|
+
// A table with checkboxes numbers its rows: the number becomes the checkbox as soon as the row is under the pointer. So
|
|
117
|
+
// does one whose rows are picked, whose first column holds each row's radio button.
|
|
118
|
+
const pickable = Boolean(onPick);
|
|
119
|
+
const numbered = props.numbered ?? (selectable || pickable);
|
|
108
120
|
// The person's choices in the quick preference panel, once made, go over what the table names itself.
|
|
109
121
|
const preferences = useUserPreferences();
|
|
110
122
|
const openOnClick = preferences.rowClick ?? ownOpenOnClick ?? false;
|
|
111
|
-
/** The page size the table opens with: the person's default when this table offers it, else the table's own. */
|
|
123
|
+
/** The page size the table opens with: every row for a table read whole, the person's default when this table offers it, else the table's own. */
|
|
112
124
|
const openingPageSize = () => {
|
|
125
|
+
if (whole)
|
|
126
|
+
return WHOLE_PAGE;
|
|
113
127
|
const preferred = readUserPreferences().pageSize;
|
|
114
128
|
return preferred !== null && pageSizes.includes(preferred) ? preferred : (ownPageSize ?? DEFAULT_PAGE_SIZE);
|
|
115
129
|
};
|
|
@@ -117,20 +131,26 @@ export function DataTable(props) {
|
|
|
117
131
|
const table = givenTable ?? own;
|
|
118
132
|
// Prepared once for the store and the panel: the same params without what would make them filter on their own.
|
|
119
133
|
const filterInputs = useMemo(() => props.filterInputs?.map(asFilterInput), [props.filterInputs]);
|
|
120
|
-
|
|
134
|
+
// A picture or a logo (`lead`) first of the page's columns, as every table draws it until the user moves it.
|
|
135
|
+
const columns = useMemo(() => leadColumns(givenColumns), [givenColumns]);
|
|
136
|
+
table.configure({ columns, rowKey, rows: givenRows, load, paging, maxSelection, onSelectionChange, onPickChange: onPick, filterInputs });
|
|
121
137
|
useImperativeHandle(ref, () => table, [table]);
|
|
122
138
|
const state = useSyncExternalStore(table.subscribe, table.getState, table.getState);
|
|
123
139
|
// The columns whose values are other records', looked up by key (`join`). Only a table with one listens to the page's
|
|
124
140
|
// lookups, and it draws again - its rows too, through `cellOptions` - each time names come in.
|
|
125
141
|
const joins = useMemo(() => columns.flatMap(column => (column.join ? [column.join] : [])), [columns]);
|
|
126
142
|
const subscribeJoins = useCallback((listener) => (joins.length ? tableJoins.subscribe(listener) : () => { }), [joins.length]);
|
|
127
|
-
const joinsVersion = useSyncExternalStore(subscribeJoins, joins.length ? tableJoins.getVersion :
|
|
143
|
+
const joinsVersion = useSyncExternalStore(subscribeJoins, joins.length ? tableJoins.getVersion : NO_VERSION, NO_VERSION);
|
|
144
|
+
// A spreadsheet's items ask the app's permissions, as a CRUD's actions do, so only a table with one listens for them.
|
|
145
|
+
const hasSheet = Boolean(excel);
|
|
146
|
+
const subscribeSheet = useCallback((listener) => (hasSheet ? subscribePermissions(listener) : () => { }), [hasSheet]);
|
|
147
|
+
useSyncExternalStore(subscribeSheet, hasSheet ? permissionsVersion : NO_VERSION, NO_VERSION);
|
|
128
148
|
const dates = useDatePreferences();
|
|
129
149
|
const timeZone = useBrowserTimeZone();
|
|
130
150
|
const numbers = useNumberLocale();
|
|
131
151
|
// `joinsVersion` is read by no cell option: a new object is what tells the memoized rows that their names came in.
|
|
132
152
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
133
|
-
const cellOptions = useMemo(() => ({ preferences: dates, timeZone: timeZone ?? undefined, locale: numbers }), [dates, timeZone, numbers, joinsVersion]);
|
|
153
|
+
const cellOptions = useMemo(() => ({ preferences: dates, timeZone: timeZone ?? undefined, locale: numbers, currency }), [dates, timeZone, numbers, currency, joinsVersion]);
|
|
134
154
|
const tableId = useId();
|
|
135
155
|
// The search box is uncontrolled: a new key clears it when a new list starts over.
|
|
136
156
|
const [searchGeneration, setSearchGeneration] = useState(0);
|
|
@@ -147,7 +167,7 @@ export function DataTable(props) {
|
|
|
147
167
|
started.current = true;
|
|
148
168
|
const saved = readSaved(stateKey);
|
|
149
169
|
table.restore({
|
|
150
|
-
pageSize: saved?.pageSize && pageSizes.includes(saved.pageSize) ? saved.pageSize : openingPageSize(),
|
|
170
|
+
pageSize: !whole && saved?.pageSize && pageSizes.includes(saved.pageSize) ? saved.pageSize : openingPageSize(),
|
|
151
171
|
sort: saved?.sort !== undefined ? saved.sort : defaultSort,
|
|
152
172
|
hidden: saved?.hidden ?? columns.filter(column => column.hidden).map(column => column.key),
|
|
153
173
|
order: saved?.order,
|
|
@@ -173,6 +193,26 @@ export function DataTable(props) {
|
|
|
173
193
|
if (started.current && givenRows)
|
|
174
194
|
table.setRows(givenRows);
|
|
175
195
|
}, [table, givenRows]);
|
|
196
|
+
// Another table's settings behind the same table (a CRUD's scope keeps its own): read as on the first draw, before the
|
|
197
|
+
// rows its new list loads, and the settings on screen are not written under the new key (`restoredKey` is what says
|
|
198
|
+
// the store already holds that key's settings).
|
|
199
|
+
const restoredKey = useRef(stateKey);
|
|
200
|
+
useLayoutEffect(() => {
|
|
201
|
+
if (!started.current || restoredKey.current === stateKey)
|
|
202
|
+
return;
|
|
203
|
+
restoredKey.current = stateKey;
|
|
204
|
+
const saved = readSaved(stateKey);
|
|
205
|
+
table.restore({
|
|
206
|
+
pageSize: !whole && saved?.pageSize && pageSizes.includes(saved.pageSize) ? saved.pageSize : openingPageSize(),
|
|
207
|
+
sort: saved?.sort !== undefined ? saved.sort : (defaultSort ?? null),
|
|
208
|
+
hidden: saved?.hidden ?? columns.filter(column => column.hidden).map(column => column.key),
|
|
209
|
+
order: saved?.order ?? [],
|
|
210
|
+
widths: saved?.widths ?? {},
|
|
211
|
+
pinned: saved?.pinned ?? {},
|
|
212
|
+
}, { notify: true });
|
|
213
|
+
setOpenSections(new Set(saved?.openSections ?? (saved?.pinsOpen ? [PINS_SECTION] : [])));
|
|
214
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
215
|
+
}, [table, stateKey]);
|
|
176
216
|
// Another list behind the same table: start over from the first page, with no search and no selection.
|
|
177
217
|
useLayoutEffect(() => {
|
|
178
218
|
if (loadedKey.current === loadKey)
|
|
@@ -183,20 +223,24 @@ export function DataTable(props) {
|
|
|
183
223
|
void table.reload({ reset: true });
|
|
184
224
|
}, [table, loadKey]);
|
|
185
225
|
useEffect(() => () => clearTimeout(searchTimer.current), []);
|
|
186
|
-
// Saved settings follow the user's choices.
|
|
226
|
+
// Saved settings follow the user's choices. What is written is the store's state now, never this draw's: the first
|
|
227
|
+
// draw, and the draw that took another key's settings, still hold the settings from before the restore, and writing
|
|
228
|
+
// those would put the opening defaults (or another key's settings) under the key.
|
|
187
229
|
const hiddenList = useMemo(() => [...state.hidden].sort(), [state.hidden]);
|
|
188
230
|
useEffect(() => {
|
|
189
231
|
if (!stateKey || !started.current)
|
|
190
232
|
return;
|
|
233
|
+
const now = table.getState();
|
|
191
234
|
writeSaved(stateKey, {
|
|
192
235
|
// Only a size the person picked here: one equal to the default is not kept, so a new default still reaches it.
|
|
193
|
-
pageSize:
|
|
194
|
-
sort:
|
|
195
|
-
hidden:
|
|
196
|
-
order:
|
|
197
|
-
widths: Object.keys(
|
|
198
|
-
pinned: Object.keys(
|
|
236
|
+
pageSize: now.query.pageSize === openingPageSize() ? undefined : now.query.pageSize,
|
|
237
|
+
sort: now.query.sort,
|
|
238
|
+
hidden: [...now.hidden].sort(),
|
|
239
|
+
order: now.order.length ? [...now.order] : undefined,
|
|
240
|
+
widths: Object.keys(now.widths).length ? now.widths : undefined,
|
|
241
|
+
pinned: Object.keys(now.pinned).length ? now.pinned : undefined,
|
|
199
242
|
});
|
|
243
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
200
244
|
}, [stateKey, state.query.pageSize, state.query.sort, hiddenList, state.order, state.widths, state.pinned]);
|
|
201
245
|
// A new default page size from the panel reaches an open table where no size was picked. Only a change counts: on the
|
|
202
246
|
// first effects the save above still holds the store's opening size and has not written the restored one yet, so a
|
|
@@ -207,7 +251,7 @@ export function DataTable(props) {
|
|
|
207
251
|
if (preferredSize.current === preferences.pageSize)
|
|
208
252
|
return;
|
|
209
253
|
preferredSize.current = preferences.pageSize;
|
|
210
|
-
if (!started.current || readSaved(stateKey)?.pageSize)
|
|
254
|
+
if (whole || !started.current || readSaved(stateKey)?.pageSize)
|
|
211
255
|
return;
|
|
212
256
|
table.setPageSize(openingPageSize());
|
|
213
257
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -232,8 +276,8 @@ export function DataTable(props) {
|
|
|
232
276
|
const labelOf = useCallback((row) => {
|
|
233
277
|
if (rowLabel)
|
|
234
278
|
return rowLabel(row);
|
|
235
|
-
const first = columns
|
|
236
|
-
const text = first ? formatCellText(first, columnValue(first, row), cellOptions) : "";
|
|
279
|
+
const first = namingColumn(columns);
|
|
280
|
+
const text = first ? formatCellText(first, columnValue(first, row), cellOptions, row) : "";
|
|
237
281
|
return text || `row ${table.keyOf(row)}`;
|
|
238
282
|
}, [rowLabel, columns, cellOptions, table]);
|
|
239
283
|
const actionsOf = usePerRow(rowActions);
|
|
@@ -260,6 +304,10 @@ export function DataTable(props) {
|
|
|
260
304
|
void result.finally(() => table.setBusy([key], false)).catch(() => undefined);
|
|
261
305
|
}
|
|
262
306
|
}, [table]);
|
|
307
|
+
// The picked row (`onPick`): a click on a row, or its radio button, picks it through the store, which follows it.
|
|
308
|
+
const pickRow = useCallback((row) => table.pick(row), [table]);
|
|
309
|
+
const pickName = `${tableId}pick`;
|
|
310
|
+
const pickedKey = state.picked ? table.keyOf(state.picked) : null;
|
|
263
311
|
const onSearch = (text) => {
|
|
264
312
|
clearTimeout(searchTimer.current);
|
|
265
313
|
if (searchDelay > 0 && text.trim())
|
|
@@ -280,9 +328,11 @@ export function DataTable(props) {
|
|
|
280
328
|
const headRef = useRef(null);
|
|
281
329
|
const colRefs = useMemo(() => new Map(), []);
|
|
282
330
|
const [offsets, setOffsets] = useState({});
|
|
331
|
+
// Pinned columns that scroll for now: on a narrow table the ones past `PINNED_SHARE` of its width give way.
|
|
332
|
+
const [released, setReleased] = useState(NONE_RELEASED);
|
|
283
333
|
const sized = Object.keys(state.widths).length > 0;
|
|
284
|
-
const views = useMemo(() => viewColumns(visible, { order: state.order, pinned: state.pinned, widths: state.widths, offsets, reorderable, resizable }), [visible, state.order, state.pinned, state.widths, offsets, reorderable, resizable]);
|
|
285
|
-
const indexOn = selectable || numbered;
|
|
334
|
+
const views = useMemo(() => viewColumns(visible, { order: state.order, pinned: state.pinned, widths: state.widths, offsets, released, reorderable, resizable }), [visible, state.order, state.pinned, state.widths, offsets, released, reorderable, resizable]);
|
|
335
|
+
const indexOn = selectable || numbered || pickable;
|
|
286
336
|
// The bands above the headings. With no group anywhere the header is the one row it always was.
|
|
287
337
|
const bands = useMemo(() => headBands(views), [views]);
|
|
288
338
|
const grouped = bands.some(band => band.group !== null);
|
|
@@ -327,8 +377,36 @@ export function DataTable(props) {
|
|
|
327
377
|
// The bar of the selected rows starts after the checkbox column, whatever width it took.
|
|
328
378
|
root?.style.setProperty("--nx-table-index-w", `${Math.round(left)}px`);
|
|
329
379
|
setOffsets(previous => (sameNumbers(previous, next) ? previous : next));
|
|
380
|
+
settlePins.current();
|
|
330
381
|
});
|
|
382
|
+
// Which pinned columns give way: measured from the headings after every render and whenever the table's width
|
|
383
|
+
// changes. Staying or scrolling changes no width, so it settles at once.
|
|
384
|
+
const settlePins = useRef(() => undefined);
|
|
385
|
+
settlePins.current = () => {
|
|
386
|
+
const scroller = scrollRef.current;
|
|
387
|
+
const cells = headLeafCells(headRef.current);
|
|
388
|
+
if (!scroller || cells.length === 0)
|
|
389
|
+
return;
|
|
390
|
+
const sides = new Map(views.map(view => [view.column.key, view.side]));
|
|
391
|
+
let frameWidth = 0;
|
|
392
|
+
const left = [];
|
|
393
|
+
const right = [];
|
|
394
|
+
for (const cell of cells) {
|
|
395
|
+
const key = cell.getAttribute("data-col");
|
|
396
|
+
const width = cell.getBoundingClientRect().width;
|
|
397
|
+
if (key === TABLE_INDEX_COLUMN || key === TABLE_ACTIONS_COLUMN)
|
|
398
|
+
frameWidth += width;
|
|
399
|
+
else if (key && sides.get(key) === "left")
|
|
400
|
+
left.push([key, width]);
|
|
401
|
+
else if (key && sides.get(key) === "right")
|
|
402
|
+
right.unshift([key, width]);
|
|
403
|
+
}
|
|
404
|
+
const next = left.length + right.length === 0 ? NONE_RELEASED : releasedPins(scroller.clientWidth, frameWidth, left, right);
|
|
405
|
+
setReleased(previous => (sameKeys(previous, next) ? previous : next));
|
|
406
|
+
};
|
|
331
407
|
// Shadows at the pinned columns while the others scroll under them: set on the frame, with no render.
|
|
408
|
+
// The width the rows can show goes on the frame too, so a message across a wide table (no rows, a hint, a failed
|
|
409
|
+
// load) stays inside what is on screen instead of centring on the whole scroll width.
|
|
332
410
|
useLayoutEffect(() => {
|
|
333
411
|
const scroller = scrollRef.current;
|
|
334
412
|
const frameElement = scroller?.parentElement;
|
|
@@ -339,9 +417,14 @@ export function DataTable(props) {
|
|
|
339
417
|
frameElement.toggleAttribute("data-scroll-start", left > 1);
|
|
340
418
|
frameElement.toggleAttribute("data-scroll-end", left < scroller.scrollWidth - scroller.clientWidth - 1);
|
|
341
419
|
};
|
|
342
|
-
|
|
420
|
+
const measure = () => {
|
|
421
|
+
frameElement.style.setProperty("--nx-table-view-w", `${scroller.clientWidth}px`);
|
|
422
|
+
update();
|
|
423
|
+
settlePins.current();
|
|
424
|
+
};
|
|
425
|
+
measure();
|
|
343
426
|
scroller.addEventListener("scroll", update, { passive: true });
|
|
344
|
-
const observer = new ResizeObserver(
|
|
427
|
+
const observer = new ResizeObserver(measure);
|
|
345
428
|
observer.observe(scroller);
|
|
346
429
|
if (scroller.firstElementChild)
|
|
347
430
|
observer.observe(scroller.firstElementChild);
|
|
@@ -467,6 +550,10 @@ export function DataTable(props) {
|
|
|
467
550
|
const barRef = useRef(null);
|
|
468
551
|
const reloadRef = useRef(null);
|
|
469
552
|
const [reloadHidden, setReloadHidden] = useState(false);
|
|
553
|
+
// An Add that cannot add yet (`add.disabled` with a `description`) says why on a press, under it.
|
|
554
|
+
const addNotice = useReadOnlyNotice(Boolean(add?.disabled && add.description), barRef, add?.description, { anchor: target => target.closest(".nx-btn"), typing: false, lock: "label" });
|
|
555
|
+
// The spreadsheet's other ways to add sit under the Add's arrow only while it can add.
|
|
556
|
+
const addable = Boolean(add && !add.disabled);
|
|
470
557
|
const addPart = Boolean(add || actions);
|
|
471
558
|
const listsPart = Boolean(toolbar);
|
|
472
559
|
useLayoutEffect(() => {
|
|
@@ -500,23 +587,35 @@ export function DataTable(props) {
|
|
|
500
587
|
void table.reload({ quiet: true });
|
|
501
588
|
};
|
|
502
589
|
// A spreadsheet: the template to fill, the file to bring back, and an export the server writes from what is on
|
|
503
|
-
// screen. With an Add, they are other ways to add, under its arrow; without one, they are in the options menu.
|
|
590
|
+
// screen. With an Add, they are other ways to add, under its arrow; without one, they are in the options menu. Each is
|
|
591
|
+
// offered only to a person the app's permissions let call every endpoint it needs, the settings included.
|
|
592
|
+
const sheet = excel?.endpoints;
|
|
593
|
+
const mayUseSheet = (...endpoints) => [sheet.settings, ...endpoints].every(endpoint => canCall(endpoint));
|
|
504
594
|
const sheetItems = excel
|
|
505
595
|
? [
|
|
506
|
-
{ id: "excel-template", label: "Download template", description: `A spreadsheet to fill in`, icon: _jsx(InlineIcon, { icon: iconDownload }),
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
596
|
+
...(mayUseSheet(sheet.template) ? [{ id: "excel-template", label: "Download template", description: `A spreadsheet to fill in`, icon: _jsx(InlineIcon, { icon: iconDownload }), onSelect: () => void downloadExcelTemplate(excel.endpoints, { params: excel.params }) }] : []),
|
|
597
|
+
...(mayUseSheet(sheet.verify, sheet.import)
|
|
598
|
+
? [
|
|
599
|
+
{ id: "excel-import", label: "Import from a spreadsheet", icon: _jsx(InlineIcon, { icon: iconFileImport }), onSelect: () => openExcelImport({ endpoints: excel.endpoints, subject: excel.subject, params: excel.params, onImported: onImported }) },
|
|
600
|
+
// A bulk insert is an Add of many rows, so it is offered only beside an Add that can add: a CRUD shows its Add
|
|
601
|
+
// only to a person who may create, and nobody types fifty rows to be refused at the end.
|
|
602
|
+
...(excel.bulk === false || !addable
|
|
603
|
+
? []
|
|
604
|
+
: [{ id: "excel-bulk", label: "Bulk insert", description: "Type the rows here, with no file", icon: _jsx(InlineIcon, { icon: iconRowInsertBottom }), onSelect: () => openExcelImport({ endpoints: excel.endpoints, subject: excel.subject, params: excel.params, onImported: onImported, mode: "bulk" }) }]),
|
|
605
|
+
]
|
|
606
|
+
: []),
|
|
607
|
+
...(sheet.export && mayUseSheet(sheet.export)
|
|
514
608
|
? [{ id: "excel-export", label: "Export to Excel", description: "Everything the search and filters leave, from the server", icon: _jsx(InlineIcon, { icon: iconFileExport }), onSelect: () => void exportExcel(excel.endpoints, exportQuery()) }]
|
|
515
609
|
: []),
|
|
516
|
-
]
|
|
610
|
+
].map((item, index) => (index === 0 ? { ...item, separator: true } : item))
|
|
517
611
|
: [];
|
|
518
612
|
// In the bar it is `block`: as wide as its own label there, and the whole line once the Add section takes one.
|
|
519
|
-
const addButton = (block = false) => add && (_jsx(Button, { variant: "primary", size: "sm", block: block, icon: _jsx(InlineIcon, { icon: iconPlus }),
|
|
613
|
+
const addButton = (block = false) => add && (_jsx(Button, { variant: "primary", size: "sm", block: block, icon: _jsx(InlineIcon, { icon: iconPlus }), "aria-disabled": add.disabled || undefined, title: add.description, onKeyDown: addNotice.onKeyDown, onClick: event => {
|
|
614
|
+
if (!add.disabled)
|
|
615
|
+
add.onClick();
|
|
616
|
+
else if (!add.onDisabledPress?.())
|
|
617
|
+
addNotice.show(event.currentTarget);
|
|
618
|
+
}, children: add.label }));
|
|
520
619
|
const optionItems = [
|
|
521
620
|
...(filterInputs?.length
|
|
522
621
|
? [{ id: "filters", label: "Filters", description: filtersOn.join(", ") || undefined, icon: _jsx(InlineIcon, { icon: iconFilter }), onSelect: () => filtersRef.current?.open() }]
|
|
@@ -531,7 +630,7 @@ export function DataTable(props) {
|
|
|
531
630
|
: []),
|
|
532
631
|
// The sheet's own Import replaces the page's `onImport` hook.
|
|
533
632
|
...(excel
|
|
534
|
-
?
|
|
633
|
+
? addable
|
|
535
634
|
? []
|
|
536
635
|
: sheetItems
|
|
537
636
|
: onImport
|
|
@@ -545,7 +644,9 @@ export function DataTable(props) {
|
|
|
545
644
|
...(reloadable && reloadHidden ? [{ id: "reload", label: "Reload", icon: _jsx(InlineIcon, { icon: iconRefresh }), separator: true, onSelect: reload }] : []),
|
|
546
645
|
];
|
|
547
646
|
const optionEntries = menuEntries(optionItems);
|
|
548
|
-
|
|
647
|
+
// Rows per page: inside the search box, in the bar without one, or beside the pager (`pager`); none for a table read whole.
|
|
648
|
+
const pageSizePicker = whole ? null : _jsx(TablePageSize, { pageSize: query.pageSize, pageSizes: pageSizes, compact: searchable && pager === "bar", onChange: value => table.setPageSize(value) });
|
|
649
|
+
const barPageSize = pager === "bar" ? pageSizePicker : null;
|
|
549
650
|
let body;
|
|
550
651
|
if (status === "loading") {
|
|
551
652
|
body = _jsx(TableSkeletonRows, { count: Math.min(query.pageSize, 8), cells: cellCount, frame: frame, sized: sized });
|
|
@@ -576,7 +677,7 @@ export function DataTable(props) {
|
|
|
576
677
|
body = rows.map((row, index) => {
|
|
577
678
|
const key = pageKeys[index];
|
|
578
679
|
const selected = state.selected.has(key);
|
|
579
|
-
return (_jsx(TableRowView, { row: row, rowKey: key, number: state.offset + index + 1, label: labelOf(row), columns: views, cellOptions: cellOptions, selectable: selectable, selected: selected, selectionFull: selectionFull && !selected, busy: state.busy.has(key), flashed: state.flashed.has(key), numbered: numbered, marks: rowMarks?.(row), frame: frame, sized: sized, actions: hasActions ? (actionsOf(row) ?? []) : null, onToggle: onToggle, onAction: onAction, onOpen: onRowOpen, openOnClick: openOnClick }, key || index));
|
|
680
|
+
return (_jsx(TableRowView, { row: row, rowKey: key, number: state.offset + index + 1, label: labelOf(row), columns: views, cellOptions: cellOptions, selectable: selectable, selected: selected, selectionFull: selectionFull && !selected, busy: state.busy.has(key), flashed: state.flashed.has(key), numbered: numbered, marks: rowMarks?.(row), strong: rowStrong?.(row) === true, frame: frame, sized: sized, actions: hasActions ? (actionsOf(row) ?? []) : null, onToggle: onToggle, onAction: onAction, onOpen: onRowOpen, openOnClick: openOnClick, onPick: pickable ? pickRow : undefined, picked: key === pickedKey, pickName: pickable ? pickName : undefined }, key || index));
|
|
580
681
|
});
|
|
581
682
|
}
|
|
582
683
|
/* The sections above the page: the records pinned, the favourites, each folded open or shut, remembered on this device */
|
|
@@ -598,7 +699,9 @@ export function DataTable(props) {
|
|
|
598
699
|
const key = table.keyOf(row);
|
|
599
700
|
const selected = state.selected.has(key);
|
|
600
701
|
const marks = rowMarks?.(row);
|
|
601
|
-
return (_jsx(TableRowView, { row: row, rowKey: key, number: index + 1, label: labelOf(row), columns: views, cellOptions: cellOptions, selectable: selectable, selected: selected, selectionFull: selectionFull && !selected, busy: state.busy.has(key), flashed: state.flashed.has(key), numbered: numbered, marks: marks?.length ? marks : own, frame: frame, sized: sized, actions: hasActions ? (actionsOf(row) ?? []) : null, onToggle: onToggle, onAction: onAction, onOpen: onRowOpen, openOnClick: openOnClick
|
|
702
|
+
return (_jsx(TableRowView, { row: row, rowKey: key, number: index + 1, label: labelOf(row), columns: views, cellOptions: cellOptions, selectable: selectable, selected: selected, selectionFull: selectionFull && !selected, busy: state.busy.has(key), flashed: state.flashed.has(key), numbered: numbered, marks: marks?.length ? marks : own, strong: rowStrong?.(row) === true, frame: frame, sized: sized, actions: hasActions ? (actionsOf(row) ?? []) : null, onToggle: onToggle, onAction: onAction, onOpen: onRowOpen, openOnClick: openOnClick,
|
|
703
|
+
// A click picks a section's row too; its radio button is the one in the list below, where the arrow keys move.
|
|
704
|
+
onPick: pickable ? pickRow : undefined, picked: key === pickedKey }, key || index));
|
|
602
705
|
});
|
|
603
706
|
};
|
|
604
707
|
const selectedRows = selecting ? [...state.selected.values()] : [];
|
|
@@ -619,8 +722,8 @@ export function DataTable(props) {
|
|
|
619
722
|
const mass = selectedRows.length ? (typeof massActions === "function" ? massActions(selectedRows) : (massActions ?? [])) : [];
|
|
620
723
|
// A column's own heading, wherever it stands: across both rows when no band is above it, in the row under its band when one is.
|
|
621
724
|
const headingCell = (view, rowSpan) => (_jsx(HeaderCell, { view: view, sized: sized, rowSpan: rowSpan, sort: query.sort, disabled: status !== "ready" || rows.length < 2, covered: selectedRows.length > 0, canPin: canPinColumn(view.column, { pinnable }), canHide: view.column.hideable !== false && visible.length > 1, neighbours: moveTargets(views, view.column.key), onSort: direction => table.setSort(direction ? { key: view.column.key, direction } : null), onPin: side => table.setPinned(view.column.key, side), onMove: beforeKey => table.moveColumn(view.column.key, beforeKey), onFit: () => fitColumn(view.column.key), onHide: () => table.setHidden(view.column.key, true), onPointerDown: event => onColumnPointerDown(event, view), onResizerDown: event => onResizerPointerDown(event, view.column.key) }, view.column.key));
|
|
622
|
-
return (_jsxs("div", { ref: rootRef, id: id, className: cx("nx-table", `nx-table--${size}`, fill && "nx-table--fill", className), style: style, "data-head-rows": headRows, "data-status": status, "data-refreshing": refreshing || undefined, "data-selecting": selecting || undefined, "data-sized": sized || undefined, children: [_jsxs("div", { ref: barRef, className: "nx-table__bar", children: [addPart && (_jsxs("div", { className: "nx-table__bar-add", children: [add &&
|
|
623
|
-
(sheetItems.length > 0 ? (_jsx(SplitButton, { fixed: true, block: true, size: "sm", menuLabel: "More ways to add", options: [{ value: "add", label: add.label, icon: _jsx(InlineIcon, { icon: iconPlus }) }, ...sheetItems.map(item => ({ value: item.id, label: item.label, icon: item.icon, description: item.description }))], onAction: value => (value === "add" ? add.onClick() : sheetItems.find(item => item.id === value)?.onSelect()) })) : (addButton(true))), actions] })), toolbar && _jsx("div", { className: "nx-table__bar-start", children: toolbar }), _jsxs("div", { className: "nx-table__bar-end", children: [filterInputs?.length ? (_jsx(TableFiltersPanel, { ref: filtersRef, anchor: optionsRef, inputs: filterInputs, value: query.filters, delay: searchDelay, matches: state.matches, total: state.total, onChange: values => table.setFilters(values) })) : null, hasColumnsPanel && (_jsx(TableColumnsPanel, { ref: columnsRef, anchor: optionsRef, columns: ordered, hidden: state.hidden, pinned: state.pinned, reorderable: reorderable, size: "sm", changed: table.hasColumnChanges(), onToggle: (key, hide) => table.setHidden(key, hide), onOrder: keys => table.setOrder(keys), onReset: () => table.resetColumns() })), reloadable && (_jsx(Button, { ref: reloadRef, size: "sm", variant: "ghost", className: "nx-table__reload", icon: _jsx(InlineIcon, { icon: iconRefresh, className: refreshing ? "nx-spin" : undefined }), "aria-label": "Reload", title: "Reload", disabled: status === "loading", onClick: reload })), optionEntries.length > 0 && (_jsx(Menu, { items: optionEntries, size: "sm", align: "end", "aria-label": "More options", onSelect: value => optionItems.find(action => action.id === value)?.onSelect(), children: _jsx(Button, { ref: optionsRef, size: "sm", variant: "ghost", icon: _jsx(InlineIcon, { icon: iconDotsVertical }), dot: filtersOn.length > 0 && "primary", "aria-label": "More options", title: filtersOn.length ? `More options · ${filtersOn.length} ${filtersOn.length === 1 ? "filter" : "filters"} on` : "More options", "data-table-options": true }) })), !searchable &&
|
|
725
|
+
return (_jsxs("div", { ref: rootRef, id: id, className: cx("nx-table", `nx-table--${size}`, fill && "nx-table--fill", className), style: style, "data-head-rows": headRows, "data-status": status, "data-refreshing": refreshing || undefined, "data-selecting": selecting || undefined, "data-sized": sized || undefined, children: [caption && _jsx("div", { className: "nx-table__caption", children: caption }), _jsxs("div", { ref: barRef, className: "nx-table__bar", children: [addPart && (_jsxs("div", { className: "nx-table__bar-add", children: [add &&
|
|
726
|
+
(addable && sheetItems.length > 0 ? (_jsx(SplitButton, { fixed: true, block: true, size: "sm", menuLabel: "More ways to add", options: [{ value: "add", label: add.label, icon: _jsx(InlineIcon, { icon: iconPlus }) }, ...sheetItems.map(item => ({ value: item.id, label: item.label, icon: item.icon, description: item.description }))], onAction: value => (value === "add" ? add.onClick() : sheetItems.find(item => item.id === value)?.onSelect()) })) : (addButton(true))), actions] })), toolbar && _jsx("div", { className: "nx-table__bar-start", children: toolbar }), _jsxs("div", { className: "nx-table__bar-end", children: [filterInputs?.length ? (_jsx(TableFiltersPanel, { ref: filtersRef, anchor: optionsRef, inputs: filterInputs, value: query.filters, delay: searchDelay, matches: state.matches, total: state.total, onChange: values => table.setFilters(values) })) : null, hasColumnsPanel && (_jsx(TableColumnsPanel, { ref: columnsRef, anchor: optionsRef, columns: ordered, hidden: state.hidden, pinned: state.pinned, reorderable: reorderable, size: "sm", changed: table.hasColumnChanges(), onToggle: (key, hide) => table.setHidden(key, hide), onOrder: keys => table.setOrder(keys), onReset: () => table.resetColumns() })), reloadable && (_jsx(Button, { ref: reloadRef, size: "sm", variant: "ghost", className: "nx-table__reload", icon: _jsx(InlineIcon, { icon: iconRefresh, className: refreshing ? "nx-spin" : undefined }), "aria-label": "Reload", title: "Reload", disabled: status === "loading", onClick: reload })), optionEntries.length > 0 && (_jsx(Menu, { items: optionEntries, size: "sm", align: "end", "aria-label": "More options", onSelect: value => optionItems.find(action => action.id === value)?.onSelect(), children: _jsx(Button, { ref: optionsRef, size: "sm", variant: "ghost", icon: _jsx(InlineIcon, { icon: iconDotsVertical }), dot: filtersOn.length > 0 && "primary", "aria-label": "More options", title: filtersOn.length ? `More options · ${filtersOn.length} ${filtersOn.length === 1 ? "filter" : "filters"} on` : "More options", "data-table-options": true }) })), !searchable && barPageSize] }), searchable && (_jsx(TextBox, { ref: searchRef, type: "search", size: "sm", className: "nx-table__search", actionsStart: barPageSize, icon: _jsx(InlineIcon, { icon: iconSearch }), placeholder: searchPlaceholder, "aria-label": searchPlaceholder.replace(/…$/, ""), "aria-controls": tableId, defaultValue: query.search, clearable: true, onValueChange: onSearch, onKeyDown: event => {
|
|
624
727
|
if (event.key === "Enter") {
|
|
625
728
|
clearTimeout(searchTimer.current);
|
|
626
729
|
table.setSearch(event.currentTarget.value);
|
|
@@ -632,13 +735,13 @@ export function DataTable(props) {
|
|
|
632
735
|
} }) })) })), bands.map(band => band.group === null ? (headingCell(band.columns[0], headRows)) : (_jsx(GroupHeading, { band: band }, `group:${band.group}:${band.columns[0].column.key}`))), sized && _jsx("td", { className: "nx-table__heading nx-table__cell--filler", rowSpan: headRows }), frame.actions && (_jsx("th", { scope: "col", className: "nx-table__heading nx-table__cell--actions", rowSpan: headRows, "data-col": TABLE_ACTIONS_COLUMN, "data-pin": "right", "data-pin-edge": frame.actions.edge || undefined, style: { right: frame.actions.offset }, children: _jsx("span", { className: "nx-visually-hidden", children: "Actions" }) }))] }), grouped && _jsx("tr", { className: "nx-table__head nx-table__head--leaves", children: bands.filter(band => band.group !== null).flatMap(band => band.columns.map(view => headingCell(view))) })] }), sections.map(section => {
|
|
633
736
|
const open = openSections.has(section.key);
|
|
634
737
|
return (_jsxs("tbody", { className: "nx-table__section", "data-section": section.key, "data-open": open || undefined, children: [_jsx(TableSectionHead, { span: span, label: section.label, icon: section.icon, count: section.rows.length, max: section.max, loading: Boolean(section.loading), open: open, onToggle: () => toggleSection(section.key) }), open && sectionRows(section)] }, section.key));
|
|
635
|
-
}), _jsx("tbody", { children: body })] }) })] }), _jsx(TableFooter, { state: state, disabled: refreshing, onPage: page => table.setPage(page) })] }));
|
|
738
|
+
}), _jsx("tbody", { children: body })] }) })] }), _jsx(TableFooter, { state: state, disabled: refreshing, whole: whole, pageSize: pager === "footer" ? pageSizePicker : null, onPage: page => table.setPage(page) }), addNotice.notice] }));
|
|
636
739
|
}
|
|
637
740
|
/** Where "Move left" and "Move right" put a column: the key it goes before, or null for the end of its side. */
|
|
638
741
|
function moveTargets(views, key) {
|
|
639
742
|
// A column moves among the columns on its own side, past the ones that stay put.
|
|
640
|
-
const
|
|
641
|
-
const group = views.filter(view => view.
|
|
743
|
+
const side = views.find(view => view.column.key === key)?.side ?? null;
|
|
744
|
+
const group = views.filter(view => view.side === side && view.movable);
|
|
642
745
|
const at = group.findIndex(view => view.column.key === key);
|
|
643
746
|
if (at < 0)
|
|
644
747
|
return { left: undefined, right: undefined };
|
|
@@ -679,11 +782,12 @@ function HeaderCell({ view, sized, rowSpan, sort, disabled, covered, canPin, can
|
|
|
679
782
|
if (canPin) {
|
|
680
783
|
if (entries.length)
|
|
681
784
|
entries.push({ separator: true });
|
|
682
|
-
|
|
785
|
+
// The side chosen, also while a narrow table lets it scroll.
|
|
786
|
+
if (view.side !== "left")
|
|
683
787
|
entries.push({ value: "pin-left", label: "Pin left", icon: _jsx(InlineIcon, { icon: iconArrowBarToLeft }) });
|
|
684
|
-
if (view.
|
|
788
|
+
if (view.side !== "right")
|
|
685
789
|
entries.push({ value: "pin-right", label: "Pin right", icon: _jsx(InlineIcon, { icon: iconArrowBarToRight }) });
|
|
686
|
-
if (view.
|
|
790
|
+
if (view.side)
|
|
687
791
|
entries.push({ value: "unpin", label: "Unpin", icon: _jsx(InlineIcon, { icon: iconPinnedOff }) });
|
|
688
792
|
}
|
|
689
793
|
if (view.movable && (neighbours.left !== undefined || neighbours.right !== undefined)) {
|
|
@@ -721,7 +825,7 @@ function HeaderCell({ view, sized, rowSpan, sort, disabled, covered, canPin, can
|
|
|
721
825
|
onHide();
|
|
722
826
|
}
|
|
723
827
|
const label = _jsx("span", { className: "nx-table__heading-label", children: column.label });
|
|
724
|
-
return (_jsxs("th", { scope: "col", className: cx("nx-table__heading", `nx-table__cell--${align}`), rowSpan: rowSpan, "data-col": column.key, "data-pin": view.pin ?? undefined, "data-pin-edge": (view.pin && view.edge) || undefined, "data-movable": view.movable || undefined, style: columnStyle(view, sized), "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : undefined, onPointerDown: onPointerDown, children: [_jsxs("div", { className: "nx-table__heading-inner", children: [sortable ? (_jsxs("button", { type: "button", className: "nx-table__sort", "data-sorted": direction ?? undefined, disabled: disabled && !direction, tabIndex: covered ? -1 : undefined, onClick: () => onSort(direction === "asc" ? "desc" : direction === "desc" ? null : "asc"), title: `Sort by ${column.label}`, children: [label, _jsx(InlineIcon, { icon: direction === "asc" ? iconArrowUp : direction === "desc" ? iconArrowDown : iconArrowsSort, className: "nx-table__sort-icon" })] })) : (label), entries.length > 0 && (_jsx(Menu, { items: entries, size: "sm", align: "end", "aria-label": `${column.label}: column`, onSelect: choose, children: _jsx(Button, { variant: "ghost", size: "sm", className: "nx-table__col-menu", icon: _jsx(InlineIcon, { icon: iconChevronDown }), tabIndex: covered ? -1 : undefined, "aria-label": `${column.label}: column options`, title: "Column options" }) }))] }), view.resizable && (_jsx("span", { className: "nx-table__resizer", "aria-hidden": "true", "data-resize": column.key, title: `Drag to resize ${column.label}, double-click to fit`, onPointerDown: onResizerDown, onDoubleClick: onFit }))] }));
|
|
828
|
+
return (_jsxs("th", { scope: "col", className: cx("nx-table__heading", `nx-table__cell--${align}`), rowSpan: rowSpan, "data-col": column.key, "data-pin": view.pin ?? undefined, "data-pin-side": view.side ?? undefined, "data-pin-edge": (view.pin && view.edge) || undefined, "data-movable": view.movable || undefined, style: columnStyle(view, sized), "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : undefined, onPointerDown: onPointerDown, children: [_jsxs("div", { className: "nx-table__heading-inner", children: [sortable ? (_jsxs("button", { type: "button", className: "nx-table__sort", "data-sorted": direction ?? undefined, disabled: disabled && !direction, tabIndex: covered ? -1 : undefined, onClick: () => onSort(direction === "asc" ? "desc" : direction === "desc" ? null : "asc"), title: `Sort by ${column.label}`, children: [label, _jsx(InlineIcon, { icon: direction === "asc" ? iconArrowUp : direction === "desc" ? iconArrowDown : iconArrowsSort, className: "nx-table__sort-icon" })] })) : (label), entries.length > 0 && (_jsx(Menu, { items: entries, size: "sm", align: "end", "aria-label": `${column.label}: column`, onSelect: choose, children: _jsx(Button, { variant: "ghost", size: "sm", className: "nx-table__col-menu", icon: _jsx(InlineIcon, { icon: iconChevronDown }), tabIndex: covered ? -1 : undefined, "aria-label": `${column.label}: column options`, title: "Column options" }) }))] }), view.resizable && (_jsx("span", { className: "nx-table__resizer", "aria-hidden": "true", "data-resize": column.key, title: `Drag to resize ${column.label}, double-click to fit`, onPointerDown: onResizerDown, onDoubleClick: onFit }))] }));
|
|
725
829
|
}
|
|
726
830
|
/** The steps a bar is squeezed into when its parts do not fit, each one keeping the steps before it. */
|
|
727
831
|
const BAR_SQUEEZES = ["search", "search reload", "search reload add"];
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type Ref, type RefObject } from "react";
|
|
2
2
|
import { type PopoverPanelHandle } from "../../Helpers/PopoverHooks.ts";
|
|
3
|
-
import type { TableColumn,
|
|
3
|
+
import type { TableColumn, TablePins, TableRow } from "../../Interfaces/TableInterfaces.ts";
|
|
4
4
|
export interface TableColumnsPanelProps<TRow extends TableRow> {
|
|
5
5
|
/** Every column the table has, in the order they are in now - hidden ones among them. */
|
|
6
6
|
columns: readonly TableColumn<TRow>[];
|
|
7
7
|
hidden: ReadonlySet<string>;
|
|
8
|
-
pinned:
|
|
8
|
+
pinned: TablePins;
|
|
9
9
|
/** False while the table is not reorderable: the panel then only shows and hides. */
|
|
10
10
|
reorderable?: boolean;
|
|
11
11
|
/** Default `md`. */
|
|
@@ -5,6 +5,7 @@ import { useEffect, useId, useRef, useState } from "react";
|
|
|
5
5
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
6
|
import { isPopoverOpen } from "../../Helpers/PopoverHelpers.js";
|
|
7
7
|
import { usePopoverPanel } from "../../Helpers/PopoverHooks.js";
|
|
8
|
+
import { columnPin } from "../../Helpers/TableHelpers.js";
|
|
8
9
|
import { Button } from "../Documents/Button.js";
|
|
9
10
|
/** The rows of the panel: a band for every run of neighbours that name the same group, as the header draws them. */
|
|
10
11
|
function panelRows(columns, groups) {
|
|
@@ -69,7 +70,7 @@ export function TableColumnsPanel({ columns, hidden, pinned, reorderable = true,
|
|
|
69
70
|
key: column.key,
|
|
70
71
|
label: column.label,
|
|
71
72
|
shown: !hidden.has(column.key),
|
|
72
|
-
pin:
|
|
73
|
+
pin: columnPin(column, pinned),
|
|
73
74
|
movable: reorderable && column.movable !== false,
|
|
74
75
|
hideable: column.hideable !== false,
|
|
75
76
|
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Ref, type RefObject } from "react";
|
|
2
2
|
import { type PopoverPanelHandle } from "../../Helpers/PopoverHooks.ts";
|
|
3
|
-
import type
|
|
3
|
+
import { type InputParams } from "../../Interfaces/FormInterfaces.ts";
|
|
4
4
|
import type { TableFilters } from "../../Interfaces/TableInterfaces.ts";
|
|
5
5
|
export interface TableFiltersPanelProps {
|
|
6
6
|
/** The filter inputs, as the table prepared them (`asFilterInput`). */
|
|
@@ -4,6 +4,7 @@ import { iconFilterOff, InlineIcon } from "nexus-icons";
|
|
|
4
4
|
import { useEffect, useId, useRef } from "react";
|
|
5
5
|
import { usePopoverPanel } from "../../Helpers/PopoverHooks.js";
|
|
6
6
|
import { activeFilters } from "../../Helpers/TableFilters.js";
|
|
7
|
+
import { EInputTypes } from "../../Interfaces/FormInterfaces.js";
|
|
7
8
|
import { Button } from "../Documents/Button.js";
|
|
8
9
|
import { Form, useForm } from "../Forms/Form.js";
|
|
9
10
|
import { formatCount } from "./DataTableParts.js";
|
|
@@ -14,7 +15,21 @@ import { formatCount } from "./DataTableParts.js";
|
|
|
14
15
|
* never clears a filter on its way out.
|
|
15
16
|
*/
|
|
16
17
|
/** Inputs typed into or dragged: their value waits until the person stops. A date box reads a date from part of one. */
|
|
17
|
-
const TYPED = new Set([
|
|
18
|
+
const TYPED = new Set([
|
|
19
|
+
EInputTypes.Text,
|
|
20
|
+
EInputTypes.Email,
|
|
21
|
+
EInputTypes.Search,
|
|
22
|
+
EInputTypes.Tel,
|
|
23
|
+
EInputTypes.Url,
|
|
24
|
+
EInputTypes.TextArea,
|
|
25
|
+
EInputTypes.Number,
|
|
26
|
+
EInputTypes.Slider,
|
|
27
|
+
EInputTypes.RangeSlider,
|
|
28
|
+
EInputTypes.Date,
|
|
29
|
+
EInputTypes.DateRange,
|
|
30
|
+
EInputTypes.Time,
|
|
31
|
+
EInputTypes.DateTime,
|
|
32
|
+
]);
|
|
18
33
|
/** The Filters panel, opened through its `open()` handle: a `Form` of the filter inputs, the count, and Clear all. */
|
|
19
34
|
export function TableFiltersPanel({ inputs, value, delay, matches, total, onChange, anchor, ref }) {
|
|
20
35
|
const titleId = useId();
|
|
@@ -57,7 +72,7 @@ export function TableFiltersPanel({ inputs, value, delay, matches, total, onChan
|
|
|
57
72
|
}, children: [_jsxs("div", { className: "nx-table-panel__head", children: [_jsx("span", { className: "nx-table-panel__title", id: titleId, children: "Filters" }), _jsx("span", { className: "nx-table-panel__count", children: count ? `${count} on` : "None" })] }), _jsx("div", { className: "nx-popover__scroll", children: shown && (_jsx(Form, { form: form, inputs: inputs, size: "sm", defaultValue: value, className: "nx-table-filters__form", onValueChange: (_, name) => {
|
|
58
73
|
if (name === null)
|
|
59
74
|
return;
|
|
60
|
-
if (delay > 0 && TYPED.has(inputs.find(params => params.name === name)?.type ??
|
|
75
|
+
if (delay > 0 && TYPED.has(inputs.find(params => params.name === name)?.type ?? 0))
|
|
61
76
|
wait();
|
|
62
77
|
else
|
|
63
78
|
apply();
|
|
@@ -41,8 +41,11 @@ export declare function TableColumnGroup<TRow extends TableRow>({ columns, width
|
|
|
41
41
|
frame: TableFrameColumns;
|
|
42
42
|
colRefs: Map<string, HTMLTableColElement>;
|
|
43
43
|
}): import("react").JSX.Element;
|
|
44
|
-
/**
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The number of a row, its checkbox, or both: the number until the pointer is over the row or something is selected. A
|
|
46
|
+
* row of a table whose rows are picked (`onPick`) also holds its radio button here, out of sight.
|
|
47
|
+
*/
|
|
48
|
+
export declare function TableIndexCell({ heading, rowSpan, own, number, numbered, selectable, checkbox, pick, }: {
|
|
46
49
|
heading?: boolean;
|
|
47
50
|
/** 2 in the heading while the header has a band of group headings above the columns' own. */
|
|
48
51
|
rowSpan?: number;
|
|
@@ -55,6 +58,8 @@ export declare function TableIndexCell({ heading, rowSpan, own, number, numbered
|
|
|
55
58
|
numbered: boolean;
|
|
56
59
|
selectable: boolean;
|
|
57
60
|
checkbox?: ReactNode;
|
|
61
|
+
/** The row's radio button, when rows are picked. */
|
|
62
|
+
pick?: ReactNode;
|
|
58
63
|
}): import("react").JSX.Element;
|
|
59
64
|
export interface TableRowViewProps<TRow extends TableRow> {
|
|
60
65
|
row: TRow;
|
|
@@ -72,6 +77,8 @@ export interface TableRowViewProps<TRow extends TableRow> {
|
|
|
72
77
|
numbered: boolean;
|
|
73
78
|
/** The row's marks (pinned, a favourite), worn one under the other where a row without any shows its number. */
|
|
74
79
|
marks?: readonly TableRowMark[];
|
|
80
|
+
/** The row reads strong (`rowStrong`): a thread with unread lines. */
|
|
81
|
+
strong?: boolean;
|
|
75
82
|
/** Where the table's own columns sit; `null` for the ones it does not have. */
|
|
76
83
|
frame: TableFrameColumns;
|
|
77
84
|
/** The user gave a column a width, so the table lays its columns out from the `<colgroup>`. */
|
|
@@ -82,9 +89,15 @@ export interface TableRowViewProps<TRow extends TableRow> {
|
|
|
82
89
|
onOpen?: (row: TRow) => void;
|
|
83
90
|
/** A single click opens the row, as a double-click does. */
|
|
84
91
|
openOnClick?: boolean;
|
|
92
|
+
/** Rows are picked: a click picks this one (a double-click still opens it), whatever `openOnClick` says. */
|
|
93
|
+
onPick?: (row: TRow) => void;
|
|
94
|
+
/** This row is the picked one. */
|
|
95
|
+
picked?: boolean;
|
|
96
|
+
/** The name of the table's radio group, for the row's own radio button; none in a section above the page. */
|
|
97
|
+
pickName?: string;
|
|
85
98
|
}
|
|
86
99
|
/** One row. It renders again only when its row, its state, or the columns change. */
|
|
87
|
-
declare function TableRowViewInner<TRow extends TableRow>({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered, marks, frame, sized, actions, onToggle, onAction, onOpen, openOnClick, }: TableRowViewProps<TRow>): import("react").JSX.Element;
|
|
100
|
+
declare function TableRowViewInner<TRow extends TableRow>({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered, marks, strong, frame, sized, actions, onToggle, onAction, onOpen, openOnClick, onPick, picked, pickName, }: TableRowViewProps<TRow>): import("react").JSX.Element;
|
|
88
101
|
export declare const TableRowView: typeof TableRowViewInner;
|
|
89
102
|
/**
|
|
90
103
|
* The head of a section above the page: one row across the table with a button that folds the section's records open
|
|
@@ -146,11 +159,16 @@ export declare function TablePageSize({ pageSize, pageSizes, compact, onChange }
|
|
|
146
159
|
}): import("react").JSX.Element | null;
|
|
147
160
|
/**
|
|
148
161
|
* Under the table: which rows show and how many the search and the filters left ("Showing 41–50 of 234 · filtered
|
|
149
|
-
* from 1,000"), the page of how many, and the pager.
|
|
162
|
+
* from 1,000"), the page of how many, the rows per page when the table keeps them here (`pageSize`), and the pager.
|
|
163
|
+
*
|
|
164
|
+
* A table read whole (`whole`, every row on one page) has nothing to page: its footer shows only while a search or the
|
|
165
|
+
* filters leave rows out, and is otherwise kept for screen readers alone, so the count is still heard as it changes.
|
|
150
166
|
*/
|
|
151
|
-
export declare function TableFooter<TRow extends TableRow>({ state, disabled, onPage }: {
|
|
167
|
+
export declare function TableFooter<TRow extends TableRow>({ state, disabled, whole, pageSize, onPage }: {
|
|
152
168
|
state: TableState<TRow>;
|
|
153
169
|
disabled: boolean;
|
|
170
|
+
whole?: boolean;
|
|
171
|
+
pageSize?: ReactNode;
|
|
154
172
|
onPage: (page: number) => void;
|
|
155
173
|
}): import("react").JSX.Element;
|
|
156
174
|
export {};
|