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.
Files changed (271) hide show
  1. package/CHANGELOG.md +2359 -0
  2. package/README.md +1911 -1123
  3. package/dist/Client.Index.d.ts +27 -0
  4. package/dist/Client.Index.js +27 -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/DocumentFrame.d.ts +50 -0
  22. package/dist/Components/Documents/DocumentFrame.js +83 -0
  23. package/dist/Components/Documents/Menu.d.ts +4 -0
  24. package/dist/Components/Documents/Menu.js +40 -24
  25. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  26. package/dist/Components/Documents/PrintDocument.js +62 -0
  27. package/dist/Components/Documents/TabButtons.d.ts +6 -0
  28. package/dist/Components/Documents/TabButtons.js +5 -4
  29. package/dist/Components/Documents/Tabs.d.ts +59 -0
  30. package/dist/Components/Documents/Tabs.js +70 -0
  31. package/dist/Components/Domains/DomainCard.d.ts +98 -0
  32. package/dist/Components/Domains/DomainCard.js +182 -0
  33. package/dist/Components/Forms/ApiForm.js +24 -3
  34. package/dist/Components/Forms/Crud.d.ts +2 -2
  35. package/dist/Components/Forms/Crud.js +628 -247
  36. package/dist/Components/Forms/CrudActions.d.ts +137 -0
  37. package/dist/Components/Forms/CrudActions.js +214 -0
  38. package/dist/Components/Forms/CrudHistory.d.ts +6 -1
  39. package/dist/Components/Forms/CrudHistory.js +14 -6
  40. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  41. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  42. package/dist/Components/Forms/CrudParts.d.ts +37 -2
  43. package/dist/Components/Forms/CrudParts.js +123 -7
  44. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  45. package/dist/Components/Forms/CrudRowForms.js +174 -0
  46. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  47. package/dist/Components/Forms/CrudScopes.js +76 -0
  48. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  49. package/dist/Components/Forms/CrudTree.js +363 -0
  50. package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
  51. package/dist/Components/Forms/CrudWorkspace.js +177 -0
  52. package/dist/Components/Forms/ExcelImport.js +3 -3
  53. package/dist/Components/Forms/Form.d.ts +6 -5
  54. package/dist/Components/Forms/Form.js +134 -21
  55. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  56. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  57. package/dist/Components/Forms/FormQuestion.d.ts +6 -0
  58. package/dist/Components/Forms/FormQuestion.js +28 -0
  59. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  60. package/dist/Components/Forms/RecordFlows.js +282 -0
  61. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  62. package/dist/Components/Forms/SubmitForm.js +182 -10
  63. package/dist/Components/Inputs/CheckBox.js +1 -1
  64. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  65. package/dist/Components/Inputs/ChoiceField.js +28 -2
  66. package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
  67. package/dist/Components/Inputs/ComputedInput.js +179 -0
  68. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  69. package/dist/Components/Inputs/Dropdown.js +8 -6
  70. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  71. package/dist/Components/Inputs/DropdownParts.js +16 -4
  72. package/dist/Components/Inputs/Field.d.ts +7 -1
  73. package/dist/Components/Inputs/Field.js +2 -2
  74. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  75. package/dist/Components/Inputs/GroupForm.js +12 -12
  76. package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
  77. package/dist/Components/Inputs/InputRenderer.js +37 -32
  78. package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
  79. package/dist/Components/Inputs/MarkdownEditor.js +32 -6
  80. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  81. package/dist/Components/Inputs/PictureInput.js +75 -0
  82. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  83. package/dist/Components/Inputs/RadioGroup.js +8 -3
  84. package/dist/Components/Inputs/RowsInput.d.ts +15 -2
  85. package/dist/Components/Inputs/RowsInput.js +96 -8
  86. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  87. package/dist/Components/Inputs/TabularForm.js +45 -32
  88. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  89. package/dist/Components/Inputs/TextArea.js +11 -2
  90. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  91. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  92. package/dist/Components/Inputs/TreeView.d.ts +76 -2
  93. package/dist/Components/Inputs/TreeView.js +227 -16
  94. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  95. package/dist/Components/Layouts/MessageBox.js +56 -7
  96. package/dist/Components/Layouts/Popup.d.ts +34 -7
  97. package/dist/Components/Layouts/Popup.js +143 -13
  98. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  99. package/dist/Components/Layouts/PopupDock.js +2 -1
  100. package/dist/Components/Layouts/PopupHost.js +5 -4
  101. package/dist/Components/Messages/MailClient.d.ts +50 -0
  102. package/dist/Components/Messages/MailClient.js +410 -0
  103. package/dist/Components/Messages/MailColumns.d.ts +21 -0
  104. package/dist/Components/Messages/MailColumns.js +92 -0
  105. package/dist/Components/Messages/MailCompose.d.ts +46 -0
  106. package/dist/Components/Messages/MailCompose.js +154 -0
  107. package/dist/Components/Messages/MailReader.d.ts +43 -0
  108. package/dist/Components/Messages/MailReader.js +207 -0
  109. package/dist/Components/Messages/MailThread.d.ts +71 -0
  110. package/dist/Components/Messages/MailThread.js +185 -0
  111. package/dist/Components/Messages/MessageBell.d.ts +41 -0
  112. package/dist/Components/Messages/MessageBell.js +76 -0
  113. package/dist/Components/Viewers/DataTable.js +153 -49
  114. package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
  115. package/dist/Components/Viewers/DataTableColumns.js +2 -1
  116. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  117. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  118. package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
  119. package/dist/Components/Viewers/DataTableParts.js +26 -14
  120. package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
  121. package/dist/Components/Viewers/OwnerFiles.js +700 -0
  122. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  123. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  124. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  125. package/dist/Components/Viewers/RecordCards.js +509 -0
  126. package/dist/Helpers/ApiModules.js +4 -0
  127. package/dist/Helpers/AuditEntries.d.ts +50 -0
  128. package/dist/Helpers/AuditEntries.js +96 -0
  129. package/dist/Helpers/BusyHooks.d.ts +6 -0
  130. package/dist/Helpers/BusyHooks.js +39 -0
  131. package/dist/Helpers/ChatBackend.d.ts +8 -1
  132. package/dist/Helpers/ChatBackend.js +18 -0
  133. package/dist/Helpers/ChatHelpers.d.ts +2 -0
  134. package/dist/Helpers/ChatHelpers.js +4 -1
  135. package/dist/Helpers/CrudBackend.d.ts +20 -2
  136. package/dist/Helpers/CrudBackend.js +46 -2
  137. package/dist/Helpers/CrudColumns.d.ts +91 -6
  138. package/dist/Helpers/CrudColumns.js +148 -7
  139. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  140. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  141. package/dist/Helpers/CrudHelpers.d.ts +116 -2
  142. package/dist/Helpers/CrudHelpers.js +256 -0
  143. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  144. package/dist/Helpers/CrudNarrowPick.js +157 -0
  145. package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
  146. package/dist/Helpers/CrudPickerPlan.js +46 -0
  147. package/dist/Helpers/CrudRowForms.d.ts +32 -0
  148. package/dist/Helpers/CrudRowForms.js +76 -0
  149. package/dist/Helpers/CrudScopes.d.ts +32 -0
  150. package/dist/Helpers/CrudScopes.js +79 -0
  151. package/dist/Helpers/DashboardHelpers.d.ts +115 -0
  152. package/dist/Helpers/DashboardHelpers.js +305 -0
  153. package/dist/Helpers/DomainCards.d.ts +140 -0
  154. package/dist/Helpers/DomainCards.js +370 -0
  155. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  156. package/dist/Helpers/DropdownHelpers.js +6 -2
  157. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  158. package/dist/Helpers/ExcelHelpers.js +12 -11
  159. package/dist/Helpers/FileAdapter.d.ts +158 -0
  160. package/dist/Helpers/FileAdapter.js +173 -0
  161. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  162. package/dist/Helpers/FilePrimaries.js +138 -0
  163. package/dist/Helpers/FormConfirm.d.ts +32 -0
  164. package/dist/Helpers/FormConfirm.js +40 -0
  165. package/dist/Helpers/FormStore.d.ts +100 -5
  166. package/dist/Helpers/FormStore.js +368 -25
  167. package/dist/Helpers/InputConditions.d.ts +56 -0
  168. package/dist/Helpers/InputConditions.js +192 -0
  169. package/dist/Helpers/InputMemory.d.ts +106 -0
  170. package/dist/Helpers/InputMemory.js +395 -0
  171. package/dist/Helpers/InputNarrowing.d.ts +50 -0
  172. package/dist/Helpers/InputNarrowing.js +148 -0
  173. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  174. package/dist/Helpers/InputParamsHelpers.js +157 -74
  175. package/dist/Helpers/MailClientHelpers.d.ts +96 -0
  176. package/dist/Helpers/MailClientHelpers.js +211 -0
  177. package/dist/Helpers/MessageBackend.d.ts +152 -0
  178. package/dist/Helpers/MessageBackend.js +329 -0
  179. package/dist/Helpers/MessageHelpers.d.ts +160 -0
  180. package/dist/Helpers/MessageHelpers.js +573 -0
  181. package/dist/Helpers/MessageUnread.d.ts +95 -0
  182. package/dist/Helpers/MessageUnread.js +245 -0
  183. package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
  184. package/dist/Helpers/NexusDriveAdapter.js +236 -0
  185. package/dist/Helpers/NumberHelpers.d.ts +156 -0
  186. package/dist/Helpers/NumberHelpers.js +242 -0
  187. package/dist/Helpers/PagedPicker.d.ts +42 -0
  188. package/dist/Helpers/PagedPicker.js +85 -0
  189. package/dist/Helpers/Permissions.js +2 -1
  190. package/dist/Helpers/QrCode.d.ts +26 -0
  191. package/dist/Helpers/QrCode.js +463 -0
  192. package/dist/Helpers/RecordCards.d.ts +205 -0
  193. package/dist/Helpers/RecordCards.js +378 -0
  194. package/dist/Helpers/RequestCache.d.ts +14 -2
  195. package/dist/Helpers/RequestCache.js +22 -3
  196. package/dist/Helpers/RowsStore.d.ts +61 -2
  197. package/dist/Helpers/RowsStore.js +203 -16
  198. package/dist/Helpers/ServerPreview.d.ts +69 -0
  199. package/dist/Helpers/ServerPreview.js +99 -0
  200. package/dist/Helpers/TableColumns.js +4 -3
  201. package/dist/Helpers/TableExport.d.ts +7 -1
  202. package/dist/Helpers/TableExport.js +9 -3
  203. package/dist/Helpers/TableFilters.d.ts +1 -1
  204. package/dist/Helpers/TableFilters.js +24 -23
  205. package/dist/Helpers/TableHelpers.d.ts +38 -4
  206. package/dist/Helpers/TableHelpers.js +78 -10
  207. package/dist/Helpers/TableJoins.d.ts +13 -0
  208. package/dist/Helpers/TableJoins.js +34 -0
  209. package/dist/Helpers/TableStore.d.ts +28 -4
  210. package/dist/Helpers/TableStore.js +73 -11
  211. package/dist/Helpers/ToolbarFit.d.ts +23 -0
  212. package/dist/Helpers/ToolbarFit.js +30 -0
  213. package/dist/Helpers/TreeStore.d.ts +156 -6
  214. package/dist/Helpers/TreeStore.js +547 -18
  215. package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
  216. package/dist/Interfaces/ApiInterfaces.js +10 -1
  217. package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
  218. package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
  219. package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
  220. package/dist/Interfaces/DashboardInterfaces.js +1 -0
  221. package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
  222. package/dist/Interfaces/DomainInterfaces.js +1 -0
  223. package/dist/Interfaces/FileInterfaces.d.ts +183 -0
  224. package/dist/Interfaces/FileInterfaces.js +6 -0
  225. package/dist/Interfaces/FormInterfaces.d.ts +460 -32
  226. package/dist/Interfaces/FormInterfaces.js +79 -1
  227. package/dist/Interfaces/InputInterfaces.d.ts +22 -3
  228. package/dist/Interfaces/MailInterfaces.d.ts +192 -0
  229. package/dist/Interfaces/MailInterfaces.js +1 -0
  230. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  231. package/dist/Interfaces/RecordInterfaces.js +1 -0
  232. package/dist/Interfaces/TableInterfaces.d.ts +102 -3
  233. package/dist/Services/ExcelApi.js +5 -0
  234. package/dist/Services/FileApi.d.ts +13 -0
  235. package/dist/Services/FileApi.js +29 -0
  236. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  237. package/dist/Services/ImageEditorLoader.js +51 -0
  238. package/dist/Services/JoinSources.d.ts +79 -6
  239. package/dist/Services/JoinSources.js +132 -21
  240. package/dist/Services/MessageApi.d.ts +18 -0
  241. package/dist/Services/MessageApi.js +64 -0
  242. package/dist/Services/ModuleImport.d.ts +7 -0
  243. package/dist/Services/ModuleImport.js +22 -0
  244. package/dist/Shared.Index.d.ts +28 -0
  245. package/dist/Shared.Index.js +28 -0
  246. package/package.json +13 -2
  247. package/src/Styles/Nexus.Avatar.css +6 -5
  248. package/src/Styles/Nexus.Button.css +39 -4
  249. package/src/Styles/Nexus.Chat.css +114 -22
  250. package/src/Styles/Nexus.Choice.css +16 -0
  251. package/src/Styles/Nexus.Crud.css +486 -21
  252. package/src/Styles/Nexus.Dashboard.css +763 -0
  253. package/src/Styles/Nexus.Domain.css +384 -0
  254. package/src/Styles/Nexus.Dropdown.css +8 -0
  255. package/src/Styles/Nexus.Editor.css +20 -0
  256. package/src/Styles/Nexus.Files.css +437 -0
  257. package/src/Styles/Nexus.Form.css +5 -2
  258. package/src/Styles/Nexus.Index.css +7 -0
  259. package/src/Styles/Nexus.Input.css +73 -0
  260. package/src/Styles/Nexus.Mail.css +1062 -0
  261. package/src/Styles/Nexus.Menu.css +10 -1
  262. package/src/Styles/Nexus.Popup.css +135 -1
  263. package/src/Styles/Nexus.Print.css +288 -0
  264. package/src/Styles/Nexus.QrCode.css +12 -0
  265. package/src/Styles/Nexus.Record.css +430 -0
  266. package/src/Styles/Nexus.Tab.Buttons.css +87 -22
  267. package/src/Styles/Nexus.Table.css +1458 -1387
  268. package/src/Styles/Nexus.Theme.Picker.css +1 -0
  269. package/src/Styles/Nexus.Themes.css +12 -6
  270. package/src/Styles/Nexus.Tokens.css +8 -3
  271. package/src/Styles/Nexus.Tree.css +41 -1
