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
@@ -1,14 +1,15 @@
1
+ import { EInputTypes } from "../Interfaces/FormInterfaces.js";
1
2
  import { addDays } from "./DateHelpers.js";
2
3
  import { fromLocalDateTime, toLocalDateTime, toUtcDateTime } from "./DateTimeHelpers.js";
3
4
  import { foldSearchText, searchWords } from "./DropdownHelpers.js";
4
- import { emptyInputValue, inputFieldName, inputValuesEqual, isEmptyInputValue } from "./InputParamsHelpers.js";
5
+ import { emptyInputValue, inputFieldName, inputTypeName, inputValuesEqual, isEmptyInputValue } from "./InputParamsHelpers.js";
5
6
  import { columnValue, readPath, searchTextOf } from "./TableHelpers.js";
6
7
  /*
7
8
  * A data table's advanced filter, with no React: input params as filters, the values that filter, a client table's
8
9
  * test of a row, and the names a server takes.
9
10
  */
10
11
  // Inputs whose value cannot filter a row, and names a Nexus page request already uses for its paging.
11
- const UNSUPPORTED = new Set(["markdown", "rich-text", "tree", "tabular", "group", "password"]);
12
+ const UNSUPPORTED = new Set([EInputTypes.Markdown, EInputTypes.RichText, EInputTypes.Tree, EInputTypes.Tabular, EInputTypes.Group, EInputTypes.Password, EInputTypes.Picture]);
12
13
  const RESERVED = new Set(["searchKeyword", "skipCount", "maxCount", "sorting", "sortType"]);
13
14
  const warned = new Set();
