nexus-shared 3.1.0 → 4.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 +2268 -446
- package/LICENSE +21 -21
- package/README.md +1705 -1084
- package/dist/Client.Index.d.ts +17 -0
- package/dist/Client.Index.js +17 -0
- 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/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Forms/ApiForm.js +19 -2
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +871 -377
- package/dist/Components/Forms/CrudActions.d.ts +136 -0
- package/dist/Components/Forms/CrudActions.js +184 -0
- package/dist/Components/Forms/CrudHistory.d.ts +66 -0
- package/dist/Components/Forms/CrudHistory.js +140 -0
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +30 -2
- package/dist/Components/Forms/CrudParts.js +56 -5
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +114 -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 +63 -0
- package/dist/Components/Forms/CrudWorkspace.js +169 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +4 -4
- package/dist/Components/Forms/Form.js +125 -16
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -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 +195 -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/CodeBox.d.ts +69 -0
- package/dist/Components/Inputs/CodeBox.js +316 -0
- package/dist/Components/Inputs/DatePicker.js +4 -1
- package/dist/Components/Inputs/DateRangePicker.js +4 -1
- package/dist/Components/Inputs/DateTimePicker.js +4 -1
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -5
- 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 +10 -11
- package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
- package/dist/Components/Inputs/InputRenderer.js +39 -31
- package/dist/Components/Inputs/NumberBox.js +4 -2
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +70 -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 +1 -1
- package/dist/Components/Inputs/RowsInput.js +18 -3
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +29 -29
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +14 -2
- package/dist/Components/Inputs/TextBox.js +4 -1
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TimePicker.js +4 -1
- package/dist/Components/Inputs/TreeView.d.ts +107 -6
- package/dist/Components/Inputs/TreeView.js +513 -30
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +13 -6
- package/dist/Components/Layouts/Popup.js +37 -9
- 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/Layouts/ThemePicker.d.ts +3 -2
- package/dist/Components/Layouts/ThemePicker.js +16 -4
- package/dist/Components/Viewers/DataTable.js +205 -61
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +46 -12
- package/dist/Components/Viewers/DataTableParts.js +62 -21
- package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
- package/dist/Components/Viewers/OwnerFiles.js +589 -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.d.ts +3 -34
- package/dist/Helpers/ApiModules.js +3 -3
- package/dist/Helpers/ApiRoutes.d.ts +68 -26
- package/dist/Helpers/ApiRoutes.js +31 -27
- package/dist/Helpers/ArrayHelpers.d.ts +0 -0
- package/dist/Helpers/ArrayHelpers.js +2 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/CodeHelpers.d.ts +97 -0
- package/dist/Helpers/CodeHelpers.js +332 -0
- package/dist/Helpers/CrudBackend.d.ts +114 -6
- package/dist/Helpers/CrudBackend.js +214 -11
- package/dist/Helpers/CrudColumns.d.ts +98 -0
- package/dist/Helpers/CrudColumns.js +160 -0
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +130 -6
- package/dist/Helpers/CrudHelpers.js +304 -12
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudRowForms.d.ts +15 -0
- package/dist/Helpers/CrudRowForms.js +43 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -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 +78 -0
- package/dist/Helpers/FileAdapter.js +102 -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 +90 -5
- package/dist/Helpers/FormStore.js +318 -22
- package/dist/Helpers/InputConditions.d.ts +50 -0
- package/dist/Helpers/InputConditions.js +168 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +385 -0
- package/dist/Helpers/InputNarrowing.d.ts +18 -0
- package/dist/Helpers/InputNarrowing.js +67 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +162 -71
- package/dist/Helpers/MessageBuilder.js +2 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
- package/dist/Helpers/NexusDriveAdapter.js +207 -0
- package/dist/Helpers/NexusHelpers.d.ts +0 -0
- package/dist/Helpers/NexusHelpers.js +1 -0
- package/dist/Helpers/NumberHelpers.d.ts +95 -0
- package/dist/Helpers/NumberHelpers.js +126 -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 +47 -0
- package/dist/Helpers/RequestCache.js +132 -0
- package/dist/Helpers/RowsStore.d.ts +37 -2
- package/dist/Helpers/RowsStore.js +128 -10
- 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 +21 -15
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +24 -3
- package/dist/Helpers/TableHelpers.js +58 -12
- package/dist/Helpers/TableJoins.d.ts +83 -0
- package/dist/Helpers/TableJoins.js +284 -0
- package/dist/Helpers/TableStore.d.ts +26 -1
- package/dist/Helpers/TableStore.js +73 -7
- package/dist/Helpers/TreeStore.d.ts +306 -11
- package/dist/Helpers/TreeStore.js +1048 -90
- package/dist/Helpers/UserPreferences.d.ts +8 -0
- package/dist/Helpers/UserPreferences.js +17 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +512 -14
- package/dist/Interfaces/FileInterfaces.d.ts +162 -0
- package/dist/Interfaces/FileInterfaces.js +4 -0
- package/dist/Interfaces/FormInterfaces.d.ts +393 -29
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +39 -3
- package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
- package/dist/Interfaces/PreferenceInterfaces.d.ts +8 -0
- package/dist/Interfaces/PreferenceInterfaces.js +2 -5
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +160 -5
- package/dist/Services/ApiProxy.d.ts +36 -1
- package/dist/Services/ApiProxy.js +182 -52
- package/dist/Services/ApiProxyRules.d.ts +203 -0
- package/dist/Services/ApiProxyRules.js +426 -0
- 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 +80 -0
- package/dist/Services/JoinSources.js +150 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Services/ServerApi.d.ts +13 -2
- package/dist/Services/ServerApi.js +8 -3
- package/dist/Shared.Index.d.ts +20 -0
- package/dist/Shared.Index.js +20 -0
- package/package.json +92 -85
- package/src/Styles/Nexus.Avatar.css +80 -79
- package/src/Styles/Nexus.Base.css +117 -117
- package/src/Styles/Nexus.Button.css +186 -180
- package/src/Styles/Nexus.Calendar.css +273 -273
- package/src/Styles/Nexus.Chat.css +1443 -1443
- package/src/Styles/Nexus.Choice.css +359 -343
- package/src/Styles/Nexus.Crud.css +542 -168
- package/src/Styles/Nexus.DateTime.css +81 -81
- package/src/Styles/Nexus.Dropdown.css +733 -725
- package/src/Styles/Nexus.Editor.css +295 -295
- package/src/Styles/Nexus.Excel.css +389 -389
- package/src/Styles/Nexus.Files.css +410 -0
- package/src/Styles/Nexus.Form.css +282 -279
- package/src/Styles/Nexus.Index.css +41 -37
- package/src/Styles/Nexus.Input.css +238 -4
- package/src/Styles/Nexus.Markdown.css +237 -237
- package/src/Styles/Nexus.Menu.css +246 -237
- package/src/Styles/Nexus.Popup.css +800 -724
- 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.Rows.css +637 -637
- package/src/Styles/Nexus.Slider.css +276 -276
- package/src/Styles/Nexus.Tab.Buttons.css +497 -432
- package/src/Styles/Nexus.Table.css +98 -35
- package/src/Styles/Nexus.Theme.Picker.css +59 -3
- package/src/Styles/Nexus.Themes.css +200 -194
- package/src/Styles/Nexus.Time.css +144 -144
- package/src/Styles/Nexus.Toast.css +263 -263
- package/src/Styles/Nexus.Tokens.css +94 -89
- package/src/Styles/Nexus.Tree.css +530 -431
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { iconAlertCircle, iconArrowAutofitWidth, iconArrowBarToLeft, iconArrowBarToRight, iconArrowDown, iconArrowLeft, iconArrowRight, iconArrowUp, iconArrowsSort, iconChecks, iconChevronDown, iconColumns3, iconDatabase, iconDotsVertical, iconEyeOff, iconDownload, iconFileExport, iconFileImport, iconFilter, iconFilterOff, iconPinnedOff, iconPlus, iconPrinter, iconRefresh, iconRestore, iconRowInsertBottom, iconSearch, iconSearchOff, iconSortAscending, iconSortDescending, iconX, InlineIcon, } from "nexus-icons";
|
|
3
|
+
import { iconAlertCircle, iconArrowAutofitWidth, iconArrowBarToLeft, iconArrowBarToRight, iconArrowDown, iconArrowLeft, iconArrowRight, iconArrowUp, iconArrowsSort, iconChecks, iconChevronDown, iconColumns3, iconDatabase, iconDotsVertical, iconEyeOff, iconDownload, iconFileExport, iconFileImport, iconFilter, iconFilterOff, iconPin, iconPinnedOff, iconPlus, iconPrinter, iconRefresh, iconRestore, iconRowInsertBottom, iconSearch, iconSearchOff, iconSortAscending, iconSortDescending, iconX, InlineIcon, } from "nexus-icons";
|
|
4
4
|
import { useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
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
|
-
import {
|
|
12
|
+
import { joinKeys, joinSourceName, tableJoins } from "../../Helpers/TableJoins.js";
|
|
13
|
+
import { canPinColumn, columnAlign, columnValue, cssLength, DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, formatCellText, headBands, MIN_COLUMN_WIDTH, orderColumns, releasedPins, TABLE_ACTIONS_COLUMN, TABLE_INDEX_COLUMN, viewColumns, } from "../../Helpers/TableHelpers.js";
|
|
12
14
|
import { TableStore } from "../../Helpers/TableStore.js";
|
|
13
15
|
import { readUserPreferences, useNumberLocale, useUserPreferences } from "../../Helpers/UserPreferences.js";
|
|
14
16
|
import { Button } from "../Documents/Button.js";
|
|
15
17
|
import { Menu } from "../Documents/Menu.js";
|
|
16
18
|
import { SplitButton } from "../Documents/SplitButton.js";
|
|
19
|
+
import { useReadOnlyNotice } from "../Inputs/ReadOnlyNotice.js";
|
|
17
20
|
import { TextBox } from "../Inputs/TextBox.js";
|
|
18
21
|
import { toast } from "../Layouts/Toaster.js";
|
|
19
22
|
import { downloadExcelTemplate, exportExcel } from "../../Services/ExcelApi.js";
|
|
23
|
+
import { joinSource } from "../../Services/JoinSources.js";
|
|
20
24
|
import { openExcelImport } from "../Forms/ExcelImport.js";
|
|
21
25
|
import { TableColumnsPanel } from "./DataTableColumns.js";
|
|
22
26
|
import { TableFiltersPanel } from "./DataTableFilters.js";
|
|
23
|
-
import { columnStyle, formatCount, TableColumnGroup, TableFooter, TableIndexCell, TableMessageRow, TablePageSize,
|
|
27
|
+
import { columnStyle, formatCount, TableColumnGroup, TableFooter, TableIndexCell, TableMessageRow, TablePageSize, TableRowView, TableSectionHead, TableSkeletonRows, } from "./DataTableParts.js";
|
|
24
28
|
/*
|
|
25
29
|
* DataTable: rows in a table with a search, sorting by column, pages, a selection with actions on the selected rows,
|
|
26
30
|
* actions on each row, and columns the user arranges (shown or hidden, in any order, resized, pinned to a side). Rows
|
|
27
|
-
* given as `
|
|
31
|
+
* given as `sections` (the records pinned, the favourites) sit above the page, each folding open and shut. Rows are given, loaded once, or
|
|
28
32
|
* loaded a page at a time; what the table shows lives in a `TableStore`, which pages (and the CRUD component) change
|
|
29
33
|
* from outside.
|
|
30
34
|
*/
|
|
@@ -37,6 +41,12 @@ export function useTable() {
|
|
|
37
41
|
return store;
|
|
38
42
|
}
|
|
39
43
|
const STATE_PREFIX = "nx-table:";
|
|
44
|
+
/** The key of the section `pinnedRows` makes. */
|
|
45
|
+
const PINS_SECTION = "pins";
|
|
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;
|
|
40
50
|
function readSaved(key) {
|
|
41
51
|
if (!key || typeof localStorage === "undefined")
|
|
42
52
|
return null;
|
|
@@ -48,7 +58,7 @@ function readSaved(key) {
|
|
|
48
58
|
return null;
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
|
-
// Settings are written in two places (the query and the columns together,
|
|
61
|
+
// Settings are written in two places (the query and the columns together, a section's fold on its own click), so a
|
|
52
62
|
// write keeps whatever it does not mention.
|
|
53
63
|
function writeSaved(key, changes) {
|
|
54
64
|
try {
|
|
@@ -71,6 +81,9 @@ function usePerRow(make) {
|
|
|
71
81
|
return value;
|
|
72
82
|
}, [cache, make]);
|
|
73
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));
|
|
74
87
|
const sameNumbers = (a, b) => {
|
|
75
88
|
const keys = Object.keys(a);
|
|
76
89
|
return keys.length === Object.keys(b).length && keys.every(key => a[key] === b[key]);
|
|
@@ -93,17 +106,24 @@ const sameNumbers = (a, b) => {
|
|
|
93
106
|
* />
|
|
94
107
|
*/
|
|
95
108
|
export function DataTable(props) {
|
|
96
|
-
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, 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, 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, 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;
|
|
97
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";
|
|
98
114
|
const reloadable = props.reloadable ?? Boolean(load);
|
|
99
115
|
const searchDelay = props.searchDelay ?? (paging === "server" ? 300 : 0);
|
|
100
|
-
// A table with checkboxes numbers its rows: the number becomes the checkbox as soon as the row is under the pointer.
|
|
101
|
-
|
|
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);
|
|
102
120
|
// The person's choices in the quick preference panel, once made, go over what the table names itself.
|
|
103
121
|
const preferences = useUserPreferences();
|
|
104
122
|
const openOnClick = preferences.rowClick ?? ownOpenOnClick ?? false;
|
|
105
|
-
/** 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. */
|
|
106
124
|
const openingPageSize = () => {
|
|
125
|
+
if (whole)
|
|
126
|
+
return WHOLE_PAGE;
|
|
107
127
|
const preferred = readUserPreferences().pageSize;
|
|
108
128
|
return preferred !== null && pageSizes.includes(preferred) ? preferred : (ownPageSize ?? DEFAULT_PAGE_SIZE);
|
|
109
129
|
};
|
|
@@ -111,18 +131,29 @@ export function DataTable(props) {
|
|
|
111
131
|
const table = givenTable ?? own;
|
|
112
132
|
// Prepared once for the store and the panel: the same params without what would make them filter on their own.
|
|
113
133
|
const filterInputs = useMemo(() => props.filterInputs?.map(asFilterInput), [props.filterInputs]);
|
|
114
|
-
table.configure({ columns, rowKey, rows: givenRows, load, paging, maxSelection, onSelectionChange, filterInputs });
|
|
134
|
+
table.configure({ columns, rowKey, rows: givenRows, load, paging, maxSelection, onSelectionChange, onPickChange: onPick, filterInputs });
|
|
115
135
|
useImperativeHandle(ref, () => table, [table]);
|
|
116
136
|
const state = useSyncExternalStore(table.subscribe, table.getState, table.getState);
|
|
137
|
+
// The columns whose values are other records', looked up by key (`join`). Only a table with one listens to the page's
|
|
138
|
+
// lookups, and it draws again - its rows too, through `cellOptions` - each time names come in.
|
|
139
|
+
const joins = useMemo(() => columns.flatMap(column => (column.join ? [column.join] : [])), [columns]);
|
|
140
|
+
const subscribeJoins = useCallback((listener) => (joins.length ? tableJoins.subscribe(listener) : () => { }), [joins.length]);
|
|
141
|
+
const joinsVersion = useSyncExternalStore(subscribeJoins, joins.length ? tableJoins.getVersion : NO_VERSION, NO_VERSION);
|
|
142
|
+
// A spreadsheet's items ask the app's permissions, as a CRUD's actions do, so only a table with one listens for them.
|
|
143
|
+
const hasSheet = Boolean(excel);
|
|
144
|
+
const subscribeSheet = useCallback((listener) => (hasSheet ? subscribePermissions(listener) : () => { }), [hasSheet]);
|
|
145
|
+
useSyncExternalStore(subscribeSheet, hasSheet ? permissionsVersion : NO_VERSION, NO_VERSION);
|
|
117
146
|
const dates = useDatePreferences();
|
|
118
147
|
const timeZone = useBrowserTimeZone();
|
|
119
148
|
const numbers = useNumberLocale();
|
|
120
|
-
|
|
149
|
+
// `joinsVersion` is read by no cell option: a new object is what tells the memoized rows that their names came in.
|
|
150
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
151
|
+
const cellOptions = useMemo(() => ({ preferences: dates, timeZone: timeZone ?? undefined, locale: numbers, currency }), [dates, timeZone, numbers, currency, joinsVersion]);
|
|
121
152
|
const tableId = useId();
|
|
122
153
|
// The search box is uncontrolled: a new key clears it when a new list starts over.
|
|
123
154
|
const [searchGeneration, setSearchGeneration] = useState(0);
|
|
124
|
-
//
|
|
125
|
-
const [
|
|
155
|
+
// Every section starts folded shut, then takes the choice this device remembers (a layout effect: no flicker).
|
|
156
|
+
const [openSections, setOpenSections] = useState(() => new Set());
|
|
126
157
|
const searchTimer = useRef(undefined);
|
|
127
158
|
const searchRef = useRef(null);
|
|
128
159
|
const started = useRef(false);
|
|
@@ -134,15 +165,16 @@ export function DataTable(props) {
|
|
|
134
165
|
started.current = true;
|
|
135
166
|
const saved = readSaved(stateKey);
|
|
136
167
|
table.restore({
|
|
137
|
-
pageSize: saved?.pageSize && pageSizes.includes(saved.pageSize) ? saved.pageSize : openingPageSize(),
|
|
168
|
+
pageSize: !whole && saved?.pageSize && pageSizes.includes(saved.pageSize) ? saved.pageSize : openingPageSize(),
|
|
138
169
|
sort: saved?.sort !== undefined ? saved.sort : defaultSort,
|
|
139
170
|
hidden: saved?.hidden ?? columns.filter(column => column.hidden).map(column => column.key),
|
|
140
171
|
order: saved?.order,
|
|
141
172
|
widths: saved?.widths,
|
|
142
173
|
pinned: saved?.pinned,
|
|
143
174
|
});
|
|
144
|
-
|
|
145
|
-
|
|
175
|
+
const open = saved?.openSections ?? (saved?.pinsOpen ? [PINS_SECTION] : []);
|
|
176
|
+
if (open.length)
|
|
177
|
+
setOpenSections(new Set(open));
|
|
146
178
|
if (givenRows)
|
|
147
179
|
table.setRows(givenRows);
|
|
148
180
|
else
|
|
@@ -159,6 +191,26 @@ export function DataTable(props) {
|
|
|
159
191
|
if (started.current && givenRows)
|
|
160
192
|
table.setRows(givenRows);
|
|
161
193
|
}, [table, givenRows]);
|
|
194
|
+
// Another table's settings behind the same table (a CRUD's scope keeps its own): read as on the first draw, before the
|
|
195
|
+
// rows its new list loads, and the settings on screen are not written under the new key (`restoredKey` is what says
|
|
196
|
+
// the store already holds that key's settings).
|
|
197
|
+
const restoredKey = useRef(stateKey);
|
|
198
|
+
useLayoutEffect(() => {
|
|
199
|
+
if (!started.current || restoredKey.current === stateKey)
|
|
200
|
+
return;
|
|
201
|
+
restoredKey.current = stateKey;
|
|
202
|
+
const saved = readSaved(stateKey);
|
|
203
|
+
table.restore({
|
|
204
|
+
pageSize: !whole && saved?.pageSize && pageSizes.includes(saved.pageSize) ? saved.pageSize : openingPageSize(),
|
|
205
|
+
sort: saved?.sort !== undefined ? saved.sort : (defaultSort ?? null),
|
|
206
|
+
hidden: saved?.hidden ?? columns.filter(column => column.hidden).map(column => column.key),
|
|
207
|
+
order: saved?.order ?? [],
|
|
208
|
+
widths: saved?.widths ?? {},
|
|
209
|
+
pinned: saved?.pinned ?? {},
|
|
210
|
+
}, { notify: true });
|
|
211
|
+
setOpenSections(new Set(saved?.openSections ?? (saved?.pinsOpen ? [PINS_SECTION] : [])));
|
|
212
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
213
|
+
}, [table, stateKey]);
|
|
162
214
|
// Another list behind the same table: start over from the first page, with no search and no selection.
|
|
163
215
|
useLayoutEffect(() => {
|
|
164
216
|
if (loadedKey.current === loadKey)
|
|
@@ -169,20 +221,24 @@ export function DataTable(props) {
|
|
|
169
221
|
void table.reload({ reset: true });
|
|
170
222
|
}, [table, loadKey]);
|
|
171
223
|
useEffect(() => () => clearTimeout(searchTimer.current), []);
|
|
172
|
-
// Saved settings follow the user's choices.
|
|
224
|
+
// Saved settings follow the user's choices. What is written is the store's state now, never this draw's: the first
|
|
225
|
+
// draw, and the draw that took another key's settings, still hold the settings from before the restore, and writing
|
|
226
|
+
// those would put the opening defaults (or another key's settings) under the key.
|
|
173
227
|
const hiddenList = useMemo(() => [...state.hidden].sort(), [state.hidden]);
|
|
174
228
|
useEffect(() => {
|
|
175
229
|
if (!stateKey || !started.current)
|
|
176
230
|
return;
|
|
231
|
+
const now = table.getState();
|
|
177
232
|
writeSaved(stateKey, {
|
|
178
233
|
// Only a size the person picked here: one equal to the default is not kept, so a new default still reaches it.
|
|
179
|
-
pageSize:
|
|
180
|
-
sort:
|
|
181
|
-
hidden:
|
|
182
|
-
order:
|
|
183
|
-
widths: Object.keys(
|
|
184
|
-
pinned: Object.keys(
|
|
234
|
+
pageSize: now.query.pageSize === openingPageSize() ? undefined : now.query.pageSize,
|
|
235
|
+
sort: now.query.sort,
|
|
236
|
+
hidden: [...now.hidden].sort(),
|
|
237
|
+
order: now.order.length ? [...now.order] : undefined,
|
|
238
|
+
widths: Object.keys(now.widths).length ? now.widths : undefined,
|
|
239
|
+
pinned: Object.keys(now.pinned).length ? now.pinned : undefined,
|
|
185
240
|
});
|
|
241
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
186
242
|
}, [stateKey, state.query.pageSize, state.query.sort, hiddenList, state.order, state.widths, state.pinned]);
|
|
187
243
|
// A new default page size from the panel reaches an open table where no size was picked. Only a change counts: on the
|
|
188
244
|
// first effects the save above still holds the store's opening size and has not written the restored one yet, so a
|
|
@@ -193,11 +249,25 @@ export function DataTable(props) {
|
|
|
193
249
|
if (preferredSize.current === preferences.pageSize)
|
|
194
250
|
return;
|
|
195
251
|
preferredSize.current = preferences.pageSize;
|
|
196
|
-
if (!started.current || readSaved(stateKey)?.pageSize)
|
|
252
|
+
if (whole || !started.current || readSaved(stateKey)?.pageSize)
|
|
197
253
|
return;
|
|
198
254
|
table.setPageSize(openingPageSize());
|
|
199
255
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
200
256
|
}, [preferences.pageSize]);
|
|
257
|
+
// Every key the rows hold for a join, asked for together: a client table's every row, since it searches and sorts them
|
|
258
|
+
// all, a server table's page, and the rows of the sections above it. What is known already is not asked again.
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (!joins.length || state.status !== "ready")
|
|
261
|
+
return;
|
|
262
|
+
const shown = [...table.getRows(), ...(pinnedRows ?? []), ...(ownSections ?? []).flatMap(section => section.rows)];
|
|
263
|
+
for (const [source, keys] of joinKeys(joins, shown))
|
|
264
|
+
tableJoins.request(joinSource(source), keys);
|
|
265
|
+
}, [joins, state.status, state.rows, pinnedRows, ownSections, table]);
|
|
266
|
+
// Names came in: a client table searches and sorts again with them.
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
if (joinsVersion)
|
|
269
|
+
table.refreshValues();
|
|
270
|
+
}, [joinsVersion, table]);
|
|
201
271
|
const visible = useMemo(() => columns.filter(column => !state.hidden.has(column.key)), [columns, state.hidden]);
|
|
202
272
|
// Every column in the order the user put them in, hidden ones among them: what the Columns panel lists.
|
|
203
273
|
const ordered = useMemo(() => orderColumns(columns, state.order), [columns, state.order]);
|
|
@@ -205,7 +275,7 @@ export function DataTable(props) {
|
|
|
205
275
|
if (rowLabel)
|
|
206
276
|
return rowLabel(row);
|
|
207
277
|
const first = columns[0];
|
|
208
|
-
const text = first ? formatCellText(first, columnValue(first, row), cellOptions) : "";
|
|
278
|
+
const text = first ? formatCellText(first, columnValue(first, row), cellOptions, row) : "";
|
|
209
279
|
return text || `row ${table.keyOf(row)}`;
|
|
210
280
|
}, [rowLabel, columns, cellOptions, table]);
|
|
211
281
|
const actionsOf = usePerRow(rowActions);
|
|
@@ -232,6 +302,10 @@ export function DataTable(props) {
|
|
|
232
302
|
void result.finally(() => table.setBusy([key], false)).catch(() => undefined);
|
|
233
303
|
}
|
|
234
304
|
}, [table]);
|
|
305
|
+
// The picked row (`onPick`): a click on a row, or its radio button, picks it through the store, which follows it.
|
|
306
|
+
const pickRow = useCallback((row) => table.pick(row), [table]);
|
|
307
|
+
const pickName = `${tableId}pick`;
|
|
308
|
+
const pickedKey = state.picked ? table.keyOf(state.picked) : null;
|
|
235
309
|
const onSearch = (text) => {
|
|
236
310
|
clearTimeout(searchTimer.current);
|
|
237
311
|
if (searchDelay > 0 && text.trim())
|
|
@@ -252,9 +326,11 @@ export function DataTable(props) {
|
|
|
252
326
|
const headRef = useRef(null);
|
|
253
327
|
const colRefs = useMemo(() => new Map(), []);
|
|
254
328
|
const [offsets, setOffsets] = useState({});
|
|
329
|
+
// Pinned columns that scroll for now: on a narrow table the ones past `PINNED_SHARE` of its width give way.
|
|
330
|
+
const [released, setReleased] = useState(NONE_RELEASED);
|
|
255
331
|
const sized = Object.keys(state.widths).length > 0;
|
|
256
|
-
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]);
|
|
257
|
-
const indexOn = selectable || numbered;
|
|
332
|
+
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]);
|
|
333
|
+
const indexOn = selectable || numbered || pickable;
|
|
258
334
|
// The bands above the headings. With no group anywhere the header is the one row it always was.
|
|
259
335
|
const bands = useMemo(() => headBands(views), [views]);
|
|
260
336
|
const grouped = bands.some(band => band.group !== null);
|
|
@@ -299,8 +375,36 @@ export function DataTable(props) {
|
|
|
299
375
|
// The bar of the selected rows starts after the checkbox column, whatever width it took.
|
|
300
376
|
root?.style.setProperty("--nx-table-index-w", `${Math.round(left)}px`);
|
|
301
377
|
setOffsets(previous => (sameNumbers(previous, next) ? previous : next));
|
|
378
|
+
settlePins.current();
|
|
302
379
|
});
|
|
380
|
+
// Which pinned columns give way: measured from the headings after every render and whenever the table's width
|
|
381
|
+
// changes. Staying or scrolling changes no width, so it settles at once.
|
|
382
|
+
const settlePins = useRef(() => undefined);
|
|
383
|
+
settlePins.current = () => {
|
|
384
|
+
const scroller = scrollRef.current;
|
|
385
|
+
const cells = headLeafCells(headRef.current);
|
|
386
|
+
if (!scroller || cells.length === 0)
|
|
387
|
+
return;
|
|
388
|
+
const sides = new Map(views.map(view => [view.column.key, view.side]));
|
|
389
|
+
let frameWidth = 0;
|
|
390
|
+
const left = [];
|
|
391
|
+
const right = [];
|
|
392
|
+
for (const cell of cells) {
|
|
393
|
+
const key = cell.getAttribute("data-col");
|
|
394
|
+
const width = cell.getBoundingClientRect().width;
|
|
395
|
+
if (key === TABLE_INDEX_COLUMN || key === TABLE_ACTIONS_COLUMN)
|
|
396
|
+
frameWidth += width;
|
|
397
|
+
else if (key && sides.get(key) === "left")
|
|
398
|
+
left.push([key, width]);
|
|
399
|
+
else if (key && sides.get(key) === "right")
|
|
400
|
+
right.unshift([key, width]);
|
|
401
|
+
}
|
|
402
|
+
const next = left.length + right.length === 0 ? NONE_RELEASED : releasedPins(scroller.clientWidth, frameWidth, left, right);
|
|
403
|
+
setReleased(previous => (sameKeys(previous, next) ? previous : next));
|
|
404
|
+
};
|
|
303
405
|
// Shadows at the pinned columns while the others scroll under them: set on the frame, with no render.
|
|
406
|
+
// The width the rows can show goes on the frame too, so a message across a wide table (no rows, a hint, a failed
|
|
407
|
+
// load) stays inside what is on screen instead of centring on the whole scroll width.
|
|
304
408
|
useLayoutEffect(() => {
|
|
305
409
|
const scroller = scrollRef.current;
|
|
306
410
|
const frameElement = scroller?.parentElement;
|
|
@@ -311,9 +415,14 @@ export function DataTable(props) {
|
|
|
311
415
|
frameElement.toggleAttribute("data-scroll-start", left > 1);
|
|
312
416
|
frameElement.toggleAttribute("data-scroll-end", left < scroller.scrollWidth - scroller.clientWidth - 1);
|
|
313
417
|
};
|
|
314
|
-
|
|
418
|
+
const measure = () => {
|
|
419
|
+
frameElement.style.setProperty("--nx-table-view-w", `${scroller.clientWidth}px`);
|
|
420
|
+
update();
|
|
421
|
+
settlePins.current();
|
|
422
|
+
};
|
|
423
|
+
measure();
|
|
315
424
|
scroller.addEventListener("scroll", update, { passive: true });
|
|
316
|
-
const observer = new ResizeObserver(
|
|
425
|
+
const observer = new ResizeObserver(measure);
|
|
317
426
|
observer.observe(scroller);
|
|
318
427
|
if (scroller.firstElementChild)
|
|
319
428
|
observer.observe(scroller.firstElementChild);
|
|
@@ -439,6 +548,10 @@ export function DataTable(props) {
|
|
|
439
548
|
const barRef = useRef(null);
|
|
440
549
|
const reloadRef = useRef(null);
|
|
441
550
|
const [reloadHidden, setReloadHidden] = useState(false);
|
|
551
|
+
// An Add that cannot add yet (`add.disabled` with a `description`) says why on a press, under it.
|
|
552
|
+
const addNotice = useReadOnlyNotice(Boolean(add?.disabled && add.description), barRef, add?.description, { anchor: target => target.closest(".nx-btn"), typing: false, lock: "label" });
|
|
553
|
+
// The spreadsheet's other ways to add sit under the Add's arrow only while it can add.
|
|
554
|
+
const addable = Boolean(add && !add.disabled);
|
|
442
555
|
const addPart = Boolean(add || actions);
|
|
443
556
|
const listsPart = Boolean(toolbar);
|
|
444
557
|
useLayoutEffect(() => {
|
|
@@ -463,28 +576,44 @@ export function DataTable(props) {
|
|
|
463
576
|
cancelAnimationFrame(frame);
|
|
464
577
|
};
|
|
465
578
|
}, [addPart, listsPart, searchable, reloadable]);
|
|
579
|
+
// A person's Reload is a refresh of what the table shows, the names it looked up included: those are marked old - still
|
|
580
|
+
// drawn, asked again with the rows - and nothing else the page keeps is touched.
|
|
466
581
|
const reload = () => {
|
|
582
|
+
for (const join of joins)
|
|
583
|
+
tableJoins.expire(joinSourceName(join.source));
|
|
467
584
|
onReload?.();
|
|
468
585
|
void table.reload({ quiet: true });
|
|
469
586
|
};
|
|
470
587
|
// A spreadsheet: the template to fill, the file to bring back, and an export the server writes from what is on
|
|
471
|
-
// screen. With an Add, they are other ways to add, under its arrow; without one, they are in the options menu.
|
|
588
|
+
// screen. With an Add, they are other ways to add, under its arrow; without one, they are in the options menu. Each is
|
|
589
|
+
// offered only to a person the app's permissions let call every endpoint it needs, the settings included.
|
|
590
|
+
const sheet = excel?.endpoints;
|
|
591
|
+
const mayUseSheet = (...endpoints) => [sheet.settings, ...endpoints].every(endpoint => canCall(endpoint));
|
|
472
592
|
const sheetItems = excel
|
|
473
593
|
? [
|
|
474
|
-
{ id: "excel-template", label: "Download template", description: `A spreadsheet to fill in`, icon: _jsx(InlineIcon, { icon: iconDownload }),
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
594
|
+
...(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 }) }] : []),
|
|
595
|
+
...(mayUseSheet(sheet.verify, sheet.import)
|
|
596
|
+
? [
|
|
597
|
+
{ 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 }) },
|
|
598
|
+
// 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
|
|
599
|
+
// only to a person who may create, and nobody types fifty rows to be refused at the end.
|
|
600
|
+
...(excel.bulk === false || !addable
|
|
601
|
+
? []
|
|
602
|
+
: [{ 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" }) }]),
|
|
603
|
+
]
|
|
604
|
+
: []),
|
|
605
|
+
...(sheet.export && mayUseSheet(sheet.export)
|
|
482
606
|
? [{ 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()) }]
|
|
483
607
|
: []),
|
|
484
|
-
]
|
|
608
|
+
].map((item, index) => (index === 0 ? { ...item, separator: true } : item))
|
|
485
609
|
: [];
|
|
486
610
|
// In the bar it is `block`: as wide as its own label there, and the whole line once the Add section takes one.
|
|
487
|
-
const addButton = (block = false) => add && (_jsx(Button, { variant: "primary", size: "sm", block: block, icon: _jsx(InlineIcon, { icon: iconPlus }),
|
|
611
|
+
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 => {
|
|
612
|
+
if (!add.disabled)
|
|
613
|
+
add.onClick();
|
|
614
|
+
else if (!add.onDisabledPress?.())
|
|
615
|
+
addNotice.show(event.currentTarget);
|
|
616
|
+
}, children: add.label }));
|
|
488
617
|
const optionItems = [
|
|
489
618
|
...(filterInputs?.length
|
|
490
619
|
? [{ id: "filters", label: "Filters", description: filtersOn.join(", ") || undefined, icon: _jsx(InlineIcon, { icon: iconFilter }), onSelect: () => filtersRef.current?.open() }]
|
|
@@ -499,7 +628,7 @@ export function DataTable(props) {
|
|
|
499
628
|
: []),
|
|
500
629
|
// The sheet's own Import replaces the page's `onImport` hook.
|
|
501
630
|
...(excel
|
|
502
|
-
?
|
|
631
|
+
? addable
|
|
503
632
|
? []
|
|
504
633
|
: sheetItems
|
|
505
634
|
: onImport
|
|
@@ -513,7 +642,9 @@ export function DataTable(props) {
|
|
|
513
642
|
...(reloadable && reloadHidden ? [{ id: "reload", label: "Reload", icon: _jsx(InlineIcon, { icon: iconRefresh }), separator: true, onSelect: reload }] : []),
|
|
514
643
|
];
|
|
515
644
|
const optionEntries = menuEntries(optionItems);
|
|
516
|
-
|
|
645
|
+
// Rows per page: inside the search box, in the bar without one, or beside the pager (`pager`); none for a table read whole.
|
|
646
|
+
const pageSizePicker = whole ? null : _jsx(TablePageSize, { pageSize: query.pageSize, pageSizes: pageSizes, compact: searchable && pager === "bar", onChange: value => table.setPageSize(value) });
|
|
647
|
+
const barPageSize = pager === "bar" ? pageSizePicker : null;
|
|
517
648
|
let body;
|
|
518
649
|
if (status === "loading") {
|
|
519
650
|
body = _jsx(TableSkeletonRows, { count: Math.min(query.pageSize, 8), cells: cellCount, frame: frame, sized: sized });
|
|
@@ -544,24 +675,33 @@ export function DataTable(props) {
|
|
|
544
675
|
body = rows.map((row, index) => {
|
|
545
676
|
const key = pageKeys[index];
|
|
546
677
|
const selected = state.selected.has(key);
|
|
547
|
-
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, frame: frame, sized: sized, actions: hasActions ? (actionsOf(row) ?? []) : null, onToggle: onToggle, onAction: onAction, onOpen: onRowOpen, openOnClick: openOnClick }, key || index));
|
|
678
|
+
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, onPick: pickable ? pickRow : undefined, picked: key === pickedKey, pickName: pickable ? pickName : undefined }, key || index));
|
|
548
679
|
});
|
|
549
680
|
}
|
|
550
|
-
/* The
|
|
681
|
+
/* The sections above the page: the records pinned, the favourites, each folded open or shut, remembered on this device */
|
|
682
|
+
const pinsSection = pinnedRows ? { key: PINS_SECTION, label: pinnedLabel, icon: iconPin, rows: pinnedRows, loading: pinnedLoading, max: pinnedMax } : null;
|
|
551
683
|
// A section with nothing in it and nothing on its way would be an empty heading, so it does not show at all.
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
const next =
|
|
555
|
-
|
|
684
|
+
const sections = [...(pinsSection ? [pinsSection] : []), ...(ownSections ?? [])].filter(section => section.rows.length > 0 || section.loading);
|
|
685
|
+
const toggleSection = (key) => {
|
|
686
|
+
const next = new Set(openSections);
|
|
687
|
+
if (!next.delete(key))
|
|
688
|
+
next.add(key);
|
|
689
|
+
setOpenSections(next);
|
|
556
690
|
if (stateKey)
|
|
557
|
-
writeSaved(stateKey, {
|
|
691
|
+
writeSaved(stateKey, { openSections: [...next], pinsOpen: undefined });
|
|
558
692
|
};
|
|
559
|
-
const
|
|
560
|
-
|
|
693
|
+
const sectionRows = (section) => {
|
|
694
|
+
// A row with no marks of its own still wears the section's icon.
|
|
695
|
+
const own = [{ icon: section.icon, label: section.label }];
|
|
696
|
+
return section.rows.map((row, index) => {
|
|
561
697
|
const key = table.keyOf(row);
|
|
562
698
|
const selected = state.selected.has(key);
|
|
563
|
-
|
|
699
|
+
const marks = rowMarks?.(row);
|
|
700
|
+
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,
|
|
701
|
+
// A click picks a section's row too; its radio button is the one in the list below, where the arrow keys move.
|
|
702
|
+
onPick: pickable ? pickRow : undefined, picked: key === pickedKey }, key || index));
|
|
564
703
|
});
|
|
704
|
+
};
|
|
565
705
|
const selectedRows = selecting ? [...state.selected.values()] : [];
|
|
566
706
|
// Select again by what the rows are: exactly the rows on the page that pass, or the whole page.
|
|
567
707
|
const pick = (label, test) => {
|
|
@@ -580,8 +720,8 @@ export function DataTable(props) {
|
|
|
580
720
|
const mass = selectedRows.length ? (typeof massActions === "function" ? massActions(selectedRows) : (massActions ?? [])) : [];
|
|
581
721
|
// 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.
|
|
582
722
|
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));
|
|
583
|
-
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 &&
|
|
584
|
-
(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 &&
|
|
723
|
+
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 &&
|
|
724
|
+
(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 => {
|
|
585
725
|
if (event.key === "Enter") {
|
|
586
726
|
clearTimeout(searchTimer.current);
|
|
587
727
|
table.setSearch(event.currentTarget.value);
|
|
@@ -590,13 +730,16 @@ export function DataTable(props) {
|
|
|
590
730
|
lastToggled.current = null;
|
|
591
731
|
if (table.selectPage(event.currentTarget.checked) > 0)
|
|
592
732
|
warnFull();
|
|
593
|
-
} }) })) })), 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))) })] }),
|
|
733
|
+
} }) })) })), 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 => {
|
|
734
|
+
const open = openSections.has(section.key);
|
|
735
|
+
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));
|
|
736
|
+
}), _jsx("tbody", { children: body })] }) })] }), _jsx(TableFooter, { state: state, disabled: refreshing, whole: whole, pageSize: pager === "footer" ? pageSizePicker : null, onPage: page => table.setPage(page) }), addNotice.notice] }));
|
|
594
737
|
}
|
|
595
738
|
/** Where "Move left" and "Move right" put a column: the key it goes before, or null for the end of its side. */
|
|
596
739
|
function moveTargets(views, key) {
|
|
597
740
|
// A column moves among the columns on its own side, past the ones that stay put.
|
|
598
|
-
const
|
|
599
|
-
const group = views.filter(view => view.
|
|
741
|
+
const side = views.find(view => view.column.key === key)?.side ?? null;
|
|
742
|
+
const group = views.filter(view => view.side === side && view.movable);
|
|
600
743
|
const at = group.findIndex(view => view.column.key === key);
|
|
601
744
|
if (at < 0)
|
|
602
745
|
return { left: undefined, right: undefined };
|
|
@@ -637,11 +780,12 @@ function HeaderCell({ view, sized, rowSpan, sort, disabled, covered, canPin, can
|
|
|
637
780
|
if (canPin) {
|
|
638
781
|
if (entries.length)
|
|
639
782
|
entries.push({ separator: true });
|
|
640
|
-
|
|
783
|
+
// The side chosen, also while a narrow table lets it scroll.
|
|
784
|
+
if (view.side !== "left")
|
|
641
785
|
entries.push({ value: "pin-left", label: "Pin left", icon: _jsx(InlineIcon, { icon: iconArrowBarToLeft }) });
|
|
642
|
-
if (view.
|
|
786
|
+
if (view.side !== "right")
|
|
643
787
|
entries.push({ value: "pin-right", label: "Pin right", icon: _jsx(InlineIcon, { icon: iconArrowBarToRight }) });
|
|
644
|
-
if (view.
|
|
788
|
+
if (view.side)
|
|
645
789
|
entries.push({ value: "unpin", label: "Unpin", icon: _jsx(InlineIcon, { icon: iconPinnedOff }) });
|
|
646
790
|
}
|
|
647
791
|
if (view.movable && (neighbours.left !== undefined || neighbours.right !== undefined)) {
|
|
@@ -679,7 +823,7 @@ function HeaderCell({ view, sized, rowSpan, sort, disabled, covered, canPin, can
|
|
|
679
823
|
onHide();
|
|
680
824
|
}
|
|
681
825
|
const label = _jsx("span", { className: "nx-table__heading-label", children: column.label });
|
|
682
|
-
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 }))] }));
|
|
826
|
+
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 }))] }));
|
|
683
827
|
}
|
|
684
828
|
/** The steps a bar is squeezed into when its parts do not fit, each one keeping the steps before it. */
|
|
685
829
|
const BAR_SQUEEZES = ["search", "search reload", "search reload add"];
|
|
@@ -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();
|