@@ -3,15 +3,27 @@ export * from "./Components/Chats/ChatButton.tsx";
3
3
  export * from "./Components/Chats/ChatComposer.tsx";
4
4
  export * from "./Components/Chats/ChatConversations.tsx";
5
5
  export * from "./Components/Chats/ChatPanel.tsx";
6
+ export * from "./Components/Chats/ChatPopup.tsx";
6
7
  export * from "./Components/Chats/ChatParts.tsx";
7
8
  export * from "./Components/Chats/ChatThread.tsx";
9
+ export * from "./Components/Dashboards/DashboardBoard.tsx";
10
+ export * from "./Components/Dashboards/DashboardWidgets.tsx";
11
+ export * from "./Components/Dashboards/WidgetFrame.tsx";
12
+ export * from "./Components/Documents/DocumentFrame.tsx";
13
+ export * from "./Components/Domains/DomainCard.tsx";
8
14
  export * from "./Components/Documents/Menu.tsx";
9
15
  export * from "./Components/Documents/SplitButton.tsx";
10
16
  export * from "./Components/Documents/TabButtons.tsx";
17
+ export * from "./Components/Documents/Tabs.tsx";
11
18
  export * from "./Components/Forms/ApiForm.tsx";
12
19
  export * from "./Components/Forms/Crud.tsx";
20
+ export { openCrudForm, type CrudFormRequest } from "./Components/Forms/CrudActions.tsx";
13
21
  export * from "./Components/Forms/CrudHistory.tsx";
22
+ export * from "./Components/Forms/CrudNarrowPicker.tsx";
14
23
  export * from "./Components/Forms/CrudParts.tsx";
