nexus-shared 4.0.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.
Files changed (163) hide show
  1. package/CHANGELOG.md +795 -0
  2. package/README.md +220 -14
  3. package/dist/Client.Index.d.ts +13 -0
  4. package/dist/Client.Index.js +13 -0
  5. package/dist/Components/Chats/ChatComposer.d.ts +7 -1
  6. package/dist/Components/Chats/ChatComposer.js +9 -6
  7. package/dist/Components/Chats/ChatParts.d.ts +15 -2
  8. package/dist/Components/Chats/ChatParts.js +15 -4
  9. package/dist/Components/Chats/ChatPopup.d.ts +46 -0
  10. package/dist/Components/Chats/ChatPopup.js +34 -0
  11. package/dist/Components/Chats/ChatThread.d.ts +9 -1
  12. package/dist/Components/Chats/ChatThread.js +54 -6
  13. package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
  14. package/dist/Components/Dashboards/DashboardBoard.js +594 -0
  15. package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
  16. package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
  17. package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
  18. package/dist/Components/Dashboards/WidgetFrame.js +45 -0
  19. package/dist/Components/Documents/Button.d.ts +6 -1
  20. package/dist/Components/Documents/Button.js +2 -2
  21. package/dist/Components/Documents/TabButtons.d.ts +6 -0
  22. package/dist/Components/Documents/TabButtons.js +5 -4
  23. package/dist/Components/Domains/DomainCard.d.ts +98 -0
  24. package/dist/Components/Domains/DomainCard.js +182 -0
  25. package/dist/Components/Forms/ApiForm.js +5 -1
  26. package/dist/Components/Forms/Crud.js +72 -24
  27. package/dist/Components/Forms/CrudActions.d.ts +5 -4
  28. package/dist/Components/Forms/CrudActions.js +42 -12
  29. package/dist/Components/Forms/CrudHistory.d.ts +6 -1
  30. package/dist/Components/Forms/CrudHistory.js +14 -6
  31. package/dist/Components/Forms/CrudParts.d.ts +11 -4
  32. package/dist/Components/Forms/CrudParts.js +76 -10
  33. package/dist/Components/Forms/CrudRowForms.js +65 -5
  34. package/dist/Components/Forms/CrudTree.js +7 -7
  35. package/dist/Components/Forms/CrudWorkspace.d.ts +15 -5
  36. package/dist/Components/Forms/CrudWorkspace.js +19 -11
  37. package/dist/Components/Forms/Form.d.ts +3 -2
  38. package/dist/Components/Forms/Form.js +33 -26
  39. package/dist/Components/Forms/FormQuestion.d.ts +6 -0
  40. package/dist/Components/Forms/FormQuestion.js +28 -0
  41. package/dist/Components/Forms/SubmitForm.js +2 -15
  42. package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
  43. package/dist/Components/Inputs/ComputedInput.js +179 -0
  44. package/dist/Components/Inputs/Dropdown.js +4 -2
  45. package/dist/Components/Inputs/GroupForm.js +3 -2
  46. package/dist/Components/Inputs/InputRenderer.d.ts +5 -1
  47. package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
  48. package/dist/Components/Inputs/MarkdownEditor.js +32 -6
  49. package/dist/Components/Inputs/PictureInput.js +8 -3
  50. package/dist/Components/Inputs/RowsInput.d.ts +14 -1
  51. package/dist/Components/Inputs/RowsInput.js +78 -5
  52. package/dist/Components/Inputs/TabularForm.js +16 -3
  53. package/dist/Components/Inputs/TreeView.d.ts +24 -1
  54. package/dist/Components/Inputs/TreeView.js +84 -7
  55. package/dist/Components/Layouts/Popup.d.ts +22 -2
  56. package/dist/Components/Layouts/Popup.js +108 -6
  57. package/dist/Components/Messages/MailClient.d.ts +50 -0
  58. package/dist/Components/Messages/MailClient.js +410 -0
  59. package/dist/Components/Messages/MailColumns.d.ts +21 -0
  60. package/dist/Components/Messages/MailColumns.js +92 -0
  61. package/dist/Components/Messages/MailCompose.d.ts +46 -0
  62. package/dist/Components/Messages/MailCompose.js +154 -0
  63. package/dist/Components/Messages/MailReader.d.ts +43 -0
  64. package/dist/Components/Messages/MailReader.js +207 -0
  65. package/dist/Components/Messages/MailThread.d.ts +71 -0
  66. package/dist/Components/Messages/MailThread.js +185 -0
  67. package/dist/Components/Messages/MessageBell.d.ts +41 -0
  68. package/dist/Components/Messages/MessageBell.js +76 -0
  69. package/dist/Components/Viewers/DataTable.js +7 -5
  70. package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
  71. package/dist/Components/Viewers/DataTableColumns.js +2 -1
  72. package/dist/Components/Viewers/DataTableParts.d.ts +3 -1
  73. package/dist/Components/Viewers/DataTableParts.js +2 -2
  74. package/dist/Components/Viewers/OwnerFiles.d.ts +57 -5
  75. package/dist/Components/Viewers/OwnerFiles.js +142 -31
  76. package/dist/Helpers/ApiModules.js +4 -0
  77. package/dist/Helpers/AuditEntries.d.ts +50 -0
  78. package/dist/Helpers/AuditEntries.js +96 -0
  79. package/dist/Helpers/ChatBackend.d.ts +8 -1
  80. package/dist/Helpers/ChatBackend.js +18 -0
  81. package/dist/Helpers/ChatHelpers.d.ts +2 -0
  82. package/dist/Helpers/ChatHelpers.js +4 -1
  83. package/dist/Helpers/CrudColumns.d.ts +44 -6
  84. package/dist/Helpers/CrudColumns.js +63 -13
  85. package/dist/Helpers/CrudHelpers.d.ts +36 -1
  86. package/dist/Helpers/CrudHelpers.js +61 -0
  87. package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
  88. package/dist/Helpers/CrudPickerPlan.js +46 -0
  89. package/dist/Helpers/CrudRowForms.d.ts +18 -1
  90. package/dist/Helpers/CrudRowForms.js +35 -2
  91. package/dist/Helpers/DashboardHelpers.d.ts +115 -0
  92. package/dist/Helpers/DashboardHelpers.js +305 -0
  93. package/dist/Helpers/DomainCards.d.ts +140 -0
  94. package/dist/Helpers/DomainCards.js +370 -0
  95. package/dist/Helpers/FileAdapter.d.ts +82 -2
  96. package/dist/Helpers/FileAdapter.js +72 -1
  97. package/dist/Helpers/FormStore.d.ts +11 -1
  98. package/dist/Helpers/FormStore.js +66 -19
  99. package/dist/Helpers/InputConditions.d.ts +6 -0
  100. package/dist/Helpers/InputConditions.js +24 -0
  101. package/dist/Helpers/InputMemory.js +40 -30
  102. package/dist/Helpers/InputNarrowing.d.ts +38 -6
  103. package/dist/Helpers/InputNarrowing.js +102 -21
  104. package/dist/Helpers/MailClientHelpers.d.ts +96 -0
  105. package/dist/Helpers/MailClientHelpers.js +211 -0
  106. package/dist/Helpers/MessageBackend.d.ts +152 -0
  107. package/dist/Helpers/MessageBackend.js +329 -0
  108. package/dist/Helpers/MessageHelpers.d.ts +160 -0
  109. package/dist/Helpers/MessageHelpers.js +573 -0
  110. package/dist/Helpers/MessageUnread.d.ts +95 -0
  111. package/dist/Helpers/MessageUnread.js +245 -0
  112. package/dist/Helpers/NexusDriveAdapter.d.ts +17 -1
  113. package/dist/Helpers/NexusDriveAdapter.js +32 -3
  114. package/dist/Helpers/NumberHelpers.d.ts +61 -0
  115. package/dist/Helpers/NumberHelpers.js +124 -8
  116. package/dist/Helpers/PagedPicker.d.ts +42 -0
  117. package/dist/Helpers/PagedPicker.js +85 -0
  118. package/dist/Helpers/RowsStore.d.ts +24 -0
  119. package/dist/Helpers/RowsStore.js +79 -10
  120. package/dist/Helpers/TableHelpers.d.ts +15 -2
  121. package/dist/Helpers/TableHelpers.js +28 -1
  122. package/dist/Helpers/TableStore.d.ts +7 -3
  123. package/dist/Helpers/TableStore.js +9 -4
  124. package/dist/Helpers/ToolbarFit.d.ts +23 -0
  125. package/dist/Helpers/ToolbarFit.js +30 -0
  126. package/dist/Helpers/TreeStore.d.ts +75 -6
  127. package/dist/Helpers/TreeStore.js +182 -18
  128. package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
  129. package/dist/Interfaces/ApiInterfaces.js +10 -1
  130. package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
  131. package/dist/Interfaces/CrudInterfaces.d.ts +78 -5
  132. package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
  133. package/dist/Interfaces/DashboardInterfaces.js +1 -0
  134. package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
  135. package/dist/Interfaces/DomainInterfaces.js +1 -0
  136. package/dist/Interfaces/FileInterfaces.d.ts +21 -0
  137. package/dist/Interfaces/FileInterfaces.js +2 -0
  138. package/dist/Interfaces/FormInterfaces.d.ts +78 -11
  139. package/dist/Interfaces/InputInterfaces.d.ts +12 -5
  140. package/dist/Interfaces/MailInterfaces.d.ts +192 -0
  141. package/dist/Interfaces/MailInterfaces.js +1 -0
  142. package/dist/Interfaces/TableInterfaces.d.ts +28 -2
  143. package/dist/Services/JoinSources.d.ts +47 -6
  144. package/dist/Services/JoinSources.js +68 -33
  145. package/dist/Services/MessageApi.d.ts +18 -0
  146. package/dist/Services/MessageApi.js +64 -0
  147. package/dist/Shared.Index.d.ts +12 -0
  148. package/dist/Shared.Index.js +12 -0
  149. package/package.json +7 -1
  150. package/src/Styles/Nexus.Button.css +29 -0
  151. package/src/Styles/Nexus.Chat.css +114 -22
  152. package/src/Styles/Nexus.Crud.css +119 -28
  153. package/src/Styles/Nexus.Dashboard.css +763 -0
  154. package/src/Styles/Nexus.Domain.css +384 -0
  155. package/src/Styles/Nexus.Editor.css +20 -0
  156. package/src/Styles/Nexus.Files.css +27 -0
  157. package/src/Styles/Nexus.Index.css +3 -0
  158. package/src/Styles/Nexus.Input.css +31 -0
  159. package/src/Styles/Nexus.Mail.css +1062 -0
  160. package/src/Styles/Nexus.Popup.css +58 -0
  161. package/src/Styles/Nexus.Table.css +1458 -1449
  162. package/src/Styles/Nexus.Theme.Picker.css +1 -0
  163. package/src/Styles/Nexus.Tree.css +10 -0
