nexus-shared 3.2.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 +1564 -0
- package/README.md +1705 -1123
- package/dist/Client.Index.d.ts +14 -0
- package/dist/Client.Index.js +14 -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 +576 -243
- package/dist/Components/Forms/CrudActions.d.ts +136 -0
- package/dist/Components/Forms/CrudActions.js +184 -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 +55 -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 +123 -17
- 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/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +5 -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 +37 -32
- 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 +11 -2
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TreeView.d.ts +53 -2
- package/dist/Components/Inputs/TreeView.js +147 -13
- 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/Viewers/DataTable.js +150 -48
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +21 -5
- package/dist/Components/Viewers/DataTableParts.js +26 -14
- 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/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/CrudBackend.d.ts +20 -2
- package/dist/Helpers/CrudBackend.js +46 -2
- package/dist/Helpers/CrudColumns.d.ts +53 -6
- package/dist/Helpers/CrudColumns.js +98 -7
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +81 -2
- package/dist/Helpers/CrudHelpers.js +195 -0
- 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 +157 -74
- package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
- package/dist/Helpers/NexusDriveAdapter.js +207 -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 +14 -2
- package/dist/Helpers/RequestCache.js +22 -3
- 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 +9 -3
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +23 -2
- package/dist/Helpers/TableHelpers.js +51 -10
- package/dist/Helpers/TableJoins.d.ts +13 -0
- package/dist/Helpers/TableJoins.js +34 -0
- package/dist/Helpers/TableStore.d.ts +21 -1
- package/dist/Helpers/TableStore.js +64 -7
- package/dist/Helpers/TreeStore.d.ts +81 -0
- package/dist/Helpers/TreeStore.js +365 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +465 -5
- package/dist/Interfaces/FileInterfaces.d.ts +162 -0
- package/dist/Interfaces/FileInterfaces.js +4 -0
- package/dist/Interfaces/FormInterfaces.d.ts +391 -30
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +15 -3
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +74 -1
- 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 +37 -5
- package/dist/Services/JoinSources.js +90 -14
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Shared.Index.d.ts +16 -0
- package/dist/Shared.Index.js +16 -0
- package/package.json +7 -2
- package/src/Styles/Nexus.Avatar.css +6 -5
- package/src/Styles/Nexus.Button.css +10 -4
- package/src/Styles/Nexus.Choice.css +16 -0
- package/src/Styles/Nexus.Crud.css +383 -9
- package/src/Styles/Nexus.Dropdown.css +8 -0
- package/src/Styles/Nexus.Files.css +410 -0
- package/src/Styles/Nexus.Form.css +5 -2
- package/src/Styles/Nexus.Index.css +4 -0
- package/src/Styles/Nexus.Input.css +42 -0
- package/src/Styles/Nexus.Menu.css +10 -1
- package/src/Styles/Nexus.Popup.css +77 -1
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Tab.Buttons.css +87 -22
- package/src/Styles/Nexus.Table.css +67 -5
- package/src/Styles/Nexus.Themes.css +12 -6
- package/src/Styles/Nexus.Tokens.css +8 -3
- package/src/Styles/Nexus.Tree.css +31 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Ref, type RefObject } from "react";
|
|
2
2
|
import { type PopoverPanelHandle } from "../../Helpers/PopoverHooks.ts";
|
|
3
|
-
import type
|
|
3
|
+
import { type InputParams } from "../../Interfaces/FormInterfaces.ts";
|
|
4
4
|
import type { TableFilters } from "../../Interfaces/TableInterfaces.ts";
|
|
5
5
|
export interface TableFiltersPanelProps {
|
|
6
6
|
/** The filter inputs, as the table prepared them (`asFilterInput`). */
|
|
@@ -4,6 +4,7 @@ import { iconFilterOff, InlineIcon } from "nexus-icons";
|
|
|
4
4
|
import { useEffect, useId, useRef } from "react";
|
|
5
5
|
import { usePopoverPanel } from "../../Helpers/PopoverHooks.js";
|
|
6
6
|
import { activeFilters } from "../../Helpers/TableFilters.js";
|
|
7
|
+
import { EInputTypes } from "../../Interfaces/FormInterfaces.js";
|
|
7
8
|
import { Button } from "../Documents/Button.js";
|
|
8
9
|
import { Form, useForm } from "../Forms/Form.js";
|
|
9
10
|
import { formatCount } from "./DataTableParts.js";
|
|
@@ -14,7 +15,21 @@ import { formatCount } from "./DataTableParts.js";
|
|
|
14
15
|
* never clears a filter on its way out.
|
|
15
16
|
*/
|
|
16
17
|
/** Inputs typed into or dragged: their value waits until the person stops. A date box reads a date from part of one. */
|
|
17
|
-
const TYPED = new Set([
|
|
18
|
+
const TYPED = new Set([
|
|
19
|
+
EInputTypes.Text,
|
|
20
|
+
EInputTypes.Email,
|
|
21
|
+
EInputTypes.Search,
|
|
22
|
+
EInputTypes.Tel,
|
|
23
|
+
EInputTypes.Url,
|
|
24
|
+
EInputTypes.TextArea,
|
|
25
|
+
EInputTypes.Number,
|
|
26
|
+
EInputTypes.Slider,
|
|
27
|
+
EInputTypes.RangeSlider,
|
|
28
|
+
EInputTypes.Date,
|
|
29
|
+
EInputTypes.DateRange,
|
|
30
|
+
EInputTypes.Time,
|
|
31
|
+
EInputTypes.DateTime,
|
|
32
|
+
]);
|
|
18
33
|
/** The Filters panel, opened through its `open()` handle: a `Form` of the filter inputs, the count, and Clear all. */
|
|
19
34
|
export function TableFiltersPanel({ inputs, value, delay, matches, total, onChange, anchor, ref }) {
|
|
20
35
|
const titleId = useId();
|
|
@@ -57,7 +72,7 @@ export function TableFiltersPanel({ inputs, value, delay, matches, total, onChan
|
|
|
57
72
|
}, children: [_jsxs("div", { className: "nx-table-panel__head", children: [_jsx("span", { className: "nx-table-panel__title", id: titleId, children: "Filters" }), _jsx("span", { className: "nx-table-panel__count", children: count ? `${count} on` : "None" })] }), _jsx("div", { className: "nx-popover__scroll", children: shown && (_jsx(Form, { form: form, inputs: inputs, size: "sm", defaultValue: value, className: "nx-table-filters__form", onValueChange: (_, name) => {
|
|
58
73
|
if (name === null)
|
|
59
74
|
return;
|
|
60
|
-
if (delay > 0 && TYPED.has(inputs.find(params => params.name === name)?.type ??
|
|
75
|
+
if (delay > 0 && TYPED.has(inputs.find(params => params.name === name)?.type ?? 0))
|
|
61
76
|
wait();
|
|
62
77
|
else
|
|
63
78
|
apply();
|
|
@@ -41,8 +41,11 @@ export declare function TableColumnGroup<TRow extends TableRow>({ columns, width
|
|
|
41
41
|
frame: TableFrameColumns;
|
|
42
42
|
colRefs: Map<string, HTMLTableColElement>;
|
|
43
43
|
}): import("react").JSX.Element;
|
|
44
|
-
/**
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The number of a row, its checkbox, or both: the number until the pointer is over the row or something is selected. A
|
|
46
|
+
* row of a table whose rows are picked (`onPick`) also holds its radio button here, out of sight.
|
|
47
|
+
*/
|
|
48
|
+
export declare function TableIndexCell({ heading, rowSpan, own, number, numbered, selectable, checkbox, pick, }: {
|
|
46
49
|
heading?: boolean;
|
|
47
50
|
/** 2 in the heading while the header has a band of group headings above the columns' own. */
|
|
48
51
|
rowSpan?: number;
|
|
@@ -55,6 +58,8 @@ export declare function TableIndexCell({ heading, rowSpan, own, number, numbered
|
|
|
55
58
|
numbered: boolean;
|
|
56
59
|
selectable: boolean;
|
|
57
60
|
checkbox?: ReactNode;
|
|
61
|
+
/** The row's radio button, when rows are picked. */
|
|
62
|
+
pick?: ReactNode;
|
|
58
63
|
}): import("react").JSX.Element;
|
|
59
64
|
export interface TableRowViewProps<TRow extends TableRow> {
|
|
60
65
|
row: TRow;
|
|
@@ -82,9 +87,15 @@ export interface TableRowViewProps<TRow extends TableRow> {
|
|
|
82
87
|
onOpen?: (row: TRow) => void;
|
|
83
88
|
/** A single click opens the row, as a double-click does. */
|
|
84
89
|
openOnClick?: boolean;
|
|
90
|
+
/** Rows are picked: a click picks this one (a double-click still opens it), whatever `openOnClick` says. */
|
|
91
|
+
onPick?: (row: TRow) => void;
|
|
92
|
+
/** This row is the picked one. */
|
|
93
|
+
picked?: boolean;
|
|
94
|
+
/** The name of the table's radio group, for the row's own radio button; none in a section above the page. */
|
|
95
|
+
pickName?: string;
|
|
85
96
|
}
|
|
86
97
|
/** One row. It renders again only when its row, its state, or the columns change. */
|
|
87
|
-
declare function TableRowViewInner<TRow extends TableRow>({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered, marks, frame, sized, actions, onToggle, onAction, onOpen, openOnClick, }: TableRowViewProps<TRow>): import("react").JSX.Element;
|
|
98
|
+
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, onPick, picked, pickName, }: TableRowViewProps<TRow>): import("react").JSX.Element;
|
|
88
99
|
export declare const TableRowView: typeof TableRowViewInner;
|
|
89
100
|
/**
|
|
90
101
|
* The head of a section above the page: one row across the table with a button that folds the section's records open
|
|
@@ -146,11 +157,16 @@ export declare function TablePageSize({ pageSize, pageSizes, compact, onChange }
|
|
|
146
157
|
}): import("react").JSX.Element | null;
|
|
147
158
|
/**
|
|
148
159
|
* 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.
|
|
160
|
+
* from 1,000"), the page of how many, the rows per page when the table keeps them here (`pageSize`), and the pager.
|
|
161
|
+
*
|
|
162
|
+
* A table read whole (`whole`, every row on one page) has nothing to page: its footer shows only while a search or the
|
|
163
|
+
* filters leave rows out, and is otherwise kept for screen readers alone, so the count is still heard as it changes.
|
|
150
164
|
*/
|
|
151
|
-
export declare function TableFooter<TRow extends TableRow>({ state, disabled, onPage }: {
|
|
165
|
+
export declare function TableFooter<TRow extends TableRow>({ state, disabled, whole, pageSize, onPage }: {
|
|
152
166
|
state: TableState<TRow>;
|
|
153
167
|
disabled: boolean;
|
|
168
|
+
whole?: boolean;
|
|
169
|
+
pageSize?: ReactNode;
|
|
154
170
|
onPage: (page: number) => void;
|
|
155
171
|
}): import("react").JSX.Element;
|
|
156
172
|
export {};
|
|
@@ -52,7 +52,7 @@ function TableCellContent({ column, row, options }) {
|
|
|
52
52
|
// A moment needs the browser's zone, unknown while the page hydrates: nothing shows until then, not another zone's time.
|
|
53
53
|
if (column.format === "date-time" && !options.timeZone && value)
|
|
54
54
|
return _jsx("span", { className: "nx-table__pending" });
|
|
55
|
-
const text = formatCellText(column, value, options);
|
|
55
|
+
const text = formatCellText(column, value, options, row);
|
|
56
56
|
if (!text)
|
|
57
57
|
return _jsx("span", { className: "nx-table__dash", children: "\u2014" });
|
|
58
58
|
return _jsx(_Fragment, { children: text });
|
|
@@ -96,8 +96,11 @@ export function TableColumnGroup({ columns, widths, frame, colRefs, }) {
|
|
|
96
96
|
const own = (key, on) => on && _jsx("col", { ref: hold(key), style: { width: widths[key] } }, key);
|
|
97
97
|
return (_jsxs("colgroup", { children: [own(TABLE_INDEX_COLUMN, Boolean(frame.index)), columns.map(view => (_jsx("col", { ref: hold(view.column.key), style: { width: widths[view.column.key] ?? cssLength(view.column.width) ?? FALLBACK_COLUMN_WIDTH } }, view.column.key))), _jsx("col", { className: "nx-table__col-filler" }), own(TABLE_ACTIONS_COLUMN, Boolean(frame.actions))] }));
|
|
98
98
|
}
|
|
99
|
-
/**
|
|
100
|
-
|
|
99
|
+
/**
|
|
100
|
+
* The number of a row, its checkbox, or both: the number until the pointer is over the row or something is selected. A
|
|
101
|
+
* row of a table whose rows are picked (`onPick`) also holds its radio button here, out of sight.
|
|
102
|
+
*/
|
|
103
|
+
export function TableIndexCell({ heading, rowSpan, own, number, numbered, selectable, checkbox, pick, }) {
|
|
101
104
|
const shared = {
|
|
102
105
|
className: cx(heading ? "nx-table__heading" : "nx-table__cell", "nx-table__cell--index"),
|
|
103
106
|
"data-col": TABLE_INDEX_COLUMN,
|
|
@@ -107,7 +110,7 @@ export function TableIndexCell({ heading, rowSpan, own, number, numbered, select
|
|
|
107
110
|
"data-swap": (numbered && selectable) || undefined,
|
|
108
111
|
style: ownStyle(own, "left"),
|
|
109
112
|
};
|
|
110
|
-
const content = (_jsxs(_Fragment, { children: [numbered && (_jsx("span", { className: "nx-table__number", "aria-hidden": selectable || undefined, children: number })), checkbox] }));
|
|
113
|
+
const content = (_jsxs(_Fragment, { children: [numbered && (_jsx("span", { className: "nx-table__number", "aria-hidden": selectable || undefined, children: number })), checkbox, pick] }));
|
|
111
114
|
return heading ? (_jsx("th", { scope: "col", rowSpan: rowSpan, ...shared, children: content })) : (_jsx("td", { ...shared, children: content }));
|
|
112
115
|
}
|
|
113
116
|
/** What in a row is a control of its own: a click there does what the control does, and never opens the row. */
|
|
@@ -126,13 +129,16 @@ function opensRow(event, single) {
|
|
|
126
129
|
return !(selection && !selection.isCollapsed && event.currentTarget.contains(selection.anchorNode));
|
|
127
130
|
}
|
|
128
131
|
/** One row. It renders again only when its row, its state, or the columns change. */
|
|
129
|
-
function TableRowViewInner({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered, marks, frame, sized, actions, onToggle, onAction, onOpen, openOnClick, }) {
|
|
132
|
+
function TableRowViewInner({ row, rowKey, number, label, columns, cellOptions, selectable, selected, selectionFull, busy, flashed, numbered, marks, frame, sized, actions, onToggle, onAction, onOpen, openOnClick, onPick, picked = false, pickName, }) {
|
|
130
133
|
const inline = actions?.filter(action => action.inline && action.icon) ?? [];
|
|
131
134
|
const inMenu = actions?.filter(action => !(action.inline && action.icon)) ?? [];
|
|
132
135
|
const items = inMenu.map(action => ({ value: action.id, label: action.label, icon: action.icon, description: action.description, tone: action.tone, disabled: action.disabled }));
|
|
133
|
-
|
|
136
|
+
// A click picks a row where rows are picked; opening it is then the double-click's.
|
|
137
|
+
const clickOpens = Boolean(onOpen && openOnClick && !onPick);
|
|
138
|
+
return (_jsxs("tr", { className: "nx-table__row", "data-key": rowKey, "data-selected": selected || undefined, "data-picked": picked || undefined, "data-busy": busy || undefined, "data-flash": flashed || undefined, "aria-busy": busy || undefined, "data-open": clickOpens || undefined, "data-pick": onPick ? "" : undefined,
|
|
134
139
|
// One way in: with a single click the row is already open by the time a double-click would arrive.
|
|
135
|
-
onClick:
|
|
140
|
+
onClick: onPick ? event => opensRow(event, true) && onPick(row) : clickOpens ? event => opensRow(event, true) && onOpen?.(row) : undefined, onDoubleClick: onOpen && !clickOpens ? 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 }) })), pick: onPick &&
|
|
141
|
+
pickName && (_jsx("input", { type: "radio", className: "nx-visually-hidden nx-table__pick", name: pickName, checked: picked, "aria-label": label, onChange: () => onPick(row) })) })), 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}` })] }) }))] }));
|
|
136
142
|
}
|
|
137
143
|
export const TableRowView = memo(TableRowViewInner);
|
|
138
144
|
/**
|
|
@@ -173,14 +179,20 @@ export function TablePageSize({ pageSize, pageSizes, compact = false, onChange }
|
|
|
173
179
|
}
|
|
174
180
|
/**
|
|
175
181
|
* 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.
|
|
182
|
+
* from 1,000"), the page of how many, the rows per page when the table keeps them here (`pageSize`), and the pager.
|
|
183
|
+
*
|
|
184
|
+
* A table read whole (`whole`, every row on one page) has nothing to page: its footer shows only while a search or the
|
|
185
|
+
* filters leave rows out, and is otherwise kept for screen readers alone, so the count is still heard as it changes.
|
|
177
186
|
*/
|
|
178
|
-
export function TableFooter({ state, disabled, onPage }) {
|
|
187
|
+
export function TableFooter({ state, disabled, whole = false, pageSize, onPage }) {
|
|
179
188
|
const { status, rows, offset, matches, total, query } = state;
|
|
180
|
-
|
|
189
|
+
const ready = status === "ready" && total > 0;
|
|
190
|
+
const narrowed = Boolean(query.search || query.filters);
|
|
191
|
+
const range = ready && (_jsxs(_Fragment, { children: [rows.length ? (_jsxs(_Fragment, { children: ["Showing ", _jsx("b", { children: `${formatCount(rows.length ? offset + 1 : 0)}–${formatCount(offset + rows.length)}` }), " of ", _jsx("b", { children: formatCount(matches) })] })) : (_jsx(_Fragment, { children: "No rows on this page" })), narrowed && matches !== total && _jsxs("span", { className: "nx-table__range-all", children: [" \u00B7 filtered from ", formatCount(total)] })] }));
|
|
192
|
+
if (whole) {
|
|
193
|
+
return (_jsx("div", { className: ready && narrowed ? "nx-table__footer" : "nx-visually-hidden", children: _jsx("p", { className: "nx-table__range", "aria-live": "polite", children: range }) }));
|
|
194
|
+
}
|
|
195
|
+
if (!ready)
|
|
181
196
|
return _jsx("div", { className: "nx-table__footer" });
|
|
182
|
-
|
|
183
|
-
const last = offset + rows.length;
|
|
184
|
-
const pages = pageCount(matches, query.pageSize);
|
|
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 })] })] }));
|
|
197
|
+
return (_jsxs("div", { className: "nx-table__footer", children: [_jsx("p", { className: "nx-table__range", "aria-live": "polite", children: range }), _jsxs("div", { className: "nx-table__paging", children: [pageSize, _jsxs("span", { className: "nx-table__page-info", children: ["Page ", formatCount(query.page), " of ", formatCount(pageCount(matches, query.pageSize))] }), _jsx(TablePager, { page: query.page, matches: matches, pageSize: query.pageSize, disabled: disabled, onPage: onPage })] })] }));
|
|
186
198
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { type ReactNode, type RefObject } from "react";
|
|
2
|
+
import type { ApiModuleValue, ApiResult, AppModule } from "../../Interfaces/ApiInterfaces.ts";
|
|
3
|
+
import { type FileOwner, type OwnerFiles, type PictureType, type StoredFile } from "../../Interfaces/FileInterfaces.ts";
|
|
4
|
+
import type { TableColumn, TableRow } from "../../Interfaces/TableInterfaces.ts";
|
|
5
|
+
import { type PopupHandle } from "../Layouts/PopupHost.tsx";
|
|
6
|
+
/** What a page declares for an owner's pictures: whose they are, and how a picture is made. */
|
|
7
|
+
export interface OwnerFilesParams<TModule extends ApiModuleValue = AppModule> extends FileOwner<TModule> {
|
|
8
|
+
/** The shape every picture is cropped to: `1` for a logo or a person, `4 / 3`, or `free` (default). */
|
|
9
|
+
aspect?: number | "free";
|
|
10
|
+
/** `circle` shows a person's picture round, and the editor's guide with it. Default `rect`. */
|
|
11
|
+
shape?: "rect" | "circle";
|
|
12
|
+
/** The largest picture kept, in pixels. Default the category's `pictureMaxSide` (1920), which it never passes. */
|
|
13
|
+
maxWidth?: number;
|
|
14
|
+
maxHeight?: number;
|
|
15
|
+
/**
|
|
16
|
+
* The kind a picture is saved as, fixed. Default: the kind it came in, and a PNG over the size limit becomes a JPEG
|
|
17
|
+
* (nothing see-through) or a WebP (keeping its see-through parts where the browser writes WebP).
|
|
18
|
+
*/
|
|
19
|
+
type?: PictureType;
|
|
20
|
+
/** 0-1, for JPEG and WebP, where the editor starts. Never above the category's `pictureQuality`. */
|
|
21
|
+
quality?: number;
|
|
22
|
+
/** Whose they are, in the window's title and the pictures' names: "Kathmandu branch". */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** Shows the files with no way to change them, whatever the backend allows. */
|
|
25
|
+
readOnly?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Why the files may not be changed here, said under them: a record that is closed, a year that is locked. Set, the
|
|
28
|
+
* files are read only, as with `readOnly`. Without it, a person the backend lets only look is told the backend's own
|
|
29
|
+
* reason (`OwnerFiles.readOnlyReason`), or that they may look but not change.
|
|
30
|
+
*
|
|
31
|
+
* @example lockReason: row?.isClosed ? "The branch is closed: open it again to change its picture." : null
|
|
32
|
+
*/
|
|
33
|
+
lockReason?: string | null;
|
|
34
|
+
/** After a change on this page: the owner's picture now (null for none). */
|
|
35
|
+
onChange?: (primary: StoredFile | null) => void;
|
|
36
|
+
}
|
|
37
|
+
/** What a person the backend lets only look at an owner's files is told, when it gave no words of its own. */
|
|
38
|
+
export declare const READ_ONLY_REASON = "You can see these files, but you may not change them.";
|
|
39
|
+
/** The longest side of a stored picture when neither the params nor the backend say: Drive's default (`PICTURE_LIMITS`). */
|
|
40
|
+
export declare const MAX_PICTURE_SIDE: 1920;
|
|
41
|
+
type Status = {
|
|
42
|
+
kind: "idle";
|
|
43
|
+
} | {
|
|
44
|
+
kind: "loading";
|
|
45
|
+
} | {
|
|
46
|
+
kind: "ready";
|
|
47
|
+
data: OwnerFiles;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "failed";
|
|
50
|
+
message: string;
|
|
51
|
+
};
|
|
52
|
+
/** Tells what a refused or failed call said, once, in the server's own words. */
|
|
53
|
+
export declare function toastFileFailure(title: string, answer: Pick<ApiResult, "message" | "aborted">): void;
|
|
54
|
+
/** One owner's files, and every change a person can make to them. `ownerId` null: nothing is read (a record not saved yet). */
|
|
55
|
+
export declare function useOwnerFiles(params: Omit<OwnerFilesParams<ApiModuleValue>, "ownerId"> & {
|
|
56
|
+
ownerId?: string | null;
|
|
57
|
+
}): {
|
|
58
|
+
status: Status;
|
|
59
|
+
data: OwnerFiles | null;
|
|
60
|
+
busy: string | null;
|
|
61
|
+
canWrite: boolean;
|
|
62
|
+
readOnlyReason: string | null;
|
|
63
|
+
pictures: boolean;
|
|
64
|
+
editorProblem: string | null;
|
|
65
|
+
actions: {
|
|
66
|
+
reload: () => Promise<void | StoredFile | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Chooses a picture, edits it, and makes it the owner's picture (the picture input's Upload). In a category that
|
|
69
|
+
* keeps one file, the new picture takes the place of the one kept (Replace).
|
|
70
|
+
*/
|
|
71
|
+
replace: () => Promise<void>;
|
|
72
|
+
/** Chooses several files and stores them: each picture through the editor, one at a time (the gallery's Upload). */
|
|
73
|
+
upload: () => Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Edits a stored picture; what is saved is a new picture, the owner's picture when the edited one was. In a category
|
|
76
|
+
* that keeps one file, the edited picture takes its place.
|
|
77
|
+
*/
|
|
78
|
+
edit: (file: StoredFile) => Promise<void>;
|
|
79
|
+
makePrimary: (file: StoredFile | null) => Promise<void> | null;
|
|
80
|
+
rename: (file: StoredFile) => Promise<void>;
|
|
81
|
+
remove: (file: StoredFile) => Promise<void>;
|
|
82
|
+
/** Shows the pictures in the editor module's viewer, from `file`. */
|
|
83
|
+
view: (file: StoredFile) => Promise<void>;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Keeps the keyboard focus in an owner's files through a change (`busy`). Its buttons stay focusable while it runs
|
|
88
|
+
* (`aria-disabled`, never `disabled`: a focused control switched off sends the focus to the page in every browser), and
|
|
89
|
+
* when it ends with the focused button gone - a deleted file's, Edit after Remove, a star once its picture is the main
|
|
90
|
+
* one - the focus goes to the first control left in the same tile, row or `role="group"` (the picture input's buttons),
|
|
91
|
+
* else the first in `root`. Only when the focus
|
|
92
|
+
* was in `root` as the change began (or on the page, after a confirmation, while `root` had it last), and only when it
|
|
93
|
+
* has dropped to the page since: a person who moved on keeps their place.
|
|
94
|
+
*/
|
|
95
|
+
export declare function useFocusThroughChange(root: RefObject<HTMLElement | null>, busy: string | null): void;
|
|
96
|
+
/** A control's props while a change runs: still focusable and announced as unavailable, its press ignored. */
|
|
97
|
+
export declare function busyProps(busy: string | null, run: () => void): {
|
|
98
|
+
readonly "aria-disabled": true | undefined;
|
|
99
|
+
readonly onClick: () => void;
|
|
100
|
+
};
|
|
101
|
+
/** The owner's picture as the page keeps it, read with the other rows' in one call. */
|
|
102
|
+
export declare function usePrimaryPicture(owner: FileOwner<ApiModuleValue> | null): StoredFile | null | undefined;
|
|
103
|
+
export interface OwnerPictureProps {
|
|
104
|
+
owner: FileOwner<ApiModuleValue> | null;
|
|
105
|
+
/** The picture's height: pixels, or any CSS length (`var(--a-size)`). Default 28, a table row's. */
|
|
106
|
+
size?: number | string;
|
|
107
|
+
aspect?: number | "free";
|
|
108
|
+
shape?: "rect" | "circle";
|
|
109
|
+
/** What it shows, for a screen reader and the tooltip: "Kathmandu branch's picture". */
|
|
110
|
+
label?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Whose picture it is, by name: without a picture, their initials stand in, on the tone the shared `Avatar` gives that
|
|
113
|
+
* name ("Ram Thapa" -> "RT"), rather than the picture icon. A person's or a company's row passes it.
|
|
114
|
+
*/
|
|
115
|
+
name?: string | null;
|
|
116
|
+
/** Makes it a button: a table cell opens the gallery with it. */
|
|
117
|
+
onClick?: () => void;
|
|
118
|
+
className?: string;
|
|
119
|
+
}
|
|
120
|
+
/** An owner's picture, small: its thumbnail, or, when it has none, the initials of its `name` or a picture icon. */
|
|
121
|
+
export declare function OwnerPicture({ owner, size, aspect, shape, label, name, onClick, className }: OwnerPictureProps): import("react").JSX.Element;
|
|
122
|
+
export interface PictureGalleryProps extends OwnerFilesParams<ApiModuleValue> {
|
|
123
|
+
/** `pictures` (two panes) or `documents` (a list). Default: documents when the category takes anything but pictures. */
|
|
124
|
+
view?: "pictures" | "documents";
|
|
125
|
+
/** Once the files are read: the view shown, `documents` or `pictures` (a window's title follows it). */
|
|
126
|
+
onViewKnown?: (view: "pictures" | "documents") => void;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* An owner's pictures, or documents for a category that takes PDF: upload several up to the category's limit, make
|
|
130
|
+
* one the main picture, view them in the editor module's viewer, edit one (saved as a new picture), delete.
|
|
131
|
+
*
|
|
132
|
+
* Pictures come in two panes, as the 2026-06-20 gallery did: the main picture large, with Upload and Remove (no main
|
|
133
|
+
* picture; the pictures stay) under it and the count and kinds; beside it the pictures as a grid, each with its own
|
|
134
|
+
* buttons inside it - View, Make main, Edit, Delete (Make main and Delete not on the main one) - shown on hover, on a
|
|
135
|
+
* keyboard's focus in the picture, and always where nothing can hover (a touch screen). Under 560px of its own width
|
|
136
|
+
* the main picture sits small beside its buttons, over the grid.
|
|
137
|
+
*/
|
|
138
|
+
export declare function PictureGallery(props: PictureGalleryProps): import("react").JSX.Element;
|
|
139
|
+
/** An owner's documents as a list: `PictureGallery` with `view="documents"`. */
|
|
140
|
+
export declare function DocumentList(props: OwnerFilesParams<ApiModuleValue>): import("react").JSX.Element;
|
|
141
|
+
export interface OpenPictureGalleryOptions extends PictureGalleryProps {
|
|
142
|
+
/** The window's title. Default: "Pictures" or "Documents", and whose. */
|
|
143
|
+
popupTitle?: ReactNode;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Opens an owner's pictures (or documents) in a window; one window per owner and category. Pictures fill a wide
|
|
147
|
+
* window edge to edge in their two panes, the grid scrolling; documents are a list in a narrower one. Without `view`,
|
|
148
|
+
* the title, icon and size follow what the category takes once its files are read: a category that takes PDF says
|
|
149
|
+
* "Documents".
|
|
150
|
+
*/
|
|
151
|
+
export declare function openPictureGallery({ popupTitle, ...props }: OpenPictureGalleryOptions): PopupHandle;
|
|
152
|
+
export interface PictureColumnParams<TRow extends TableRow> extends Omit<OwnerFilesParams<ApiModuleValue>, "ownerId" | "title" | "onChange" | "lockReason">, Partial<Omit<TableColumn<TRow>, "render" | "value">> {
|
|
153
|
+
/** The row's field that holds the owner's id. Default `id`. */
|
|
154
|
+
ownerKey?: string;
|
|
155
|
+
/** Whose the row's pictures are, for the window's title: `row => row.branchName`. */
|
|
156
|
+
rowTitle?: (row: TRow) => string;
|
|
157
|
+
/**
|
|
158
|
+
* The row's field with a name whose initials stand in for a row with no picture (`"fullname"`), as the shared `Avatar`
|
|
159
|
+
* draws a person. Without it, such a row shows the picture icon.
|
|
160
|
+
*/
|
|
161
|
+
nameKey?: string;
|
|
162
|
+
/** Why a row's pictures may not be changed (its window shows them read only, with this sentence), or nothing. */
|
|
163
|
+
lockReason?: string | null | ((row: TRow) => string | null | undefined);
|
|
164
|
+
/** After a change in the window a row opened. */
|
|
165
|
+
onChange?: (row: TRow, primary: StoredFile | null) => void;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A column with each row's picture: every visible row's read in one call per page, and a click opens the row's
|
|
169
|
+
* gallery. A change there redraws the row's picture without a reload.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* columns: [pictureColumn({ module: NexusModule.Enterprise, category: "branch-picture", rowTitle: row => row.branchName }), ...]
|
|
173
|
+
*/
|
|
174
|
+
export declare function pictureColumn<TRow extends TableRow>({ module, category, ownerKey, rowTitle, nameKey, lockReason, onChange, aspect, shape, maxWidth, maxHeight, type, quality, readOnly, view, ...column }: PictureColumnParams<TRow> & Pick<PictureGalleryProps, "view">): TableColumn<TRow>;
|
|
175
|
+
export {};
|