14
15
  function filterable(params) {
@@ -17,7 +18,7 @@ function filterable(params) {
17
18
  return true;
18
19
  if (!warned.has(params.name)) {
19
20
  warned.add(params.name);
20
- console.warn(`DataTable: the filter "${params.name}" is ignored: ${reserved ? "a page request already sends that name" : `a ${params.type} input cannot filter rows`}.`);
21
+ console.warn(`DataTable: the filter "${params.name}" is ignored: ${reserved ? "a page request already sends that name" : `a ${inputTypeName(params.type)} input cannot filter rows`}.`);
21
22
  }
22
23
  return false;
23
24
  }
@@ -62,12 +63,12 @@ function cellTest(params, value, column, bound) {
62
63
  // A moment's day is the one the table shows it on; any other value is read as an ISO date.
63
64
  const dayOf = (cell) => (column?.format === "date-time" ? toLocalDateTime(cell)?.date : String(cell).slice(0, 10));
64
65
  switch (params.type) {
65
- case "text":
66
- case "email":
67
- case "search":
68
- case "tel":
69
- case "url":
70
- case "textarea": {
66
+ case EInputTypes.Text:
67
+ case EInputTypes.Email:
68
+ case EInputTypes.Search:
69
+ case EInputTypes.Tel:
70
+ case EInputTypes.Url:
71
+ case EInputTypes.TextArea: {
71
72
  // Every word, folded as the search folds it, in the text the cell shows (an option's label, Yes or No).
72
73
  const words = searchWords(String(value));
73
74
  return (cell, row) => {
@@ -75,31 +76,31 @@ function cellTest(params, value, column, bound) {
75
76
  return words.every(word => text.includes(word));
76
77
  };
77
78
  }
78
- case "checkbox":
79
- case "switch":
80
- case "radio":
79
+ case EInputTypes.CheckBox:
80
+ case EInputTypes.Switch:
81
+ case EInputTypes.Radio:
81
82
  return cell => cell === true || cell === "true" || cell === 1;
82
- case "number":
83
- case "slider": {
83
+ case EInputTypes.Number:
84
+ case EInputTypes.Slider: {
84
85
  const number = Number(value);
85
86
  return cell => (bound === "From" ? Number(cell) >= number : bound === "To" ? Number(cell) <= number : Number(cell) === number);
86
87
  }
87
- case "range-slider": {
88
+ case EInputTypes.RangeSlider: {
88
89
  const [low, high] = value;
89
90
  return cell => Number(cell) >= low && Number(cell) <= high;
90
91
  }
91
- case "date":
92
+ case EInputTypes.Date:
92
93
  return cell => dayOf(cell) === value;
93
- case "date-range": {
94
+ case EInputTypes.DateRange: {
94
95
  const [from, to] = value;
95
96
  return cell => {
96
97
  const day = dayOf(cell);
97
98
  return day !== undefined && (!from || day >= from) && (!to || day <= to);
98
99
  };
99
100
  }
100
- case "time":
101
+ case EInputTypes.Time:
101
102
  return cell => String(cell).slice(0, 5) === String(value).slice(0, 5);
102
- case "date-time": {
103
+ case EInputTypes.DateTime: {
103
104
  // The same minute, however each side writes the moment ("…Z", "+05:45", or no zone for UTC).
104
105
  const minute = (moment) => toUtcDateTime(moment)?.slice(0, 16);
105
106
  const wanted = minute(value);
@@ -121,7 +122,7 @@ export function filterMatcher(columns, inputs, filters) {
121
122
  const tests = activeInputs(inputs, filters).map(([params, value]) => {
122
123
  const { name } = params;
123
124
  let column = columns.find(candidate => candidate.key === name);
124
- const [, key, bound] = (!column && params.type === "number" && /^(.+)(From|To)$/.exec(name)) || [];
125
+ const [, key, bound] = (!column && params.type === EInputTypes.Number && /^(.+)(From|To)$/.exec(name)) || [];
125
126
  column ??= columns.find(candidate => candidate.key === key);
126
127
  const test = cellTest(params, value, column, column ? bound : undefined);
127
128
  return (row) => {
@@ -149,11 +150,11 @@ export function filterRequest(filters, inputs, columns, timeZone) {
149
150
  if (to)
150
151
  request[`${name}Before`] = moments ? fromLocalDateTime(addDays(to, 1), "00:00", timeZone) : addDays(to, 1);
151
152
  };
152
- if (params.type === "date-range")
153
+ if (params.type === EInputTypes.DateRange)
153
154
  days(...value);
154
- else if (params.type === "date" && moments)
155
+ else if (params.type === EInputTypes.Date && moments)
155
156
  days(value, value);
156
- else if (params.type === "range-slider")
157
+ else if (params.type === EInputTypes.RangeSlider)
157
158
  [request[`${name}From`], request[`${name}To`]] = value;
158
159
  else
159
160
  request[name] = value;
@@ -1,6 +1,6 @@
1
1
  import type { DatePreferences } from "../Interfaces/DateInterfaces.ts";
2
2
  import type { InputParams } from "../Interfaces/FormInterfaces.ts";
3
- import type { TableColumn, TableColumnOption, TableHeadBand, TableKey, TablePage, TablePinSide, TableQuery, TableRow, TableSort, TableViewColumn } from "../Interfaces/TableInterfaces.ts";
3
+ import type { TableColumn, TableColumnOption, TableHeadBand, TableKey, TablePage, TablePinSide, TablePins, TableQuery, TableRow, TableSort, TableViewColumn } from "../Interfaces/TableInterfaces.ts";
4
4
  /** Rows per page when nothing says otherwise. */
5
5
  export declare const DEFAULT_PAGE_SIZE = 10;
6
6
  /** Choices of rows per page. */
@@ -26,12 +26,17 @@ export interface CellTextOptions {
26
26
  timeZone?: string;
27
27
  /** How numbers are written: a locale, or the person's settings (`numberLocale`, `useNumberLocale`). Default `en-US`. */
28
28
  locale?: string;
29
+ /** The currency of a `money` column without its own (`DataTableProps.currency`): "Rs.". */
30
+ currency?: string | null;
29
31
  }
30
32
  /**
31
33
  * A value as the cell writes it, in the column's format: "1,250.00", "Rs. 4,500.00", "12.5%", the date and time in the
32
- * user's settings, "Yes" or "No", an option's label. Empty values give "".
34
+ * user's settings, "Yes" or "No", an option's label. Empty values give "". `row` is the record, for a currency the
35
+ * column reads from it (`currency: row => row.currencyText`).
33
36
  */
34
- export declare function formatCellText<TRow extends TableRow>(column: TableColumn<TRow>, value: unknown, options?: CellTextOptions): string;
37
+ export declare function formatCellText<TRow extends TableRow>(column: TableColumn<TRow>, value: unknown, options?: CellTextOptions, row?: TRow): string;
38
+ /** A money column's currency: its own (a text, or read from the row), else the table's; "" for none. */
39
+ export declare function columnCurrency<TRow extends TableRow>(column: TableColumn<TRow>, options?: CellTextOptions, row?: TRow): string;
35
40
  /** Text the search looks in: the column's `text`, an option's label, or the value as text (dates as ISO text). */
36
41
  export declare function searchTextOf<TRow extends TableRow>(column: TableColumn<TRow>, row: TRow): string;
37
42
  /** Compares two values of a column for sorting: numbers as numbers, text as a person reads it ("Room 9" before "Room 10"). */
@@ -76,6 +81,19 @@ export declare const TABLE_ACTIONS_COLUMN = "#actions";
76
81
  export declare const MIN_COLUMN_WIDTH = 56;
77
82
  /** Whether a key names one of the table's own columns, not a column of the page's. */
78
83
  export declare const isOwnColumn: (key: string) => boolean;
84
+ /**
85
+ * The columns as the table arranges them before the user does: every `lead` column (a picture, a logo) first, the
86
+ * others in the order given. When the page pins other columns on the left, a lead column without a side of its own is
87
+ * pinned there too, so it still comes first. The same array when there is nothing to move.
88
+ */
89
+ export declare function leadColumns<TRow extends TableRow>(columns: readonly TableColumn<TRow>[]): readonly TableColumn<TRow>[];
90
+ /**
91
+ * The column whose text names a row when no `rowLabel` is given ("Delete Kathmandu?"): the first that is not a `lead`
92
+ * column, whose picture has no words.
93
+ */
94
+ export declare function namingColumn<TRow extends TableRow>(columns: readonly TableColumn<TRow>[]): TableColumn<TRow> | undefined;
95
+ /** The side a column is pinned to: the user's choice (`null` when they unpinned it), else its own `pinned`. */
96
+ export declare function columnPin(column: Pick<TableColumn, "key" | "pinned">, pinned: TablePins): TablePinSide | null;
79
97
  /**
80
98
  * The columns in the order the user put them: the keys of `order` first, in that order, then any column it does not
81
99
  * name, where the columns were given. A key of a column that is gone is left out.
@@ -91,11 +109,27 @@ export interface ColumnRights {
91
109
  }
92
110
  export interface ViewColumnsOptions extends ColumnRights {
93
111
  order?: readonly string[];
94
- pinned?: Readonly<Record<string, TablePinSide>>;
112
+ pinned?: TablePins;
95
113
  widths?: Readonly<Record<string, number>>;
96
114
  /** Pixels from the pinned side, measured from the columns before it. */
97
115
  offsets?: Readonly<Record<string, number>>;
116
+ /** Pinned columns that scroll for now, the table being too narrow for them (`releasedPins`): their `pin` is null. */
117
+ released?: ReadonlySet<string>;
98
118
  }
119
+ /**
120
+ * The most of a table's width on screen that the columns staying in place may take: the numbers, the checkboxes and the
121
+ * actions, which always stay, and the pinned columns. Past it the pinned ones give way (`releasedPins`), so a phone
122
+ * keeps at least 40% of the table for the columns that scroll.
123
+ */
124
+ export declare const PINNED_SHARE = 0.6;
125
+ /**
126
+ * Which pinned columns scroll for now, the table being narrow: the pinned ones stay from the outer edge in - the left
127
+ * side first - while they and the columns that always stay (`frame`: the numbers and checkboxes, the actions; their
128
+ * width in pixels) take at most `share` of `view`, the width on screen; every one past that scrolls with the rest,
129
+ * and so do the ones inside it on the same side. `left` and `right`: each side's pinned columns with their widths,
130
+ * from the outer edge in. A table not laid out (`view` 0, in a closed tab) releases none.
131
+ */
132
+ export declare function releasedPins(view: number, frame: number, left: readonly (readonly [string, number])[], right: readonly (readonly [string, number])[], share?: number): Set<string>;
99
133
  /**
100
134
  * The columns as the table draws them: in the user's order, with the pinned ones moved to their side (keeping their
101
135
  * order among themselves), each with its width, its distance from that edge, and what the user may do with it.
@@ -72,14 +72,18 @@ function toNumber(value) {
72
72
  }
73
73
  /**
74
74
  * A value as the cell writes it, in the column's format: "1,250.00", "Rs. 4,500.00", "12.5%", the date and time in the
75
- * user's settings, "Yes" or "No", an option's label. Empty values give "".
75
+ * user's settings, "Yes" or "No", an option's label. Empty values give "". `row` is the record, for a currency the
76
+ * column reads from it (`currency: row => row.currencyText`).
76
77
  */
77
- export function formatCellText(column, value, options = {}) {
78
+ export function formatCellText(column, value, options = {}, row) {
78
79
  const option = columnOption(column, value);
79
80
  if (option)
80
81
  return option.label;
81
82
  if (isEmpty(value))
82
83
  return "";
84
+ // A list of rows a record's details draw as a table (`column.table`): elsewhere, how many it holds.
85
+ if (column.table && Array.isArray(value))
86
+ return String(value.length);
83
87
  const { prefix = "", suffix = "" } = column;
84
88
  switch (column.format) {
85
89
  case "number":
@@ -90,7 +94,8 @@ export function formatCellText(column, value, options = {}) {
90
94
  return String(value);
91
95
  const decimals = column.decimals ?? (column.format === "money" ? 2 : undefined);
92
96
  const text = formatNumber(number, { decimals, fixedDecimals: column.format === "money" || column.decimals !== undefined, grouping: true, locale: options.locale });
93
- return `${prefix}${text}${column.format === "percent" && !suffix ? "%" : suffix}`;
97
+ const currency = column.format === "money" ? columnCurrency(column, options, row) : "";
98
+ return `${currency ? currency + " " : ""}${prefix}${text}${column.format === "percent" && !suffix ? "%" : suffix}`;
94
99
  }
95
100
  case "date":
96
101
  return formatDate(value, options.preferences) || String(value);
@@ -104,6 +109,11 @@ export function formatCellText(column, value, options = {}) {
104
109
  return `${prefix}${typeof value === "object" ? JSON.stringify(value) : String(value)}${suffix}`;
105
110
  }
106
111
  }
112
+ /** A money column's currency: its own (a text, or read from the row), else the table's; "" for none. */
113
+ export function columnCurrency(column, options = {}, row) {
114
+ const own = typeof column.currency === "function" ? (row ? column.currency(row) : undefined) : column.currency;
115
+ return (own ?? options.currency ?? "").trim();
116
+ }
107
117
  /** Text the search looks in: the column's `text`, an option's label, or the value as text (dates as ISO text). */
108
118
  export function searchTextOf(column, row) {
109
119
  if (column.text)
@@ -225,6 +235,30 @@ export const TABLE_ACTIONS_COLUMN = "#actions";
225
235
  export const MIN_COLUMN_WIDTH = 56;
226
236
  /** Whether a key names one of the table's own columns, not a column of the page's. */
227
237
  export const isOwnColumn = (key) => key.startsWith("#");
238
+ /**
239
+ * The columns as the table arranges them before the user does: every `lead` column (a picture, a logo) first, the
240
+ * others in the order given. When the page pins other columns on the left, a lead column without a side of its own is
241
+ * pinned there too, so it still comes first. The same array when there is nothing to move.
242
+ */
243
+ export function leadColumns(columns) {
244
+ if (!columns.some(column => column.lead))
245
+ return columns;
246
+ const pinsLeft = columns.some(column => !column.lead && column.pinned === "left");
247
+ const leads = columns.filter(column => column.lead).map(column => (pinsLeft && !column.pinned ? { ...column, pinned: "left" } : column));
248
+ const arranged = [...leads, ...columns.filter(column => !column.lead)];
249
+ return arranged.every((column, index) => column === columns[index]) ? columns : arranged;
250
+ }
251
+ /**
252
+ * The column whose text names a row when no `rowLabel` is given ("Delete Kathmandu?"): the first that is not a `lead`
253
+ * column, whose picture has no words.
254
+ */
255
+ export function namingColumn(columns) {
256
+ return columns.find(column => !column.lead) ?? columns[0];
257
+ }
258
+ /** The side a column is pinned to: the user's choice (`null` when they unpinned it), else its own `pinned`. */
259
+ export function columnPin(column, pinned) {
260
+ return Object.hasOwn(pinned, column.key) ? (pinned[column.key] ?? null) : (column.pinned ?? null);
261
+ }
228
262
  /**
229
263
  * The columns in the order the user put them: the keys of `order` first, in that order, then any column it does not
230
264
  * name, where the columns were given. A key of a column that is gone is left out.
@@ -256,18 +290,52 @@ export function moveColumnKey(keys, key, beforeKey) {
256
290
  rest.splice(at, 0, key);
257
291
  return rest;
258
292
  }
293
+ /**
294
+ * The most of a table's width on screen that the columns staying in place may take: the numbers, the checkboxes and the
295
+ * actions, which always stay, and the pinned columns. Past it the pinned ones give way (`releasedPins`), so a phone
296
+ * keeps at least 40% of the table for the columns that scroll.
297
+ */
298
+ export const PINNED_SHARE = 0.6;
299
+ /**
300
+ * Which pinned columns scroll for now, the table being narrow: the pinned ones stay from the outer edge in - the left
301
+ * side first - while they and the columns that always stay (`frame`: the numbers and checkboxes, the actions; their
302
+ * width in pixels) take at most `share` of `view`, the width on screen; every one past that scrolls with the rest,
303
+ * and so do the ones inside it on the same side. `left` and `right`: each side's pinned columns with their widths,
304
+ * from the outer edge in. A table not laid out (`view` 0, in a closed tab) releases none.
305
+ */
306
+ export function releasedPins(view, frame, left, right, share = PINNED_SHARE) {
307
+ const released = new Set();
308
+ if (!(view > 0))
309
+ return released;
310
+ const room = view * share;
311
+ let used = frame;
312
+ for (const side of [left, right]) {
313
+ let open = true;
314
+ for (const [key, width] of side) {
315
+ if (open && used + width <= room + 0.5)
316
+ used += width;
317
+ else {
318
+ open = false;
319
+ released.add(key);
320
+ }
321
+ }
322
+ }
323
+ return released;
324
+ }
259
325
  /**
260
326
  * The columns as the table draws them: in the user's order, with the pinned ones moved to their side (keeping their
261
327
  * order among themselves), each with its width, its distance from that edge, and what the user may do with it.
262
328
  */
263
329
  export function viewColumns(columns, options = {}) {
264
- const { order, pinned = {}, widths = {}, offsets = {}, reorderable = true, resizable = true } = options;
330
+ const { order, pinned = {}, widths = {}, offsets = {}, released, reorderable = true, resizable = true } = options;
265
331
  const groups = { left: [], middle: [], right: [] };
266
332
  for (const column of orderColumns(columns, order)) {
267
- const pin = pinned[column.key] ?? column.pinned ?? null;
268
- groups[pin === "left" ? "left" : pin === "right" ? "right" : "middle"].push({
333
+ const side = columnPin(column, pinned);
334
+ groups[side === "left" ? "left" : side === "right" ? "right" : "middle"].push({
269
335
  column,
270
- pin,
336
+ // A pinned column the table is too narrow for keeps its side and scrolls with the rest.
337
+ pin: side && !released?.has(column.key) ? side : null,
338
+ side,
271
339
  offset: offsets[column.key] ?? 0,
272
340
  edge: false,
273
341
  resizable: resizable && column.resizable !== false,
@@ -275,11 +343,11 @@ export function viewColumns(columns, options = {}) {
275
343
  width: widths[column.key],
276
344
  });
277
345
  }
278
- // The shadow that says columns scroll under a pinned side belongs to its last column.
279
- const last = groups.left[groups.left.length - 1];
346
+ // The shadow that says columns scroll under a pinned side belongs to its last column that stays.
347
+ const last = groups.left.filter(view => view.pin).at(-1);
280
348
  if (last)
281
349
  last.edge = true;
282
- const first = groups.right[0];
350
+ const first = groups.right.find(view => view.pin);
283
351
  if (first)
284
352
  first.edge = true;
285
353
  return [...groups.left, ...groups.middle, ...groups.right];
@@ -29,6 +29,12 @@ export declare class TableJoins {
29
29
  * in - or that it is missing, on its way, or could not be read. A key nobody has asked for yet reads as on its way.
30
30
  */
31
31
  peek(name: string, key: string): JoinedState;
32
+ /**
33
+ * The record kept for `key` while its answer is fresh - within `lifetime`, and not marked old since (`expire`: a save
34
+ * there, a Reload) - at once and with no call; `undefined` otherwise. What names a value from a form's first draw
35
+ * without standing in for a record that may have changed since.
36
+ */
37
+ peekFresh(name: string, key: string, lifetime?: number): TableJoinedRecord | undefined;
32
38
  /**
33
39
  * Asks for every key the cache cannot answer fresh. The keys asked for in one moment - by every table and input on
34
40
  * the page - go in one call per source, after the moment is over; a key already on its way is not asked again.
@@ -36,6 +42,11 @@ export declare class TableJoins {
36
42
  request(source: TableJoinSource, keys: Iterable<string>): void;
37
43
  private queue;
38
44
  private send;
45
+ /**
46
+ * Records the page already holds, kept as if a lookup of `source` had just answered them: a form opened on a record
47
+ * whose parent the page shows - a tree's row - names that parent with no call.
48
+ */
49
+ remember(source: TableJoinSource, records: readonly TableJoinedRecord[]): void;
39
50
  /**
40
51
  * The records behind these keys, once they are in: what a picker shows for the values a form already holds. The
41
52
  * calls and the answers are the ones the tables share, so a form opened from a table costs nothing more.
@@ -45,6 +56,8 @@ export declare class TableJoins {
45
56
  * A whole list kept under `name` - a picker's options - loaded once per lifetime; a second ask while it loads waits for
46
57
  * the same call. A list that failed is not kept, so the next ask tries again.
47
58
  */
59
+ /** The list kept under `name` while it is fresh, without loading it; `undefined` when there is none yet, or it aged. */
60
+ peekList(name: string, lifetime?: number): readonly TableJoinedRecord[] | undefined;
48
61
  list(name: string, load: (signal: AbortSignal) => Promise<readonly TableJoinedRecord[]>, lifetime?: number): Promise<readonly TableJoinedRecord[]>;
49
62
  /**
50
63
  * Marks answers as old - a source's, some of its keys, or every one - after the records behind them may have changed:
@@ -65,6 +65,15 @@ export class TableJoins {
65
65
  return FAILED;
66
66
  return LOADING;
67
67
  }
68
+ /**
69
+ * The record kept for `key` while its answer is fresh - within `lifetime`, and not marked old since (`expire`: a save
70
+ * there, a Reload) - at once and with no call; `undefined` otherwise. What names a value from a form's first draw
71
+ * without standing in for a record that may have changed since.
72
+ */
73
+ peekFresh(name, key, lifetime = JOIN_LIFETIME) {
74
+ const entry = this.sources.get(name)?.entries.get(key);
75
+ return entry?.record && Date.now() - entry.at < lifetime ? entry.record : undefined;
76
+ }
68
77
  /**
69
78
  * Asks for every key the cache cannot answer fresh. The keys asked for in one moment - by every table and input on
70
79
  * the page - go in one call per source, after the moment is over; a key already on its way is not asked again.
@@ -136,6 +145,26 @@ export class TableJoins {
136
145
  this.notify();
137
146
  }
138
147
  }
148
+ /**
149
+ * Records the page already holds, kept as if a lookup of `source` had just answered them: a form opened on a record
150
+ * whose parent the page shows - a tree's row - names that parent with no call.
151
+ */
152
+ remember(source, records) {
153
+ const cache = this.cacheOf(source.name);
154
+ const keyField = source.key ?? "id";
155
+ const at = Date.now();
156
+ let changed = false;
157
+ for (const record of records) {
158
+ const key = record[keyField];
159
+ if (key === undefined || key === null || key === "")
160
+ continue;
161
+ cache.entries.set(String(key), { record, at });
162
+ cache.failedAt.delete(String(key));
163
+ changed = true;
164
+ }
165
+ if (changed)
166
+ this.notify();
167
+ }
139
168
  /**
140
169
  * The records behind these keys, once they are in: what a picker shows for the values a form already holds. The
141
170
  * calls and the answers are the ones the tables share, so a form opened from a table costs nothing more.
@@ -157,6 +186,11 @@ export class TableJoins {
157
186
  * A whole list kept under `name` - a picker's options - loaded once per lifetime; a second ask while it loads waits for
158
187
  * the same call. A list that failed is not kept, so the next ask tries again.
159
188
  */
189
+ /** The list kept under `name` while it is fresh, without loading it; `undefined` when there is none yet, or it aged. */
190
+ peekList(name, lifetime = JOIN_LIFETIME) {
191
+ const kept = this.lists.get(name);
192
+ return kept?.rows && Date.now() - kept.at < lifetime ? kept.rows : undefined;
193
+ }
160
194
  list(name, load, lifetime = JOIN_LIFETIME) {
161
195
  const kept = this.lists.get(name);
162
196
  if (kept?.pending)
@@ -1,5 +1,5 @@
1
1
  import type { InputParams } from "../Interfaces/FormInterfaces.ts";
2
- import type { TableColumn, TableKey, TableLoader, TablePaging, TablePinSide, TableRow, TableSort, TableState } from "../Interfaces/TableInterfaces.ts";
2
+ import type { TableColumn, TableKey, TableLoader, TablePaging, TablePinSide, TablePins, TableRow, TableSort, TableState } from "../Interfaces/TableInterfaces.ts";
3
3
  /** What the table component tells its store on every render. */
4
4
  export interface TableStoreOptions<TRow extends TableRow = TableRow> {
5
5
  columns: readonly TableColumn<TRow>[];
@@ -10,6 +10,8 @@ export interface TableStoreOptions<TRow extends TableRow = TableRow> {
10
10
  paging: TablePaging;
11
11
  maxSelection?: number;
12
12
  onSelectionChange?: (rows: TRow[]) => void;
13
+ /** After the picked row changes: another row, its newest copy, or none (`DataTable` `onPick`). */
14
+ onPickChange?: (row: TRow | null) => void;
13
15
  /** The advanced filter's inputs, as the table prepared them (`asFilterInput`). */
14
16
  filterInputs?: readonly InputParams[];
15
17
  }
@@ -63,14 +65,19 @@ export declare class TableStore<TRow extends TableRow = TableRow> {
63
65
  /** Takes the component's props. Tells nobody: the component calls it while rendering. */
64
66
  configure(options: TableStoreOptions<TRow>): void;
65
67
  private set;
66
- /** Puts back what was saved on this device (page size, sort, columns), before the first load. Tells nobody. */
68
+ /**
69
+ * Puts back what was saved on this device (page size, sort, columns): before the first load, telling nobody, or, with
70
+ * `notify`, over a table already shown (another key's settings), its rows shown again in them on the first page.
71
+ */
67
72
  restore(saved: {
68
73
  pageSize?: number;
69
74
  sort?: TableSort | null;
70
75
  hidden?: readonly string[];
71
76
  order?: readonly string[];
72
77
  widths?: Readonly<Record<string, number>>;
73
- pinned?: Readonly<Record<string, TablePinSide>>;
78
+ pinned?: TablePins;
79
+ }, options?: {
80
+ notify?: boolean;
74
81
  }): void;
75
82
  /** Shows these rows: every row there is. The table searches, sorts, and pages them. */
76
83
  setRows(rows: readonly TRow[]): void;
@@ -126,6 +133,19 @@ export declare class TableStore<TRow extends TableRow = TableRow> {
126
133
  private refreshSelected;
127
134
  /** A client table forgets selected rows that are gone from its rows. */
128
135
  private dropMissingSelection;
136
+ /**
137
+ * Picks a row (`DataTable` `onPick`), or none: the one row whose own records the page shows beside the table. It
138
+ * follows the record from then on - its newest copy after an upsert, none once it is removed or gone after a load -
139
+ * and every change is told to `onPickChange`.
140
+ */
141
+ pick(row: TRow | null): void;
142
+ /**
143
+ * The picked row as these rows hold it: its newest copy, or none when they are every row there is (`whole`, a client
144
+ * table) and it is not among them. A server page without it keeps it, since it may be on another page.
145
+ */
146
+ private followPicked;
147
+ /** Tells the page when a change left another copy of the picked row, or none. */
148
+ private pickedChanged;
129
149
  setBusy(keys: readonly TableKey[], on: boolean): void;
130
150
  flash(keys: readonly TableKey[]): void;
131
151
  setHidden(key: string, hidden: boolean): void;
@@ -139,7 +159,11 @@ export declare class TableStore<TRow extends TableRow = TableRow> {
139
159
  setWidths(widths: Readonly<Record<string, number>>): void;
140
160
  /** Back to widths that follow the content. */
141
161
  clearWidths(): void;
142
- /** Keeps a column on the left or the right while the others scroll, or lets it scroll again (`null`). */
162
+ /**
163
+ * Keeps a column on the left or the right while the others scroll, or lets it scroll again (`null`). A choice equal
164
+ * to the column's own `pinned` is no change to remember; unpinning a column its params pin is remembered as `null`,
165
+ * which wins over them.
166
+ */
143
167
  setPinned(key: string, side: TablePinSide | null): void;
144
168
  /** Every column back as its params say: the given order, widths from the content, no pins, and the hidden ones. */
145
169
  resetColumns(): void;