nexus-shared 3.0.0 → 3.2.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 +442 -0
- package/README.md +1123 -1000
- package/dist/Client.Index.d.ts +6 -0
- package/dist/Client.Index.js +6 -0
- package/dist/Components/Chats/ChatParts.d.ts +2 -2
- package/dist/Components/Chats/ChatParts.js +3 -7
- package/dist/Components/Chats/ChatThread.js +3 -3
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/Menu.js +21 -9
- package/dist/Components/Documents/SplitButton.d.ts +3 -1
- package/dist/Components/Documents/SplitButton.js +7 -5
- package/dist/Components/Documents/TabButtons.d.ts +5 -2
- package/dist/Components/Documents/TabButtons.js +2 -2
- package/dist/Components/Forms/ApiForm.d.ts +1 -1
- package/dist/Components/Forms/ApiForm.js +1 -1
- package/dist/Components/Forms/Crud.js +486 -206
- package/dist/Components/Forms/CrudHistory.d.ts +66 -0
- package/dist/Components/Forms/CrudHistory.js +140 -0
- package/dist/Components/Forms/CrudParts.d.ts +5 -1
- package/dist/Components/Forms/CrudParts.js +38 -5
- package/dist/Components/Forms/ExcelImport.d.ts +23 -7
- package/dist/Components/Forms/ExcelImport.js +189 -60
- package/dist/Components/Forms/Form.js +9 -14
- package/dist/Components/Forms/SubmitForm.d.ts +11 -2
- package/dist/Components/Forms/SubmitForm.js +23 -12
- 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.js +4 -1
- package/dist/Components/Inputs/DropdownParts.d.ts +1 -1
- package/dist/Components/Inputs/DropdownParts.js +4 -6
- package/dist/Components/Inputs/InputRenderer.js +3 -0
- package/dist/Components/Inputs/NumberBox.js +26 -2
- package/dist/Components/Inputs/RowsInput.js +2 -2
- package/dist/Components/Inputs/Slider.js +4 -1
- package/dist/Components/Inputs/TabularForm.js +42 -10
- package/dist/Components/Inputs/TextArea.js +4 -1
- package/dist/Components/Inputs/TextBox.js +4 -1
- package/dist/Components/Inputs/TimePanel.js +4 -3
- package/dist/Components/Inputs/TimePicker.js +4 -1
- package/dist/Components/Inputs/TreeView.d.ts +55 -5
- package/dist/Components/Inputs/TreeView.js +372 -23
- package/dist/Components/Layouts/Popup.js +1 -4
- package/dist/Components/Layouts/ThemePicker.d.ts +21 -3
- package/dist/Components/Layouts/ThemePicker.js +89 -6
- package/dist/Components/Layouts/ThemeScript.d.ts +4 -2
- package/dist/Components/Layouts/ThemeScript.js +4 -2
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +6 -3
- package/dist/Components/Layouts/ThemeSwitcher.js +9 -8
- package/dist/Components/Viewers/Avatar.d.ts +21 -0
- package/dist/Components/Viewers/Avatar.js +21 -0
- package/dist/Components/Viewers/DataTable.js +252 -62
- package/dist/Components/Viewers/DataTableColumns.d.ts +7 -3
- package/dist/Components/Viewers/DataTableColumns.js +25 -68
- package/dist/Components/Viewers/DataTableFilters.d.ts +21 -0
- package/dist/Components/Viewers/DataTableFilters.js +68 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +28 -10
- package/dist/Components/Viewers/DataTableParts.js +42 -13
- package/dist/Helpers/AnimationHelpers.d.ts +11 -0
- package/dist/Helpers/AnimationHelpers.js +20 -2
- 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/CodeHelpers.d.ts +97 -0
- package/dist/Helpers/CodeHelpers.js +332 -0
- package/dist/Helpers/CrudBackend.d.ts +101 -8
- package/dist/Helpers/CrudBackend.js +172 -11
- package/dist/Helpers/CrudColumns.d.ts +51 -0
- package/dist/Helpers/CrudColumns.js +69 -0
- package/dist/Helpers/CrudHelpers.d.ts +50 -5
- package/dist/Helpers/CrudHelpers.js +109 -12
- package/dist/Helpers/ExcelBackend.d.ts +3 -3
- package/dist/Helpers/ExcelBackend.js +8 -2
- package/dist/Helpers/ExcelHelpers.d.ts +36 -8
- package/dist/Helpers/ExcelHelpers.js +55 -13
- package/dist/Helpers/FormStore.d.ts +9 -5
- package/dist/Helpers/FormStore.js +25 -5
- package/dist/Helpers/InputParamsHelpers.js +8 -0
- package/dist/Helpers/MessageBuilder.js +3 -0
- package/dist/Helpers/NexusHelpers.d.ts +0 -0
- package/dist/Helpers/NexusHelpers.js +1 -0
- package/dist/Helpers/NumberHelpers.d.ts +7 -1
- package/dist/Helpers/NumberHelpers.js +41 -3
- package/dist/Helpers/PopoverHelpers.d.ts +17 -5
- package/dist/Helpers/PopoverHelpers.js +52 -17
- package/dist/Helpers/PopoverHooks.d.ts +24 -0
- package/dist/Helpers/PopoverHooks.js +62 -0
- package/dist/Helpers/RequestCache.d.ts +35 -0
- package/dist/Helpers/RequestCache.js +113 -0
- package/dist/Helpers/RowsStore.d.ts +7 -2
- package/dist/Helpers/RowsStore.js +15 -3
- package/dist/Helpers/TableFilters.d.ts +25 -0
- package/dist/Helpers/TableFilters.js +162 -0
- package/dist/Helpers/TableHelpers.d.ts +7 -4
- package/dist/Helpers/TableHelpers.js +15 -6
- package/dist/Helpers/TableJoins.d.ts +70 -0
- package/dist/Helpers/TableJoins.js +250 -0
- package/dist/Helpers/TableStore.d.ts +16 -3
- package/dist/Helpers/TableStore.js +25 -5
- package/dist/Helpers/ThemeHelpers.d.ts +21 -4
- package/dist/Helpers/ThemeHelpers.js +46 -9
- package/dist/Helpers/TreeStore.d.ts +225 -11
- package/dist/Helpers/TreeStore.js +683 -90
- package/dist/Helpers/UserPreferences.d.ts +22 -0
- package/dist/Helpers/UserPreferences.js +116 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +67 -10
- package/dist/Interfaces/ExcelInterfaces.d.ts +12 -0
- package/dist/Interfaces/FormInterfaces.d.ts +37 -6
- package/dist/Interfaces/InputInterfaces.d.ts +24 -0
- package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
- package/dist/Interfaces/PreferenceInterfaces.d.ts +51 -0
- package/dist/Interfaces/PreferenceInterfaces.js +25 -0
- package/dist/Interfaces/TableInterfaces.d.ts +145 -16
- package/dist/Interfaces/ThemeInterfaces.d.ts +25 -0
- package/dist/Interfaces/ThemeInterfaces.js +15 -0
- 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.d.ts +3 -1
- package/dist/Services/ExcelApi.js +1 -1
- package/dist/Services/JoinSources.d.ts +48 -0
- package/dist/Services/JoinSources.js +74 -0
- package/dist/Services/ServerApi.d.ts +13 -2
- package/dist/Services/ServerApi.js +8 -3
- package/dist/Services/ThemeService.d.ts +18 -2
- package/dist/Services/ThemeService.js +80 -16
- package/dist/Shared.Index.d.ts +6 -0
- package/dist/Shared.Index.js +6 -0
- package/package.json +4 -2
- package/src/Styles/Nexus.Avatar.css +79 -0
- package/src/Styles/Nexus.Base.css +51 -4
- package/src/Styles/Nexus.Button.css +27 -0
- package/src/Styles/Nexus.Calendar.css +4 -6
- package/src/Styles/Nexus.Chat.css +5 -51
- package/src/Styles/Nexus.Choice.css +4 -6
- package/src/Styles/Nexus.Crud.css +16 -9
- package/src/Styles/Nexus.Dropdown.css +12 -61
- package/src/Styles/Nexus.Editor.css +4 -6
- package/src/Styles/Nexus.Excel.css +34 -15
- package/src/Styles/Nexus.Form.css +25 -67
- package/src/Styles/Nexus.Index.css +1 -0
- package/src/Styles/Nexus.Input.css +794 -637
- package/src/Styles/Nexus.Markdown.css +0 -28
- package/src/Styles/Nexus.Menu.css +8 -30
- package/src/Styles/Nexus.Popup.css +22 -29
- package/src/Styles/Nexus.RichText.css +2 -32
- package/src/Styles/Nexus.Rows.css +51 -10
- package/src/Styles/Nexus.Slider.css +3 -5
- package/src/Styles/Nexus.Tab.Buttons.css +20 -7
- package/src/Styles/Nexus.Table.css +1387 -1296
- package/src/Styles/Nexus.Theme.Picker.css +342 -232
- package/src/Styles/Nexus.Time.css +9 -12
- package/src/Styles/Nexus.Toast.css +6 -8
- package/src/Styles/Nexus.Tokens.css +17 -0
- package/src/Styles/Nexus.Tree.css +76 -11
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { iconArrowBackUp,
|
|
4
|
-
import { useEffect, useId,
|
|
3
|
+
import { iconArrowBackUp, iconGripVertical, iconPin, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
5
5
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
|
-
import {
|
|
6
|
+
import { isPopoverOpen } from "../../Helpers/PopoverHelpers.js";
|
|
7
|
+
import { usePopoverPanel } from "../../Helpers/PopoverHooks.js";
|
|
7
8
|
import { Button } from "../Documents/Button.js";
|
|
8
9
|
/** The rows of the panel: a band for every run of neighbours that name the same group, as the header draws them. */
|
|
9
10
|
function panelRows(columns, groups) {
|
|
@@ -42,15 +43,12 @@ function dropIndex(boxes, y, from) {
|
|
|
42
43
|
}
|
|
43
44
|
return boxes.length - 1;
|
|
44
45
|
}
|
|
45
|
-
/** The room kept between the panel and the window's edge, and the least height it is ever given. */
|
|
46
|
-
const PANEL_MARGIN = 8;
|
|
47
|
-
const PANEL_MIN_HEIGHT = 160;
|
|
48
46
|
/** The text of a label that may be a node, for an accessible name. */
|
|
49
47
|
function textOf(label) {
|
|
50
48
|
return typeof label === "string" || typeof label === "number" ? String(label) : "the column";
|
|
51
49
|
}
|
|
52
50
|
/**
|
|
53
|
-
* The
|
|
51
|
+
* The Columns panel, opened through its `open()` handle under `anchor`: a checkbox per column, bands with their columns under
|
|
54
52
|
* them, and a handle to drag a row into another place. The panel is a popover under the button, so it closes on Esc,
|
|
55
53
|
* on a click outside, and on Tab.
|
|
56
54
|
*
|
|
@@ -58,15 +56,10 @@ function textOf(label) {
|
|
|
58
56
|
* <TableColumnsPanel columns={columns} hidden={state.hidden} pinned={state.pinned}
|
|
59
57
|
* onToggle={(key, hide) => table.setHidden(key, hide)} onOrder={keys => table.setOrder(keys)} />
|
|
60
58
|
*/
|
|
61
|
-
export function TableColumnsPanel({ columns, hidden, pinned, reorderable = true, size = "md", changed = false, onToggle, onOrder, onReset,
|
|
59
|
+
export function TableColumnsPanel({ columns, hidden, pinned, reorderable = true, size = "md", changed = false, onToggle, onOrder, onReset, anchor, ref, }) {
|
|
62
60
|
const id = useId();
|
|
63
61
|
const panelId = `${id}-columns`;
|
|
64
|
-
const
|
|
65
|
-
const triggerRef = useRef(null);
|
|
66
|
-
const panelRef = useRef(null);
|
|
67
|
-
const [open, setOpen] = useState(false);
|
|
68
|
-
// Set on pointer down while the panel is open: that press closes it, and its click must not open it again.
|
|
69
|
-
const dismissing = useRef(false);
|
|
62
|
+
const { panelRef, open, onToggle: onPanelToggle } = usePopoverPanel(anchor, ref);
|
|
70
63
|
// While a row is held: its key, the list it may move in, and where it would land.
|
|
71
64
|
const [drag, setDrag] = useState(null);
|
|
72
65
|
const [said, setSaid] = useState("");
|
|
@@ -84,25 +77,6 @@ export function TableColumnsPanel({ columns, hidden, pinned, reorderable = true,
|
|
|
84
77
|
const rows = panelRows(all, groups);
|
|
85
78
|
const shownCount = all.filter(column => column.shown).length;
|
|
86
79
|
const bands = rows.some(row => row.band !== null);
|
|
87
|
-
function place() {
|
|
88
|
-
const panel = panelRef.current;
|
|
89
|
-
const trigger = triggerRef.current;
|
|
90
|
-
if (!panel || !trigger)
|
|
91
|
-
return;
|
|
92
|
-
// Measured at its own height, then capped to the room on the side it landed on: a table with many columns keeps
|
|
93
|
-
// its heading, its note and Reset in the window, and the list of columns scrolls instead.
|
|
94
|
-
panel.style.maxHeight = "";
|
|
95
|
-
placeUnderAnchor(panel, trigger.getBoundingClientRect(), { align: "end" });
|
|
96
|
-
const top = panel.getBoundingClientRect().top;
|
|
97
|
-
panel.style.maxHeight = `${Math.max(PANEL_MIN_HEIGHT, window.innerHeight - top - PANEL_MARGIN)}px`;
|
|
98
|
-
}
|
|
99
|
-
// The panel is fixed, so it follows the button while the page or a scroller moves.
|
|
100
|
-
useLayoutEffect(() => {
|
|
101
|
-
if (!open)
|
|
102
|
-
return;
|
|
103
|
-
return followAnchor(place);
|
|
104
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
-
}, [open]);
|
|
106
80
|
// A panel opened again starts without the last move's message, so a screen reader is not told about an old move.
|
|
107
81
|
useEffect(() => {
|
|
108
82
|
if (!open)
|
|
@@ -205,39 +179,22 @@ export function TableColumnsPanel({ columns, hidden, pinned, reorderable = true,
|
|
|
205
179
|
const lastShown = column.shown && shownCount <= 1;
|
|
206
180
|
return (_jsxs("li", { className: cx("nx-columns__row", inBand && "nx-columns__row--child"), "data-held": drag?.key === column.key ? "" : undefined, "data-drop": drag && drag.list === list && drag.key !== column.key && drag.to === index ? "" : undefined, children: [column.movable && !bandOff ? (_jsx("button", { type: "button", className: "nx-columns__handle", "aria-label": `Move ${textOf(column.label)}`, title: inBand ? `Drag to move it inside ${list}, or use the arrow keys` : "Drag to move, or use the arrow keys", onPointerDown: event => onHandlePointerDown(event, list, index, column.key, textOf(column.label)), onKeyDown: event => onHandleKeyDown(event, list, index, textOf(column.label)), children: _jsx(InlineIcon, { icon: iconGripVertical }) })) : (_jsx("span", { className: "nx-columns__handle nx-columns__handle--fixed", "aria-hidden": "true" })), _jsxs("label", { className: "nx-choice nx-choice--sm nx-columns__choice", children: [_jsx("input", { type: "checkbox", className: "nx-choice__control nx-choice__control--checkbox", checked: column.shown && !bandOff, disabled: bandOff || !column.hideable || lastShown, title: bandOff ? `${list} is hidden` : column.hideable ? undefined : "Always shown", onChange: event => onToggle(column.key, !event.currentTarget.checked) }), _jsx("span", { className: "nx-choice__text", children: _jsx("span", { className: "nx-choice__label nx-columns__label", children: column.label }) })] }), column.pin && (_jsxs("span", { className: "nx-columns__pin", title: `Pinned to the ${column.pin === "left" ? "left" : "right"}`, children: [_jsx(InlineIcon, { icon: iconPin }), _jsx("span", { className: "nx-visually-hidden", children: `Pinned to the ${column.pin === "left" ? "left" : "right"}` })] }))] }, column.key));
|
|
207
181
|
}
|
|
208
|
-
return (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (event.key === "Tab")
|
|
227
|
-
hidePopover(event.currentTarget);
|
|
228
|
-
}, children: [_jsxs("div", { className: "nx-columns__head", children: [_jsx("span", { className: "nx-columns__title", id: `${panelId}-title`, children: "Columns" }), _jsxs("span", { className: "nx-columns__count", children: [shownCount, " of ", all.length, " shown"] })] }), _jsx("ul", { className: "nx-columns__list", "data-list": "", children: rows.map((row, index) => {
|
|
229
|
-
if (row.band === null)
|
|
230
|
-
return columnRow(row.column, "", index, false, false);
|
|
231
|
-
const band = row.band;
|
|
232
|
-
// A band whose columns were split into two runs is drawn twice, so a row is known by its first column, never
|
|
233
|
-
// by the band's name: two rows under one name would be one row to React.
|
|
234
|
-
const bandId = `band:${row.columns[0].key}`;
|
|
235
|
-
const shown = row.columns.filter(column => column.shown).length;
|
|
236
|
-
const movable = row.columns.every(column => column.movable);
|
|
237
|
-
const last = shown > 0 && shownCount - shown < 1;
|
|
238
|
-
return (_jsxs("li", { className: "nx-columns__row nx-columns__row--band", "data-held": drag?.key === bandId ? "" : undefined, "data-drop": drag && drag.list === "" && drag.key !== bandId && drag.to === index ? "" : undefined, children: [_jsxs("div", { className: "nx-columns__band", children: [movable ? (_jsx("button", { type: "button", className: "nx-columns__handle", "aria-label": `Move ${band}`, title: "Drag to move the whole band, or use the arrow keys", onPointerDown: event => onHandlePointerDown(event, "", index, bandId, band), onKeyDown: event => onHandleKeyDown(event, "", index, band), children: _jsx(InlineIcon, { icon: iconGripVertical }) })) : (_jsx("span", { className: "nx-columns__handle nx-columns__handle--fixed", "aria-hidden": "true" })), _jsxs("label", { className: "nx-choice nx-choice--sm nx-columns__choice", children: [_jsx("input", { type: "checkbox", className: "nx-choice__control nx-choice__control--checkbox", checked: shown > 0, disabled: last, ref: input => {
|
|
239
|
-
if (input)
|
|
240
|
-
input.indeterminate = shown > 0 && shown < row.columns.length;
|
|
241
|
-
}, "aria-label": `${band}: ${shown} of ${row.columns.length} shown`, onChange: event => toggleBand(band, row.columns, event.currentTarget.checked) }), _jsx("span", { className: "nx-choice__text", children: _jsx("span", { className: "nx-choice__label nx-columns__label", children: band }) })] }), _jsxs("span", { className: "nx-columns__band-count", children: [shown, " / ", row.columns.length] })] }), _jsx("ul", { className: "nx-columns__children", "data-list": band, children: row.columns.map((column, childIndex) => columnRow(column, band, childIndex, true, shown === 0)) })] }, bandId));
|
|
242
|
-
}) }), _jsxs("p", { className: "nx-columns__note", children: [reorderable ? "Drag a handle, or move it with the arrow keys." : "This table keeps the order of its columns.", bands ? " A column under a band moves inside it; the band moves as one." : ""] }), onReset && (_jsx("div", { className: "nx-columns__foot", children: _jsx(Button, { size: "sm", variant: "ghost", icon: _jsx(InlineIcon, { icon: iconArrowBackUp }), disabled: !changed, onClick: () => onReset(), children: "Reset columns" }) })), _jsx("span", { className: "nx-visually-hidden", "aria-live": "polite", children: said })] })] }));
|
|
182
|
+
return (_jsx(_Fragment, { children: _jsxs("div", { ref: panelRef, id: panelId, popover: "auto", role: "dialog", tabIndex: -1, "aria-labelledby": `${panelId}-title`, className: cx("nx-popover", "nx-table-panel", "nx-columns", size === "sm" && "nx-columns--sm"), "data-dragging": drag ? "" : undefined, onToggle: onPanelToggle, onKeyDown: event => {
|
|
183
|
+
if (event.key === "Tab")
|
|
184
|
+
hidePopover(event.currentTarget);
|
|
185
|
+
}, children: [_jsxs("div", { className: "nx-table-panel__head", children: [_jsx("span", { className: "nx-table-panel__title", id: `${panelId}-title`, children: "Columns" }), _jsxs("span", { className: "nx-table-panel__count", children: [shownCount, " of ", all.length, " shown"] })] }), _jsx("ul", { className: "nx-columns__list nx-popover__scroll", "data-list": "", children: rows.map((row, index) => {
|
|
186
|
+
if (row.band === null)
|
|
187
|
+
return columnRow(row.column, "", index, false, false);
|
|
188
|
+
const band = row.band;
|
|
189
|
+
// A band whose columns were split into two runs is drawn twice, so a row is known by its first column, never
|
|
190
|
+
// by the band's name: two rows under one name would be one row to React.
|
|
191
|
+
const bandId = `band:${row.columns[0].key}`;
|
|
192
|
+
const shown = row.columns.filter(column => column.shown).length;
|
|
193
|
+
const movable = row.columns.every(column => column.movable);
|
|
194
|
+
const last = shown > 0 && shownCount - shown < 1;
|
|
195
|
+
return (_jsxs("li", { className: "nx-columns__row nx-columns__row--band", "data-held": drag?.key === bandId ? "" : undefined, "data-drop": drag && drag.list === "" && drag.key !== bandId && drag.to === index ? "" : undefined, children: [_jsxs("div", { className: "nx-columns__band", children: [movable ? (_jsx("button", { type: "button", className: "nx-columns__handle", "aria-label": `Move ${band}`, title: "Drag to move the whole band, or use the arrow keys", onPointerDown: event => onHandlePointerDown(event, "", index, bandId, band), onKeyDown: event => onHandleKeyDown(event, "", index, band), children: _jsx(InlineIcon, { icon: iconGripVertical }) })) : (_jsx("span", { className: "nx-columns__handle nx-columns__handle--fixed", "aria-hidden": "true" })), _jsxs("label", { className: "nx-choice nx-choice--sm nx-columns__choice", children: [_jsx("input", { type: "checkbox", className: "nx-choice__control nx-choice__control--checkbox", checked: shown > 0, disabled: last, ref: input => {
|
|
196
|
+
if (input)
|
|
197
|
+
input.indeterminate = shown > 0 && shown < row.columns.length;
|
|
198
|
+
}, "aria-label": `${band}: ${shown} of ${row.columns.length} shown`, onChange: event => toggleBand(band, row.columns, event.currentTarget.checked) }), _jsx("span", { className: "nx-choice__text", children: _jsx("span", { className: "nx-choice__label nx-columns__label", children: band }) })] }), _jsxs("span", { className: "nx-columns__band-count", children: [shown, " / ", row.columns.length] })] }), _jsx("ul", { className: "nx-columns__children", "data-list": band, children: row.columns.map((column, childIndex) => columnRow(column, band, childIndex, true, shown === 0)) })] }, bandId));
|
|
199
|
+
}) }), _jsxs("p", { className: "nx-columns__note", children: [reorderable ? "Drag a handle, or move it with the arrow keys." : "This table keeps the order of its columns.", bands ? " A column under a band moves inside it; the band moves as one." : ""] }), onReset && (_jsx("div", { className: "nx-table-panel__foot", children: _jsx(Button, { size: "sm", variant: "ghost", icon: _jsx(InlineIcon, { icon: iconArrowBackUp }), disabled: !changed, onClick: () => onReset(), children: "Reset columns" }) })), _jsx("span", { className: "nx-visually-hidden", "aria-live": "polite", children: said })] }) }));
|
|
243
200
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Ref, type RefObject } from "react";
|
|
2
|
+
import { type PopoverPanelHandle } from "../../Helpers/PopoverHooks.ts";
|
|
3
|
+
import type { InputParams } from "../../Interfaces/FormInterfaces.ts";
|
|
4
|
+
import type { TableFilters } from "../../Interfaces/TableInterfaces.ts";
|
|
5
|
+
export interface TableFiltersPanelProps {
|
|
6
|
+
/** The filter inputs, as the table prepared them (`asFilterInput`). */
|
|
7
|
+
inputs: readonly InputParams[];
|
|
8
|
+
/** The table's filters: a change from outside, such as Clear filters, loads them into the form. */
|
|
9
|
+
value: TableFilters | undefined;
|
|
10
|
+
/** Milliseconds a typed value waits: the table's search delay. */
|
|
11
|
+
delay: number;
|
|
12
|
+
/** The rows the search and the filters leave, and every row, for the foot. */
|
|
13
|
+
matches: number;
|
|
14
|
+
total: number;
|
|
15
|
+
onChange: (values: Readonly<Record<string, unknown>>) => void;
|
|
16
|
+
/** The options button (⋮) the panel opens under; Esc gives the focus back to it. */
|
|
17
|
+
anchor: RefObject<HTMLElement | null>;
|
|
18
|
+
ref?: Ref<PopoverPanelHandle>;
|
|
19
|
+
}
|
|
20
|
+
/** The Filters panel, opened through its `open()` handle: a `Form` of the filter inputs, the count, and Clear all. */
|
|
21
|
+
export declare function TableFiltersPanel({ inputs, value, delay, matches, total, onChange, anchor, ref }: TableFiltersPanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { iconFilterOff, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { useEffect, useId, useRef } from "react";
|
|
5
|
+
import { usePopoverPanel } from "../../Helpers/PopoverHooks.js";
|
|
6
|
+
import { activeFilters } from "../../Helpers/TableFilters.js";
|
|
7
|
+
import { Button } from "../Documents/Button.js";
|
|
8
|
+
import { Form, useForm } from "../Forms/Form.js";
|
|
9
|
+
import { formatCount } from "./DataTableParts.js";
|
|
10
|
+
/*
|
|
11
|
+
* The Filters panel: the table's filter inputs as one small form under the options button (⋮), applied as they change.
|
|
12
|
+
* A choice applies at once; a value that can come a key or a step at a time (text, a number, a slider, a date) waits
|
|
13
|
+
* the table's search delay, or Enter, or the panel closing. Esc closes the panel, an open list or calendar first, and
|
|
14
|
+
* never clears a filter on its way out.
|
|
15
|
+
*/
|
|
16
|
+
/** 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(["text", "email", "search", "tel", "url", "textarea", "number", "slider", "range-slider", "date", "date-range", "time", "date-time"]);
|
|
18
|
+
/** The Filters panel, opened through its `open()` handle: a `Form` of the filter inputs, the count, and Clear all. */
|
|
19
|
+
export function TableFiltersPanel({ inputs, value, delay, matches, total, onChange, anchor, ref }) {
|
|
20
|
+
const titleId = useId();
|
|
21
|
+
const form = useForm();
|
|
22
|
+
// The form is drawn on the first open and kept: what was typed, and the labels a server list loaded, stay.
|
|
23
|
+
const { panelRef, shown, onToggle } = usePopoverPanel(anchor, ref, () => form.focus());
|
|
24
|
+
const pending = useRef(undefined);
|
|
25
|
+
const count = value ? Object.keys(value).length : 0;
|
|
26
|
+
const apply = () => {
|
|
27
|
+
clearTimeout(pending.current);
|
|
28
|
+
pending.current = undefined;
|
|
29
|
+
onChange(form.getValue());
|
|
30
|
+
};
|
|
31
|
+
const wait = () => {
|
|
32
|
+
clearTimeout(pending.current);
|
|
33
|
+
pending.current = setTimeout(apply, delay);
|
|
34
|
+
};
|
|
35
|
+
const flush = () => pending.current !== undefined && apply();
|
|
36
|
+
// Filters changed from outside reach the form, unless a typed value is still on its way.
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (form.started && pending.current === undefined && JSON.stringify(activeFilters(inputs, form.getValue())) !== JSON.stringify(value))
|
|
39
|
+
form.load(value ?? {});
|
|
40
|
+
}, [form, inputs, value]);
|
|
41
|
+
useEffect(() => () => clearTimeout(pending.current), []);
|
|
42
|
+
return (_jsxs("div", { ref: panelRef, popover: "auto", role: "dialog", tabIndex: -1, "aria-labelledby": titleId, className: "nx-popover nx-table-panel nx-table-filters", onToggle: onToggle, onBeforeToggle: event => {
|
|
43
|
+
// Before the click that closes it lands: a clear that click makes then comes after what was typed.
|
|
44
|
+
if (event.target === event.currentTarget && event.newState === "closed")
|
|
45
|
+
flush();
|
|
46
|
+
}, onKeyDownCapture: event => {
|
|
47
|
+
// Kept from the inputs, whose Esc clears a value first: with no list or calendar open, Esc closes the panel.
|
|
48
|
+
if (event.key === "Escape" && !event.currentTarget.querySelector(":popover-open"))
|
|
49
|
+
event.stopPropagation();
|
|
50
|
+
}, onKeyDown: event => {
|
|
51
|
+
if (event.key === "Enter")
|
|
52
|
+
flush();
|
|
53
|
+
}, onInput: () => {
|
|
54
|
+
// A key that changes nothing the box reads ("2025-01-01 -") is typing too: a waiting value waits on.
|
|
55
|
+
if (pending.current !== undefined)
|
|
56
|
+
wait();
|
|
57
|
+
}, 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
|
+
if (name === null)
|
|
59
|
+
return;
|
|
60
|
+
if (delay > 0 && TYPED.has(inputs.find(params => params.name === name)?.type ?? ""))
|
|
61
|
+
wait();
|
|
62
|
+
else
|
|
63
|
+
apply();
|
|
64
|
+
} })) }), _jsxs("div", { className: "nx-table-panel__foot", children: [_jsxs("span", { className: "nx-table-filters__rows", "aria-live": "polite", children: [formatCount(matches), " of ", formatCount(total), " rows"] }), _jsx(Button, { size: "sm", variant: "ghost", icon: _jsx(InlineIcon, { icon: iconFilterOff }), disabled: !count, onClick: () => {
|
|
65
|
+
form.load({});
|
|
66
|
+
apply();
|
|
67
|
+
}, children: "Clear all" })] })] }));
|
|
68
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type IconNode } from "nexus-icons";
|
|
2
2
|
import { type CSSProperties, type MouseEvent, type ReactNode } from "react";
|
|
3
3
|
import { type CellTextOptions } from "../../Helpers/TableHelpers.ts";
|
|
4
|
-
import type { TableColumn, TableRow, TableRowAction, TableState, TableViewColumn } from "../../Interfaces/TableInterfaces.ts";
|
|
4
|
+
import type { TableColumn, TableRow, TableRowAction, TableRowMark, TableState, TableViewColumn } from "../../Interfaces/TableInterfaces.ts";
|
|
5
5
|
/** A number as the table writes counts: 1,234. */
|
|
6
6
|
export declare const formatCount: (count: number) => string;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* A value in its column's format: text, a tag for an option with a tone, a check for `true`. Empty values show a dash.
|
|
9
|
+
* A `join` column shows a placeholder until the record it looks up is in, and "Not found" for a key with no record; a
|
|
10
|
+
* column with a `tag` shows it after the value: "Asia" then "Trashed".
|
|
11
|
+
*/
|
|
8
12
|
export declare function TableCellValue<TRow extends TableRow>({ column, row, options }: {
|
|
9
13
|
column: TableColumn<TRow>;
|
|
10
14
|
row: TRow;
|
|
@@ -66,8 +70,8 @@ export interface TableRowViewProps<TRow extends TableRow> {
|
|
|
66
70
|
busy: boolean;
|
|
67
71
|
flashed: boolean;
|
|
68
72
|
numbered: boolean;
|
|
69
|
-
/**
|
|
70
|
-
|
|
73
|
+
/** The row's marks (pinned, a favourite), worn one under the other where a row without any shows its number. */
|
|
74
|
+
marks?: readonly TableRowMark[];
|
|
71
75
|
/** Where the table's own columns sit; `null` for the ones it does not have. */
|
|
72
76
|
frame: TableFrameColumns;
|
|
73
77
|
/** The user gave a column a width, so the table lays its columns out from the `<colgroup>`. */
|
|
@@ -80,13 +84,27 @@ export interface TableRowViewProps<TRow extends TableRow> {
|
|
|
80
84
|
openOnClick?: boolean;
|
|
81
85
|
}
|
|
82
86
|
/** One row. It renders again only when its row, its state, or the columns change. */
|
|
83
|
-
declare function TableRowViewInner<TRow extends TableRow>({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered,
|
|
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;
|
|
84
88
|
export declare const TableRowView: typeof TableRowViewInner;
|
|
85
89
|
/**
|
|
86
|
-
* The head of the
|
|
87
|
-
* how many there are, and the most
|
|
90
|
+
* The head of a section above the page: one row across the table with a button that folds the section's records open
|
|
91
|
+
* and shut, how many there are, and the most it can hold.
|
|
92
|
+
*/
|
|
93
|
+
export declare function TableSectionHead({ span, label, icon, count, max, loading, open, onToggle }: {
|
|
94
|
+
span: number;
|
|
95
|
+
label: string;
|
|
96
|
+
icon: IconNode;
|
|
97
|
+
count: number;
|
|
98
|
+
max?: number;
|
|
99
|
+
loading: boolean;
|
|
100
|
+
open: boolean;
|
|
101
|
+
onToggle: () => void;
|
|
102
|
+
}): import("react").JSX.Element;
|
|
103
|
+
/**
|
|
104
|
+
* The pinned section's head, by its 3.1.0 name: a `TableSectionHead` with the pin icon.
|
|
105
|
+
* @deprecated since 3.2.0, when favourites became a second section: use `TableSectionHead`, which takes the icon.
|
|
88
106
|
*/
|
|
89
|
-
export declare function TablePinnedHead(
|
|
107
|
+
export declare function TablePinnedHead(props: {
|
|
90
108
|
span: number;
|
|
91
109
|
label: string;
|
|
92
110
|
count: number;
|
|
@@ -127,8 +145,8 @@ export declare function TablePageSize({ pageSize, pageSizes, compact, onChange }
|
|
|
127
145
|
onChange: (size: number) => void;
|
|
128
146
|
}): import("react").JSX.Element | null;
|
|
129
147
|
/**
|
|
130
|
-
* Under the table: which rows show and how many the search left ("Showing 41–50 of 234 · filtered
|
|
131
|
-
* of how many, and the pager.
|
|
148
|
+
* 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.
|
|
132
150
|
*/
|
|
133
151
|
export declare function TableFooter<TRow extends TableRow>({ state, disabled, onPage }: {
|
|
134
152
|
state: TableState<TRow>;
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { iconCheck, iconChevronDown, iconChevronLeft, iconChevronRight, iconDotsVertical, iconLoader2, iconPin, InlineIcon } from "nexus-icons";
|
|
4
4
|
import { memo } from "react";
|
|
5
5
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
6
|
import { columnAlign, columnOption, columnValue, cssLength, formatCellText, pageButtons, pageCount, TABLE_ACTIONS_COLUMN, TABLE_INDEX_COLUMN, } from "../../Helpers/TableHelpers.js";
|
|
7
|
+
import { joinedState } from "../../Helpers/TableJoins.js";
|
|
7
8
|
import { Button } from "../Documents/Button.js";
|
|
8
9
|
import { Menu } from "../Documents/Menu.js";
|
|
9
10
|
/*
|
|
10
11
|
* The parts of a data table: a cell in its column's format, a row with its number and checkbox in one column and its
|
|
11
|
-
* actions, the widths of the columns the user set, the head of the
|
|
12
|
+
* actions, the widths of the columns the user set, the head of a section above the page, the pager, and the footer. The
|
|
12
13
|
* details view of the CRUD draws values with the same `TableCellValue`.
|
|
13
14
|
*/
|
|
14
15
|
const countFormat = new Intl.NumberFormat("en-US");
|
|
15
16
|
/** A number as the table writes counts: 1,234. */
|
|
16
17
|
export const formatCount = (count) => countFormat.format(count);
|
|
17
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* A value in its column's format: text, a tag for an option with a tone, a check for `true`. Empty values show a dash.
|
|
20
|
+
* A `join` column shows a placeholder until the record it looks up is in, and "Not found" for a key with no record; a
|
|
21
|
+
* column with a `tag` shows it after the value: "Asia" then "Trashed".
|
|
22
|
+
*/
|
|
18
23
|
export function TableCellValue({ column, row, options }) {
|
|
24
|
+
const joined = column.join ? joinedState(column.join, row) : null;
|
|
25
|
+
if (joined?.status === "loading")
|
|
26
|
+
return _jsx("span", { className: "nx-table__bone", "aria-label": "Loading" });
|
|
27
|
+
if (joined?.status === "missing" || joined?.status === "failed") {
|
|
28
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { className: "nx-table__dash", children: "\u2014" }), joined.status === "missing" && _jsx(TableTagView, { tag: { label: "Not found", tone: "danger" } })] }));
|
|
29
|
+
}
|
|
30
|
+
const tag = column.tag?.(row, joined?.status === "ready" ? joined.record : undefined);
|
|
31
|
+
const value = _jsx(TableCellContent, { column: column, row: row, options: options });
|
|
32
|
+
if (!tag)
|
|
33
|
+
return value;
|
|
34
|
+
return (_jsxs(_Fragment, { children: [value, _jsx(TableTagView, { tag: tag })] }));
|
|
35
|
+
}
|
|
36
|
+
/** A short tag after a cell's value, in the look of a column option's tag. */
|
|
37
|
+
function TableTagView({ tag }) {
|
|
38
|
+
return (_jsx("span", { className: "nx-table__tag nx-table__tag--after", "data-tone": tag.tone, children: tag.label }));
|
|
39
|
+
}
|
|
40
|
+
function TableCellContent({ column, row, options }) {
|
|
19
41
|
const value = columnValue(column, row);
|
|
20
42
|
if (column.render)
|
|
21
43
|
return _jsx(_Fragment, { children: column.render(row, value) });
|
|
@@ -104,21 +126,28 @@ function opensRow(event, single) {
|
|
|
104
126
|
return !(selection && !selection.isCollapsed && event.currentTarget.contains(selection.anchorNode));
|
|
105
127
|
}
|
|
106
128
|
/** One row. It renders again only when its row, its state, or the columns change. */
|
|
107
|
-
function TableRowViewInner({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered,
|
|
129
|
+
function TableRowViewInner({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered, marks, frame, sized, actions, onToggle, onAction, onOpen, openOnClick, }) {
|
|
108
130
|
const inline = actions?.filter(action => action.inline && action.icon) ?? [];
|
|
109
131
|
const inMenu = actions?.filter(action => !(action.inline && action.icon)) ?? [];
|
|
110
132
|
const items = inMenu.map(action => ({ value: action.id, label: action.label, icon: action.icon, description: action.description, tone: action.tone, disabled: action.disabled }));
|
|
111
|
-
return (_jsxs("tr", { className: "nx-table__row", "data-key": rowKey, "data-
|
|
133
|
+
return (_jsxs("tr", { className: "nx-table__row", "data-key": rowKey, "data-selected": selected || undefined, "data-busy": busy || undefined, "data-flash": flashed || undefined, "aria-busy": busy || undefined, "data-open": (onOpen && openOnClick) || undefined,
|
|
112
134
|
// One way in: with a single click the row is already open by the time a double-click would arrive.
|
|
113
|
-
onClick: onOpen && openOnClick ? event => opensRow(event, true) && onOpen(row) : undefined, onDoubleClick: onOpen && !openOnClick ? event => opensRow(event, false) && onOpen(row) : undefined, children: [frame.index && (_jsx(TableIndexCell, { own: frame.index, number:
|
|
135
|
+
onClick: onOpen && openOnClick ? event => opensRow(event, true) && onOpen(row) : undefined, onDoubleClick: onOpen && !openOnClick ? event => opensRow(event, false) && onOpen(row) : undefined, children: [frame.index && (_jsx(TableIndexCell, { own: frame.index, number: marks?.length ? (_jsx("span", { className: "nx-table__marks", children: marks.map(mark => (_jsx(InlineIcon, { icon: mark.icon, className: "nx-table__mark", label: mark.label }, mark.label))) })) : (formatCount(number)), numbered: numbered || Boolean(marks?.length), selectable: selectable, checkbox: selectable && (_jsx("label", { className: "nx-choice nx-choice--sm nx-table__check", title: selectionFull ? "No more rows can be selected." : undefined, children: _jsx("input", { type: "checkbox", className: "nx-choice__control nx-choice__control--checkbox", checked: selected, disabled: selectionFull && !selected, "aria-label": `Select ${label}`, onClick: event => onToggle(row, event.currentTarget.checked, event), onChange: () => undefined }) })) })), columns.map(view => (_jsx("td", { className: cx("nx-table__cell", `nx-table__cell--${columnAlign(view.column)}`, view.column.wrap && "nx-table__cell--wrap"), "data-col": view.column.key, "data-pin": view.pin ?? undefined, "data-pin-edge": (view.pin && view.edge) || undefined, style: columnStyle(view, sized), children: _jsx("span", { className: "nx-table__value", children: _jsx(TableCellValue, { column: view.column, row: row, options: cellOptions }) }) }, view.column.key))), sized && _jsx("td", { className: "nx-table__cell nx-table__cell--filler" }), frame.actions && (_jsx("td", { className: "nx-table__cell nx-table__cell--actions", "data-col": TABLE_ACTIONS_COLUMN, "data-pin": "right", "data-pin-edge": frame.actions.edge || undefined, style: ownStyle(frame.actions, "right"), children: _jsxs("div", { className: "nx-table__actions", children: [inline.map(action => (_jsx(Button, { variant: "ghost", size: "sm", icon: action.icon, hoverTone: action.tone === "danger" ? "danger" : undefined, "aria-label": `${action.label}: ${label}`, title: action.label, "data-action": action.id, disabled: busy || action.disabled, onClick: () => onAction(row, action) }, action.id))), items.length > 0 && (_jsx(Menu, { items: items, align: "end", "aria-label": `Actions: ${label}`, onSelect: value => onAction(row, inMenu.find(action => action.id === value)), children: _jsx(Button, { variant: "ghost", size: "sm", icon: _jsx(InlineIcon, { icon: iconDotsVertical }), loading: busy, "aria-label": `More actions: ${label}`, title: "More actions" }) })), items.length === 0 && busy && _jsx(Button, { variant: "ghost", size: "sm", loading: true, "aria-label": `Working: ${label}` })] }) }))] }));
|
|
114
136
|
}
|
|
115
137
|
export const TableRowView = memo(TableRowViewInner);
|
|
116
138
|
/**
|
|
117
|
-
* The head of the
|
|
118
|
-
* how many there are, and the most
|
|
139
|
+
* The head of a section above the page: one row across the table with a button that folds the section's records open
|
|
140
|
+
* and shut, how many there are, and the most it can hold.
|
|
141
|
+
*/
|
|
142
|
+
export function TableSectionHead({ span, label, icon, count, max, loading, open, onToggle }) {
|
|
143
|
+
return (_jsx("tr", { className: "nx-table__row nx-table__section-row", children: _jsx("td", { colSpan: span, className: "nx-table__section-cell", children: _jsxs("button", { type: "button", className: "nx-table__section-toggle", "aria-expanded": open, onClick: onToggle, children: [_jsx(InlineIcon, { icon: iconChevronRight, className: "nx-table__section-caret" }), _jsx(InlineIcon, { icon: icon, className: "nx-table__section-icon" }), _jsx("span", { className: "nx-table__section-title", children: label }), (count > 0 || !loading) && (_jsx("span", { className: "nx-table__section-count", children: max ? `${formatCount(count)} of ${formatCount(max)}` : formatCount(count) })), loading && _jsx(InlineIcon, { icon: iconLoader2, className: "nx-spin nx-table__section-loading" })] }) }) }));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The pinned section's head, by its 3.1.0 name: a `TableSectionHead` with the pin icon.
|
|
147
|
+
* @deprecated since 3.2.0, when favourites became a second section: use `TableSectionHead`, which takes the icon.
|
|
119
148
|
*/
|
|
120
|
-
export function TablePinnedHead(
|
|
121
|
-
return
|
|
149
|
+
export function TablePinnedHead(props) {
|
|
150
|
+
return _jsx(TableSectionHead, { ...props, icon: iconPin });
|
|
122
151
|
}
|
|
123
152
|
/** Placeholder rows while the first rows load, in the shape of the columns. */
|
|
124
153
|
export function TableSkeletonRows({ count, cells, frame, sized }) {
|
|
@@ -143,8 +172,8 @@ export function TablePageSize({ pageSize, pageSizes, compact = false, onChange }
|
|
|
143
172
|
return (_jsx(Menu, { items: items, kind: "radio", size: "sm", align: compact ? "start" : "end", "aria-label": "Rows per page", onSelect: value => onChange(Number(value)), children: _jsx(Button, { variant: "ghost", size: "sm", iconEnd: _jsx(InlineIcon, { icon: iconChevronDown }), className: cx("nx-table__page-size", compact && "nx-table__page-size--field"), "aria-label": `Rows per page: ${pageSize}`, title: "Rows per page", children: compact ? formatCount(pageSize) : `${formatCount(pageSize)} / page` }) }));
|
|
144
173
|
}
|
|
145
174
|
/**
|
|
146
|
-
* Under the table: which rows show and how many the search left ("Showing 41–50 of 234 · filtered
|
|
147
|
-
* of how many, and the pager.
|
|
175
|
+
* Under the table: which rows show and how many the search and the filters left ("Showing 41–50 of 234 · filtered
|
|
176
|
+
* from 1,000"), the page of how many, and the pager.
|
|
148
177
|
*/
|
|
149
178
|
export function TableFooter({ state, disabled, onPage }) {
|
|
150
179
|
const { status, rows, offset, matches, total, query } = state;
|
|
@@ -153,5 +182,5 @@ export function TableFooter({ state, disabled, onPage }) {
|
|
|
153
182
|
const first = rows.length ? offset + 1 : 0;
|
|
154
183
|
const last = offset + rows.length;
|
|
155
184
|
const pages = pageCount(matches, query.pageSize);
|
|
156
|
-
return (_jsxs("div", { className: "nx-table__footer", children: [_jsxs("p", { className: "nx-table__range", "aria-live": "polite", children: [rows.length ? (_jsxs(_Fragment, { children: ["Showing ", _jsx("b", { children: `${formatCount(first)}–${formatCount(last)}` }), " of ", _jsx("b", { children: formatCount(matches) })] })) : (_jsx(_Fragment, { children: "No rows on this page" })), query.search && matches !== total && _jsxs("span", { className: "nx-table__range-all", children: [" \u00B7 filtered from ", formatCount(total)] })] }), _jsxs("div", { className: "nx-table__paging", children: [_jsxs("span", { className: "nx-table__page-info", children: ["Page ", formatCount(query.page), " of ", formatCount(pages)] }), _jsx(TablePager, { page: query.page, matches: matches, pageSize: query.pageSize, disabled: disabled, onPage: onPage })] })] }));
|
|
185
|
+
return (_jsxs("div", { className: "nx-table__footer", children: [_jsxs("p", { className: "nx-table__range", "aria-live": "polite", children: [rows.length ? (_jsxs(_Fragment, { children: ["Showing ", _jsx("b", { children: `${formatCount(first)}–${formatCount(last)}` }), " of ", _jsx("b", { children: formatCount(matches) })] })) : (_jsx(_Fragment, { children: "No rows on this page" })), (query.search || query.filters) && matches !== total && _jsxs("span", { className: "nx-table__range-all", children: [" \u00B7 filtered from ", formatCount(total)] })] }), _jsxs("div", { className: "nx-table__paging", children: [_jsxs("span", { className: "nx-table__page-info", children: ["Page ", formatCount(query.page), " of ", formatCount(pages)] }), _jsx(TablePager, { page: query.page, matches: matches, pageSize: query.pageSize, disabled: disabled, onPage: onPage })] })] }));
|
|
157
186
|
}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether things may move, as the stylesheets read it: `data-nx-motion` on `<html>`, which `ThemeScript` works out
|
|
3
|
+
* from the visitor's Animations choice and the system setting. Motion started from script (a flight, a smooth scroll)
|
|
4
|
+
* asks this, never `prefers-reduced-motion`, so "on" animates on a machine that reports reduced motion.
|
|
5
|
+
*/
|
|
6
|
+
export declare function motionAllowed(): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a scroll may be smooth: the visitor allows motion, and so does the system, because a browser whose system
|
|
9
|
+
* turned animations off scrolls at once whatever it is asked, and a smooth scroll that is skipped must not be waited on.
|
|
10
|
+
*/
|
|
11
|
+
export declare function smoothScrollAllowed(): boolean;
|
|
1
12
|
/**
|
|
2
13
|
* Calls `done` once the element's CSS animation `name` has finished, or at once when the element runs no animation of
|
|
3
14
|
* that name. Call it after the class that starts the animation is on the element, e.g. in a layout effect.
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Exit animations. A closing element gets a class that starts a CSS animation, and the next step (closing a dialog,
|
|
3
|
-
* removing a toast) waits for it. When no animation runs, the step must not wait: with
|
|
4
|
-
* reports when its animations are turned off) or when
|
|
3
|
+
* removing a toast) waits for it. When no animation runs, the step must not wait: with animations off (the visitor's
|
|
4
|
+
* choice, or the system's under "system" - Windows reports reduced motion when its animations are turned off) or when
|
|
5
|
+
* a theme sets `animation: none`.
|
|
5
6
|
*/
|
|
7
|
+
import { MOTION_ATTRIBUTE } from "../Interfaces/ThemeInterfaces.js";
|
|
8
|
+
import { REDUCED_MOTION_QUERY } from "./ThemeHelpers.js";
|
|
9
|
+
/**
|
|
10
|
+
* Whether things may move, as the stylesheets read it: `data-nx-motion` on `<html>`, which `ThemeScript` works out
|
|
11
|
+
* from the visitor's Animations choice and the system setting. Motion started from script (a flight, a smooth scroll)
|
|
12
|
+
* asks this, never `prefers-reduced-motion`, so "on" animates on a machine that reports reduced motion.
|
|
13
|
+
*/
|
|
14
|
+
export function motionAllowed() {
|
|
15
|
+
return typeof document === "undefined" || document.documentElement.getAttribute(MOTION_ATTRIBUTE) !== "reduce";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether a scroll may be smooth: the visitor allows motion, and so does the system, because a browser whose system
|
|
19
|
+
* turned animations off scrolls at once whatever it is asked, and a smooth scroll that is skipped must not be waited on.
|
|
20
|
+
*/
|
|
21
|
+
export function smoothScrollAllowed() {
|
|
22
|
+
return motionAllowed() && !(typeof window !== "undefined" && window.matchMedia?.(REDUCED_MOTION_QUERY).matches);
|
|
23
|
+
}
|
|
6
24
|
/**
|
|
7
25
|
* Calls `done` once the element's CSS animation `name` has finished, or at once when the element runs no animation of
|
|
8
26
|
* that name. Call it after the class that starts the animation is on the element, e.g. in a layout effect.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NexusModule, type ApiEndpoint, type ApiModuleInfo, type ApiModuleValue } from "../Interfaces/ApiInterfaces.ts";
|
|
2
|
-
import { apiEndpoint, type ApiEndpointExtras, type ApiUrlParts } from "./ApiRoutes.ts";
|
|
2
|
+
import { apiEndpoint, type ApiControllerOptions, type ApiEndpointExtras, type ApiUrlParts } from "./ApiRoutes.ts";
|
|
3
3
|
/** Where the proxy route sits in an app: `app/api/app/[module]/[...path]/route.ts`. */
|
|
4
4
|
export declare const DEFAULT_PROXY_PATH = "/api/app";
|
|
5
5
|
/** An environment variable, read when it is needed (never inlined at build time), so one build serves every environment. */
|
|
@@ -67,39 +67,8 @@ export declare abstract class ApiModules<TModule extends ApiModuleValue = ApiMod
|
|
|
67
67
|
protected readEnv(name: string | undefined): string | undefined;
|
|
68
68
|
/** Endpoints typed to this app's modules: `MODULES.endpoint.get(HrModule.Staff, "Employee/Birthdays")`. */
|
|
69
69
|
readonly endpoint: Record<keyof typeof apiEndpoint, (module: TModule, path: string, extras?: ApiEndpointExtras) => ApiEndpoint<TModule>>;
|
|
70
|
-
/** A controller's standard endpoints, typed to this app's modules
|
|
71
|
-
controller(module: TModule, controller: string, subject?: string):
|
|
72
|
-
module: TModule;
|
|
73
|
-
controller: string;
|
|
74
|
-
listing: ApiEndpoint<TModule>;
|
|
75
|
-
activeListing: ApiEndpoint<TModule>;
|
|
76
|
-
findListing: ApiEndpoint<TModule>;
|
|
77
|
-
pagination: ApiEndpoint<TModule>;
|
|
78
|
-
lists: Record<import("../Shared.Index.ts").CrudMode, ApiEndpoint<TModule>>;
|
|
79
|
-
pages: Record<import("../Shared.Index.ts").CrudMode, ApiEndpoint<TModule>>;
|
|
80
|
-
pins: ApiEndpoint<TModule>;
|
|
81
|
-
details: ApiEndpoint<TModule>;
|
|
82
|
-
create: ApiEndpoint<TModule>;
|
|
83
|
-
update: ApiEndpoint<TModule>;
|
|
84
|
-
delete: ApiEndpoint<TModule>;
|
|
85
|
-
trash: ApiEndpoint<TModule>;
|
|
86
|
-
trashFromDelete: ApiEndpoint<TModule>;
|
|
87
|
-
recover: ApiEndpoint<TModule>;
|
|
88
|
-
flag: ApiEndpoint<TModule>;
|
|
89
|
-
pin: ApiEndpoint<TModule>;
|
|
90
|
-
audit: ApiEndpoint<TModule>;
|
|
91
|
-
accessActions: ApiEndpoint<TModule>;
|
|
92
|
-
mass: {
|
|
93
|
-
description: ApiEndpoint<TModule>;
|
|
94
|
-
trash: ApiEndpoint<TModule>;
|
|
95
|
-
trashFromDelete: ApiEndpoint<TModule>;
|
|
96
|
-
flag: ApiEndpoint<TModule>;
|
|
97
|
-
pin: ApiEndpoint<TModule>;
|
|
98
|
-
delete: ApiEndpoint<TModule>;
|
|
99
|
-
recover: ApiEndpoint<TModule>;
|
|
100
|
-
};
|
|
101
|
-
action: (method: import("../Shared.Index.ts").HttpMethod, action: string, extras?: Partial<Omit<ApiEndpoint<number>, "path" | "method" | "module">> | undefined) => ApiEndpoint<TModule>;
|
|
102
|
-
};
|
|
70
|
+
/** A controller's standard endpoints, typed to this app's modules: its generation and what it lacks in `options`. See `apiController`. */
|
|
71
|
+
controller(module: TModule, controller: string, subject?: string, options?: ApiControllerOptions): import("./ApiRoutes.ts").ApiControllerEndpoints<TModule>;
|
|
103
72
|
}
|
|
104
73
|
/** The Nexus modules, their names in proxy URLs, and the environment variables with their roots (as in the previous project). */
|
|
105
74
|
export declare const NEXUS_MODULES: readonly ApiModuleInfo<NexusModule>[];
|
|
@@ -103,9 +103,9 @@ export class ApiModules {
|
|
|
103
103
|
}
|
|
104
104
|
/** Endpoints typed to this app's modules: `MODULES.endpoint.get(HrModule.Staff, "Employee/Birthdays")`. */
|
|
105
105
|
endpoint = apiEndpoint;
|
|
106
|
-
/** A controller's standard endpoints, typed to this app's modules
|
|
107
|
-
controller(module, controller, subject) {
|
|
108
|
-
return apiController(module, controller, subject);
|
|
106
|
+
/** A controller's standard endpoints, typed to this app's modules: its generation and what it lacks in `options`. See `apiController`. */
|
|
107
|
+
controller(module, controller, subject, options) {
|
|
108
|
+
return apiController(module, controller, subject, options);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
/** The Nexus modules, their names in proxy URLs, and the environment variables with their roots (as in the previous project). */
|