@@ -0,0 +1,123 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * A widget's size on the board's 12-column grid: `S` 3×1, `M` 6×2, `L` 6×4, `W` 12×2, `T` 12×4 (columns × rows).
4
+ * Presets rather than free resizing: fewer layouts that look broken, and nothing to work out on a phone, where every
5
+ * widget is the full width.
6
+ */
7
+ export type WidgetSize = "S" | "M" | "L" | "W" | "T";
8
+ /** Where a widget may go: `own` only on its module's dashboard, `anywhere` on any dashboard (the Home too). */
9
+ export type WidgetPlacement = "own" | "anywhere";
10
+ /** One widget on a board: which, and how big. A layout is an ordered list of these: no coordinates are kept. */
11
+ export interface DashboardLayoutItem {
12
+ code: string;
13
+ size: WidgetSize;
14
+ }
15
+ /** A board's layout: the widgets in the order they flow, the grid filling every gap (`grid-auto-flow: dense`). */
16
+ export type DashboardLayout = readonly DashboardLayoutItem[];
17
+ /** The version a saved layout carries, so two windows never overwrite each other unseen. */
18
+ export type DashboardVersion = string | number | null;
19
+ /**
20
+ * What a save (or a reset) answered. `saved` with the new version; `stale` when another window saved first (the
21
+ * server's 409): its layout and version, which the board shows in place of the edit; `failed` with a sentence.
22
+ */
23
+ export type DashboardSaveResult = {
24
+ status: "saved";
25
+ version: DashboardVersion;
26
+ layout?: DashboardLayout;
27
+ } | {
28
+ status: "stale";
29
+ layout: DashboardLayout;
30
+ version: DashboardVersion;
31
+ } | {
32
+ status: "failed";
33
+ message?: string;
34
+ };
35
+ /** What a widget's `load` and `render` are told. */
36
+ export interface WidgetContext {
37
+ /** The board's place (`home`, `message`, ...). */
38
+ place: string;
39
+ /** The size it is drawn at. */
40
+ size: WidgetSize;
41
+ }
42
+ /** What a widget's `render` is told besides its data. */
43
+ export interface WidgetRenderContext extends WidgetContext {
44
+ /** Reads the widget's data again now. */
45
+ refresh: () => void;
46
+ }
47
+ /**
48
+ * A widget answered by its module's one call per draw: the board gathers the codes of every widget of `module` that
49
+ * is near the screen and asks the app's `loadModule(module, codes)` once. `codes` are the parts this widget needs from
50
+ * that answer (default: its own code); `pick` turns them into its data (default: the one part, or every part by code
51
+ * when there are several).
52
+ */
53
+ export interface WidgetBatch<TData = unknown> {
54
+ module: string;
55
+ codes?: readonly string[];
56
+ pick?: (parts: Readonly<Record<string, unknown>>) => TData;
57
+ }
58
+ /**
59
+ * A widget, declared once in the app's code: what it is, where it may go, its sizes, who may see it, where its data
60
+ * comes from and how it is drawn. Nothing about a widget lives in a database: only layouts do.
61
+ *
62
+ * @example
63
+ * const unread: WidgetDefinition<MailCounts> = {
64
+ * code: "message.counts", module: "message", placement: "anywhere", title: "Mail", sizes: ["S", "M"],
65
+ * batch: { module: "message" }, refresh: 60,
66
+ * render: counts => <StatWidget items={[{ label: "Unread", value: counts.unread, href: "/admin/message?list=2" }]} />,
67
+ * };
68
+ */
69
+ export interface WidgetDefinition<TData = unknown> {
70
+ /** Unique in the app, `module.name`: `message.counts`, `requisition.my-approvals`. What a layout keeps. */
71
+ code: string;
72
+ /** The module it belongs to: the Add list groups by it, and an `own` widget goes only on its module's board. */
73
+ module: string;
74
+ placement: WidgetPlacement;
75
+ /** Its heading on the board and in the Add list. */
76
+ title: string;
77
+ /** A sentence under the title in the Add list. */
78
+ description?: string;
79
+ /** Beside the title in the Add list. */
80
+ icon?: ReactNode;
81
+ /** The sizes it can take, the first the default. At least one. */
82
+ sizes: readonly WidgetSize[];
83
+ /**
84
+ * The permission key whose holder may see it (none: everyone signed in). The board does not read it: the app's
85
+ * `canSee(widget)` does, with whatever it knows of the person and the company.
86
+ */
87
+ key?: string;
88
+ /** Its own data call. A widget with neither `load` nor `batch` needs no data (a version, a clock). */
89
+ load?: (context: WidgetContext) => Promise<TData>;
90
+ /** Its part of its module's one call per draw, in place of `load`. */
91
+ batch?: WidgetBatch<TData>;
92
+ /** Seconds its data stays fresh while the page is open: read again after that while it is on screen. Default: the page's life. */
93
+ refresh?: number;
94
+ /** Draws it from its data. */
95
+ render: (data: TData, context: WidgetRenderContext) => ReactNode;
96
+ /** Whether the data is nothing to show: the frame then says `empty`. Default: `null`, `undefined` or an empty list. */
97
+ isEmpty?: (data: TData) => boolean;
98
+ /** What an empty widget says. Default "Nothing to show yet." */
99
+ empty?: ReactNode;
100
+ /** A link in its heading, to the page its data comes from. */
101
+ link?: {
102
+ href: string;
103
+ label?: string;
104
+ };
105
+ }
106
+ /** What `loadModule` answers: each asked code's part. A code left out is a part the person may not see (empty). */
107
+ export type WidgetModuleAnswer = Readonly<Record<string, unknown>>;
108
+ /** The app's one call per module per draw. */
109
+ export type WidgetModuleLoader = (module: string, codes: readonly string[]) => Promise<WidgetModuleAnswer>;
110
+ /** A widget's data as the board holds it. */
111
+ export type WidgetState<TData = unknown> = {
112
+ status: "idle";
113
+ } | {
114
+ status: "loading";
115
+ data?: TData;
116
+ } | {
117
+ status: "ready";
118
+ data: TData;
119
+ at: number;
120
+ } | {
121
+ status: "failed";
122
+ message: string;
123
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ChatDomainValue, ChatId } from "./ChatInterfaces.ts";
3
+ import type { Tone } from "./TypeInterfaces.ts";
4
+ /** How a value is written: a plain text, a date or a moment in the reader's settings, days, money, a person's name. */
5
+ export type DomainCardValueKind = "text" | "number" | "date" | "datetime" | "days" | "money" | "person";
6
+ /** One field of a card: its label and value, written by its kind. A field with no value is left out. */
7
+ export interface DomainCardField {
8
+ label: string;
9
+ /** An ISO date for `date`, a UTC moment for `datetime`, a number for `days`, `number` and `money`. */
10
+ value: string | number | null | undefined;
11
+ /** Default `text`. */
12
+ kind?: DomainCardValueKind;
13
+ /** Words after the value, quieter: "second half", "from the 2081/82 year". */
14
+ note?: string;
15
+ /** Written in place of a missing value; without it the field is left out. */
16
+ empty?: string;
17
+ /** A `money` field's currency, over the card's. */
18
+ currency?: string;
19
+ }
20
+ /** A figure: a number that matters, drawn large in a row of them ("Available 14 days", "Taken 5 days"). */
21
+ export interface DomainCardFigure {
22
+ label: string;
23
+ /** A number is written by its kind; a text as it is. A missing value is written as "-". */
24
+ value: number | string | null | undefined;
25
+ /** Default `number`. `days` writes the unit "day" or "days" by itself. */
26
+ kind?: Extract<DomainCardValueKind, "number" | "days" | "money">;
27
+ /** Words after the value, smaller: "items". Left out for `days` (its own) and `money` (its currency). */
28
+ unit?: string;
29
+ /** Marks the figure with its colour beside it: `warning` for days held, `danger` for an overdrawn balance. */
30
+ tone?: Tone;
31
+ /** A short line under the figure: "2 of them this request". */
32
+ hint?: string;
33
+ /** A `money` figure's currency, over the card's. */
34
+ currency?: string;
35
+ }
36
+ /** A column of a card's lines. */
37
+ export interface DomainCardLineColumn {
38
+ label: string;
39
+ /** Default `text`; `number`, `money` and `days` sit at the end. */
40
+ kind?: DomainCardValueKind;
41
+ /** Default: the end for numbers, the start for the rest. */
42
+ align?: "start" | "end";
43
+ }
44
+ /** A short table on a card: the first items of a requisition, with how many more there are. */
45
+ export interface DomainCardLines {
46
+ columns: readonly DomainCardLineColumn[];
47
+ /** One row per line, a value per column, written by the column's kind. */
48
+ rows: readonly (readonly (string | number | null | undefined)[])[];
49
+ /** Lines the card does not show: "and 3 more". */
50
+ more?: number;
51
+ /** What the lines are, for a screen reader: "Items". Default "Lines". */
52
+ label?: string;
53
+ }
54
+ /** Where the record can be opened in full: a link at the end of the line under the title. */
55
+ export interface DomainCardLink {
56
+ href: string;
57
+ label: string;
58
+ /** `_blank` opens a new tab. */
59
+ target?: string;
60
+ }
61
+ /** A card as it is drawn: what an app's `view` makes of its module's answer. */
62
+ export interface DomainCardParams {
63
+ /** What the record is, in a few words: "Home leave · 2 days". */
64
+ title: string;
65
+ /** Its number, on the quiet line under the title: "LR2082-0007". */
66
+ code?: string | null;
67
+ /** Words on the line under the title, after the code: the request's own title. */
68
+ subtitle?: string | null;
69
+ /** Where it stands, as a tag in its tone: "In approval" (info), "Rejected" (danger). */
70
+ state?: {
71
+ label: string;
72
+ tone?: Tone;
73
+ } | null;
74
+ fields?: readonly DomainCardField[];
75
+ figures?: readonly DomainCardFigure[];
76
+ lines?: DomainCardLines | null;
77
+ link?: DomainCardLink | null;
78
+ /** The currency every `money` value is written in, unless it names its own: "Rs.". */
79
+ currency?: string | null;
80
+ }
81
+ /** What a card's `load`, `view` and `extra` are told besides the record. */
82
+ export interface DomainCardContext {
83
+ /** The record's kind: the app's own domain value. */
84
+ domainType: ChatDomainValue;
85
+ /** The record's id, as the reader may open it (a token sealed for them). */
86
+ id: ChatId;
87
+ /** Who reads, when the page says (a thread's reader). */
88
+ viewer?: ChatId;
89
+ /** The record's words as the message holds them: "LR2082-0007 · Puja Shrestha". */
90
+ label?: string | null;
91
+ /** The kind's name on screen, from `defineChatDomains`: "Leave request". */
92
+ kind?: string;
93
+ }
94
+ /** How many of each part a card holds: what its placeholder draws while it loads, so it does not jump when it lands. */
95
+ export interface DomainCardShape {
96
+ /** The quiet line under the title: the code, the subtitle, the link. */
97
+ subtitle: boolean;
98
+ state: boolean;
99
+ fields: number;
100
+ figures: number;
101
+ /** Which figures, counted from 0, have a hint under them. */
102
+ hints: readonly number[];
103
+ /** Rows of lines shown, and their columns. */
104
+ lines: number;
105
+ columns: number;
106
+ /** An "and 3 more" under the lines. */
107
+ more: boolean;
108
+ link: boolean;
109
+ }
110
+ /**
111
+ * The card of one kind of record: how to read it and how to show it. Give it to the kind's row of `defineChatDomains`
112
+ * (`card`); `domainCard(...)` types `view` by what `load` answers.
113
+ */
114
+ export interface DomainCardDefinition<TData = unknown> {
115
+ /**
116
+ * Reads the record from the module that owns it: its card door, which checks the reader. A refusal of the reader
117
+ * (403) or a record that is not there for them (404) is shown as "You cannot open this ..."; any other failure as a
118
+ * failure with Try again. The signal aborts when nobody waits for the answer any more.
119
+ */
120
+ load(id: ChatId, signal: AbortSignal, context: DomainCardContext): Promise<TData>;
121
+ /** Maps the answer to what the card draws. */
122
+ view(data: TData, context: DomainCardContext): DomainCardParams;
123
+ /** One more piece after the lines, made of this package's parts, for a kind that needs it. */
124
+ extra?(data: TData, context: DomainCardContext): ReactNode;
125
+ /** What the placeholder holds the first time a card of this kind loads; later ones hold the last card's shape. */
126
+ held?: Partial<DomainCardShape>;
127
+ /** How long an answer is kept for every card of the page, in milliseconds. Default 30,000; 0 keeps none. */
128
+ lifetime?: number;
129
+ }
130
+ /** Where one card stands. */
131
+ export type DomainCardStatus = "loading" | "ready" | "refused" | "failed";
132
+ /** One card's read, as its component draws it. */
133
+ export interface DomainCardSnapshot<TData = unknown> {
134
+ status: DomainCardStatus;
135
+ /** The record as last read: kept on screen while it is read again, and when reading again fails. */
136
+ data?: TData;
137
+ /** A read is on its way while the card shows what it read before. */
138
+ refreshing: boolean;
139
+ /** The last read again failed: the card on screen is the one read before. */
140
+ refreshFailed: boolean;
141
+ /** When a read again found the record changed from the one on screen (milliseconds since 1970). */
142
+ changedAt?: number;
143
+ /** Why the last read failed. */
144
+ error?: unknown;
145
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -16,6 +16,14 @@ export interface FileOwner<TModule extends ApiModuleValue = AppModule> {
16
16
  }
17
17
  /** Which copy of a stored file: the file itself, or its small picture. */
18
18
  export type FileCopy = "original" | "thumbnail";
19
+ /**
20
+ * One way an owner's files change: a file added, renamed, made the owner's picture (or none), or deleted. In words, as a
21
+ * category's kinds are MIME types; they are the Nexus core's `EFileChanges` (Add 1, Rename 2, Primary 3, Remove 4) one
22
+ * for one. A record whose files may only be added to - a posted goods receipt's documents - allows `["add"]`.
23
+ */
24
+ export type FileChange = "add" | "rename" | "primary" | "remove";
25
+ /** Every change, in the order the core numbers them. */
26
+ export declare const FILE_CHANGES: readonly FileChange[];
19
27
  /** One stored file, as the adapter reads it. */
20
28
  export interface StoredFile {
21
29
  /** Its id, as the backend answers it (with Nexus, encrypted for the person asking). */
@@ -35,6 +43,14 @@ export interface StoredFile {
35
43
  isPrimary: boolean;
36
44
  /** When it was stored: an ISO UTC moment, or null when the backend does not say. */
37
45
  createdAt: string | null;
46
+ /**
47
+ * Its Delete waits for a review: in a category that holds removals (`FileCategoryInfo.holdsRemovals`, a reviewed
48
+ * gallery) a Delete only marks the file, which is still served and listed until the reviewer decides, and the person
49
+ * may Keep it (`FileAdapter.keep`) until then. Left out (or false) for every other file.
50
+ */
51
+ pendingRemoval?: boolean;
52
+ /** When its removal was asked, while it waits for the review: an ISO UTC moment, when the backend says. */
53
+ removalAt?: string;
38
54
  }
39
55
  /** What a category takes, as the backend says: what a gallery tells the person before an upload is refused. */
40
56
  export interface FileCategoryInfo {
@@ -64,6 +80,11 @@ export interface FileCategoryInfo {
64
80
  * picture under it (a lower quality, then a smaller size). Default `PICTURE_LIMITS.maxBytes` (300 KB), at most `maxBytes`.
65
81
  */
66
82
  pictureMaxBytes?: number;
83
+ /**
84
+ * A Delete here waits for a review (a company's public gallery, which Nexus support approves): the file is marked
85
+ * `pendingRemoval`, not removed, until the reviewer decides. Left out (or false) where a Delete removes at once.
86
+ */
87
+ holdsRemovals?: boolean;
67
88
  }
68
89
  /** An owner's files and what their category takes. */
69
90
  export interface OwnerFiles {
@@ -1,3 +1,5 @@
1
+ /** Every change, in the order the core numbers them. */
2
+ export const FILE_CHANGES = ["add", "rename", "primary", "remove"];
1
3
  /** The MIME types of pictures (what a canvas can read back), in the order a file picker offers them. */
2
4
  export const PICTURE_TYPES = ["image/jpeg", "image/png", "image/webp"];
3
5
  /** Whether a MIME type is a picture this package can show and edit. */
@@ -124,20 +124,29 @@ export interface InputLayoutParams {
124
124
  */
125
125
  dependsOn?: readonly string[];
126
126
  /**
127
- * Another input of the same form whose value narrows this one's options (Form only): a Package list of the Group picked.
127
+ * Another input of the same form whose value narrows this one's options (Form, and the rows of its tabular and group
128
+ * forms): a Package list of the Group picked.
128
129
  * The options are the records whose `key` field holds that value (static `options` filtered; `loadOptions` asked with
129
130
  * `request.narrow`, which `crudPicker` answers from the server when its endpoint takes the parent, else from its kept list);
130
131
  * the list loads afresh when the value changes, offers nothing while it is empty, and says what to pick first. A value
131
132
  * picked here is cleared when a person changes the other one. It follows the other input as `dependsOn` would.
132
133
  *
134
+ * Several narrow at once (a list): the options must fit every one, the box says what to pick first for the first one
135
+ * still empty, and the list loads afresh when any changes. In a row of a tabular or group form a narrowing names an
136
+ * input of the same row, or of the form around the rows with `from: "form"`: an asset line's Tags, the units of the
137
+ * line's Item in the form's Store. A value picked is cleared when a person changes one it follows (every row's, for
138
+ * the form's), never when code sets it (a draft loading).
139
+ *
133
140
  * @example { type: EInputTypes.Dropdown, name: "subscriptionPackageId", label: "Package", ...crudPicker(PACKAGES, { labelKey: "packageName" }), narrowedBy: { name: "subscriptionGroupId" } }
141
+ * @example narrowedBy: [{ name: "storeId", from: "form" }, { name: "itemId" }] // a line's Tags: the form's Store, the row's Item
134
142
  */
135
- narrowedBy?: InputNarrowing;
143
+ narrowedBy?: InputNarrowing | readonly InputNarrowing[];
136
144
  /**
137
145
  * When the input shows (Form only): while every condition holds, each on another input's value as it is now. The form
138
146
  * follows the inputs named as `dependsOn` would, so the input comes and goes as they change, keeps what was typed in it
139
147
  * (its value stays in the data, as a `hidden` one's does) and is not checked while it is hidden. A `hidden: true`, or a
140
- * `hidden` getter, still hides it whatever the conditions say.
148
+ * `hidden` getter, still hides it whatever the conditions say. A CRUD's Add or Edit saved while it is hidden leaves it
149
+ * on the row only as the server holds it: as the answer gives it, else as what was sent gives it (`crudSavedRecord`).
141
150
  *
142
151
  * @example showWhen: { name: "counterType", in: [0, 1] }
143
152
  * @example showWhen: [{ name: "counterType", in: [2] }, { name: "subscriptionMiscellaneousFeeId", filled: false }]
@@ -198,12 +207,14 @@ export interface InputLayoutParams {
198
207
  */
199
208
  spanWhen?: InputSpanWhen;
200
209
  /**
201
- * A value worked out from other inputs (Form only), shown and never typed: written when the form opens (new values, a
202
- * loaded record) and again whenever an input in `from` changes - by a person, by code, a load, a reset - or the form's
203
- * `computeWith` changes. It is no form value: never sent, never checked, never a change, never remembered or kept in a
204
- * draft. Meant for a Number (its `decimals`, `prefix`, `suffix`, in the person's number format); any other type shows
205
- * its text. Null, undefined, "" or NaN leave the box blank. The box is an `<output>` on the read-only tint, with no
206
- * lock, no steppers and no Tab stop. In a tabular or group form's rows it is ignored, as `showWhen` is.
210
+ * A value worked out from other inputs, shown and never typed (unless `solve` makes it so): written when the form opens
211
+ * (new values, a loaded record) and again whenever an input in `from` changes - by a person, by code, a load, a reset -
212
+ * or the form's `computeWith` changes. It is no form value: never sent, never checked, never a change, never remembered
213
+ * or kept in a draft. Meant for a Number (its `decimals`, `prefix`, `suffix`, in the person's number format); any other
214
+ * type shows its text. Null, undefined, "" or NaN leave the box blank. The box is an `<output>` on the read-only tint,
215
+ * with no lock, no steppers and no Tab stop. In a tabular or group form's rows it works on each row alike: `from` names
216
+ * the row's inputs, `values` is the row, `withValue` the rows' `computeWith`, and the rows hold no value under its name
217
+ * (a loaded row's field of that name is dropped).
207
218
  *
208
219
  * @example compute: { from: ["price", "discountPercentage", "vatPercentage"], value: values => priceAmounts(values).netAmount }
209
220
  * @example compute: { from: [], value: (_values, preview) => preview?.netAmount } // with computeWith={preview}
@@ -220,6 +231,28 @@ export interface InputCompute<TWith = any> {
220
231
  from: readonly string[];
221
232
  /** The value to show. `withValue` is the form's `computeWith`, as the page passed it (`undefined` when it passed none). */
222
233
  value(values: FormValues, withValue: TWith): unknown;
234
+ /**
235
+ * Works it backwards, which makes the box editable (a Number input): a person types the figure they want and leaves the
236
+ * box (Tab, a click away, or Enter), and this answers the values of the inputs it is worked out from that give it - the
237
+ * inverse of `value`. The form writes them as the person's own change (their rules, the form's `onFieldChange`, the
238
+ * unsaved-changes state, Save and the leave guard see those inputs; the worked-out input still holds no value), then
239
+ * shows the figure worked forward again, with a line under the box: what was set ("Price set to 2,458.21"), and, when
240
+ * the figure typed cannot be reached exactly, the nearest it now shows. Answer a sentence to refuse (the box goes back,
241
+ * the sentence shows under it), or `null` to change nothing. Escape puts back what the box showed; an emptied box changes
242
+ * nothing. The box is read-only, as without `solve`, while the form is read-only, disabled or loading. In a row it
243
+ * works the same on that row (the rows' `onRowChange` runs for what it sets); a table says the line under the rows,
244
+ * since its cells have none, and Enter with a new figure sets it rather than moving down the column.
245
+ *
246
+ * @example solve: (typed, values) => { const found = priceFromNet(typed, values); return found.ok ? { values: { price: found.price } } : found.message; }
247
+ */
248
+ solve?(typed: number, values: FormValues, withValue: TWith): InputSolved | string | null;
249
+ }
250
+ /** What an input worked backwards answers (`InputCompute.solve`): the values of the inputs it is worked out from. */
251
+ export interface InputSolved {
252
+ /** The inputs' new values, by name: `{ price: 2458.21 }`. */
253
+ values: FormValues;
254
+ /** The line under the box, in place of the form's own ("Price set to 2,458.21."). */
255
+ note?: string;
223
256
  }
224
257
  /** An input's label that follows another input's value (`InputLayoutParams.labelWhen`). */
225
258
  export interface InputLabelWhen {
@@ -300,6 +333,11 @@ export interface InputNarrowingByInput {
300
333
  placeholder?: string;
301
334
  /** Also offers the options whose `key` field is empty: a fee of every group beside the group's own. */
302
335
  orUnset?: boolean;
336
+ /**
337
+ * For an input in a row of a tabular or group form, where `name` is: `"row"` (default) an input of the same row - the
338
+ * line's Item - or `"form"` an input of the form around the rows - the document's Store. Outside rows, ignored.
339
+ */
340
+ from?: "row" | "form";
303
341
  value?: never;
304
342
  }
305
343
  /**
@@ -385,6 +423,11 @@ export interface InputValidationContext {
385
423
  /** One row of a tabular or group form: the values by input name, plus any other fields the row came with, such as its id. */
386
424
  export type RowValue = Record<string, unknown>;
387
425
  /** Props shared by `TabularForm` and `GroupForm`. The value is an array of rows; each row holds one value per input. */
426
+ /** A value of the form around rows that a row input is narrowed by (`RowsInputProps.formValues`), and its label. */
427
+ export interface RowsFormValue {
428
+ value: unknown;
429
+ label?: ReactNode;
430
+ }
388
431
  export interface RowsInputProps<TRow extends RowValue = RowValue> {
389
432
  /** The inputs of one row, as params. Keep the array stable (a constant or `useMemo`): a new one checks every row again. */
390
433
  inputs: readonly InputParams[];
@@ -474,6 +517,18 @@ export interface RowsInputProps<TRow extends RowValue = RowValue> {
474
517
  * value is checked the same way in every row — so change what is offered here, not what is required.
475
518
  */
476
519
  cellParams?: (row: TRow, name: string, index: number) => Partial<InputParams> | null | undefined;
520
+ /**
521
+ * The values of the form around the rows that a row input's `narrowedBy` names `from: "form"` (the document's Store),
522
+ * each with its label for "Pick a store first". A `Form` sets them as they change, and the rows draw again then; a rows
523
+ * input used on its own takes them from the page.
524
+ */
525
+ formValues?: Readonly<Record<string, RowsFormValue>>;
526
+ /**
527
+ * What a row input worked out from others (`compute`) is handed as `withValue`, beside the row's values: a rate the
528
+ * whole document shares, the company's VAT. Every row's worked-out cells draw again when a new one comes (compared by
529
+ * identity: pass state or `useMemo`). A `Form` hands the rows its own `computeWith` unless their params name one.
530
+ */
531
+ computeWith?: unknown;
477
532
  /** Every input is read-only, and rows cannot be added, removed, or moved. */
478
533
  readOnly?: boolean;
479
534
  /** What the rows say when someone tries to change them. Default "Read-only: these rows cannot be changed." */
@@ -759,6 +814,15 @@ export interface ApiRequest {
759
814
  }
760
815
  /** Sends a form's values with the app's own client. Return what it answers: a `Response`, a parsed body, or an `ApiResult`. */
761
816
  export type ApiSend<TValues extends FormValues = FormValues> = (body: unknown, context: FormSubmitContext<TValues>) => Promise<unknown>;
817
+ /**
818
+ * What an `ApiForm`'s save sent (`onSuccess`): the form's values as they were sent - values typed while it ran are not
819
+ * among them - and the body `transform` made of them (the values themselves without one). A row built from the save
820
+ * reads the body: a field `transform` cleared is cleared there, where the values still hold what was typed.
821
+ */
822
+ export interface ApiFormSent<TValues extends FormValues = FormValues> {
823
+ values: TValues;
824
+ body: unknown;
825
+ }
762
826
  /** Props of `ApiForm`: a `SubmitForm` that sends the values to an API and shows the server's messages on their inputs. */
763
827
  export interface ApiFormProps<TValues extends FormValues = FormValues> extends Omit<SubmitFormProps<TValues>, "onSubmit" | "onSaved"> {
764
828
  /**
@@ -779,8 +843,11 @@ export interface ApiFormProps<TValues extends FormValues = FormValues> extends O
779
843
  * any JSON by its HTTP status.
780
844
  */
781
845
  readResponse?: (body: unknown, status: number | undefined) => ApiResult;
782
- /** After a save, once the saved values are the form's own, with what the server sent back: close a popup, update a table row. */
783
- onSuccess?: (result: unknown, values: TValues, form: FormStore<TValues>) => void;
846
+ /**
847
+ * After a save, once the saved values are the form's own, with what the server sent back: close a popup, update a table
848
+ * row. `sent` is what that save sent: the values as they were then, and the body `transform` made of them.
849
+ */
850
+ onSuccess?: (result: unknown, values: TValues, form: FormStore<TValues>, sent: ApiFormSent<TValues>) => void;
784
851
  /** After a failed save, with the server's answer. The form already shows its messages. */
785
852
  onError?: (response: ApiResult, values: TValues) => void;
786
853
  /** The toast after a save: text, or `false` for none. Default: the server's message, else the endpoint's ("Room saved."), else "Saved." */
@@ -161,11 +161,18 @@ export interface DropdownPageRequest {
161
161
  * of its own should honour it (`crudPicker` does); a static list is narrowed by the form itself. `orUnset`: the options
162
162
  * whose `key` field is empty are offered too (`narrowedBy.orUnset`), and a `null` value then offers only those.
163
163
  */
164
- narrow?: {
165
- key: string;
166
- value: OptionValue | null;
167
- orUnset?: boolean;
168
- };
164
+ narrow?: DropdownNarrow;
165
+ /**
166
+ * Every narrowing, when several narrow at once (`narrowedBy` as a list: the form's Store and the row's Item): the
167
+ * options must fit each. `narrow` is the first of them, for a source that reads one. Absent with one narrowing or none.
168
+ */
169
+ narrows?: readonly DropdownNarrow[];
170
+ }
171
+ /** One narrowing of a picker's options: the records whose `key` field holds `value` (`orUnset`: or is empty). */
172
+ export interface DropdownNarrow {
173
+ key: string;
174
+ value: OptionValue | null;
175
+ orUnset?: boolean;
169
176
  }
170
177
  /** What `loadOptions` returns for one page. */
171
178
  export interface DropdownPage<TItem extends DropdownItem = DropdownItem> {