24
+ export * from "./Components/Forms/CrudRowForms.tsx";
25
+ export * from "./Components/Forms/CrudScopes.tsx";
26
+ export * from "./Components/Forms/CrudTree.tsx";
15
27
  export * from "./Components/Forms/ExcelImport.tsx";
16
28
  export * from "./Components/Forms/Form.tsx";
17
29
  export * from "./Components/Forms/SubmitForm.tsx";
@@ -27,6 +39,7 @@ export * from "./Components/Inputs/GroupForm.tsx";
27
39
  export * from "./Components/Inputs/InputRenderer.tsx";
28
40
  export * from "./Components/Inputs/MarkdownEditor.tsx";
29
41
  export * from "./Components/Inputs/NumberBox.tsx";
42
+ export * from "./Components/Inputs/PictureInput.tsx";
30
43
  export * from "./Components/Inputs/RadioGroup.tsx";
31
44
  export * from "./Components/Inputs/ReadOnlyNotice.tsx";
32
45
  export * from "./Components/Inputs/RichTextEditor.tsx";
@@ -45,17 +58,31 @@ export * from "./Components/Layouts/PopupRules.ts";
45
58
  export * from "./Components/Layouts/ThemePicker.tsx";
46
59
  export * from "./Components/Layouts/ThemeSwitcher.tsx";
47
60
  export * from "./Components/Layouts/Toaster.tsx";
61
+ export * from "./Components/Messages/MailClient.tsx";
62
+ export * from "./Components/Messages/MailColumns.tsx";
63
+ export * from "./Components/Messages/MailCompose.tsx";
64
+ export * from "./Components/Messages/MailReader.tsx";
65
+ export * from "./Components/Messages/MailThread.tsx";
66
+ export * from "./Components/Messages/MessageBell.tsx";
48
67
  export * from "./Components/Viewers/Avatar.tsx";
49
68
  export * from "./Components/Viewers/DataTable.tsx";
50
69
  export * from "./Components/Viewers/DataTableParts.tsx";
70
+ export * from "./Components/Viewers/OwnerFiles.tsx";
71
+ export * from "./Components/Viewers/RecordCards.tsx";
51
72
  export * from "./Helpers/ChatHooks.ts";
52
73
  export * from "./Helpers/DatePreferences.ts";
53
74
  export * from "./Helpers/DragHelpers.ts";
54
75
  export * from "./Helpers/PopoverHelpers.ts";
55
76
  export * from "./Helpers/PopoverHooks.ts";
77
+ export * from "./Helpers/ServerPreview.ts";
78
+ export { downloadText } from "./Helpers/TableExport.ts";
56
79
  export * from "./Helpers/UserPreferences.ts";
57
80
  export * from "./Services/BrowserApi.ts";
58
81
  export * from "./Services/ChatApi.ts";
59
82
  export * from "./Services/ExcelApi.ts";
83
+ export * from "./Services/FileApi.ts";
84
+ export * from "./Services/ImageEditorLoader.ts";
85
+ export * from "./Services/ModuleImport.ts";
60
86
  export * from "./Services/JoinSources.ts";
87
+ export * from "./Services/MessageApi.ts";
61
88
  export * from "./Services/ThemeService.ts";
@@ -6,15 +6,27 @@ export * from "./Components/Chats/ChatButton.js";
6
6
  export * from "./Components/Chats/ChatComposer.js";
7
7
  export * from "./Components/Chats/ChatConversations.js";
8
8
  export * from "./Components/Chats/ChatPanel.js";
9
+ export * from "./Components/Chats/ChatPopup.js";
9
10
  export * from "./Components/Chats/ChatParts.js";
10
11
  export * from "./Components/Chats/ChatThread.js";
12
+ export * from "./Components/Dashboards/DashboardBoard.js";
13
+ export * from "./Components/Dashboards/DashboardWidgets.js";
14
+ export * from "./Components/Dashboards/WidgetFrame.js";
15
+ export * from "./Components/Documents/DocumentFrame.js";
16
+ export * from "./Components/Domains/DomainCard.js";
11
17
  export * from "./Components/Documents/Menu.js";
12
18
  export * from "./Components/Documents/SplitButton.js";
13
19
  export * from "./Components/Documents/TabButtons.js";
20
+ export * from "./Components/Documents/Tabs.js";
14
21
  export * from "./Components/Forms/ApiForm.js";
15
22
  export * from "./Components/Forms/Crud.js";
23
+ export { openCrudForm } from "./Components/Forms/CrudActions.js";
16
24
  export * from "./Components/Forms/CrudHistory.js";
25
+ export * from "./Components/Forms/CrudNarrowPicker.js";
17
26
  export * from "./Components/Forms/CrudParts.js";
27
+ export * from "./Components/Forms/CrudRowForms.js";
28
+ export * from "./Components/Forms/CrudScopes.js";
29
+ export * from "./Components/Forms/CrudTree.js";
18
30
  export * from "./Components/Forms/ExcelImport.js";
19
31
  export * from "./Components/Forms/Form.js";
20
32
  export * from "./Components/Forms/SubmitForm.js";
@@ -30,6 +42,7 @@ export * from "./Components/Inputs/GroupForm.js";
30
42
  export * from "./Components/Inputs/InputRenderer.js";
31
43
  export * from "./Components/Inputs/MarkdownEditor.js";
32
44
  export * from "./Components/Inputs/NumberBox.js";
45
+ export * from "./Components/Inputs/PictureInput.js";
33
46
  export * from "./Components/Inputs/RadioGroup.js";
34
47
  export * from "./Components/Inputs/ReadOnlyNotice.js";
35
48
  export * from "./Components/Inputs/RichTextEditor.js";
@@ -48,19 +61,33 @@ export * from "./Components/Layouts/PopupRules.js";
48
61
  export * from "./Components/Layouts/ThemePicker.js";
49
62
  export * from "./Components/Layouts/ThemeSwitcher.js";
50
63
  export * from "./Components/Layouts/Toaster.js";
64
+ export * from "./Components/Messages/MailClient.js";
65
+ export * from "./Components/Messages/MailColumns.js";
66
+ export * from "./Components/Messages/MailCompose.js";
67
+ export * from "./Components/Messages/MailReader.js";
68
+ export * from "./Components/Messages/MailThread.js";
69
+ export * from "./Components/Messages/MessageBell.js";
51
70
  export * from "./Components/Viewers/Avatar.js";
52
71
  export * from "./Components/Viewers/DataTable.js";
53
72
  export * from "./Components/Viewers/DataTableParts.js";
73
+ export * from "./Components/Viewers/OwnerFiles.js";
74
+ export * from "./Components/Viewers/RecordCards.js";
54
75
  // Helpers
55
76
  export * from "./Helpers/ChatHooks.js";
56
77
  export * from "./Helpers/DatePreferences.js";
57
78
  export * from "./Helpers/DragHelpers.js";
58
79
  export * from "./Helpers/PopoverHelpers.js";
59
80
  export * from "./Helpers/PopoverHooks.js";
81
+ export * from "./Helpers/ServerPreview.js";
82
+ export { downloadText } from "./Helpers/TableExport.js";
60
83
  export * from "./Helpers/UserPreferences.js";
61
84
  // Services
62
85
  export * from "./Services/BrowserApi.js";
63
86
  export * from "./Services/ChatApi.js";
64
87
  export * from "./Services/ExcelApi.js";
88
+ export * from "./Services/FileApi.js";
89
+ export * from "./Services/ImageEditorLoader.js";
90
+ export * from "./Services/ModuleImport.js";
65
91
  export * from "./Services/JoinSources.js";
92
+ export * from "./Services/MessageApi.js";
66
93
  export * from "./Services/ThemeService.js";
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from "react";
1
2
  import type { ChatSnapshot, ChatStore } from "../../Helpers/ChatStore.ts";
2
3
  import type { ChatMessage, ChatParticipant } from "../../Interfaces/ChatInterfaces.ts";
3
4
  import { type MarkdownToolbarItem } from "../Inputs/MarkdownEditor.tsx";
