nexus-shared 3.2.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2359 -0
- package/README.md +1911 -1123
- package/dist/Client.Index.d.ts +27 -0
- package/dist/Client.Index.js +27 -0
- package/dist/Components/Chats/ChatComposer.d.ts +7 -1
- package/dist/Components/Chats/ChatComposer.js +9 -6
- package/dist/Components/Chats/ChatParts.d.ts +15 -2
- package/dist/Components/Chats/ChatParts.js +15 -4
- package/dist/Components/Chats/ChatPopup.d.ts +46 -0
- package/dist/Components/Chats/ChatPopup.js +34 -0
- package/dist/Components/Chats/ChatThread.d.ts +9 -1
- package/dist/Components/Chats/ChatThread.js +54 -6
- package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
- package/dist/Components/Dashboards/DashboardBoard.js +594 -0
- package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
- package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
- package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
- package/dist/Components/Dashboards/WidgetFrame.js +45 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
- package/dist/Components/Documents/DocumentFrame.js +83 -0
- package/dist/Components/Documents/Menu.d.ts +4 -0
- package/dist/Components/Documents/Menu.js +40 -24
- package/dist/Components/Documents/PrintDocument.d.ts +95 -0
- package/dist/Components/Documents/PrintDocument.js +62 -0
- package/dist/Components/Documents/TabButtons.d.ts +6 -0
- package/dist/Components/Documents/TabButtons.js +5 -4
- package/dist/Components/Documents/Tabs.d.ts +59 -0
- package/dist/Components/Documents/Tabs.js +70 -0
- package/dist/Components/Domains/DomainCard.d.ts +98 -0
- package/dist/Components/Domains/DomainCard.js +182 -0
- package/dist/Components/Forms/ApiForm.js +24 -3
- package/dist/Components/Forms/Crud.d.ts +2 -2
- package/dist/Components/Forms/Crud.js +628 -247
- package/dist/Components/Forms/CrudActions.d.ts +137 -0
- package/dist/Components/Forms/CrudActions.js +214 -0
- package/dist/Components/Forms/CrudHistory.d.ts +6 -1
- package/dist/Components/Forms/CrudHistory.js +14 -6
- package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
- package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
- package/dist/Components/Forms/CrudParts.d.ts +37 -2
- package/dist/Components/Forms/CrudParts.js +123 -7
- package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
- package/dist/Components/Forms/CrudRowForms.js +174 -0
- package/dist/Components/Forms/CrudScopes.d.ts +28 -0
- package/dist/Components/Forms/CrudScopes.js +76 -0
- package/dist/Components/Forms/CrudTree.d.ts +90 -0
- package/dist/Components/Forms/CrudTree.js +363 -0
- package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
- package/dist/Components/Forms/CrudWorkspace.js +177 -0
- package/dist/Components/Forms/ExcelImport.js +3 -3
- package/dist/Components/Forms/Form.d.ts +6 -5
- package/dist/Components/Forms/Form.js +134 -21
- package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
- package/dist/Components/Forms/FormLeaveGuard.js +65 -0
- package/dist/Components/Forms/FormQuestion.d.ts +6 -0
- package/dist/Components/Forms/FormQuestion.js +28 -0
- package/dist/Components/Forms/RecordFlows.d.ts +50 -0
- package/dist/Components/Forms/RecordFlows.js +282 -0
- package/dist/Components/Forms/SubmitForm.d.ts +37 -2
- package/dist/Components/Forms/SubmitForm.js +182 -10
- package/dist/Components/Inputs/CheckBox.js +1 -1
- package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
- package/dist/Components/Inputs/ChoiceField.js +28 -2
- package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
- package/dist/Components/Inputs/ComputedInput.js +179 -0
- package/dist/Components/Inputs/Dropdown.d.ts +13 -0
- package/dist/Components/Inputs/Dropdown.js +8 -6
- package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
- package/dist/Components/Inputs/DropdownParts.js +16 -4
- package/dist/Components/Inputs/Field.d.ts +7 -1
- package/dist/Components/Inputs/Field.js +2 -2
- package/dist/Components/Inputs/GroupForm.d.ts +6 -6
- package/dist/Components/Inputs/GroupForm.js +12 -12
- package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
- package/dist/Components/Inputs/InputRenderer.js +37 -32
- package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
- package/dist/Components/Inputs/MarkdownEditor.js +32 -6
- package/dist/Components/Inputs/PictureInput.d.ts +41 -0
- package/dist/Components/Inputs/PictureInput.js +75 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
- package/dist/Components/Inputs/RadioGroup.js +8 -3
- package/dist/Components/Inputs/RowsInput.d.ts +15 -2
- package/dist/Components/Inputs/RowsInput.js +96 -8
- package/dist/Components/Inputs/TabularForm.d.ts +5 -5
- package/dist/Components/Inputs/TabularForm.js +45 -32
- package/dist/Components/Inputs/TextArea.d.ts +6 -0
- package/dist/Components/Inputs/TextArea.js +11 -2
- package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
- package/dist/Components/Inputs/TextBoxTypes.js +17 -0
- package/dist/Components/Inputs/TreeView.d.ts +76 -2
- package/dist/Components/Inputs/TreeView.js +227 -16
- package/dist/Components/Layouts/MessageBox.d.ts +15 -2
- package/dist/Components/Layouts/MessageBox.js +56 -7
- package/dist/Components/Layouts/Popup.d.ts +34 -7
- package/dist/Components/Layouts/Popup.js +143 -13
- package/dist/Components/Layouts/PopupDock.d.ts +3 -1
- package/dist/Components/Layouts/PopupDock.js +2 -1
- package/dist/Components/Layouts/PopupHost.js +5 -4
- package/dist/Components/Messages/MailClient.d.ts +50 -0
- package/dist/Components/Messages/MailClient.js +410 -0
- package/dist/Components/Messages/MailColumns.d.ts +21 -0
- package/dist/Components/Messages/MailColumns.js +92 -0
- package/dist/Components/Messages/MailCompose.d.ts +46 -0
- package/dist/Components/Messages/MailCompose.js +154 -0
- package/dist/Components/Messages/MailReader.d.ts +43 -0
- package/dist/Components/Messages/MailReader.js +207 -0
- package/dist/Components/Messages/MailThread.d.ts +71 -0
- package/dist/Components/Messages/MailThread.js +185 -0
- package/dist/Components/Messages/MessageBell.d.ts +41 -0
- package/dist/Components/Messages/MessageBell.js +76 -0
- package/dist/Components/Viewers/DataTable.js +153 -49
- package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
- package/dist/Components/Viewers/DataTableColumns.js +2 -1
- package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
- package/dist/Components/Viewers/DataTableFilters.js +17 -2
- package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
- package/dist/Components/Viewers/DataTableParts.js +26 -14
- package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
- package/dist/Components/Viewers/OwnerFiles.js +700 -0
- package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
- package/dist/Components/Viewers/QrCodeImage.js +15 -0
- package/dist/Components/Viewers/RecordCards.d.ts +16 -0
- package/dist/Components/Viewers/RecordCards.js +509 -0
- package/dist/Helpers/ApiModules.js +4 -0
- package/dist/Helpers/AuditEntries.d.ts +50 -0
- package/dist/Helpers/AuditEntries.js +96 -0
- package/dist/Helpers/BusyHooks.d.ts +6 -0
- package/dist/Helpers/BusyHooks.js +39 -0
- package/dist/Helpers/ChatBackend.d.ts +8 -1
- package/dist/Helpers/ChatBackend.js +18 -0
- package/dist/Helpers/ChatHelpers.d.ts +2 -0
- package/dist/Helpers/ChatHelpers.js +4 -1
- package/dist/Helpers/CrudBackend.d.ts +20 -2
- package/dist/Helpers/CrudBackend.js +46 -2
- package/dist/Helpers/CrudColumns.d.ts +91 -6
- package/dist/Helpers/CrudColumns.js +148 -7
- package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
- package/dist/Helpers/CrudDetailsTabs.js +126 -0
- package/dist/Helpers/CrudHelpers.d.ts +116 -2
- package/dist/Helpers/CrudHelpers.js +256 -0
- package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
- package/dist/Helpers/CrudNarrowPick.js +157 -0
- package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
- package/dist/Helpers/CrudPickerPlan.js +46 -0
- package/dist/Helpers/CrudRowForms.d.ts +32 -0
- package/dist/Helpers/CrudRowForms.js +76 -0
- package/dist/Helpers/CrudScopes.d.ts +32 -0
- package/dist/Helpers/CrudScopes.js +79 -0
- package/dist/Helpers/DashboardHelpers.d.ts +115 -0
- package/dist/Helpers/DashboardHelpers.js +305 -0
- package/dist/Helpers/DomainCards.d.ts +140 -0
- package/dist/Helpers/DomainCards.js +370 -0
- package/dist/Helpers/DropdownHelpers.d.ts +3 -1
- package/dist/Helpers/DropdownHelpers.js +6 -2
- package/dist/Helpers/ExcelHelpers.d.ts +1 -1
- package/dist/Helpers/ExcelHelpers.js +12 -11
- package/dist/Helpers/FileAdapter.d.ts +158 -0
- package/dist/Helpers/FileAdapter.js +173 -0
- package/dist/Helpers/FilePrimaries.d.ts +48 -0
- package/dist/Helpers/FilePrimaries.js +138 -0
- package/dist/Helpers/FormConfirm.d.ts +32 -0
- package/dist/Helpers/FormConfirm.js +40 -0
- package/dist/Helpers/FormStore.d.ts +100 -5
- package/dist/Helpers/FormStore.js +368 -25
- package/dist/Helpers/InputConditions.d.ts +56 -0
- package/dist/Helpers/InputConditions.js +192 -0
- package/dist/Helpers/InputMemory.d.ts +106 -0
- package/dist/Helpers/InputMemory.js +395 -0
- package/dist/Helpers/InputNarrowing.d.ts +50 -0
- package/dist/Helpers/InputNarrowing.js +148 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
- package/dist/Helpers/InputParamsHelpers.js +157 -74
- package/dist/Helpers/MailClientHelpers.d.ts +96 -0
- package/dist/Helpers/MailClientHelpers.js +211 -0
- package/dist/Helpers/MessageBackend.d.ts +152 -0
- package/dist/Helpers/MessageBackend.js +329 -0
- package/dist/Helpers/MessageHelpers.d.ts +160 -0
- package/dist/Helpers/MessageHelpers.js +573 -0
- package/dist/Helpers/MessageUnread.d.ts +95 -0
- package/dist/Helpers/MessageUnread.js +245 -0
- package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
- package/dist/Helpers/NexusDriveAdapter.js +236 -0
- package/dist/Helpers/NumberHelpers.d.ts +156 -0
- package/dist/Helpers/NumberHelpers.js +242 -0
- package/dist/Helpers/PagedPicker.d.ts +42 -0
- package/dist/Helpers/PagedPicker.js +85 -0
- package/dist/Helpers/Permissions.js +2 -1
- package/dist/Helpers/QrCode.d.ts +26 -0
- package/dist/Helpers/QrCode.js +463 -0
- package/dist/Helpers/RecordCards.d.ts +205 -0
- package/dist/Helpers/RecordCards.js +378 -0
- package/dist/Helpers/RequestCache.d.ts +14 -2
- package/dist/Helpers/RequestCache.js +22 -3
- package/dist/Helpers/RowsStore.d.ts +61 -2
- package/dist/Helpers/RowsStore.js +203 -16
- package/dist/Helpers/ServerPreview.d.ts +69 -0
- package/dist/Helpers/ServerPreview.js +99 -0
- package/dist/Helpers/TableColumns.js +4 -3
- package/dist/Helpers/TableExport.d.ts +7 -1
- package/dist/Helpers/TableExport.js +9 -3
- package/dist/Helpers/TableFilters.d.ts +1 -1
- package/dist/Helpers/TableFilters.js +24 -23
- package/dist/Helpers/TableHelpers.d.ts +38 -4
- package/dist/Helpers/TableHelpers.js +78 -10
- package/dist/Helpers/TableJoins.d.ts +13 -0
- package/dist/Helpers/TableJoins.js +34 -0
- package/dist/Helpers/TableStore.d.ts +28 -4
- package/dist/Helpers/TableStore.js +73 -11
- package/dist/Helpers/ToolbarFit.d.ts +23 -0
- package/dist/Helpers/ToolbarFit.js +30 -0
- package/dist/Helpers/TreeStore.d.ts +156 -6
- package/dist/Helpers/TreeStore.js +547 -18
- package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
- package/dist/Interfaces/ApiInterfaces.js +10 -1
- package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
- package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
- package/dist/Interfaces/DashboardInterfaces.js +1 -0
- package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
- package/dist/Interfaces/DomainInterfaces.js +1 -0
- package/dist/Interfaces/FileInterfaces.d.ts +183 -0
- package/dist/Interfaces/FileInterfaces.js +6 -0
- package/dist/Interfaces/FormInterfaces.d.ts +460 -32
- package/dist/Interfaces/FormInterfaces.js +79 -1
- package/dist/Interfaces/InputInterfaces.d.ts +22 -3
- package/dist/Interfaces/MailInterfaces.d.ts +192 -0
- package/dist/Interfaces/MailInterfaces.js +1 -0
- package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
- package/dist/Interfaces/RecordInterfaces.js +1 -0
- package/dist/Interfaces/TableInterfaces.d.ts +102 -3
- package/dist/Services/ExcelApi.js +5 -0
- package/dist/Services/FileApi.d.ts +13 -0
- package/dist/Services/FileApi.js +29 -0
- package/dist/Services/ImageEditorLoader.d.ts +14 -0
- package/dist/Services/ImageEditorLoader.js +51 -0
- package/dist/Services/JoinSources.d.ts +79 -6
- package/dist/Services/JoinSources.js +132 -21
- package/dist/Services/MessageApi.d.ts +18 -0
- package/dist/Services/MessageApi.js +64 -0
- package/dist/Services/ModuleImport.d.ts +7 -0
- package/dist/Services/ModuleImport.js +22 -0
- package/dist/Shared.Index.d.ts +28 -0
- package/dist/Shared.Index.js +28 -0
- package/package.json +13 -2
- package/src/Styles/Nexus.Avatar.css +6 -5
- package/src/Styles/Nexus.Button.css +39 -4
- package/src/Styles/Nexus.Chat.css +114 -22
- package/src/Styles/Nexus.Choice.css +16 -0
- package/src/Styles/Nexus.Crud.css +486 -21
- package/src/Styles/Nexus.Dashboard.css +763 -0
- package/src/Styles/Nexus.Domain.css +384 -0
- package/src/Styles/Nexus.Dropdown.css +8 -0
- package/src/Styles/Nexus.Editor.css +20 -0
- package/src/Styles/Nexus.Files.css +437 -0
- package/src/Styles/Nexus.Form.css +5 -2
- package/src/Styles/Nexus.Index.css +7 -0
- package/src/Styles/Nexus.Input.css +73 -0
- package/src/Styles/Nexus.Mail.css +1062 -0
- package/src/Styles/Nexus.Menu.css +10 -1
- package/src/Styles/Nexus.Popup.css +135 -1
- package/src/Styles/Nexus.Print.css +288 -0
- package/src/Styles/Nexus.QrCode.css +12 -0
- package/src/Styles/Nexus.Record.css +430 -0
- package/src/Styles/Nexus.Tab.Buttons.css +87 -22
- package/src/Styles/Nexus.Table.css +1458 -1387
- package/src/Styles/Nexus.Theme.Picker.css +1 -0
- package/src/Styles/Nexus.Themes.css +12 -6
- package/src/Styles/Nexus.Tokens.css +8 -3
- package/src/Styles/Nexus.Tree.css +41 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { iconArrowRight, iconChevronDown, iconChevronUp, iconLoader2, iconLock, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { useEffect, useId, useImperativeHandle, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { chatDomainLabel } from "../../Helpers/ChatBackend.js";
|
|
6
|
+
import { chatTimeLabel } from "../../Helpers/ChatHelpers.js";
|
|
7
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
8
|
+
import { useBrowserTimeZone, useDatePreferences } from "../../Helpers/DatePreferences.js";
|
|
9
|
+
import { domainCardContext, domainCardFailedText, domainCardFields, domainCardNoun, domainCardOf, domainCardRefusedText, domainCardShape, domainCardValueText, DomainCardRun, heldDomainCardShape, rememberDomainCardShape, } from "../../Helpers/DomainCards.js";
|
|
10
|
+
import { useNumberLocale } from "../../Helpers/UserPreferences.js";
|
|
11
|
+
import { ChatFailure } from "../Chats/ChatParts.js";
|
|
12
|
+
import { Button } from "../Documents/Button.js";
|
|
13
|
+
/** A record's card, read live from its module: see the notes at the top of this file. */
|
|
14
|
+
export function DomainCard({ card: given, kind: givenKind, ...props }) {
|
|
15
|
+
const registered = domainCardOf(props.domain);
|
|
16
|
+
const card = given === undefined ? registered?.card : (given ?? undefined);
|
|
17
|
+
const kind = givenKind ?? registered?.info.label ?? chatDomainLabel(props.domain);
|
|
18
|
+
const { id } = props;
|
|
19
|
+
if (!card || id === null || id === undefined || id === "")
|
|
20
|
+
return _jsx(DomainLine, { kind: kind, label: props.label, className: props.className });
|
|
21
|
+
// A record of its own is a read of its own: another id starts from its placeholder, never from the last record.
|
|
22
|
+
return _jsx(LiveDomainCard, { ...props, id: id, card: card, kind: kind }, `${String(props.domain)}|${String(id)}|${String(props.viewer ?? "")}`);
|
|
23
|
+
}
|
|
24
|
+
/** One record's card read, for a page that draws it its own way: the snapshot, and `run.reload()` / `run.retry()`. */
|
|
25
|
+
export function useDomainCard({ domain, id, card, viewer, label, kind, revision }) {
|
|
26
|
+
const context = useMemo(() => domainCardContext(domain, id, { ...(viewer !== null && viewer !== undefined && { viewer }), label, kind }), [domain, id, viewer, label, kind]);
|
|
27
|
+
const latest = useRef({ card, context });
|
|
28
|
+
latest.current = { card, context };
|
|
29
|
+
const run = useMemo(() => new DomainCardRun({ domainType: domain, id, viewer, lifetime: card.lifetime, load: signal => latest.current.card.load(id, signal, latest.current.context) }),
|
|
30
|
+
// A new card definition for the same record keeps the run: `load` is read when it is called.
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
[domain, id, viewer]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
run.start();
|
|
35
|
+
return () => run.stop();
|
|
36
|
+
}, [run]);
|
|
37
|
+
const snapshot = useSyncExternalStore(run.subscribe, run.getSnapshot, run.getSnapshot);
|
|
38
|
+
// A change seen after the card was drawn reads the record again; the revision it opened with does not.
|
|
39
|
+
const seen = useRef(revision);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (revision === seen.current)
|
|
42
|
+
return;
|
|
43
|
+
seen.current = revision;
|
|
44
|
+
run.reload();
|
|
45
|
+
}, [revision, run]);
|
|
46
|
+
const shown = useMemo(() => {
|
|
47
|
+
if (snapshot.data === undefined)
|
|
48
|
+
return {};
|
|
49
|
+
try {
|
|
50
|
+
return { params: card.view(snapshot.data, context) };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error("A record's card could not be drawn from its answer:", error);
|
|
54
|
+
return { viewError: error };
|
|
55
|
+
}
|
|
56
|
+
}, [snapshot.data, card, context]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (shown.params)
|
|
59
|
+
rememberDomainCardShape(domain, domainCardShape(shown.params));
|
|
60
|
+
}, [domain, shown.params]);
|
|
61
|
+
return { run, snapshot, context, ...shown };
|
|
62
|
+
}
|
|
63
|
+
function LiveDomainCard({ domain, id, label, revision, viewer, card, kind, headingLevel = 3, collapsible = true, defaultCollapsed = false, collapsed: heldCollapsed, onCollapsedChange, className, ref }) {
|
|
64
|
+
const state = useDomainCard({ domain, id, card, viewer, label, kind, revision });
|
|
65
|
+
const { run, snapshot, params, viewError, context } = state;
|
|
66
|
+
const [ownCollapsed, setOwnCollapsed] = useState(defaultCollapsed);
|
|
67
|
+
const collapsed = heldCollapsed ?? ownCollapsed;
|
|
68
|
+
const setCollapsed = (next) => {
|
|
69
|
+
setOwnCollapsed(next);
|
|
70
|
+
onCollapsedChange?.(next);
|
|
71
|
+
};
|
|
72
|
+
useImperativeHandle(ref, () => ({ reload: () => run.reload() }), [run]);
|
|
73
|
+
// Said aloud once, when a read again changes what is on screen or fails.
|
|
74
|
+
const [spoken, setSpoken] = useState("");
|
|
75
|
+
const noun = domainCardNoun(kind);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (snapshot.changedAt)
|
|
78
|
+
setSpoken(`The ${noun} was updated.`);
|
|
79
|
+
}, [snapshot.changedAt, noun]);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (snapshot.refreshFailed)
|
|
82
|
+
setSpoken(`The ${noun} could not be read again.`);
|
|
83
|
+
}, [snapshot.refreshFailed, noun]);
|
|
84
|
+
const live = (_jsx("span", { className: "nx-visually-hidden", "aria-live": "polite", children: spoken }));
|
|
85
|
+
if (snapshot.status === "refused") {
|
|
86
|
+
return (_jsxs("section", { className: cx("nx-domain-card", className), "data-state": "refused", "aria-label": kind ?? label ?? undefined, "aria-busy": snapshot.refreshing || undefined, children: [_jsx(DomainLine, { kind: kind, label: label }), _jsxs("p", { className: "nx-domain-card__notice", children: [_jsx(InlineIcon, { icon: iconLock }), domainCardRefusedText(kind)] }), live] }));
|
|
87
|
+
}
|
|
88
|
+
if (snapshot.status === "failed" || (viewError !== undefined && !params)) {
|
|
89
|
+
return (_jsxs("section", { className: cx("nx-domain-card", className), "data-state": "failed", "aria-label": kind ?? label ?? undefined, children: [_jsx(DomainLine, { kind: kind, label: label }), _jsx(ChatFailure, { message: domainCardFailedText(kind), onRetry: () => run.retry() }), live] }));
|
|
90
|
+
}
|
|
91
|
+
if (snapshot.status === "loading" || !params) {
|
|
92
|
+
return (_jsxs(_Fragment, { children: [_jsx(DomainCardHeld, { shape: heldDomainCardShape(domain, card), kind: kind, label: label, headingLevel: headingLevel, collapsible: collapsible, collapsed: collapsed, onCollapsedChange: setCollapsed, className: className }), live] }));
|
|
93
|
+
}
|
|
94
|
+
const status = snapshot.refreshing ? (_jsx("span", { className: "nx-domain-card__meta", title: "Reading again", children: _jsx(InlineIcon, { icon: iconLoader2, className: "nx-spin" }) })) : snapshot.refreshFailed ? (_jsxs("span", { className: "nx-domain-card__meta nx-domain-card__meta--failed", children: ["Not up to date", _jsx("button", { type: "button", className: "nx-domain-card__retry", onClick: () => run.retry(), children: "Try again" })] })) : snapshot.changedAt ? (_jsx(DomainCardUpdated, { at: snapshot.changedAt })) : undefined;
|
|
95
|
+
return (_jsxs(_Fragment, { children: [_jsx(DomainCardFrame, { params: params, kind: kind, headingLevel: headingLevel, status: status, busy: snapshot.refreshing, collapsible: collapsible, collapsed: collapsed, onCollapsedChange: setCollapsed, className: className, "data-state": "ready", children: card.extra && snapshot.data !== undefined ? card.extra(snapshot.data, context) : null }), live] }));
|
|
96
|
+
}
|
|
97
|
+
/** "Updated 2:45 PM": the record changed while its card was open. */
|
|
98
|
+
function DomainCardUpdated({ at }) {
|
|
99
|
+
const preferences = useDatePreferences();
|
|
100
|
+
const zone = useBrowserTimeZone();
|
|
101
|
+
const moment = new Date(at).toISOString();
|
|
102
|
+
return (_jsxs("span", { className: "nx-domain-card__meta", children: ["Updated ", _jsx("time", { dateTime: moment, children: chatTimeLabel(moment, { ...preferences, ...(zone && { timeZone: zone }) }) })] }));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A card drawn from params alone: the look every record's card shares. `DomainCard` draws it once a record is read; a
|
|
106
|
+
* page that already holds a record (a dashboard, a print preview) can draw it straight.
|
|
107
|
+
*/
|
|
108
|
+
export function DomainCardFrame({ params, kind, headingLevel = 3, status, busy = false, collapsible = false, collapsed: heldCollapsed, defaultCollapsed = false, onCollapsedChange, children, className, "data-state": dataState, }) {
|
|
109
|
+
const [ownCollapsed, setOwnCollapsed] = useState(defaultCollapsed);
|
|
110
|
+
const collapsed = collapsible && (heldCollapsed ?? ownCollapsed);
|
|
111
|
+
const titleId = useId();
|
|
112
|
+
const detailsId = useId();
|
|
113
|
+
const preferences = useDatePreferences();
|
|
114
|
+
const zone = useBrowserTimeZone();
|
|
115
|
+
const locale = useNumberLocale();
|
|
116
|
+
const values = { preferences, timeZone: zone, locale, currency: params.currency };
|
|
117
|
+
const Heading = `h${headingLevel}`;
|
|
118
|
+
const fields = domainCardFields(params);
|
|
119
|
+
const figures = params.figures ?? [];
|
|
120
|
+
const lines = params.lines && params.lines.rows.length > 0 ? params.lines : null;
|
|
121
|
+
const extra = children === undefined || children === null || children === false ? null : children;
|
|
122
|
+
// Only what can fold has a fold button: the fields, the lines and what a kind adds; the figures always show.
|
|
123
|
+
const foldable = collapsible && (fields.length > 0 || lines !== null || extra !== null);
|
|
124
|
+
const toggle = () => {
|
|
125
|
+
const next = !collapsed;
|
|
126
|
+
setOwnCollapsed(next);
|
|
127
|
+
onCollapsedChange?.(next);
|
|
128
|
+
};
|
|
129
|
+
return (_jsxs("section", { className: cx("nx-domain-card", className), "aria-labelledby": titleId, "aria-busy": busy || undefined, "data-state": dataState, children: [_jsxs("div", { className: "nx-domain-card__head", children: [_jsxs(Heading, { className: "nx-domain-card__title", id: titleId, children: [kind && _jsx("span", { className: "nx-table__tag nx-table__tag--plain nx-domain-card__kind", children: kind }), kind && " ", _jsx("span", { children: params.title })] }), _jsxs("div", { className: "nx-domain-card__status", children: [status, params.state && (_jsx("span", { className: "nx-table__tag nx-domain-card__state", "data-tone": params.state.tone && params.state.tone !== "neutral" ? params.state.tone : undefined, children: params.state.label })), foldable && (_jsx(Button, { variant: "ghost", size: "sm", className: "nx-domain-card__toggle", icon: _jsx(InlineIcon, { icon: collapsed ? iconChevronDown : iconChevronUp }), "aria-expanded": !collapsed, "aria-controls": detailsId, "aria-label": `${collapsed ? "Show" : "Hide"} the details: ${params.title}`, title: collapsed ? "Show the details" : "Hide the details", onClick: toggle }))] }), (params.code || params.subtitle || params.link) && (_jsxs("div", { className: "nx-domain-card__subtitle", children: [_jsxs("span", { title: [params.code, params.subtitle].filter(Boolean).join(" · ") || undefined, children: [params.code && _jsx("span", { className: "nx-domain-card__code", children: params.code }), params.code && params.subtitle && " · ", params.subtitle] }), params.link && (_jsxs("a", { className: "nx-domain-card__link", href: params.link.href, target: params.link.target, rel: params.link.target === "_blank" ? "noopener" : undefined, children: [params.link.label, _jsx(InlineIcon, { icon: iconArrowRight })] }))] }))] }), _jsxs("div", { className: "nx-domain-card__body", hidden: collapsed && figures.length === 0, children: [figures.length > 0 && (_jsx("dl", { className: "nx-domain-card__figures", children: figures.map((figure, index) => (_jsxs("div", { className: "nx-domain-card__figure", "data-tone": figure.tone && figure.tone !== "neutral" ? figure.tone : undefined, children: [_jsx("dt", { children: figure.label }), _jsx("dd", { children: _jsx(DomainFigureValue, { figure: figure, values: values }) }), figure.hint && _jsx("dd", { className: "nx-domain-card__hint", children: figure.hint })] }, `${figure.label}-${index}`))) })), _jsxs("div", { className: "nx-domain-card__details", id: detailsId, hidden: collapsed || (fields.length === 0 && lines === null && extra === null), children: [fields.length > 0 && (_jsx("dl", { className: "nx-domain-card__fields", children: fields.map((field, index) => {
|
|
130
|
+
const text = domainCardValueText(field.value, field.kind, { ...values, currency: field.currency ?? values.currency });
|
|
131
|
+
return (_jsxs("div", { className: "nx-domain-card__field", "data-kind": field.kind ?? "text", "data-empty": text ? undefined : "", children: [_jsx("dt", { children: field.label }), _jsxs("dd", { children: [text || field.empty, field.note && _jsxs("span", { className: "nx-domain-card__note", children: [" \u00B7 ", field.note] })] })] }, `${field.label}-${index}`));
|
|
132
|
+
}) })), lines && (_jsxs(_Fragment, { children: [_jsxs("table", { className: "nx-domain-card__lines", children: [_jsx("caption", { className: "nx-visually-hidden", children: lines.label ?? "Lines" }), _jsx("thead", { children: _jsx("tr", { children: lines.columns.map((column, index) => (_jsx("th", { scope: "col", "data-align": alignOf(column.kind, column.align), children: column.label }, `${column.label}-${index}`))) }) }), _jsx("tbody", { children: lines.rows.map((row, rowIndex) => (_jsx("tr", { children: lines.columns.map((column, index) => {
|
|
133
|
+
const text = domainCardValueText(row[index], column.kind, values);
|
|
134
|
+
// The first column keeps to one line, cut with "..." on a narrow card: its whole words on hover.
|
|
135
|
+
return (_jsx("td", { "data-align": alignOf(column.kind, column.align), title: index === 0 && text ? text : undefined, children: text }, index));
|
|
136
|
+
}) }, rowIndex))) })] }), (lines.more ?? 0) > 0 && _jsxs("p", { className: "nx-domain-card__more", children: ["and ", lines.more, " more"] })] })), extra] })] })] }));
|
|
137
|
+
}
|
|
138
|
+
const alignOf = (kind, align) => align ?? (kind === "number" || kind === "money" || kind === "days" ? "end" : "start");
|
|
139
|
+
/** A figure's parts: the words before it (money's currency), the figure, the words after it (days, a unit). */
|
|
140
|
+
function figureParts(figure, values) {
|
|
141
|
+
const { value } = figure;
|
|
142
|
+
if (value === null || value === undefined || value === "" || (typeof value === "number" && !Number.isFinite(value)))
|
|
143
|
+
return { shown: "-" };
|
|
144
|
+
if (figure.kind === "money") {
|
|
145
|
+
const currency = (figure.currency ?? values.currency ?? "").trim();
|
|
146
|
+
return { before: currency || undefined, shown: domainCardValueText(value, "money", { ...values, currency: null }) };
|
|
147
|
+
}
|
|
148
|
+
if (figure.kind === "days") {
|
|
149
|
+
// "14 days": the same helper as every other days value, its number large and its word small.
|
|
150
|
+
const text = domainCardValueText(value, "days", values);
|
|
151
|
+
const space = text.lastIndexOf(" ");
|
|
152
|
+
return space > 0 ? { shown: text.slice(0, space), after: text.slice(space + 1) } : { shown: text };
|
|
153
|
+
}
|
|
154
|
+
return { shown: typeof value === "string" ? value : domainCardValueText(value, "number", values), after: figure.unit };
|
|
155
|
+
}
|
|
156
|
+
/** A figure's value: large, its unit smaller beside it, a real space between them for a screen reader. */
|
|
157
|
+
function DomainFigureValue({ figure, values }) {
|
|
158
|
+
const { before, shown, after } = figureParts(figure, values);
|
|
159
|
+
return (_jsxs(_Fragment, { children: [before && _jsx("span", { className: "nx-domain-card__unit", children: before }), before && " ", _jsx("span", { className: "nx-domain-card__value", children: shown }), after && " ", after && _jsx("span", { className: "nx-domain-card__unit", children: after })] }));
|
|
160
|
+
}
|
|
161
|
+
/** A bone on the line of the text it stands for. */
|
|
162
|
+
const Bone = ({ width }) => _jsx("span", { className: "nx-table__bone nx-domain-card__bone", style: { width } });
|
|
163
|
+
/**
|
|
164
|
+
* The card while it loads: the frame's own parts, each holding its line with a bone, in the shape of the kind's last
|
|
165
|
+
* card - so when the record lands the thread under it does not move. The record's words name it for a screen reader.
|
|
166
|
+
*/
|
|
167
|
+
function DomainCardHeld({ shape, kind, label, headingLevel, collapsible, collapsed, onCollapsedChange, className }) {
|
|
168
|
+
const titleId = useId();
|
|
169
|
+
const detailsId = useId();
|
|
170
|
+
const Heading = `h${headingLevel}`;
|
|
171
|
+
const details = shape.fields > 0 || shape.lines > 0;
|
|
172
|
+
const foldable = collapsible && details;
|
|
173
|
+
const folded = foldable && collapsed;
|
|
174
|
+
const columns = Math.max(shape.columns, 1);
|
|
175
|
+
return (_jsxs("section", { className: cx("nx-domain-card", className), "aria-labelledby": titleId, "aria-busy": "true", "data-state": "loading", children: [_jsxs("div", { className: "nx-domain-card__head", children: [_jsxs(Heading, { className: "nx-domain-card__title", id: titleId, children: [kind && _jsx("span", { className: "nx-table__tag nx-table__tag--plain nx-domain-card__kind", children: kind }), kind && " ", _jsxs("span", { children: [_jsx(Bone, { width: "8rem" }), _jsx("span", { className: "nx-visually-hidden", children: label ? `${label}, loading` : "Loading" })] })] }), _jsxs("div", { className: "nx-domain-card__status", children: [shape.state && _jsx("span", { className: "nx-table__tag nx-table__tag--plain nx-domain-card__bone-tag", "aria-hidden": "true" }), foldable && (_jsx(Button, { variant: "ghost", size: "sm", className: "nx-domain-card__toggle", icon: _jsx(InlineIcon, { icon: folded ? iconChevronDown : iconChevronUp }), "aria-expanded": !folded, "aria-controls": detailsId, "aria-label": `${folded ? "Show" : "Hide"} the details`, title: folded ? "Show the details" : "Hide the details", onClick: () => onCollapsedChange(!folded) }))] }), shape.subtitle && (_jsxs("div", { className: "nx-domain-card__subtitle", "aria-hidden": "true", children: [_jsx("span", { children: _jsx(Bone, { width: "10rem" }) }), shape.link && (_jsx("span", { className: "nx-domain-card__link", children: _jsx(Bone, { width: "7rem" }) }))] }))] }), _jsxs("div", { className: "nx-domain-card__body", hidden: folded && shape.figures === 0, "aria-hidden": "true", children: [shape.figures > 0 && (_jsx("dl", { className: "nx-domain-card__figures", children: Array.from({ length: shape.figures }, (_, index) => (_jsxs("div", { className: "nx-domain-card__figure", children: [_jsx("dt", { children: _jsx(Bone, { width: "3.5rem" }) }), _jsx("dd", { children: _jsx("span", { className: "nx-domain-card__value", children: _jsx(Bone, { width: "2.5rem" }) }) }), shape.hints.includes(index) && (_jsx("dd", { className: "nx-domain-card__hint", children: _jsx(Bone, { width: "5rem" }) }))] }, index))) })), _jsxs("div", { className: "nx-domain-card__details", id: detailsId, hidden: folded || !details, children: [shape.fields > 0 && (_jsx("dl", { className: "nx-domain-card__fields", children: Array.from({ length: shape.fields }, (_, index) => (_jsxs("div", { className: "nx-domain-card__field", children: [_jsx("dt", { children: _jsx(Bone, { width: "3.5rem" }) }), _jsx("dd", { children: _jsx(Bone, { width: "6rem" }) })] }, index))) })), shape.lines > 0 && (_jsxs(_Fragment, { children: [_jsxs("table", { className: "nx-domain-card__lines", children: [_jsx("thead", { children: _jsx("tr", { children: Array.from({ length: columns }, (_, index) => (_jsx("th", { children: _jsx(Bone, { width: "3rem" }) }, index))) }) }), _jsx("tbody", { children: Array.from({ length: shape.lines }, (_, row) => (_jsx("tr", { children: Array.from({ length: columns }, (_, index) => (_jsx("td", { children: _jsx(Bone, { width: index === 0 ? "7rem" : "2.5rem" }) }, index))) }, row))) })] }), shape.more && (_jsx("p", { className: "nx-domain-card__more", children: _jsx(Bone, { width: "5rem" }) }))] }))] })] })] }));
|
|
176
|
+
}
|
|
177
|
+
/** A record named by its kind and its words, with nothing read: a message about a kind with no card. */
|
|
178
|
+
export function DomainLine({ kind, label, className }) {
|
|
179
|
+
if (!kind && !label)
|
|
180
|
+
return null;
|
|
181
|
+
return (_jsxs("p", { className: cx("nx-domain-line", className), children: [kind && _jsx("span", { className: "nx-table__tag nx-table__tag--plain", children: kind }), kind && label && " ", label && _jsx("span", { className: "nx-domain-line__label", children: label })] }));
|
|
182
|
+
}
|
|
@@ -39,6 +39,10 @@ export function ApiForm(props) {
|
|
|
39
39
|
const latest = useRef(props);
|
|
40
40
|
latest.current = props;
|
|
41
41
|
const answerRef = useRef(null);
|
|
42
|
+
// What the save that answered sent: the values and the body made of them, for `onSuccess`.
|
|
43
|
+
const sentRef = useRef(null);
|
|
44
|
+
// The body the question before a save showed (`confirm`), and the submit it is for.
|
|
45
|
+
const keptRef = useRef(null);
|
|
42
46
|
const [load, setLoad] = useState(() => (loadValue ? { state: "loading" } : { state: "done" }));
|
|
43
47
|
const [attempt, setAttempt] = useState(0);
|
|
44
48
|
useEffect(() => {
|
|
@@ -59,11 +63,25 @@ export function ApiForm(props) {
|
|
|
59
63
|
});
|
|
60
64
|
return () => controller.abort();
|
|
61
65
|
}, [store, attempt]);
|
|
66
|
+
/**
|
|
67
|
+
* With `confirm`, the body is made once, before the question, so the question shows exactly what is sent: kept with
|
|
68
|
+
* the submit it was made for, and sent by that submit alone. Without `confirm` nothing calls this.
|
|
69
|
+
*/
|
|
70
|
+
async function makeBody(values, context) {
|
|
71
|
+
keptRef.current = null;
|
|
72
|
+
const { transform } = latest.current;
|
|
73
|
+
const body = transform ? await transform(values, context) : values;
|
|
74
|
+
keptRef.current = { context, body };
|
|
75
|
+
return body;
|
|
76
|
+
}
|
|
62
77
|
async function send(values, context) {
|
|
63
78
|
// No `readResponse` means the client's own reader: the app's (`configureApi({ read })`), else the built-in one.
|
|
64
79
|
const { api, transform, readResponse } = latest.current;
|
|
65
80
|
answerRef.current = null;
|
|
66
|
-
|
|
81
|
+
sentRef.current = null;
|
|
82
|
+
const kept = keptRef.current;
|
|
83
|
+
keptRef.current = null;
|
|
84
|
+
const body = kept && kept.context === context ? kept.body : transform ? await transform(values, context) : values;
|
|
67
85
|
let answer;
|
|
68
86
|
if (typeof api === "function")
|
|
69
87
|
answer = await toApiResult(await api(body, context), readResponse ?? getApiReader() ?? readApiResponse);
|
|
@@ -76,6 +94,7 @@ export function ApiForm(props) {
|
|
|
76
94
|
}
|
|
77
95
|
if (answer.ok) {
|
|
78
96
|
answerRef.current = answer;
|
|
97
|
+
sentRef.current = { values, body };
|
|
79
98
|
return { ok: true };
|
|
80
99
|
}
|
|
81
100
|
latest.current.onError?.(answer, values);
|
|
@@ -91,10 +110,12 @@ export function ApiForm(props) {
|
|
|
91
110
|
const endpoint = isApiEndpoint(api) ? api : undefined;
|
|
92
111
|
if (successMessage !== false)
|
|
93
112
|
toast.success(successMessage ?? answer?.message ?? messages.success(endpoint?.action ?? "save", endpoint?.subject));
|
|
94
|
-
onSuccess?.(answer?.result, values, store);
|
|
113
|
+
onSuccess?.(answer?.result, values, store, sentRef.current ?? { values, body: values });
|
|
95
114
|
}
|
|
96
115
|
if (load.state === "failed") {
|
|
97
116
|
return (_jsxs("div", { className: cx("nx-form-load-failed", rest.className), style: rest.style, role: "alert", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), _jsxs("p", { children: [_jsx("strong", { children: "Could not load the values." }), " ", load.message] }), _jsxs("div", { className: "nx-form-load-failed__actions", children: [rest.onCancel && (_jsx(Button, { size: "sm", onClick: rest.onCancel, children: rest.cancelLabel ?? "Cancel" })), _jsx(Button, { size: "sm", icon: _jsx(InlineIcon, { icon: iconRefresh }), onClick: () => setAttempt(count => count + 1), children: "Retry" })] })] }));
|
|
98
117
|
}
|
|
99
|
-
|
|
118
|
+
// `makeBody` is the package's own hand-over to its SubmitForm, not a prop of SubmitForm's.
|
|
119
|
+
const internals = { makeBody };
|
|
120
|
+
return _jsx(SubmitForm, { ...rest, ...internals, form: store, loading: Boolean(rest.loading) || load.state === "loading", onSubmit: send, onSaved: saved });
|
|
100
121
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CrudProps } from "../../Interfaces/CrudInterfaces.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type FormValues } from "../../Interfaces/FormInterfaces.ts";
|
|
3
3
|
/**
|
|
4
4
|
* A CRUD page in one component: a data table of a controller's records with its lists, Add and Edit popups, details,
|
|
5
5
|
* and actions on one record or many, each shown only when its endpoint exists and the user may call it.
|
|
@@ -14,4 +14,4 @@ import type { FormValues } from "../../Interfaces/FormInterfaces.ts";
|
|
|
14
14
|
* paging="server"
|
|
15
15
|
* />
|
|
16
16
|
*/
|
|
17
|
-
export declare function Crud<TRow extends FormValues = FormValues>(
|
|
17
|
+
export declare function Crud<TRow extends FormValues = FormValues>(given: CrudProps<TRow>): import("react").JSX.Element;
|