nexus-shared 2.0.0 → 3.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 +446 -0
- package/LICENSE +21 -21
- package/README.md +1084 -903
- package/dist/Client.Index.d.ts +14 -0
- package/dist/Client.Index.js +14 -0
- package/dist/Components/Chats/Chat.d.ts +28 -0
- package/dist/Components/Chats/Chat.js +19 -0
- package/dist/Components/Chats/ChatButton.d.ts +26 -0
- package/dist/Components/Chats/ChatButton.js +41 -0
- package/dist/Components/Chats/ChatComposer.d.ts +41 -0
- package/dist/Components/Chats/ChatComposer.js +177 -0
- package/dist/Components/Chats/ChatConversations.d.ts +35 -0
- package/dist/Components/Chats/ChatConversations.js +38 -0
- package/dist/Components/Chats/ChatPanel.d.ts +66 -0
- package/dist/Components/Chats/ChatPanel.js +94 -0
- package/dist/Components/Chats/ChatParts.d.ts +87 -0
- package/dist/Components/Chats/ChatParts.js +96 -0
- package/dist/Components/Chats/ChatThread.d.ts +22 -0
- package/dist/Components/Chats/ChatThread.js +96 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/Menu.js +44 -28
- package/dist/Components/Documents/SplitButton.d.ts +3 -1
- package/dist/Components/Documents/SplitButton.js +12 -8
- package/dist/Components/Documents/TabButtons.d.ts +29 -8
- package/dist/Components/Documents/TabButtons.js +24 -5
- package/dist/Components/Forms/ApiForm.d.ts +6 -4
- package/dist/Components/Forms/ApiForm.js +15 -14
- package/dist/Components/Forms/Crud.js +342 -71
- package/dist/Components/Forms/CrudParts.d.ts +5 -1
- package/dist/Components/Forms/CrudParts.js +37 -5
- package/dist/Components/Forms/ExcelImport.d.ts +58 -0
- package/dist/Components/Forms/ExcelImport.js +319 -0
- package/dist/Components/Forms/Form.js +9 -13
- package/dist/Components/Forms/SubmitForm.d.ts +11 -2
- package/dist/Components/Forms/SubmitForm.js +23 -12
- package/dist/Components/Inputs/DateTimePicker.js +2 -1
- package/dist/Components/Inputs/DropdownParts.d.ts +1 -1
- package/dist/Components/Inputs/DropdownParts.js +4 -6
- package/dist/Components/Inputs/GroupForm.js +4 -2
- package/dist/Components/Inputs/InputRenderer.d.ts +2 -0
- package/dist/Components/Inputs/InputRenderer.js +1 -1
- package/dist/Components/Inputs/NumberBox.js +24 -2
- package/dist/Components/Inputs/ReadOnlyNotice.js +2 -1
- package/dist/Components/Inputs/RowsInput.d.ts +12 -1
- package/dist/Components/Inputs/RowsInput.js +55 -6
- package/dist/Components/Inputs/Slider.js +4 -1
- package/dist/Components/Inputs/TabularForm.d.ts +7 -3
- package/dist/Components/Inputs/TabularForm.js +117 -20
- package/dist/Components/Inputs/TimePanel.js +4 -3
- package/dist/Components/Inputs/TimePicker.js +2 -1
- package/dist/Components/Layouts/Popup.js +1 -4
- package/dist/Components/Layouts/ThemePicker.d.ts +20 -3
- package/dist/Components/Layouts/ThemePicker.js +77 -6
- package/dist/Components/Layouts/ThemeScript.d.ts +4 -2
- package/dist/Components/Layouts/ThemeScript.js +4 -2
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +6 -3
- package/dist/Components/Layouts/ThemeSwitcher.js +39 -18
- package/dist/Components/Viewers/Avatar.d.ts +21 -0
- package/dist/Components/Viewers/Avatar.js +21 -0
- package/dist/Components/Viewers/DataTable.js +262 -60
- package/dist/Components/Viewers/DataTableColumns.d.ts +32 -0
- package/dist/Components/Viewers/DataTableColumns.js +200 -0
- package/dist/Components/Viewers/DataTableFilters.d.ts +21 -0
- package/dist/Components/Viewers/DataTableFilters.js +68 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +8 -4
- package/dist/Components/Viewers/DataTableParts.js +24 -12
- package/dist/Helpers/AnimationHelpers.d.ts +11 -0
- package/dist/Helpers/AnimationHelpers.js +20 -2
- package/dist/Helpers/ApiClient.d.ts +22 -0
- package/dist/Helpers/ApiClient.js +4 -1
- package/dist/Helpers/ApiFormHelpers.d.ts +27 -4
- package/dist/Helpers/ApiFormHelpers.js +73 -9
- package/dist/Helpers/ApiModules.d.ts +0 -4
- package/dist/Helpers/ApiModules.js +1 -0
- package/dist/Helpers/ApiResponses.d.ts +14 -4
- package/dist/Helpers/ApiResponses.js +116 -25
- package/dist/Helpers/ApiRoutes.d.ts +0 -10
- package/dist/Helpers/ApiRoutes.js +2 -10
- package/dist/Helpers/ChatBackend.d.ts +134 -0
- package/dist/Helpers/ChatBackend.js +332 -0
- package/dist/Helpers/ChatHelpers.d.ts +189 -0
- package/dist/Helpers/ChatHelpers.js +486 -0
- package/dist/Helpers/ChatHooks.d.ts +81 -0
- package/dist/Helpers/ChatHooks.js +175 -0
- package/dist/Helpers/ChatStore.d.ts +132 -0
- package/dist/Helpers/ChatStore.js +394 -0
- package/dist/Helpers/ChatSummaries.d.ts +39 -0
- package/dist/Helpers/ChatSummaries.js +118 -0
- package/dist/Helpers/CrudBackend.d.ts +54 -9
- package/dist/Helpers/CrudBackend.js +103 -6
- package/dist/Helpers/CrudHelpers.d.ts +39 -4
- package/dist/Helpers/CrudHelpers.js +76 -11
- package/dist/Helpers/ExcelBackend.d.ts +106 -0
- package/dist/Helpers/ExcelBackend.js +296 -0
- package/dist/Helpers/ExcelHelpers.d.ts +126 -0
- package/dist/Helpers/ExcelHelpers.js +344 -0
- package/dist/Helpers/FormStore.d.ts +11 -5
- package/dist/Helpers/FormStore.js +44 -6
- package/dist/Helpers/MessageBuilder.js +1 -2
- package/dist/Helpers/NumberHelpers.d.ts +7 -1
- package/dist/Helpers/NumberHelpers.js +41 -3
- package/dist/Helpers/PopoverHelpers.d.ts +46 -9
- package/dist/Helpers/PopoverHelpers.js +163 -24
- package/dist/Helpers/PopoverHooks.d.ts +24 -0
- package/dist/Helpers/PopoverHooks.js +62 -0
- package/dist/Helpers/RowsStore.d.ts +14 -3
- package/dist/Helpers/RowsStore.js +37 -3
- package/dist/Helpers/TableColumns.d.ts +7 -0
- package/dist/Helpers/TableColumns.js +41 -13
- package/dist/Helpers/TableExport.js +12 -12
- package/dist/Helpers/TableFilters.d.ts +25 -0
- package/dist/Helpers/TableFilters.js +162 -0
- package/dist/Helpers/TableHelpers.d.ts +15 -4
- package/dist/Helpers/TableHelpers.js +28 -4
- package/dist/Helpers/TableStore.d.ts +11 -3
- package/dist/Helpers/TableStore.js +16 -5
- package/dist/Helpers/ThemeHelpers.d.ts +21 -4
- package/dist/Helpers/ThemeHelpers.js +46 -9
- package/dist/Helpers/UserPreferences.d.ts +14 -0
- package/dist/Helpers/UserPreferences.js +99 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +29 -4
- package/dist/Interfaces/ApiInterfaces.js +16 -10
- package/dist/Interfaces/ChatInterfaces.d.ts +240 -0
- package/dist/Interfaces/ChatInterfaces.js +1 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +92 -12
- package/dist/Interfaces/ExcelInterfaces.d.ts +227 -0
- package/dist/Interfaces/ExcelInterfaces.js +45 -0
- package/dist/Interfaces/FormInterfaces.d.ts +85 -7
- package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
- package/dist/Interfaces/PreferenceInterfaces.d.ts +43 -0
- package/dist/Interfaces/PreferenceInterfaces.js +28 -0
- package/dist/Interfaces/TableInterfaces.d.ts +116 -9
- package/dist/Interfaces/ThemeInterfaces.d.ts +25 -0
- package/dist/Interfaces/ThemeInterfaces.js +15 -0
- package/dist/Services/BrowserApi.d.ts +18 -1
- package/dist/Services/BrowserApi.js +18 -0
- package/dist/Services/ChatApi.d.ts +37 -0
- package/dist/Services/ChatApi.js +98 -0
- package/dist/Services/ExcelApi.d.ts +48 -0
- package/dist/Services/ExcelApi.js +196 -0
- package/dist/Services/ServerApi.d.ts +6 -1
- package/dist/Services/ServerApi.js +3 -0
- package/dist/Services/ThemeService.d.ts +18 -2
- package/dist/Services/ThemeService.js +80 -16
- package/dist/Shared.Index.d.ts +10 -0
- package/dist/Shared.Index.js +10 -0
- package/package.json +85 -82
- package/src/Styles/Nexus.Avatar.css +79 -0
- package/src/Styles/Nexus.Base.css +117 -70
- package/src/Styles/Nexus.Button.css +180 -151
- package/src/Styles/Nexus.Calendar.css +273 -275
- package/src/Styles/Nexus.Chat.css +1443 -0
- package/src/Styles/Nexus.Choice.css +343 -345
- package/src/Styles/Nexus.Crud.css +168 -84
- package/src/Styles/Nexus.DateTime.css +81 -81
- package/src/Styles/Nexus.Dropdown.css +725 -774
- package/src/Styles/Nexus.Editor.css +295 -297
- package/src/Styles/Nexus.Excel.css +389 -0
- package/src/Styles/Nexus.Form.css +279 -298
- package/src/Styles/Nexus.Index.css +37 -34
- package/src/Styles/Nexus.Input.css +602 -637
- package/src/Styles/Nexus.Markdown.css +237 -265
- package/src/Styles/Nexus.Menu.css +237 -249
- package/src/Styles/Nexus.Popup.css +724 -731
- package/src/Styles/Nexus.RichText.css +2 -32
- package/src/Styles/Nexus.Rows.css +637 -566
- package/src/Styles/Nexus.Slider.css +276 -278
- package/src/Styles/Nexus.Tab.Buttons.css +432 -364
- package/src/Styles/Nexus.Table.css +1386 -1014
- package/src/Styles/Nexus.Theme.Picker.css +286 -159
- package/src/Styles/Nexus.Themes.css +194 -194
- package/src/Styles/Nexus.Time.css +144 -147
- package/src/Styles/Nexus.Toast.css +263 -265
- package/src/Styles/Nexus.Tokens.css +89 -72
- package/src/Styles/Nexus.Tree.css +431 -435
|
@@ -4,6 +4,11 @@ export type ApiModuleValue = string | number;
|
|
|
4
4
|
/**
|
|
5
5
|
* The Nexus backend modules, the modules of `NexusApiModules`. The values are the backend's `EModules` flags, so they
|
|
6
6
|
* can be combined and compared with permissions. Another backend has an enum of its own and its own `ApiModules` class.
|
|
7
|
+
*
|
|
8
|
+
* **These numbers are the backend's, not this package's.** A permission is a bitmask of them, so a value that differs
|
|
9
|
+
* from the backend by one bit does not fail loudly: it silently grants or denies the wrong module. `Chat` was added
|
|
10
|
+
* at bit 6 in the backend on 2026-09-25 and every module after it moved up one bit; this list moved with it, which
|
|
11
|
+
* changed the value of `Accounting` through `Portfolio`. See CHANGELOG.md.
|
|
7
12
|
*/
|
|
8
13
|
export declare const NexusModule: {
|
|
9
14
|
/** The app's own API routes (`/api/...`), not a backend module. */
|
|
@@ -14,6 +19,7 @@ export declare const NexusModule: {
|
|
|
14
19
|
readonly Enterprise: number;
|
|
15
20
|
readonly Drive: number;
|
|
16
21
|
readonly Message: number;
|
|
22
|
+
readonly Chat: number;
|
|
17
23
|
readonly Accounting: number;
|
|
18
24
|
readonly Report: number;
|
|
19
25
|
readonly Requisition: number;
|
|
@@ -79,6 +85,20 @@ export interface ApiEndpoint<TModule extends ApiModuleValue = AppModule> {
|
|
|
79
85
|
}
|
|
80
86
|
/** Where a call goes: an endpoint, or a URL ("/api/demo-rooms", "https://…"). */
|
|
81
87
|
export type ApiTarget = ApiEndpoint | string;
|
|
88
|
+
/**
|
|
89
|
+
* One problem in a failed answer, as the backend explains it: plain words for the user, the codes behind them, and the
|
|
90
|
+
* input it is about. A backend answers every wrong field at once, so a failure can carry several.
|
|
91
|
+
*/
|
|
92
|
+
export interface ApiErrorMessage {
|
|
93
|
+
/** What the user is shown: the server's own sentence ("This Region name already exists."). */
|
|
94
|
+
message: string;
|
|
95
|
+
/** The code of that exact refusal ("RGN_X009"), for the log and for a page to react to. Never shown to the user. */
|
|
96
|
+
messageCode?: string;
|
|
97
|
+
/** The kind of problem as a number (Nexus `errorCode`). Never shown to the user. */
|
|
98
|
+
errorCode?: number;
|
|
99
|
+
/** The input the message is about, as the server names it ("regionName"); several named with "|". */
|
|
100
|
+
inputName?: string;
|
|
101
|
+
}
|
|
82
102
|
/** A server's answer, read as success or failure. Every call answers with one; it never throws for a failed request. */
|
|
83
103
|
export interface ApiResult<T = unknown> {
|
|
84
104
|
ok: boolean;
|
|
@@ -86,12 +106,14 @@ export interface ApiResult<T = unknown> {
|
|
|
86
106
|
status?: number;
|
|
87
107
|
/** What the server sent back: the record, the list, the saved id. */
|
|
88
108
|
result?: T;
|
|
89
|
-
/** The server's message, or one built for the failure. */
|
|
109
|
+
/** The server's own message (its problems joined, in its order), or one built for the failure by status. */
|
|
90
110
|
message?: string;
|
|
91
|
-
/** The
|
|
111
|
+
/** The first problem's code for its message, such as "RGN_X009" (Nexus `messageCode`). Never shown as the sentence. */
|
|
92
112
|
messageCode?: string;
|
|
93
|
-
/** The
|
|
113
|
+
/** The first problem's error number (Nexus `errorCode`). Never shown as the sentence. */
|
|
94
114
|
errorCode?: number;
|
|
115
|
+
/** Every problem the server explained, in its order. A failure it explained carries at least one. */
|
|
116
|
+
errorMessages?: ApiErrorMessage[];
|
|
95
117
|
/** Messages by field name, as the server names them ("Email", "addresses[1].city"); matched to inputs without case. */
|
|
96
118
|
errors?: Record<string, string>;
|
|
97
119
|
/** Nothing reached the server: offline, or the server is down. */
|
|
@@ -136,7 +158,10 @@ export interface ApiCallOptions {
|
|
|
136
158
|
/** Tries again after a network failure or a 502, 503, or 504. Default 1 for GET, 0 for the rest (a save must not run twice). */
|
|
137
159
|
retries?: number;
|
|
138
160
|
credentials?: RequestCredentials;
|
|
139
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Reads the answer. Default: the app's reader (`configureApi({ read })` / `configureServerApi({ read })`), else
|
|
163
|
+
* `readApiResponse`: Nexus answers, ASP.NET Core problem details, any JSON by its status.
|
|
164
|
+
*/
|
|
140
165
|
read?: (body: unknown, status: number | undefined) => ApiResult;
|
|
141
166
|
/** What the call does, for its messages. Default: the endpoint's, or by method. */
|
|
142
167
|
action?: MessageAction;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Nexus backend modules, the modules of `NexusApiModules`. The values are the backend's `EModules` flags, so they
|
|
3
3
|
* can be combined and compared with permissions. Another backend has an enum of its own and its own `ApiModules` class.
|
|
4
|
+
*
|
|
5
|
+
* **These numbers are the backend's, not this package's.** A permission is a bitmask of them, so a value that differs
|
|
6
|
+
* from the backend by one bit does not fail loudly: it silently grants or denies the wrong module. `Chat` was added
|
|
7
|
+
* at bit 6 in the backend on 2026-09-25 and every module after it moved up one bit; this list moved with it, which
|
|
8
|
+
* changed the value of `Accounting` through `Portfolio`. See CHANGELOG.md.
|
|
4
9
|
*/
|
|
5
10
|
export const NexusModule = {
|
|
6
11
|
/** The app's own API routes (`/api/...`), not a backend module. */
|
|
@@ -11,14 +16,15 @@ export const NexusModule = {
|
|
|
11
16
|
Enterprise: 1 << 3,
|
|
12
17
|
Drive: 1 << 4,
|
|
13
18
|
Message: 1 << 5,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
Chat: 1 << 6,
|
|
20
|
+
Accounting: 1 << 7,
|
|
21
|
+
Report: 1 << 8,
|
|
22
|
+
Requisition: 1 << 9,
|
|
23
|
+
Tenant: 1 << 10,
|
|
24
|
+
Subscription: 1 << 11,
|
|
25
|
+
Reservation: 1 << 12,
|
|
26
|
+
School: 1 << 13,
|
|
27
|
+
Pharmacy: 1 << 14,
|
|
28
|
+
Staffing: 1 << 15,
|
|
29
|
+
Portfolio: 1 << 16,
|
|
24
30
|
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { DropdownPage, DropdownPageRequest } from "./InputInterfaces.ts";
|
|
2
|
+
/** An id, as the backend hands it out: a number, or text (a GUID, an encrypted id). */
|
|
3
|
+
export type ChatId = string | number;
|
|
4
|
+
/**
|
|
5
|
+
* A domain's value in an app's own enum: a number (`EDomainTypes.Leave`) or a string (`"leave"`). The backend will
|
|
6
|
+
* own the enum; until it does, a plain number is the value, and the components never read it — they only pass it on.
|
|
7
|
+
*/
|
|
8
|
+
export type ChatDomainValue = string | number;
|
|
9
|
+
/**
|
|
10
|
+
* Where an app names its domain enum for the types, once, next to its other registrations:
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* export enum EDomainTypes { Leave = 1, Procurement = 2, Applicant = 3 }
|
|
14
|
+
* declare module "nexus-shared" {
|
|
15
|
+
* interface ChatRegister { domain: EDomainTypes }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Every chat component, scope, and store then takes only those domains. Without it, any number.
|
|
19
|
+
*/
|
|
20
|
+
export interface ChatRegister {
|
|
21
|
+
}
|
|
22
|
+
/** The app's domain type: the one named in `ChatRegister`, else any number. A component can also take one as `<Chat<EDomainTypes> …>`. */
|
|
23
|
+
export type AppChatDomain = ChatRegister extends {
|
|
24
|
+
domain: infer TDomain extends ChatDomainValue;
|
|
25
|
+
} ? TDomain : number;
|
|
26
|
+
/**
|
|
27
|
+
* Which conversation this is: the kind of record, the record, and who is reading it. Everything else a chat needs —
|
|
28
|
+
* where its messages come from, who may write — follows from these three.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const scope = { domain: EDomainTypes.Leave, parentId: leave.id, userId: me.id };
|
|
32
|
+
*/
|
|
33
|
+
export interface ChatScope<TDomain extends ChatDomainValue = AppChatDomain> {
|
|
34
|
+
/** Which kind of record the conversation hangs on: a leave request, a purchase order, an applicant, a tender. */
|
|
35
|
+
domain: TDomain;
|
|
36
|
+
/** The record's own id. The domain and the id together are the conversation. */
|
|
37
|
+
parentId: ChatId;
|
|
38
|
+
/** Who is reading and writing. Their messages sit on the right, and the unread count is theirs. */
|
|
39
|
+
userId: ChatId;
|
|
40
|
+
}
|
|
41
|
+
/** One domain's name, for the panel's heading and messages. An app registers its own with `defineChatDomains`. */
|
|
42
|
+
export interface ChatDomainInfo<TDomain extends ChatDomainValue = AppChatDomain> {
|
|
43
|
+
domain: TDomain;
|
|
44
|
+
/** Its name in a URL or a cache key: `leave`. Letters, digits, dots, dashes, and underscores. */
|
|
45
|
+
key: string;
|
|
46
|
+
/** Its name on screen: "Leave request". */
|
|
47
|
+
label: string;
|
|
48
|
+
}
|
|
49
|
+
/** `sent`: the server has it. `sending`: on its way. `failed`: it did not arrive, and the text is kept to send again. */
|
|
50
|
+
export type ChatMessageStatus = "sent" | "sending" | "failed";
|
|
51
|
+
/** A file on a message. The chat shows and links it; uploading is the app's `ChatSource`. */
|
|
52
|
+
export interface ChatAttachment {
|
|
53
|
+
id?: ChatId;
|
|
54
|
+
name: string;
|
|
55
|
+
/** Bytes, shown as "248 KB". */
|
|
56
|
+
size?: number;
|
|
57
|
+
/** Where it opens or downloads. Without one, the name shows but does not link. */
|
|
58
|
+
url?: string | null;
|
|
59
|
+
contentType?: string | null;
|
|
60
|
+
}
|
|
61
|
+
/** One message in a conversation. A backend maps its own rows to this in `ChatBackend.readMessage`. */
|
|
62
|
+
export interface ChatMessage {
|
|
63
|
+
/** The server's id. A message still on its way carries its `clientId` here until the answer brings the real one. */
|
|
64
|
+
id: ChatId;
|
|
65
|
+
/** Who wrote it. Compared with the scope's `userId` to decide which side it sits on. */
|
|
66
|
+
authorId: ChatId;
|
|
67
|
+
/** Their name, for the avatar and the line above the message. Without one, the chat says "Unknown". */
|
|
68
|
+
authorName?: string | null;
|
|
69
|
+
/** A photo URL. Without one, the initials of the name on a tone picked from it. */
|
|
70
|
+
authorAvatar?: string | null;
|
|
71
|
+
/** What they wrote, as plain text. Newlines are kept; nothing is parsed as HTML or Markdown. */
|
|
72
|
+
body: string;
|
|
73
|
+
/** When it was written, as a UTC moment (`2026-10-01T03:21:04.000Z`). Shown in the reader's own zone. */
|
|
74
|
+
createdAt: string;
|
|
75
|
+
/** When it was last changed, as a UTC moment. Shown as "edited". */
|
|
76
|
+
editedAt?: string | null;
|
|
77
|
+
/** Default `sent`. `sending` and `failed` belong to messages this browser has not had answered yet. */
|
|
78
|
+
status?: ChatMessageStatus;
|
|
79
|
+
/** The message this one answers, quoted above it. */
|
|
80
|
+
replyToId?: ChatId | null;
|
|
81
|
+
attachments?: readonly ChatAttachment[];
|
|
82
|
+
/** Written by the system, not a person ("Sita approved the request."): a centered note, with no avatar or side. */
|
|
83
|
+
isSystem?: boolean;
|
|
84
|
+
/** Who it has reached. Two ticks once anyone has it, filled once anyone has read it. */
|
|
85
|
+
deliveredTo?: readonly ChatId[];
|
|
86
|
+
/** Who has read it. */
|
|
87
|
+
readBy?: readonly ChatId[];
|
|
88
|
+
/** The people named with @ in the body, for the backend to tell them. */
|
|
89
|
+
mentions?: readonly ChatId[];
|
|
90
|
+
/** Set while a message is on its way or failed, so Retry can send the very same draft again. */
|
|
91
|
+
clientId?: string;
|
|
92
|
+
/** Why it failed, under the message, beside Retry. */
|
|
93
|
+
error?: string | null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* What the trigger icon draws itself from, and the only thing a list of 500 rows needs per row. The three states are
|
|
97
|
+
* `messageCount === 0` (quiet), messages but none unread (highlighted), and `unreadCount > 0` (highlighted, badged).
|
|
98
|
+
*/
|
|
99
|
+
export interface ChatSummary {
|
|
100
|
+
/** Messages in the conversation. 0 leaves the icon quiet. */
|
|
101
|
+
messageCount: number;
|
|
102
|
+
/** Messages this user has not read. */
|
|
103
|
+
unreadCount: number;
|
|
104
|
+
/** When the last message was written, as a UTC moment, for the trigger's tooltip. */
|
|
105
|
+
lastMessageAt?: string | null;
|
|
106
|
+
/** The last message's text, cut to a line, for the tooltip. */
|
|
107
|
+
lastMessageText?: string | null;
|
|
108
|
+
/** Who wrote the last message, for the tooltip. */
|
|
109
|
+
lastMessageBy?: string | null;
|
|
110
|
+
/** How many take part. */
|
|
111
|
+
participantCount?: number;
|
|
112
|
+
/** How many conversations the record holds, when it holds more than one. */
|
|
113
|
+
conversationCount?: number;
|
|
114
|
+
}
|
|
115
|
+
/** The three looks of the trigger icon, in the order the owner described them. */
|
|
116
|
+
export type ChatHighlight = "quiet" | "active" | "unread";
|
|
117
|
+
/** One page of history: the newest messages first, then older ones with `before`, or only the new ones with `after`. */
|
|
118
|
+
export interface ChatHistoryRequest {
|
|
119
|
+
/** Messages older than this one, for the page above. */
|
|
120
|
+
before?: ChatId | null;
|
|
121
|
+
/** Messages newer than this one, for a refresh. `before` and `after` are never both set. */
|
|
122
|
+
after?: ChatId | null;
|
|
123
|
+
/** How many to send. */
|
|
124
|
+
take: number;
|
|
125
|
+
/** Aborted when the conversation changes or the panel unmounts. */
|
|
126
|
+
signal?: AbortSignal;
|
|
127
|
+
}
|
|
128
|
+
/** What a history request answers with. Messages may come in any order; the store sorts them oldest first. */
|
|
129
|
+
export interface ChatHistory {
|
|
130
|
+
messages: readonly ChatMessage[];
|
|
131
|
+
/** Whether older messages remain. Default: the page was full (`messages.length >= take`). */
|
|
132
|
+
hasMore?: boolean;
|
|
133
|
+
/** The conversation's counts, when the answer carries them, so one call fills the header too. */
|
|
134
|
+
summary?: ChatSummary;
|
|
135
|
+
/** Everyone taking part, when the answer carries them, so the panel needs no second call. */
|
|
136
|
+
participants?: readonly ChatParticipant[];
|
|
137
|
+
/** Whether this user may write here. Default true. */
|
|
138
|
+
canSend?: boolean;
|
|
139
|
+
}
|
|
140
|
+
/** What the composer sends. */
|
|
141
|
+
export interface ChatDraft {
|
|
142
|
+
body: string;
|
|
143
|
+
replyToId?: ChatId | null;
|
|
144
|
+
/** The people named with @ while writing it. */
|
|
145
|
+
mentions?: readonly ChatId[];
|
|
146
|
+
/** Files picked in the composer. A source that cannot take files leaves them out of its request. */
|
|
147
|
+
files?: readonly File[];
|
|
148
|
+
/** This browser's id for the message, so the answer replaces the right one and Retry sends the same draft. */
|
|
149
|
+
clientId: string;
|
|
150
|
+
}
|
|
151
|
+
/** Someone in a conversation, or someone who could be added to it. */
|
|
152
|
+
export interface ChatParticipant {
|
|
153
|
+
id: ChatId;
|
|
154
|
+
name: string;
|
|
155
|
+
avatar?: string | null;
|
|
156
|
+
/** What they are here: "Manager", "Co-worker", "Requested by". Shown under the name. */
|
|
157
|
+
role?: string | null;
|
|
158
|
+
/** They read but cannot write. */
|
|
159
|
+
readOnly?: boolean;
|
|
160
|
+
/** How far they have read, as a UTC moment. */
|
|
161
|
+
lastReadAt?: string | null;
|
|
162
|
+
/** Someone who has left or is no longer active: shown, never offered again. */
|
|
163
|
+
inactive?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** What a participant search receives: the same request a dropdown's `loadOptions` takes, so one loader serves both. */
|
|
166
|
+
export type ChatParticipantQuery = DropdownPageRequest;
|
|
167
|
+
/** What a participant search answers: the same page a dropdown takes. */
|
|
168
|
+
export type ChatParticipantPage = DropdownPage<ChatParticipant>;
|
|
169
|
+
/**
|
|
170
|
+
* Where one conversation's data comes from. The store calls only this, so it never knows about HTTP: the API source
|
|
171
|
+
* (`apiChatSource`) talks to the app's `ChatBackend`, and `localChatSource` keeps everything in memory for a demo or
|
|
172
|
+
* a test. Only `history` and `send` are required.
|
|
173
|
+
*/
|
|
174
|
+
export interface ChatSource {
|
|
175
|
+
/** One page of messages. */
|
|
176
|
+
history(request: ChatHistoryRequest): Promise<ChatHistory>;
|
|
177
|
+
/** Sends one message, and answers with the saved one: its real id, its server time. */
|
|
178
|
+
send(draft: ChatDraft, signal?: AbortSignal): Promise<ChatMessage>;
|
|
179
|
+
/** Everyone in the conversation, and the people who could join, searched. Without it, the panel shows no participants. */
|
|
180
|
+
participants?(query: ChatParticipantQuery): Promise<ChatParticipantPage>;
|
|
181
|
+
/** Adds people to the conversation. Without it, the panel offers no Add. */
|
|
182
|
+
addParticipants?(ids: readonly ChatId[], signal?: AbortSignal): Promise<readonly ChatParticipant[]>;
|
|
183
|
+
/** Tells the server the user has read up to a message. Without it, unread counts clear in the browser only. */
|
|
184
|
+
markRead?(upTo: ChatId, signal?: AbortSignal): Promise<void>;
|
|
185
|
+
/** Removes a message. Without it, the panel offers no Delete. */
|
|
186
|
+
remove?(id: ChatId, signal?: AbortSignal): Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* Pushes new messages in, from a socket or an event stream, and returns the function that stops it. While a source
|
|
189
|
+
* watches, the store does not poll.
|
|
190
|
+
*/
|
|
191
|
+
watch?(onMessages: (messages: readonly ChatMessage[]) => void): () => void;
|
|
192
|
+
}
|
|
193
|
+
/** `one-to-one`: two people, named after the other one. `group`: three or more, or any conversation with a name. */
|
|
194
|
+
export type ChatConversationType = "one-to-one" | "group";
|
|
195
|
+
/** One conversation at a place. One of these shows on its own; several offer a switch between them. */
|
|
196
|
+
export interface ChatConversation {
|
|
197
|
+
id: ChatId;
|
|
198
|
+
type: ChatConversationType;
|
|
199
|
+
/** A group's name. A one-to-one has none: it is called after the other person. */
|
|
200
|
+
title?: string | null;
|
|
201
|
+
/** Everyone in it. Needed to name a one-to-one, and to fill the people column without a second call. */
|
|
202
|
+
participants?: readonly ChatParticipant[];
|
|
203
|
+
/** Its own counts, so the switch can show which conversation is waiting. */
|
|
204
|
+
summary?: ChatSummary;
|
|
205
|
+
/** When it was started, as a UTC moment. */
|
|
206
|
+
createdAt?: string | null;
|
|
207
|
+
createdBy?: ChatId | null;
|
|
208
|
+
/** This user may write in it. Default true. */
|
|
209
|
+
canSend?: boolean;
|
|
210
|
+
/** Nobody writes in it any more: it is read, never written to. */
|
|
211
|
+
closed?: boolean;
|
|
212
|
+
}
|
|
213
|
+
/** What starting a conversation sends. The backend settles the type; this is what the user asked for. */
|
|
214
|
+
export interface NewChatRequest {
|
|
215
|
+
/** One other person makes a one-to-one, more make a group. */
|
|
216
|
+
type: ChatConversationType;
|
|
217
|
+
/** Everyone to put in it, the starter aside. */
|
|
218
|
+
userIds: readonly ChatId[];
|
|
219
|
+
/** A group's name. */
|
|
220
|
+
title?: string | null;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The conversations at one place: what there is, and how to start another. A panel without one shows the single
|
|
224
|
+
* conversation its `ChatSource` serves, which is what a page that already knows its conversation wants.
|
|
225
|
+
*/
|
|
226
|
+
export interface ChatPlaceSource {
|
|
227
|
+
/** Every conversation here this user may see, the one with the newest message first. */
|
|
228
|
+
conversations(signal?: AbortSignal): Promise<readonly ChatConversation[]>;
|
|
229
|
+
/** Starts one. A one-to-one that already exists comes back instead of a second one. Without it, no New chat. */
|
|
230
|
+
create?(request: NewChatRequest, signal?: AbortSignal): Promise<ChatConversation>;
|
|
231
|
+
/** Who can be put in a conversation here. Without it, New chat has nobody to pick. */
|
|
232
|
+
participants?(query: ChatParticipantQuery): Promise<ChatParticipantPage>;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* A scope of any domain. Helpers that only pass the domain on — a cache key, the counts of a record — take this, so a
|
|
236
|
+
* component generic over the app's own enum can hand them its scope without a cast.
|
|
237
|
+
*/
|
|
238
|
+
export type AnyChatScope = ChatScope<ChatDomainValue>;
|
|
239
|
+
/** A conversation's key in a cache: `"1:482"`, the domain and the parent id. */
|
|
240
|
+
export type ChatScopeKey = string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode, Ref } from "react";
|
|
2
2
|
import type { PopupSize } from "../Components/Layouts/Popup.tsx";
|
|
3
|
+
import type { PopupHandle } from "../Components/Layouts/PopupHost.tsx";
|
|
4
|
+
import type { FormStore } from "../Helpers/FormStore.ts";
|
|
3
5
|
import type { TableStore } from "../Helpers/TableStore.ts";
|
|
4
6
|
import type { ApiEndpoint } from "./ApiInterfaces.ts";
|
|
5
7
|
import type { FormProps, FormValues, InputParams } from "./FormInterfaces.ts";
|
|
6
8
|
import type { InputDesign } from "./InputInterfaces.ts";
|
|
7
9
|
import type { DataTableProps, TableColumn, TableMassAction, TablePaging, TableRowAction } from "./TableInterfaces.ts";
|
|
8
10
|
/**
|
|
9
|
-
* The lists of a controller: every active record (`normal`), the flagged ones, the
|
|
10
|
-
*
|
|
11
|
+
* The lists of a controller: every active record (`normal`), the flagged ones, the trash, and the deleted ones
|
|
12
|
+
* (which can go back to the trash). The records the user pinned are not a list of their own: they sit above the
|
|
11
13
|
* active list, and in it.
|
|
12
14
|
*/
|
|
13
|
-
export type CrudMode = "normal" | "flag" | "
|
|
15
|
+
export type CrudMode = "normal" | "flag" | "trash" | "deleted";
|
|
14
16
|
/** The built-in actions on one record. */
|
|
15
|
-
export type CrudRowActionName = "view" | "edit" | "flag" | "pin" | "
|
|
17
|
+
export type CrudRowActionName = "view" | "edit" | "flag" | "pin" | "trash" | "recover" | "delete" | "trashFromDelete";
|
|
16
18
|
/** The built-in actions on many records at once. */
|
|
17
|
-
export type CrudMassActionName = "flag" | "pin" | "
|
|
19
|
+
export type CrudMassActionName = "flag" | "pin" | "trash" | "recover" | "delete" | "trashFromDelete" | "description";
|
|
18
20
|
/** Every built-in action: Add, the row actions, and a new description for many records. */
|
|
19
21
|
export type CrudActionName = "create" | CrudRowActionName | "description";
|
|
20
22
|
/**
|
|
@@ -38,8 +40,6 @@ export interface CrudEndpoints {
|
|
|
38
40
|
flag?: ApiEndpoint;
|
|
39
41
|
/** Pins a record, or unpins it: `Room/Pin/{id}`. */
|
|
40
42
|
pin?: ApiEndpoint;
|
|
41
|
-
archive?: ApiEndpoint;
|
|
42
|
-
restore?: ApiEndpoint;
|
|
43
43
|
trash?: ApiEndpoint;
|
|
44
44
|
recover?: ApiEndpoint;
|
|
45
45
|
delete?: ApiEndpoint;
|
|
@@ -52,10 +52,29 @@ export type CrudInputs<TRow extends FormValues = FormValues> = readonly InputPar
|
|
|
52
52
|
create?: readonly InputParams[];
|
|
53
53
|
edit?: readonly InputParams[];
|
|
54
54
|
} | ((action: "create" | "edit", row: TRow | null) => readonly InputParams[] | Promise<readonly InputParams[]>);
|
|
55
|
+
/** What a form popup's side panel is handed: the live form, and which record it was opened on. */
|
|
56
|
+
export interface CrudFormAsideContext<TRow extends FormValues = FormValues> {
|
|
57
|
+
/**
|
|
58
|
+
* The form of this popup. Read it with `getValue()` and follow it with `subscribeValue()`: the panel then updates
|
|
59
|
+
* itself without the form around it re-rendering, which is what keeps a long form typeable while the panel works.
|
|
60
|
+
*/
|
|
61
|
+
form: FormStore<TRow>;
|
|
62
|
+
action: "create" | "edit";
|
|
63
|
+
/** The record being edited, or null for a new one. */
|
|
64
|
+
row: TRow | null;
|
|
65
|
+
}
|
|
55
66
|
/** The Add and Edit popups and their forms. */
|
|
56
67
|
export interface CrudFormOptions<TRow extends FormValues = FormValues> {
|
|
57
68
|
/** Width of the popups. Default `md`, or `lg` for forms of more than 8 inputs. */
|
|
58
69
|
size?: PopupSize;
|
|
70
|
+
/**
|
|
71
|
+
* A panel beside the form, in the same popup: a preview of what will be written, a summary, a help panel. It is
|
|
72
|
+
* given the popup's own `FormStore`, so it can follow what is typed.
|
|
73
|
+
*
|
|
74
|
+
* Side by side where there is room; under about 1000px the two stack and the **form comes first**, since that is
|
|
75
|
+
* what the popup is for. Pair it with a wide `size` (`xl` or `full`): a panel in a 560px popup leaves the form none.
|
|
76
|
+
*/
|
|
77
|
+
aside?: ReactNode | ((context: CrudFormAsideContext<TRow>) => ReactNode);
|
|
59
78
|
/** Values of a new record, over each input's default. */
|
|
60
79
|
newValues?: Partial<TRow> | (() => Partial<TRow>);
|
|
61
80
|
/**
|
|
@@ -80,6 +99,17 @@ export interface CrudFormOptions<TRow extends FormValues = FormValues> {
|
|
|
80
99
|
*/
|
|
81
100
|
draftKey?: string;
|
|
82
101
|
}
|
|
102
|
+
/** What a details popup's side panel is handed: the record, in the shape a form's side panel is handed it. */
|
|
103
|
+
export interface CrudDetailsAsideContext<TRow extends FormValues = FormValues> {
|
|
104
|
+
/**
|
|
105
|
+
* The record, in a `FormStore` no input writes to: the row as the list has it at once, then the `details`
|
|
106
|
+
* endpoint's record when that lands. Read it with `getValue()` and follow it with `subscribeValue()`, exactly as a
|
|
107
|
+
* form's panel does — so the panel beside the Edit form can be the panel beside the details, unchanged.
|
|
108
|
+
*/
|
|
109
|
+
form: FormStore<TRow>;
|
|
110
|
+
/** The row the popup was opened on, as the list has it. */
|
|
111
|
+
row: TRow;
|
|
112
|
+
}
|
|
83
113
|
/** The details popup that View opens. */
|
|
84
114
|
export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
85
115
|
/** The fields to show. Default: every column, hidden ones too. */
|
|
@@ -88,6 +118,35 @@ export interface CrudDetailsOptions<TRow extends FormValues = FormValues> {
|
|
|
88
118
|
render?: (row: TRow) => ReactNode;
|
|
89
119
|
/** Default `md`. */
|
|
90
120
|
size?: PopupSize;
|
|
121
|
+
/**
|
|
122
|
+
* A panel beside the details, in the same popup: what the record generates, a chart, a preview. It is laid out as
|
|
123
|
+
* the Add and Edit popups' `form.aside` is, except that here the panel takes the larger share, since the details
|
|
124
|
+
* beside it are a short list of fields. Pair it with a wide `size` (`xl` or `full`).
|
|
125
|
+
*/
|
|
126
|
+
aside?: ReactNode | ((context: CrudDetailsAsideContext<TRow>) => ReactNode);
|
|
127
|
+
/**
|
|
128
|
+
* What the popup is called, as a noun: the row's menu offers "View {label}" and the popup's title is
|
|
129
|
+
* "{Subject} {label}". Default `details`. A popup whose panel is the point says so: `schema`, `preview`.
|
|
130
|
+
*/
|
|
131
|
+
label?: string;
|
|
132
|
+
/** Buttons of the page's own in the footer, before Edit and Close: "View history". */
|
|
133
|
+
actions?: readonly CrudDetailsAction<TRow>[];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A button in the details popup's footer. One that names the endpoint it calls shows only to a user who may call
|
|
137
|
+
* it, by the same rule every built-in action follows - so it appears and disappears with the user's rights, with
|
|
138
|
+
* nothing to change here when they come from the backend.
|
|
139
|
+
*/
|
|
140
|
+
export interface CrudDetailsAction<TRow extends FormValues = FormValues> {
|
|
141
|
+
id: string;
|
|
142
|
+
label: string;
|
|
143
|
+
icon?: ReactNode;
|
|
144
|
+
/** What it calls: the button shows only when the user may call it. */
|
|
145
|
+
endpoint?: ApiEndpoint;
|
|
146
|
+
/** Hidden for this record, by a rule of the page's own. */
|
|
147
|
+
hidden?: (row: TRow) => boolean;
|
|
148
|
+
/** On a click, with the record and the popup (close it, or leave it open beside what this opens). */
|
|
149
|
+
onSelect: (row: TRow, popup: PopupHandle) => void;
|
|
91
150
|
}
|
|
92
151
|
/** What changed, for `onChange`. */
|
|
93
152
|
export interface CrudChange<TRow extends FormValues = FormValues> {
|
|
@@ -97,7 +156,7 @@ export interface CrudChange<TRow extends FormValues = FormValues> {
|
|
|
97
156
|
mode: CrudMode;
|
|
98
157
|
}
|
|
99
158
|
/** Table settings a CRUD passes on to its `DataTable`. */
|
|
100
|
-
export type CrudTableOptions<TRow extends FormValues = FormValues> = Pick<DataTableProps<TRow>, "pageSize" | "pageSizes" | "defaultSort" | "numbered" | "maxHeight" | "size" | "searchPlaceholder" | "maxSelection" | "emptyMessage" | "columnsMenu" | "stateKey" | "searchDelay" | "reorderable" | "resizable" | "pinnable" | "moreActions" | "exportable" | "printable" | "exportName" | "onImport">;
|
|
159
|
+
export type CrudTableOptions<TRow extends FormValues = FormValues> = Pick<DataTableProps<TRow>, "pageSize" | "pageSizes" | "defaultSort" | "numbered" | "maxHeight" | "fill" | "excel" | "size" | "searchPlaceholder" | "maxSelection" | "emptyMessage" | "columnsMenu" | "stateKey" | "searchDelay" | "filterInputs" | "reorderable" | "resizable" | "pinnable" | "moreActions" | "exportable" | "printable" | "exportName" | "onImport" | "openOnClick">;
|
|
101
160
|
/** Props of `Crud`. */
|
|
102
161
|
export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
103
162
|
/** The endpoints: `MODULES.controller(NexusModule.Reservation, "Guest", "guest")`, or an object of your own. */
|
|
@@ -133,8 +192,10 @@ export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
|
133
192
|
/** The details popup, or `false` for none. */
|
|
134
193
|
details?: false | CrudDetailsOptions<TRow>;
|
|
135
194
|
/**
|
|
136
|
-
* Keeps lists this long (milliseconds) while the page is open, so going back to a list shows it at once
|
|
137
|
-
*
|
|
195
|
+
* Keeps lists this long (milliseconds) while the page is open, so going back to a list shows it at once. A change is
|
|
196
|
+
* **written into** every list kept, from the record the answer carried: a record flagged joins the flagged list and
|
|
197
|
+
* one trashed joins the trash, with no request on either. Only a list the record cannot answer for is dropped, and
|
|
198
|
+
* that one loads when it is shown. Default 5 minutes; `false` loads every time. Cleared when the page closes.
|
|
138
199
|
*/
|
|
139
200
|
cache?: false | {
|
|
140
201
|
lifetime?: number;
|
|
@@ -143,7 +204,7 @@ export interface CrudProps<TRow extends FormValues = FormValues> {
|
|
|
143
204
|
toolbar?: ReactNode;
|
|
144
205
|
/** Settings of the table. */
|
|
145
206
|
table?: CrudTableOptions<TRow>;
|
|
146
|
-
/** After every change the CRUD made: a record added, saved,
|
|
207
|
+
/** After every change the CRUD made: a record added, saved, trashed, deleted. */
|
|
147
208
|
onChange?: (change: CrudChange<TRow>) => void;
|
|
148
209
|
/** The table's name for screen readers. Default: the plural of the subject ("Guests"). */
|
|
149
210
|
"aria-label"?: string;
|
|
@@ -160,8 +221,27 @@ export interface CrudHandle<TRow extends FormValues = FormValues> {
|
|
|
160
221
|
/** The list shown. */
|
|
161
222
|
mode: CrudMode;
|
|
162
223
|
setMode: (mode: CrudMode) => void;
|
|
163
|
-
/** Loads the list again, past the cache. */
|
|
224
|
+
/** Loads the list again, past the cache. A last resort: `apply` writes a change in without a request. */
|
|
164
225
|
reload: () => Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Records a page's **own** action changed, written where the built-in actions write theirs: into the table, the
|
|
228
|
+
* pinned band and every list kept in the cache - no request, on any list. Each record is merged over the one the
|
|
229
|
+
* table holds, so the key and the fields that changed are enough:
|
|
230
|
+
*
|
|
231
|
+
* ```ts
|
|
232
|
+
* const answer = await api.send(REGION_MARKS.flag, { params: { id: row.id } });
|
|
233
|
+
* if (answer.ok) handle.current?.apply([{ ...row, recordStatus: answer.result }]);
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* Which list each record now belongs in comes from the record itself (`CrudBackend.listMembership`), so one that
|
|
237
|
+
* no longer belongs to the list on screen leaves it, and the other lists are patched behind it. `gone` is for an
|
|
238
|
+
* action that ends a record: it is taken out of every list.
|
|
239
|
+
*
|
|
240
|
+
* It does not call `onChange`, since the page calling it is the one that made the change.
|
|
241
|
+
*/
|
|
242
|
+
apply: (rows: readonly Partial<TRow>[], options?: {
|
|
243
|
+
gone?: boolean;
|
|
244
|
+
}) => void;
|
|
165
245
|
/** Opens the Add popup, with these values over the new record's. */
|
|
166
246
|
create: (values?: Partial<TRow>) => void;
|
|
167
247
|
/** Opens the Edit popup of a record. */
|