@@ -32,10 +33,15 @@ export interface ChatComposerProps {
32
33
  height?: number;
33
34
  /** Why the user may not write here. */
34
35
  readOnlyMessage?: string;
36
+ /**
37
+ * What the writer should know before sending, in a line over the writing area that the writing area is described by:
38
+ * "You were sent a blind copy. Replying shows you to everyone."
39
+ */
40
+ notice?: ReactNode;
35
41
  /** Most characters in one message. Default 4,000. */
36
42
  maxLength?: number;
37
43
  autoFocus?: boolean;
38
44
  className?: string;
39
45
  }
40
46
  /** Writes and sends a message, in Markdown. */
41
- export declare function ChatComposer({ store, state, draftKey, placeholder, files, replyTo, onCancelReply, sendKey, mentions, mentionHref, toolbar, height, readOnlyMessage, maxLength, autoFocus, className, }: ChatComposerProps): import("react").JSX.Element;
47
+ export declare function ChatComposer({ store, state, draftKey, placeholder, files, replyTo, onCancelReply, sendKey, mentions, mentionHref, toolbar, height, readOnlyMessage, notice, maxLength, autoFocus, className, }: ChatComposerProps): import("react").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { iconPaperclip, iconSend, iconX, InlineIcon } from "nexus-icons";
4
- import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { iconInfoCircle, iconPaperclip, iconSend, iconX, InlineIcon } from "nexus-icons";
4
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
5
5
  import { applyChatMention, chatFileSize, findChatMention, matchChatMentions, readChatMentions } from "../../Helpers/ChatHelpers.js";
6
6
  import { cx } from "../../Helpers/ClassHelpers.js";
7
7
  import { continueMarkdownList } from "../../Helpers/MarkdownEditing.js";
@@ -54,8 +54,9 @@ function insideCodeFence(value, caret) {
54
54
  return (before.match(/^```/gm) ?? []).length % 2 === 1;
55
55
  }
56
56
  /** Writes and sends a message, in Markdown. */
57
- export function ChatComposer({ store, state, draftKey, placeholder = "Write a message…", files = false, replyTo, onCancelReply, sendKey = "enter", mentions, mentionHref, toolbar = CHAT_TOOLBAR, height = 84, readOnlyMessage = "You can read this conversation, but not write in it.", maxLength = 4000, autoFocus = false, className, }) {
57
+ export function ChatComposer({ store, state, draftKey, placeholder = "Write a message…", files = false, replyTo, onCancelReply, sendKey = "enter", mentions, mentionHref, toolbar = CHAT_TOOLBAR, height = 84, readOnlyMessage = "You can read this conversation, but not write in it.", notice, maxLength = 4000, autoFocus = false, className, }) {
58
58
  const boxRef = useRef(null);
59
+ const noticeId = useId();
59
60
  const fileRef = useRef(null);
60
61
  // The text lives here so Send, the draft, and clearing after a send all read the same thing. A message is short, so
61
62
  // a render per keystroke costs nothing — unlike the long documents the editor is otherwise built for.
@@ -169,9 +170,11 @@ export function ChatComposer({ store, state, draftKey, placeholder = "Write a me
169
170
  return (_jsx("div", { className: cx("nx-chat-composer nx-chat-composer--readonly", className), children: _jsx("p", { children: readOnlyMessage }) }));
170
171
  }
171
172
  const empty = text.trim() === "" && picked.length === 0;
172
- return (_jsxs("div", { className: cx("nx-chat-composer", className), children: [replyTo && (_jsxs("div", { className: "nx-chat-composer__reply", children: [_jsxs("span", { className: "nx-chat-composer__reply-text", children: [_jsx("span", { className: "nx-chat-composer__reply-author", children: replyTo.authorName || "Unknown" }), _jsx("span", { children: markdownToPlainText(replyTo.body) })] }), _jsx("button", { type: "button", onClick: onCancelReply, "aria-label": "Stop replying", children: _jsx(InlineIcon, { icon: iconX }) })] })), picked.length > 0 && (_jsx("ul", { className: "nx-chat-composer__files", children: picked.map((file, index) => (_jsxs("li", { children: [_jsx("span", { className: "nx-chat-composer__file-name", children: file.name }), _jsx("span", { className: "nx-chat-composer__file-size", children: chatFileSize(file.size) }), _jsx("button", { type: "button", onClick: () => setPicked(held => held.filter((_, at) => at !== index)), "aria-label": `Remove ${file.name}`, children: _jsx(InlineIcon, { icon: iconX }) })] }, `${file.name}-${index}`))) })), picking && (_jsx("ul", { className: "nx-chat-mentions", role: "listbox", "aria-label": "People", children: offered.map((person, index) => (_jsx("li", { children: _jsxs("button", { type: "button", className: cx("nx-chat-mentions__item", index === at && "is-at"), role: "option", "aria-selected": index === at,
173
+ return (_jsxs("div", { className: cx("nx-chat-composer", className), children: [notice && (_jsxs("p", { className: "nx-chat-composer__notice", id: noticeId, children: [_jsx(InlineIcon, { icon: iconInfoCircle }), _jsx("span", { children: notice })] })), replyTo && (_jsxs("div", { className: "nx-chat-composer__reply", children: [_jsxs("span", { className: "nx-chat-composer__reply-text", children: [_jsx("span", { className: "nx-chat-composer__reply-author", children: replyTo.authorName || "Unknown" }), _jsx("span", { children: markdownToPlainText(replyTo.body) })] }), _jsx("button", { type: "button", onClick: onCancelReply, "aria-label": "Stop replying", children: _jsx(InlineIcon, { icon: iconX }) })] })), picked.length > 0 && (_jsx("ul", { className: "nx-chat-composer__files", children: picked.map((file, index) => (_jsxs("li", { children: [_jsx("span", { className: "nx-chat-composer__file-name", children: file.name }), _jsx("span", { className: "nx-chat-composer__file-size", children: chatFileSize(file.size) }), _jsx("button", { type: "button", onClick: () => setPicked(held => held.filter((_, at) => at !== index)), "aria-label": `Remove ${file.name}`, children: _jsx(InlineIcon, { icon: iconX }) })] }, `${file.name}-${index}`))) })), picking && (_jsx("ul", { className: "nx-chat-mentions", role: "listbox", "aria-label": "People", children: offered.map((person, index) => (_jsx("li", { children: _jsxs("button", { type: "button", className: cx("nx-chat-mentions__item", index === at && "is-at"), role: "option", "aria-selected": index === at,
173
174
  // The editor must not lose the caret before the name goes in.
174
- onMouseDown: event => event.preventDefault(), onMouseEnter: () => setAt(index), onClick: () => putMention(person), children: [_jsx(ChatAvatar, { name: person.name, src: person.avatar, size: "sm" }), _jsx("span", { className: "nx-chat-mentions__name", children: person.name }), person.role && _jsx("span", { className: "nx-chat-mentions__role", children: person.role })] }) }, String(person.id)))) })), _jsx(MarkdownEditor, { ref: boxRef, className: "nx-chat-composer__editor", "aria-label": "Message", placeholder: placeholder, value: text, onValueChange: change, onKeyDown: keyDown,
175
+ onMouseDown: event => event.preventDefault(), onMouseEnter: () => setAt(index), onClick: () => putMention(person), children: [_jsx(ChatAvatar, { name: person.name, src: person.avatar, size: "sm" }), _jsx("span", { className: "nx-chat-mentions__name", children: person.name }), person.role && _jsx("span", { className: "nx-chat-mentions__role", children: person.role })] }) }, String(person.id)))) })), _jsx(MarkdownEditor, { ref: boxRef, className: "nx-chat-composer__editor", "aria-label": "Message", "aria-describedby": notice ? noticeId : undefined, placeholder: placeholder, value: text, onValueChange: change, onKeyDown: keyDown,
175
176
  // Moving the caret away from an `@…` closes the list, as clicking elsewhere in the text does.
176
- onSelect: () => setMention(mentions ? findChatMention(boxRef.current?.value ?? "", boxRef.current?.selectionStart ?? 0) : null), onBlur: () => setMention(null), modes: ["write", "preview"], toolbar: toolbar, height: height, minHeight: 72, maxHeight: 360, heightKey: "nx-chat", showCount: false, maxLength: maxLength }), _jsxs("div", { className: "nx-chat-composer__row", children: [_jsx("span", { className: "nx-chat-composer__hint", children: sendKey === "enter" ? "Enter sends · Shift+Enter for a line" : "Ctrl+Enter sends" }), files && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "nx-chat-composer__attach", onClick: () => fileRef.current?.click(), "aria-label": "Attach a file", title: "Attach a file", children: _jsx(InlineIcon, { icon: iconPaperclip }) }), _jsx("input", { ref: fileRef, type: "file", multiple: true, hidden: true, onChange: addFiles })] })), _jsxs("button", { type: "button", className: "nx-chat-composer__send", onClick: send, disabled: empty, "aria-label": "Send", title: sendKey === "enter" ? "Send (Enter)" : "Send (Ctrl+Enter)", children: [_jsx(InlineIcon, { icon: iconSend }), _jsx("span", { children: "Send" })] })] })] }));
177
+ onSelect: () => setMention(mentions ? findChatMention(boxRef.current?.value ?? "", boxRef.current?.selectionStart ?? 0) : null), onBlur: () => setMention(null), modes: ["write", "preview"], toolbar: toolbar,
178
+ // One row on a phone too: the buttons that do not fit go into a menu, so the thread above keeps its room.
179
+ toolbarOverflow: "menu", height: height, minHeight: 72, maxHeight: 360, heightKey: "nx-chat", showCount: false, maxLength: maxLength }), _jsxs("div", { className: "nx-chat-composer__row", children: [_jsx("span", { className: "nx-chat-composer__hint", children: sendKey === "enter" ? "Enter sends · Shift+Enter for a line" : "Ctrl+Enter sends" }), files && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "nx-chat-composer__attach", onClick: () => fileRef.current?.click(), "aria-label": "Attach a file", title: "Attach a file", children: _jsx(InlineIcon, { icon: iconPaperclip }) }), _jsx("input", { ref: fileRef, type: "file", multiple: true, hidden: true, onChange: addFiles })] })), _jsxs("button", { type: "button", className: "nx-chat-composer__send", onClick: send, disabled: empty, "aria-label": "Send", title: sendKey === "enter" ? "Send (Enter)" : "Send (Ctrl+Enter)", children: [_jsx(InlineIcon, { icon: iconSend }), _jsx("span", { children: "Send" })] })] })] }));
177
180
  }
@@ -40,6 +40,15 @@ export declare function ChatSystemNote({ message, time }: {
40
40
  message: ChatMessage;
41
41
  time: ChatTimeOptions;
42
42
  }): import("react").JSX.Element;
43
+ /**
44
+ * A notification (`kind: "notification"`): what an app wrote about an event, under the name of the person who acted -
45
+ * "Approved at Supervisor. Now with Accounts." It is a quiet line across the thread, not a bubble on a side: the bell,
46
+ * who acted and when, and the words under them, so a thread of replies reads around its events.
47
+ */
48
+ export declare function ChatEventNote({ message, time }: {
49
+ message: ChatMessage;
50
+ time: ChatTimeOptions;
51
+ }): import("react").JSX.Element;
43
52
  export interface ChatMessageProps {
44
53
  message: ChatMessage;
45
54
  /** This user wrote it: it sits on the right. */
@@ -76,10 +85,14 @@ export declare function ChatEmpty({ message, hint }: {
76
85
  message?: string;
77
86
  hint?: ReactNode;
78
87
  }): import("react").JSX.Element;
79
- /** Something that did not load, with a way to try again. */
80
- export declare function ChatFailure({ message, onRetry }: {
88
+ /**
89
+ * Something that did not load, with a way to try again. `flat`: its tint without an edge, for a failure that already
90
+ * sits in a framed panel (a dashboard widget).
91
+ */
92
+ export declare function ChatFailure({ message, onRetry, flat }: {
81
93
  message: string;
82
94
  onRetry?: () => void;
95
+ flat?: boolean;
83
96
  }): import("react").JSX.Element;
84
97
  /** Messages on their way in. */
85
98
  export declare function ChatLoading({ label }: {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { iconAlertCircle, iconCheck, iconChecks, iconCornerUpLeft, iconFile, iconLoader2, iconRefresh, iconTrash, iconX, InlineIcon } from "nexus-icons";
3
+ import { iconAlertCircle, iconBell, iconCheck, iconChecks, iconCornerUpLeft, iconFile, iconLoader2, iconRefresh, iconTrash, iconX, InlineIcon } from "nexus-icons";
4
4
  import { cx } from "../../Helpers/ClassHelpers.js";
5
5
  import { chatDeliveryState, chatFileSize, chatTimeLabel, sameChatId, CHAT_DELIVERY_LABELS } from "../../Helpers/ChatHelpers.js";
6
6
  import { markdownToPlainText } from "../../Helpers/MarkdownHelpers.js";
@@ -57,6 +57,14 @@ export function ChatQuote({ message, onOpen }) {
57
57
  export function ChatSystemNote({ message, time }) {
58
58
  return (_jsxs("div", { className: "nx-chat-note", children: [_jsx("span", { children: markdownToPlainText(message.body) }), _jsx("time", { dateTime: message.createdAt, children: chatTimeLabel(message.createdAt, time) })] }));
59
59
  }
60
+ /**
61
+ * A notification (`kind: "notification"`): what an app wrote about an event, under the name of the person who acted -
62
+ * "Approved at Supervisor. Now with Accounts." It is a quiet line across the thread, not a bubble on a side: the bell,
63
+ * who acted and when, and the words under them, so a thread of replies reads around its events.
64
+ */
65
+ export function ChatEventNote({ message, time }) {
66
+ return (_jsxs("div", { className: "nx-chat-event", children: [_jsx(InlineIcon, { icon: iconBell, className: "nx-chat-event__icon" }), _jsxs("div", { className: "nx-chat-event__text", children: [_jsxs("span", { className: "nx-chat-event__meta", children: [_jsx("span", { className: "nx-chat-event__author", children: message.authorName || "Unknown" }), _jsx("time", { dateTime: message.createdAt, children: chatTimeLabel(message.createdAt, time) })] }), message.body && _jsx(MarkdownView, { className: "nx-chat-event__body", value: message.body })] })] }));
67
+ }
60
68
  /**
61
69
  * One message. Its own messages sit on the right in the primary tint, everyone else's on the left on a surface. The
62
70
  * actions are one row of icon buttons that appear on hover or keyboard focus — and never move the message, so a long
@@ -86,9 +94,12 @@ export function ChatParticipantList({ participants, userId, empty = "Nobody else
86
94
  export function ChatEmpty({ message = "No messages yet.", hint }) {
87
95
  return (_jsxs("div", { className: "nx-chat-empty", children: [_jsx("p", { children: message }), hint && _jsx("p", { className: "nx-chat-empty__hint", children: hint })] }));
88
96
  }
89
- /** Something that did not load, with a way to try again. */
90
- export function ChatFailure({ message, onRetry }) {
91
- return (_jsxs("div", { className: "nx-chat-failure", role: "alert", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), _jsx("span", { children: message }), onRetry && (_jsxs("button", { type: "button", onClick: onRetry, children: [_jsx(InlineIcon, { icon: iconRefresh }), " Try again"] }))] }));
97
+ /**
98
+ * Something that did not load, with a way to try again. `flat`: its tint without an edge, for a failure that already
99
+ * sits in a framed panel (a dashboard widget).
100
+ */
101
+ export function ChatFailure({ message, onRetry, flat = false }) {
102
+ return (_jsxs("div", { className: flat ? "nx-chat-failure nx-chat-failure--flat" : "nx-chat-failure", role: "alert", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), _jsx("span", { children: message }), onRetry && (_jsxs("button", { type: "button", onClick: onRetry, children: [_jsx(InlineIcon, { icon: iconRefresh }), " Try again"] }))] }));
92
103
  }
93
104
  /** Messages on their way in. */
94
105
  export function ChatLoading({ label = "Loading…" }) {
@@ -0,0 +1,46 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ChatId, ChatMessage, ChatSource, ChatSummary } from "../../Interfaces/ChatInterfaces.ts";
3
+ import { type PopupSize, type PopupWindowState } from "../Layouts/Popup.tsx";
4
+ export interface ChatPopupProps {
5
+ open: boolean;
6
+ /** Asked to close: its close button, Esc inside it, or its bar on the dock. */
7
+ onClose: () => void;
8
+ /** Where the lines come from: a thread's discussion (`mailDiscussionSource(mail.threadSource(id))`), a chat's source. */
9
+ source: ChatSource;
10
+ /** The conversation: keys its lines, so two popups on one conversation never share a store by accident. */
11
+ conversationId: ChatId;
12
+ /** Who is reading: their lines sit on the right. */
13
+ userId: ChatId;
14
+ /** The title bar: the chat's name, "Discussion: Cable stock". */
15
+ title: ReactNode;
16
+ /** Muted after the title, and under it on the dock's bar: the thread it belongs to, the people. */
17
+ description?: ReactNode;
18
+ /** Its icon. Default the chat's. */
19
+ icon?: ReactNode;
20
+ /** What an empty conversation says. Default "No messages yet." */
21
+ emptyMessage?: string;
22
+ /** The reply box's placeholder. Default "Write a message…". */
23
+ placeholder?: string;
24
+ /** Where the unsent text is kept. Default `chat-popup:<conversation>:<reader>`. */
25
+ draftKey?: string;
26
+ /** Width: `xs` 360, `sm` 440 (default), `md` 560. */
27
+ size?: Extract<PopupSize, "xs" | "sm" | "md">;
28
+ /** Height, a CSS length. Default `30rem`, never taller than the window. */
29
+ height?: string;
30
+ /** The minimise button. Default true. */
31
+ minimizable?: boolean;
32
+ /** The window state, when the page controls it. */
33
+ windowState?: PopupWindowState;
34
+ onWindowStateChange?: (state: PopupWindowState) => void;
35
+ /** Every time the counts change, a read included: a list's row follows it. */
36
+ onSummary?: (summary: ChatSummary) => void;
37
+ /** After a line of the reader's own is saved. */
38
+ onSent?: (message: ChatMessage) => void;
39
+ /** Lines per page. Default 30. */
40
+ pageSize?: number;
41
+ /** Milliseconds between checks for new lines. 0 turns it off. Default 15,000. */
42
+ pollInterval?: number;
43
+ className?: string;
44
+ }
45
+ /** A conversation in a docked, minimisable window. */
46
+ export declare function ChatPopup({ open, onClose, source, conversationId, userId, title, description, icon, emptyMessage, placeholder, draftKey, size, height, minimizable, windowState, onWindowStateChange, onSummary, onSent, pageSize, pollInterval, className, }: ChatPopupProps): import("react").JSX.Element;
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { iconMessageCircle, InlineIcon } from "nexus-icons";
4
+ import { useEffect, useMemo, useRef, useState } from "react";
5
+ import { useChat } from "../../Helpers/ChatHooks.js";
6
+ import { Popup } from "../Layouts/Popup.js";
7
+ import { ChatComposer } from "./ChatComposer.js";
8
+ import { ChatThread } from "./ChatThread.js";
9
+ /** A conversation in a docked, minimisable window. */
10
+ export function ChatPopup({ open, onClose, source, conversationId, userId, title, description, icon, emptyMessage, placeholder = "Write a message…", draftKey, size = "sm", height = "30rem", minimizable = true, windowState, onWindowStateChange, onSummary, onSent, pageSize, pollInterval, className, }) {
11
+ const [replyTo, setReplyTo] = useState(null);
12
+ // Its own place for the store: the thread drawn as mail behind it reads every line, the popup its discussion.
13
+ const scope = useMemo(() => ({ domain: "chat-popup", parentId: conversationId, userId }), [conversationId, userId]);
14
+ const { store, state } = useChat({ scope, conversationId, source, pageSize, pollInterval, enabled: open });
15
+ const latest = useRef({ onSummary, onSent });
16
+ latest.current = { onSummary, onSent };
17
+ useEffect(() => {
18
+ if (state.loaded)
19
+ latest.current.onSummary?.(state.summary);
20
+ }, [state.loaded, state.summary]);
21
+ // A line of the reader's own, saved: the page may count it (a Discuss button's number).
22
+ const known = useRef(new Set());
23
+ useEffect(() => {
24
+ for (const message of state.messages) {
25
+ const key = String(message.clientId ?? message.id);
26
+ if (message.status !== "sent" || known.current.has(key))
27
+ continue;
28
+ known.current.add(key);
29
+ if (message.clientId && String(message.authorId) === String(userId))
30
+ latest.current.onSent?.(message);
31
+ }
32
+ }, [state.messages, userId]);
33
+ return (_jsx(Popup, { open: open, onClose: onClose, title: title, description: description, icon: icon ?? _jsx(InlineIcon, { icon: iconMessageCircle }), size: size, height: height, docked: true, minimizable: minimizable, movable: false, reopen: "restore", flush: true, windowState: windowState, onWindowStateChange: onWindowStateChange, className: className, children: _jsxs("div", { className: "nx-chat-panel nx-chat-popup", children: [_jsx("div", { className: "nx-chat-panel__body", children: _jsx(ChatThread, { store: store, state: state, userId: userId, onReply: setReplyTo, emptyMessage: emptyMessage }) }), _jsx(ChatComposer, { store: store, state: state, draftKey: draftKey ?? `chat-popup:${String(conversationId)}:${String(userId)}`, placeholder: placeholder, replyTo: replyTo, onCancelReply: () => setReplyTo(null), autoFocus: true })] }) }));
34
+ }
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from "react";
1
2
  import { type ChatTimeOptions } from "../../Helpers/ChatHelpers.ts";
2
3
  import type { ChatSnapshot, ChatStore } from "../../Helpers/ChatStore.ts";
3
4
  import type { ChatId, ChatMessage } from "../../Interfaces/ChatInterfaces.ts";
@@ -16,7 +17,14 @@ export interface ChatThreadProps {
16
17
  marksRead?: boolean;
17
18
  /** Shown when there is not a single message. */
18
19
  emptyMessage?: string;
20
+ /**
21
+ * What the conversation opens with, above its first message and scrolling with it - a mail thread's subject, people
22
+ * and record - so a tall head is read in the thread's one scroll, never in a box of its own. With one, the thread opens
23
+ * at its top, or at the first unread line when that would be out of view, and older pages load from the "Earlier
24
+ * messages" button only, so the lead stays where the reader scrolled it.
25
+ */
26
+ lead?: ReactNode;
19
27
  className?: string;
20
28
  }
21
29
  /** The messages of a conversation, in days and runs, with the lines that break them up. */
22
- export declare function ChatThread({ store, state, userId, time, replies, onReply, marksRead, emptyMessage, className }: ChatThreadProps): import("react").JSX.Element;
30
+ export declare function ChatThread({ store, state, userId, time, replies, onReply, marksRead, emptyMessage, lead, className }: ChatThreadProps): import("react").JSX.Element;
@@ -7,7 +7,7 @@ import { cx } from "../../Helpers/ClassHelpers.js";
7
7
  import { chatBadgeText, chatMessageKey, groupChatMessages, sameChatId } from "../../Helpers/ChatHelpers.js";
8
8
  import { useBrowserTimeZone, useDatePreferences } from "../../Helpers/DatePreferences.js";
9
9
  import { Button } from "../Documents/Button.js";
10
- import { ChatAvatar, ChatDayDivider, ChatEmpty, ChatFailure, ChatLoading, ChatMessageItem, ChatSystemNote, ChatUnreadLine } from "./ChatParts.js";
10
+ import { ChatAvatar, ChatDayDivider, ChatEmpty, ChatEventNote, ChatFailure, ChatLoading, ChatMessageItem, ChatSystemNote, ChatUnreadLine } from "./ChatParts.js";
11
11
  /*
12
12
  * The thread: the conversation itself, scrolling. It opens at the newest message, keeps itself at the bottom while the
13
13
  * reader is already there, and when they are not, it leaves the scroll alone and offers a button down to the newest
@@ -21,7 +21,7 @@ const EDGE = 32;
21
21
  /** How near the top loading the page above starts. */
22
22
  const TOP_EDGE = 120;
23
23
  /** The messages of a conversation, in days and runs, with the lines that break them up. */
24
- export function ChatThread({ store, state, userId, time, replies = true, onReply, marksRead = true, emptyMessage, className }) {
24
+ export function ChatThread({ store, state, userId, time, replies = true, onReply, marksRead = true, emptyMessage, lead, className }) {
25
25
  const preferences = useDatePreferences();
26
26
  const zone = useBrowserTimeZone();
27
27
  const settings = useMemo(() => ({ ...preferences, ...(zone && { timeZone: zone }), ...time }), [preferences, zone, time]);
@@ -32,6 +32,8 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
32
32
  const anchor = useRef(null);
33
33
  const lastKey = useRef(null);
34
34
  const count = useRef(0);
35
+ // With a lead, the first messages place the thread once: at its top, or at the first unread line.
36
+ const placed = useRef(false);
35
37
  const days = useMemo(() => groupChatMessages(state.messages, { userId, unreadFrom: state.unreadFrom, ...settings }), [state.messages, state.unreadFrom, userId, settings]);
36
38
  const byId = useMemo(() => new Map(state.messages.map(message => [String(message.id), message])), [state.messages]);
37
39
  const scrollToEnd = useCallback((smooth) => {
@@ -41,11 +43,44 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
41
43
  // Smooth only when the visitor and the system both allow motion; otherwise it jumps.
42
44
  list.scrollTo({ top: list.scrollHeight, behavior: smooth && smoothScrollAllowed() ? "smooth" : "auto" });
43
45
  }, []);
46
+ /** Whether the newest message is on screen: the reader is at the bottom. */
47
+ const measureBottom = useCallback(() => {
48
+ const list = listRef.current;
49
+ if (!list)
50
+ return;
51
+ const bottom = list.scrollHeight - list.scrollTop - list.clientHeight <= EDGE;
52
+ setAtBottom(bottom);
53
+ if (bottom)
54
+ setMissed(0);
55
+ }, []);
56
+ /**
57
+ * A thread with a lead opens at its top - the lead and the first messages - unless its first unread line would be out of
58
+ * view there; then that line opens at the top. The reader is at the bottom only if the newest message shows.
59
+ */
60
+ const placeFirst = useCallback(() => {
61
+ const list = listRef.current;
62
+ if (!list)
63
+ return;
64
+ const unread = list.querySelector(".nx-chat-unread-line");
65
+ const top = list.getBoundingClientRect().top;
66
+ const within = (element) => element.getBoundingClientRect().bottom - top + list.scrollTop;
67
+ const run = unread?.nextElementSibling instanceof HTMLElement ? unread.nextElementSibling : null;
68
+ list.scrollTop = unread && run && within(run) > list.clientHeight ? Math.max(0, within(unread) - unread.offsetHeight - 8) : 0;
69
+ measureBottom();
70
+ }, [measureBottom]);
44
71
  // New messages: stay at the bottom when already there, otherwise hold the place and count what came in.
45
72
  useLayoutEffect(() => {
46
73
  const list = listRef.current;
47
74
  if (!list)
48
75
  return;
76
+ if (lead && !placed.current && state.messages.length > 0 && !state.loading) {
77
+ placed.current = true;
78
+ count.current = state.messages.length;
79
+ const newest = state.messages[state.messages.length - 1];
80
+ lastKey.current = newest ? chatMessageKey(newest) : null;
81
+ placeFirst();
82
+ return;
83
+ }
49
84
  if (anchor.current) {
50
85
  // A page of older messages: the scroll keeps the message it was on, rather than jumping to the new top.
51
86
  list.scrollTop = anchor.current.top + (list.scrollHeight - anchor.current.height);
@@ -67,7 +102,18 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
67
102
  setMissed(value => value + 1);
68
103
  // `missed` is read, not depended on: a second message must not re-run the first one's scroll.
69
104
  // eslint-disable-next-line react-hooks/exhaustive-deps
70
- }, [state.messages, atBottom, userId, scrollToEnd]);
105
+ }, [state.messages, state.loading, atBottom, userId, scrollToEnd, lead, placeFirst]);
106
+ // A lead that grows (a record's card landing, its details unfolded) moves the newest message down: whether the reader is
107
+ // at the bottom is worked out again, so a line that arrives next does not pull the lead out of view.
108
+ const leadRef = useRef(null);
109
+ useLayoutEffect(() => {
110
+ const element = leadRef.current;
111
+ if (!element)
112
+ return;
113
+ const observer = new ResizeObserver(() => measureBottom());
114
+ observer.observe(element);
115
+ return () => observer.disconnect();
116
+ }, [lead !== undefined, measureBottom]); // eslint-disable-line react-hooks/exhaustive-deps
71
117
  // The conversation counts as read once its newest message has been on screen.
72
118
  useEffect(() => {
73
119
  if (!marksRead || !atBottom || state.loading || state.summary.unreadCount === 0)
@@ -82,13 +128,15 @@ export function ChatThread({ store, state, userId, time, replies = true, onReply
82
128
  setAtBottom(bottom);
83
129
  if (bottom)
84
130
  setMissed(0);
85
- if (list.scrollTop <= TOP_EDGE && state.hasMore && !state.loadingOlder) {
131
+ // Under a lead the top is the lead's: earlier pages come from their button, so the lead is not pushed away.
132
+ if (!lead && list.scrollTop <= TOP_EDGE && state.hasMore && !state.loadingOlder) {
86
133
  anchor.current = { height: list.scrollHeight, top: list.scrollTop };
87
134
  void store.loadOlder();
88
135
  }
89
- }, [state.hasMore, state.loadingOlder, store]);
136
+ }, [state.hasMore, state.loadingOlder, store, lead]);
90
137
  const quiet = !state.loading && state.messages.length === 0;
91
- return (_jsxs("div", { className: cx("nx-chat-thread", className), children: [_jsxs("div", { className: "nx-chat-thread__list", ref: listRef, onScroll: onScroll, role: "log", "aria-live": "polite", "aria-relevant": "additions", tabIndex: 0, children: [state.loadingOlder && _jsx(ChatLoading, { label: "Loading earlier messages\u2026" }), !state.loadingOlder && state.hasMore && (_jsx("div", { className: "nx-chat-thread__older", children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => void store.loadOlder(), children: "Earlier messages" }) })), state.loading && state.messages.length === 0 && _jsx(ChatLoading, { label: "Loading the conversation\u2026" }), state.error && _jsx(ChatFailure, { message: state.error, onRetry: () => void store.refresh() }), quiet && !state.error && _jsx(ChatEmpty, { message: emptyMessage ?? "No messages yet.", hint: _jsxs(_Fragment, { children: [_jsx(InlineIcon, { icon: iconMessageCircle }), " Write the first one below."] }) }), days.map(day => (_jsxs("section", { className: "nx-chat-thread__day", children: [_jsx(ChatDayDivider, { label: day.label }), day.runs.map((run, runIndex) => (_jsxs(Fragment, { children: [run.startsUnread && _jsx(ChatUnreadLine, { count: state.summary.unreadCount }), _jsx("div", { className: cx("nx-chat-run", run.mine && "nx-chat-run--mine", run.system && "nx-chat-run--system"), children: run.system ? (run.messages.map(message => _jsx(ChatSystemNote, { message: message, time: settings }, chatMessageKey(message)))) : (_jsxs(_Fragment, { children: [!run.mine && _jsx(ChatAvatarColumn, { name: run.authorName, src: run.authorAvatar }), _jsx("div", { className: "nx-chat-run__messages", children: run.messages.map((message, index) => (_jsx(ChatMessageItem, { message: message, mine: run.mine, first: index === 0, last: index === run.messages.length - 1, time: settings, participants: state.participants, userId: userId, quoted: message.replyToId ? byId.get(String(message.replyToId)) : undefined, onReply: replies && onReply ? onReply : undefined, onRemove: run.mine && state.features.remove ? message => void store.remove(message.id) : undefined, onRetry: store.retry, onDiscard: store.discard }, chatMessageKey(message)))) })] })) })] }, `${day.date}-${runIndex}`)))] }, day.date)))] }), !atBottom && (_jsxs("button", { type: "button", className: "nx-chat-thread__jump", onClick: () => scrollToEnd(true), "aria-label": missed > 0 ? `${missed} new, go to the newest` : "Go to the newest message", children: [_jsx(InlineIcon, { icon: iconArrowDown }), missed > 0 && _jsx("span", { className: "nx-chat-thread__jump-count", children: chatBadgeText(missed) })] }))] }));
138
+ const lines = (_jsxs(_Fragment, { children: [state.loadingOlder && _jsx(ChatLoading, { label: "Loading earlier messages\u2026" }), !state.loadingOlder && state.hasMore && (_jsx("div", { className: "nx-chat-thread__older", children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => void store.loadOlder(), children: "Earlier messages" }) })), state.loading && state.messages.length === 0 && _jsx(ChatLoading, { label: "Loading the conversation\u2026" }), state.error && _jsx(ChatFailure, { message: state.error, onRetry: () => void store.refresh() }), quiet && !state.error && _jsx(ChatEmpty, { message: emptyMessage ?? "No messages yet.", hint: _jsxs(_Fragment, { children: [_jsx(InlineIcon, { icon: iconMessageCircle }), " Write the first one below."] }) }), days.map(day => (_jsxs("section", { className: "nx-chat-thread__day", children: [_jsx(ChatDayDivider, { label: day.label }), day.runs.map((run, runIndex) => (_jsxs(Fragment, { children: [run.startsUnread && _jsx(ChatUnreadLine, { count: state.summary.unreadCount }), _jsx("div", { className: cx("nx-chat-run", run.mine && "nx-chat-run--mine", (run.system || run.notification) && "nx-chat-run--system"), children: run.system ? (run.messages.map(message => _jsx(ChatSystemNote, { message: message, time: settings }, chatMessageKey(message)))) : run.notification ? (run.messages.map(message => _jsx(ChatEventNote, { message: message, time: settings }, chatMessageKey(message)))) : (_jsxs(_Fragment, { children: [!run.mine && _jsx(ChatAvatarColumn, { name: run.authorName, src: run.authorAvatar }), _jsx("div", { className: "nx-chat-run__messages", children: run.messages.map((message, index) => (_jsx(ChatMessageItem, { message: message, mine: run.mine, first: index === 0, last: index === run.messages.length - 1, time: settings, participants: state.participants, userId: userId, quoted: message.replyToId ? byId.get(String(message.replyToId)) : undefined, onReply: replies && onReply ? onReply : undefined, onRemove: run.mine && state.features.remove ? message => void store.remove(message.id) : undefined, onRetry: store.retry, onDiscard: store.discard }, chatMessageKey(message)))) })] })) })] }, `${day.date}-${runIndex}`)))] }, day.date)))] }));
139
+ return (_jsxs("div", { className: cx("nx-chat-thread", className), children: [lead ? (_jsxs("div", { className: "nx-chat-thread__list", ref: listRef, onScroll: onScroll, tabIndex: 0, role: "region", "aria-label": "Thread", children: [_jsx("div", { ref: leadRef, className: "nx-chat-thread__lead", children: lead }), _jsx("div", { className: "nx-chat-thread__log", role: "log", "aria-live": "polite", "aria-relevant": "additions", children: lines })] })) : (_jsx("div", { className: "nx-chat-thread__list", ref: listRef, onScroll: onScroll, role: "log", "aria-live": "polite", "aria-relevant": "additions", tabIndex: 0, children: lines })), !atBottom && (_jsxs("button", { type: "button", className: "nx-chat-thread__jump", onClick: () => scrollToEnd(true), "aria-label": missed > 0 ? `${missed} new, go to the newest` : "Go to the newest message", children: [_jsx(InlineIcon, { icon: iconArrowDown }), missed > 0 && _jsx("span", { className: "nx-chat-thread__jump-count", children: chatBadgeText(missed) })] }))] }));
92
140
  }
93
141
  /** The avatar beside a run of somebody else's messages, kept out of the bubbles so they line up under each other. */
94
142
  function ChatAvatarColumn({ name, src }) {
@@ -0,0 +1,62 @@
1
+ import { type ReactNode } from "react";
2
+ import type { DashboardLayout, DashboardLayoutItem, DashboardSaveResult, DashboardVersion, WidgetDefinition, WidgetModuleLoader } from "../../Interfaces/DashboardInterfaces.ts";
3
+ export interface DashboardBoardProps {
4
+ /** The place this board is: `home`, or a module's dashboard (`message`, `staffing`). */
5
+ place: string;
6
+ /** The module whose `own` widgets may go here. Default: `place` (the Home has no module of that name, so none). */
7
+ placeModule?: string | null;
8
+ /** Every widget the app declares; the board draws those allowed here that the person may see. */
9
+ widgets: readonly WidgetDefinition<any>[];
10
+ /** Whether the person may see a widget now: the company holds its module, the person its key. Default: every widget. */
11
+ canSee?: (widget: WidgetDefinition<any>) => boolean;
12
+ /** The person's saved layout for this place, or `null` when they saved none (the default is drawn). */
13
+ layout?: DashboardLayout | null;
14
+ /** The saved layout's version, given back with a save so a stale one is refused. */
15
+ version?: DashboardVersion;
16
+ /** The layout drawn when the person has none, and what Reset goes back to: the company's for this place, else the code's. */
17
+ defaultLayout: DashboardLayout;
18
+ /**
19
+ * Saves the person's layout (the whole of it: widgets they cannot see now keep their places), with the version it was
20
+ * edited from. Answer `stale` with the saved layout when the version is old (the server's 409). Without it the board's
21
+ * edits last as long as the page.
22
+ */
23
+ onSave?: (layout: DashboardLayoutItem[], version: DashboardVersion) => Promise<DashboardSaveResult>;
24
+ /** Forgets the person's layout: Reset then Save. Without it, Reset saves the default as theirs through `onSave`. */
25
+ onReset?: (version: DashboardVersion) => Promise<DashboardSaveResult>;
26
+ /** Shows Customise. Default true. */
27
+ canCustomise?: boolean;
28
+ /** Offers "Save as the company's default" beside Save, with `onSaveCompanyDefault`. */
29
+ canSaveCompanyDefault?: boolean;
30
+ onSaveCompanyDefault?: (layout: DashboardLayoutItem[]) => Promise<DashboardSaveResult | void>;
31
+ /** The one call per module per draw, for widgets with `batch`. */
32
+ loadModule?: WidgetModuleLoader;
33
+ /** Data read with the page (on the server), by widget code: drawn at once, kept as if asked now. */
34
+ initialData?: Readonly<Record<string, unknown>>;
35
+ /** A module's name in the Add list. Default: the module's code with a capital. */
36
+ moduleLabel?: (module: string) => ReactNode;
37
+ /** Moves a link inside the board through the app's router (`router.push`). Default: the browser follows it. */
38
+ onNavigate?: (href: string) => void;
39
+ /** The board's heading. */
40
+ title?: ReactNode;
41
+ /** More controls in the board's bar, before Refresh and Customise. */
42
+ actions?: ReactNode;
43
+ /** How near the screen a widget starts to load, as a margin. Default `200px`. */
44
+ loadMargin?: string;
45
+ className?: string;
46
+ }
47
+ /**
48
+ * A dashboard of widgets on a 12-column grid, for one place (the Home, a module's dashboard). Each widget takes a size
49
+ * preset (S 3×1, M 6×2, L 6×4, W 12×2, T 12×4), the grid fills every gap (`grid-auto-flow: dense`), and a layout is
50
+ * an ordered list of `{ code, size }`; on a phone every widget is the full width, in order. Widgets load only when near
51
+ * the screen, each module asked once per draw (`loadModule`), kept for the page's life or their `refresh` seconds.
52
+ *
53
+ * Customise turns on edit mode: move by dragging a widget's handle or with the arrow keys on a focused widget, Enter or
54
+ * Space for its size menu (only its sizes), Delete to remove it; Add widget opens a drawer of the widgets this place
55
+ * may hold, by module; Save, Cancel, Reset to default. A save another window beat (`stale`) shows the layout saved
56
+ * there and says so.
57
+ *
58
+ * @example
59
+ * <DashboardBoard place="home" widgets={WIDGETS} canSee={canSee} layout={saved?.items} version={saved?.version}
60
+ * defaultLayout={HOME_DEFAULT} loadModule={loadWidgets} onSave={saveLayout} onReset={resetLayout} />
61
+ */
62
+ export declare function DashboardBoard({ place, placeModule, widgets, canSee, layout, version, defaultLayout, onSave, onReset, canCustomise, canSaveCompanyDefault, onSaveCompanyDefault, loadModule, initialData, moduleLabel, onNavigate, title, actions, loadMargin, className, }: DashboardBoardProps): import("react").JSX.Element;