nexus-shared 1.1.20 → 2.0.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 (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +903 -0
  3. package/dist/Client.Index.d.ts +44 -0
  4. package/dist/Client.Index.js +49 -0
  5. package/dist/Components/Documents/Button.d.ts +29 -0
  6. package/dist/Components/Documents/Button.js +11 -0
  7. package/dist/Components/Documents/Menu.d.ts +102 -0
  8. package/dist/Components/Documents/Menu.js +238 -0
  9. package/dist/Components/Documents/SplitButton.d.ts +64 -0
  10. package/dist/Components/Documents/SplitButton.js +127 -0
  11. package/dist/Components/Documents/TabButtons.d.ts +62 -0
  12. package/dist/Components/Documents/TabButtons.js +19 -0
  13. package/dist/Components/Forms/ApiForm.d.ts +21 -0
  14. package/dist/Components/Forms/ApiForm.js +99 -0
  15. package/dist/Components/Forms/Crud.d.ts +17 -0
  16. package/dist/Components/Forms/Crud.js +556 -0
  17. package/dist/Components/Forms/CrudParts.d.ts +15 -0
  18. package/dist/Components/Forms/CrudParts.js +36 -0
  19. package/dist/Components/Forms/Form.d.ts +52 -0
  20. package/dist/Components/Forms/Form.js +319 -0
  21. package/dist/Components/Forms/SubmitForm.d.ts +65 -0
  22. package/dist/Components/Forms/SubmitForm.js +198 -0
  23. package/dist/Components/Inputs/Calendar.d.ts +69 -0
  24. package/dist/Components/Inputs/Calendar.js +258 -0
  25. package/dist/Components/Inputs/CheckBox.d.ts +53 -0
  26. package/dist/Components/Inputs/CheckBox.js +87 -0
  27. package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
  28. package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
  29. package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
  30. package/dist/Components/Inputs/ChoiceField.js +169 -0
  31. package/dist/Components/Inputs/DatePicker.d.ts +54 -0
  32. package/dist/Components/Inputs/DatePicker.js +246 -0
  33. package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
  34. package/dist/Components/Inputs/DateRangePicker.js +283 -0
  35. package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
  36. package/dist/Components/Inputs/DateTimePicker.js +399 -0
  37. package/dist/Components/Inputs/Dropdown.d.ts +117 -0
  38. package/dist/Components/Inputs/Dropdown.js +839 -0
  39. package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
  40. package/dist/Components/Inputs/DropdownParts.js +102 -0
  41. package/dist/Components/Inputs/EditorShell.d.ts +45 -0
  42. package/dist/Components/Inputs/EditorShell.js +214 -0
  43. package/dist/Components/Inputs/Field.d.ts +53 -0
  44. package/dist/Components/Inputs/Field.js +16 -0
  45. package/dist/Components/Inputs/GroupForm.d.ts +20 -0
  46. package/dist/Components/Inputs/GroupForm.js +170 -0
  47. package/dist/Components/Inputs/InputField.d.ts +59 -0
  48. package/dist/Components/Inputs/InputField.js +169 -0
  49. package/dist/Components/Inputs/InputRenderer.d.ts +35 -0
  50. package/dist/Components/Inputs/InputRenderer.js +99 -0
  51. package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
  52. package/dist/Components/Inputs/MarkdownEditor.js +289 -0
  53. package/dist/Components/Inputs/NumberBox.d.ts +53 -0
  54. package/dist/Components/Inputs/NumberBox.js +228 -0
  55. package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
  56. package/dist/Components/Inputs/RadioGroup.js +43 -0
  57. package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
  58. package/dist/Components/Inputs/ReadOnlyNotice.js +116 -0
  59. package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
  60. package/dist/Components/Inputs/RichTextEditor.js +1170 -0
  61. package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
  62. package/dist/Components/Inputs/RichTextParts.js +157 -0
  63. package/dist/Components/Inputs/RowsInput.d.ts +104 -0
  64. package/dist/Components/Inputs/RowsInput.js +259 -0
  65. package/dist/Components/Inputs/Slider.d.ts +87 -0
  66. package/dist/Components/Inputs/Slider.js +216 -0
  67. package/dist/Components/Inputs/TabularForm.d.ts +23 -0
  68. package/dist/Components/Inputs/TabularForm.js +283 -0
  69. package/dist/Components/Inputs/TextArea.d.ts +43 -0
  70. package/dist/Components/Inputs/TextArea.js +31 -0
  71. package/dist/Components/Inputs/TextBox.d.ts +52 -0
  72. package/dist/Components/Inputs/TextBox.js +58 -0
  73. package/dist/Components/Inputs/TextField.d.ts +31 -0
  74. package/dist/Components/Inputs/TextField.js +35 -0
  75. package/dist/Components/Inputs/TimePanel.d.ts +34 -0
  76. package/dist/Components/Inputs/TimePanel.js +288 -0
  77. package/dist/Components/Inputs/TimePicker.d.ts +54 -0
  78. package/dist/Components/Inputs/TimePicker.js +239 -0
  79. package/dist/Components/Inputs/TreeView.d.ts +160 -0
  80. package/dist/Components/Inputs/TreeView.js +947 -0
  81. package/dist/Components/Layouts/MessageBox.d.ts +71 -0
  82. package/dist/Components/Layouts/MessageBox.js +104 -0
  83. package/dist/Components/Layouts/Popup.d.ts +92 -0
  84. package/dist/Components/Layouts/Popup.js +376 -0
  85. package/dist/Components/Layouts/PopupContext.d.ts +2 -0
  86. package/dist/Components/Layouts/PopupContext.js +4 -0
  87. package/dist/Components/Layouts/PopupDock.d.ts +22 -0
  88. package/dist/Components/Layouts/PopupDock.js +121 -0
  89. package/dist/Components/Layouts/PopupHost.d.ts +56 -0
  90. package/dist/Components/Layouts/PopupHost.js +163 -0
  91. package/dist/Components/Layouts/PopupRules.d.ts +30 -0
  92. package/dist/Components/Layouts/PopupRules.js +63 -0
  93. package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
  94. package/dist/Components/Layouts/ThemePicker.js +22 -0
  95. package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
  96. package/dist/Components/Layouts/ThemeScript.js +13 -0
  97. package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
  98. package/dist/Components/Layouts/ThemeSwitcher.js +29 -0
  99. package/dist/Components/Layouts/Toaster.d.ts +76 -0
  100. package/dist/Components/Layouts/Toaster.js +172 -0
  101. package/dist/Components/Viewers/DataTable.d.ts +25 -0
  102. package/dist/Components/Viewers/DataTable.js +542 -0
  103. package/dist/Components/Viewers/DataTableParts.d.ts +134 -0
  104. package/dist/Components/Viewers/DataTableParts.js +145 -0
  105. package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
  106. package/dist/Components/Viewers/MarkdownView.js +79 -0
  107. package/dist/Components/Viewers/RichTextView.d.ts +18 -0
  108. package/dist/Components/Viewers/RichTextView.js +17 -0
  109. package/dist/Helpers/AnimationHelpers.d.ts +6 -0
  110. package/dist/Helpers/AnimationHelpers.js +35 -0
  111. package/dist/Helpers/ApiClient.d.ts +74 -0
  112. package/dist/Helpers/ApiClient.js +197 -0
  113. package/dist/Helpers/ApiFormHelpers.d.ts +26 -0
  114. package/dist/Helpers/ApiFormHelpers.js +56 -0
  115. package/dist/Helpers/ApiModules.d.ts +135 -0
  116. package/dist/Helpers/ApiModules.js +194 -0
  117. package/dist/Helpers/ApiResponses.d.ts +41 -0
  118. package/dist/Helpers/ApiResponses.js +159 -0
  119. package/dist/Helpers/ApiRoutes.d.ts +150 -0
  120. package/dist/Helpers/ApiRoutes.js +191 -0
  121. package/dist/Helpers/ApiToasts.d.ts +21 -0
  122. package/dist/Helpers/ApiToasts.js +22 -0
  123. package/dist/Helpers/ClassHelpers.d.ts +3 -0
  124. package/dist/Helpers/ClassHelpers.js +4 -0
  125. package/dist/Helpers/CrudBackend.d.ts +56 -0
  126. package/dist/Helpers/CrudBackend.js +119 -0
  127. package/dist/Helpers/CrudHelpers.d.ts +76 -0
  128. package/dist/Helpers/CrudHelpers.js +202 -0
  129. package/dist/Helpers/DateHelpers.d.ts +88 -0
  130. package/dist/Helpers/DateHelpers.js +363 -0
  131. package/dist/Helpers/DatePreferences.d.ts +14 -0
  132. package/dist/Helpers/DatePreferences.js +94 -0
  133. package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
  134. package/dist/Helpers/DateTimeHelpers.js +286 -0
  135. package/dist/Helpers/DragHelpers.d.ts +129 -0
  136. package/dist/Helpers/DragHelpers.js +281 -0
  137. package/dist/Helpers/DropdownHelpers.d.ts +56 -0
  138. package/dist/Helpers/DropdownHelpers.js +180 -0
  139. package/dist/Helpers/FormDrafts.d.ts +18 -0
  140. package/dist/Helpers/FormDrafts.js +76 -0
  141. package/dist/Helpers/FormStore.d.ts +231 -0
  142. package/dist/Helpers/FormStore.js +734 -0
  143. package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
  144. package/dist/Helpers/InputParamsHelpers.js +388 -0
  145. package/dist/Helpers/MarkdownEditing.d.ts +27 -0
  146. package/dist/Helpers/MarkdownEditing.js +250 -0
  147. package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
  148. package/dist/Helpers/MarkdownHelpers.js +775 -0
  149. package/dist/Helpers/MessageBuilder.d.ts +74 -0
  150. package/dist/Helpers/MessageBuilder.js +254 -0
  151. package/dist/Helpers/NepaliCalendar.d.ts +25 -0
  152. package/dist/Helpers/NepaliCalendar.js +107 -0
  153. package/dist/Helpers/NumberHelpers.d.ts +71 -0
  154. package/dist/Helpers/NumberHelpers.js +202 -0
  155. package/dist/Helpers/Permissions.d.ts +62 -0
  156. package/dist/Helpers/Permissions.js +133 -0
  157. package/dist/Helpers/PopoverHelpers.d.ts +43 -0
  158. package/dist/Helpers/PopoverHelpers.js +91 -0
  159. package/dist/Helpers/RichTextHelpers.d.ts +77 -0
  160. package/dist/Helpers/RichTextHelpers.js +1072 -0
  161. package/dist/Helpers/RowsStore.d.ts +160 -0
  162. package/dist/Helpers/RowsStore.js +584 -0
  163. package/dist/Helpers/TableColumns.d.ts +40 -0
  164. package/dist/Helpers/TableColumns.js +254 -0
  165. package/dist/Helpers/TableExport.d.ts +30 -0
  166. package/dist/Helpers/TableExport.js +97 -0
  167. package/dist/Helpers/TableHelpers.d.ts +102 -0
  168. package/dist/Helpers/TableHelpers.js +281 -0
  169. package/dist/Helpers/TableStore.d.ts +142 -0
  170. package/dist/Helpers/TableStore.js +530 -0
  171. package/dist/Helpers/ThemeHelpers.d.ts +15 -0
  172. package/dist/Helpers/ThemeHelpers.js +16 -0
  173. package/dist/Helpers/TimeHelpers.d.ts +73 -0
  174. package/dist/Helpers/TimeHelpers.js +253 -0
  175. package/dist/Helpers/TreeStore.d.ts +188 -0
  176. package/dist/Helpers/TreeStore.js +577 -0
  177. package/dist/Helpers/ValidationHelpers.d.ts +124 -0
  178. package/dist/Helpers/ValidationHelpers.js +301 -0
  179. package/dist/Interfaces/ApiInterfaces.d.ts +149 -0
  180. package/dist/Interfaces/ApiInterfaces.js +24 -0
  181. package/dist/Interfaces/CrudInterfaces.d.ts +171 -0
  182. package/{src/sso-client.tsx → dist/Interfaces/CrudInterfaces.js} +1 -1
  183. package/dist/Interfaces/DateInterfaces.d.ts +154 -0
  184. package/dist/Interfaces/DateInterfaces.js +29 -0
  185. package/dist/Interfaces/FormInterfaces.d.ts +352 -0
  186. package/dist/Interfaces/FormInterfaces.js +1 -0
  187. package/dist/Interfaces/InputInterfaces.d.ts +141 -0
  188. package/dist/Interfaces/InputInterfaces.js +1 -0
  189. package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
  190. package/dist/Interfaces/MessageInterfaces.js +5 -0
  191. package/dist/Interfaces/TableInterfaces.d.ts +291 -0
  192. package/dist/Interfaces/TableInterfaces.js +1 -0
  193. package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
  194. package/dist/Interfaces/ThemeInterfaces.js +17 -0
  195. package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
  196. package/dist/Interfaces/TypeInterfaces.js +1 -0
  197. package/dist/Server.Index.d.ts +2 -0
  198. package/dist/Server.Index.js +5 -0
  199. package/dist/Services/ApiProxy.d.ts +32 -0
  200. package/dist/Services/ApiProxy.js +111 -0
  201. package/dist/Services/BrowserApi.d.ts +78 -0
  202. package/dist/Services/BrowserApi.js +111 -0
  203. package/dist/Services/ServerApi.d.ts +49 -0
  204. package/dist/Services/ServerApi.js +75 -0
  205. package/dist/Services/ThemeService.d.ts +17 -0
  206. package/dist/Services/ThemeService.js +83 -0
  207. package/dist/Shared.Index.d.ts +43 -0
  208. package/dist/Shared.Index.js +47 -0
  209. package/package.json +82 -33
  210. package/src/Styles/Nexus.Base.css +70 -0
  211. package/src/Styles/Nexus.Button.css +151 -0
  212. package/src/Styles/Nexus.Calendar.css +275 -0
  213. package/src/Styles/Nexus.Choice.css +345 -0
  214. package/src/Styles/Nexus.Crud.css +84 -0
  215. package/src/Styles/Nexus.DateTime.css +81 -0
  216. package/src/Styles/Nexus.Dropdown.css +774 -0
  217. package/src/Styles/Nexus.Editor.css +297 -0
  218. package/src/Styles/Nexus.Form.css +298 -0
  219. package/src/Styles/Nexus.Index.css +34 -0
  220. package/src/Styles/Nexus.Input.css +637 -0
  221. package/src/Styles/Nexus.Markdown.css +265 -0
  222. package/src/Styles/Nexus.Menu.css +249 -0
  223. package/src/Styles/Nexus.Popup.css +731 -0
  224. package/src/Styles/Nexus.RichText.css +463 -0
  225. package/src/Styles/Nexus.Rows.css +566 -0
  226. package/src/Styles/Nexus.Slider.css +278 -0
  227. package/src/Styles/Nexus.Tab.Buttons.css +364 -0
  228. package/src/Styles/Nexus.Table.css +1014 -0
  229. package/src/Styles/Nexus.Theme.Picker.css +159 -0
  230. package/src/Styles/Nexus.Themes.css +194 -0
  231. package/src/Styles/Nexus.Time.css +147 -0
  232. package/src/Styles/Nexus.Toast.css +265 -0
  233. package/src/Styles/Nexus.Tokens.css +72 -0
  234. package/src/Styles/Nexus.Tree.css +435 -0
  235. package/src/api-services/authentication-service.tsx +0 -23
  236. package/src/api-services/preference-service.tsx +0 -5
  237. package/src/api-services/system-service.tsx +0 -34
  238. package/src/client.ts +0 -31
  239. package/src/components/documents/button.tsx +0 -142
  240. package/src/components/documents/icon-box.tsx +0 -93
  241. package/src/components/documents/page-title.tsx +0 -7
  242. package/src/components/documents/tab-button.tsx +0 -172
  243. package/src/components/documents/tag.tsx +0 -30
  244. package/src/components/index.js +0 -0
  245. package/src/components/inputs/checkbox-input.tsx +0 -66
  246. package/src/components/inputs/input-box.tsx +0 -48
  247. package/src/components/inputs/input-element.tsx +0 -65
  248. package/src/components/inputs/input-form.tsx +0 -164
  249. package/src/components/inputs/input.tsx +0 -181
  250. package/src/components/inputs/number-input.tsx +0 -108
  251. package/src/components/inputs/radiobox-input.tsx +0 -53
  252. package/src/components/inputs/textarea-input.tsx +0 -47
  253. package/src/components/inputs/textbox-input.tsx +0 -45
  254. package/src/components/layouts/global-dialogbox.tsx +0 -431
  255. package/src/components/layouts/global-layout.tsx +0 -59
  256. package/src/components/layouts/layout-helpers.tsx +0 -20
  257. package/src/components/layouts/panels/user-panel.tsx +0 -112
  258. package/src/components/layouts/utility-menu.tsx +0 -49
  259. package/src/components/panels/theme-panel.tsx +0 -46
  260. package/src/helpers/bitwise-helpers.tsx +0 -11
  261. package/src/helpers/browser-helpers.tsx +0 -156
  262. package/src/helpers/datasource-helpers.tsx +0 -99
  263. package/src/helpers/element-helpers.tsx +0 -57
  264. package/src/helpers/input-helpers.tsx +0 -74
  265. package/src/helpers/string-helpers.tsx +0 -28
  266. package/src/helpers/utility-helpers.tsx +0 -45
  267. package/src/helpers/validation-helpers.tsx +0 -302
  268. package/src/index.ts +0 -26
  269. package/src/interface.ts +0 -19
  270. package/src/interfaces/auth-token-interfaces.tsx +0 -6
  271. package/src/interfaces/browser-interfaces.tsx +0 -36
  272. package/src/interfaces/button-interfaces.tsx +0 -63
  273. package/src/interfaces/datasource-interfaces.tsx +0 -22
  274. package/src/interfaces/datatable-interfaces.tsx +0 -25
  275. package/src/interfaces/dialogbox-interfaces.tsx +0 -5
  276. package/src/interfaces/exception-interfaces.tsx +0 -99
  277. package/src/interfaces/http-interfaces.tsx +0 -129
  278. package/src/interfaces/icon-interfaces.tsx +0 -129
  279. package/src/interfaces/input-interfaces.tsx +0 -410
  280. package/src/interfaces/layout-interfaces.tsx +0 -191
  281. package/src/interfaces/menu-interfaces.tsx +0 -49
  282. package/src/interfaces/message-interfaces.tsx +0 -32
  283. package/src/interfaces/permission-interfaces.tsx +0 -9
  284. package/src/interfaces/storage-interfaces.tsx +0 -5
  285. package/src/interfaces/system-interfaces.tsx +0 -22
  286. package/src/interfaces/theme-interfaces.tsx +0 -123
  287. package/src/interfaces/type-interfaces.tsx +0 -28
  288. package/src/interfaces/user-interfaces.tsx +0 -47
  289. package/src/nexus-client.tsx +0 -21
  290. package/src/nexus.environments.tsx +0 -66
  291. package/src/proxy-api/proxy-backend.tsx +0 -60
  292. package/src/proxy-api/proxy-constants.tsx +0 -28
  293. package/src/proxy-api/proxy-helpers.tsx +0 -91
  294. package/src/proxy-api-client.tsx +0 -1
  295. package/src/proxy-api-server.tsx +0 -2
  296. package/src/services/http-client-services.tsx +0 -75
  297. package/src/services/http-services.tsx +0 -158
  298. package/src/services/loader-service.tsx +0 -185
  299. package/src/services/localstorage-service.tsx +0 -119
  300. package/src/services/message-services.tsx +0 -383
  301. package/src/services/theme-service.tsx +0 -161
  302. package/src/services/user-services.tsx +0 -10
  303. package/src/sso-config/auth-token-validation.ts +0 -16
  304. package/src/sso-config/callback-route.tsx +0 -35
  305. package/src/sso-config/cookie-encryption.ts +0 -51
  306. package/src/sso-config/cookie-helpers.tsx +0 -102
  307. package/src/sso-config/forgot-password-route.tsx +0 -36
  308. package/src/sso-config/oauth-callback-state.ts +0 -61
  309. package/src/sso-config/pkce-helpers.ts +0 -9
  310. package/src/sso-config/provider-complete-route.tsx +0 -18
  311. package/src/sso-config/redirect-context-actions.tsx +0 -45
  312. package/src/sso-config/redirect-context-helpers.tsx +0 -159
  313. package/src/sso-config/redirect-context-persist.tsx +0 -18
  314. package/src/sso-config/redirect-context-route.tsx +0 -45
  315. package/src/sso-config/refresh-route.tsx +0 -23
  316. package/src/sso-config/sign-in-route.tsx +0 -74
  317. package/src/sso-config/sign-out-route.tsx +0 -57
  318. package/src/sso-config/sign-up-route.tsx +0 -57
  319. package/src/sso-config/sso-backend-flow.ts +0 -38
  320. package/src/sso-config/sso-complete-route.tsx +0 -50
  321. package/src/sso-config/sso-config.ts +0 -59
  322. package/src/sso-config/sso-interfaces.tsx +0 -99
  323. package/src/sso-config/sso-response-helpers.tsx +0 -20
  324. package/src/sso-server.tsx +0 -21
  325. package/src/styles/nexus.animation.css +0 -269
  326. package/src/styles/nexus.core.css +0 -119
  327. package/src/styles/nexus.dialog.css +0 -144
  328. package/src/styles/nexus.icon.css +0 -51
  329. package/src/styles/nexus.input.css +0 -207
  330. package/src/styles/nexus.loader.css +0 -11
  331. package/src/styles/nexus.logic.css +0 -47
  332. package/src/styles/nexus.utility.css +0 -347
@@ -0,0 +1,542 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { iconAlertCircle, iconArrowAutofitWidth, iconArrowBarToLeft, iconArrowBarToRight, iconArrowDown, iconArrowLeft, iconArrowRight, iconArrowUp, iconArrowsSort, iconChevronDown, iconColumns3, iconDatabase, iconDotsVertical, iconEyeOff, iconFileExport, iconFileImport, iconPinnedOff, iconPrinter, iconRefresh, iconRestore, iconSearch, iconSearchOff, iconSortAscending, iconSortDescending, iconX, InlineIcon, } from "nexus-icons";
4
+ import { useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { useBrowserTimeZone, useDatePreferences } from "../../Helpers/DatePreferences.js";
7
+ import { fitColumnWidth, startColumnDrag, startColumnResize } from "../../Helpers/TableColumns.js";
8
+ import { exportTableCsv, printTable } from "../../Helpers/TableExport.js";
9
+ import { canPinColumn, columnAlign, columnValue, cssLength, DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, formatCellText, MIN_COLUMN_WIDTH, TABLE_ACTIONS_COLUMN, TABLE_INDEX_COLUMN, viewColumns, } from "../../Helpers/TableHelpers.js";
10
+ import { TableStore } from "../../Helpers/TableStore.js";
11
+ import { Button } from "../Documents/Button.js";
12
+ import { Menu } from "../Documents/Menu.js";
13
+ import { SplitButton } from "../Documents/SplitButton.js";
14
+ import { TextBox } from "../Inputs/TextBox.js";
15
+ import { toast } from "../Layouts/Toaster.js";
16
+ import { columnStyle, formatCount, TableColumnGroup, TableFooter, TableIndexCell, TableMessageRow, TablePageSize, TablePinnedHead, TableRowView, TableSkeletonRows, } from "./DataTableParts.js";
17
+ /*
18
+ * DataTable: rows in a table with a search, sorting by column, pages, a selection with actions on the selected rows,
19
+ * actions on each row, and columns the user arranges (shown or hidden, in any order, resized, pinned to a side). Rows
20
+ * given as `pinnedRows` sit in a section above the page that folds open and shut. Rows are given, loaded once, or
21
+ * loaded a page at a time; what the table shows lives in a `TableStore`, which pages (and the CRUD component) change
22
+ * from outside.
23
+ */
24
+ /**
25
+ * The store of a table, for the page: pass it as `table`, then call `table.reload()`, `table.upsert(row)`, or
26
+ * `table.getSelected()` from anywhere.
27
+ */
28
+ export function useTable() {
29
+ const [store] = useState(() => new TableStore());
30
+ return store;
31
+ }
32
+ const STATE_PREFIX = "nx-table:";
33
+ function readSaved(key) {
34
+ if (!key || typeof localStorage === "undefined")
35
+ return null;
36
+ try {
37
+ const text = localStorage.getItem(STATE_PREFIX + key);
38
+ return text ? JSON.parse(text) : null;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ // Settings are written in two places (the query and the columns together, the pinned section on its own click), so a
45
+ // write keeps whatever it does not mention.
46
+ function writeSaved(key, changes) {
47
+ try {
48
+ localStorage.setItem(STATE_PREFIX + key, JSON.stringify({ ...readSaved(key), ...changes }));
49
+ }
50
+ catch {
51
+ // Storage full or blocked: the table works without it.
52
+ }
53
+ }
54
+ /** Each row's actions and marks, made once per row object, so rows that did not change do not render again. */
55
+ function usePerRow(make) {
56
+ const cache = useMemo(() => new WeakMap(), [make]);
57
+ return useCallback((row) => {
58
+ if (!make)
59
+ return undefined;
60
+ if (cache.has(row))
61
+ return cache.get(row);
62
+ const value = make(row);
63
+ cache.set(row, value);
64
+ return value;
65
+ }, [cache, make]);
66
+ }
67
+ const sameNumbers = (a, b) => {
68
+ const keys = Object.keys(a);
69
+ return keys.length === Object.keys(b).length && keys.every(key => a[key] === b[key]);
70
+ };
71
+ /**
72
+ * A data table. Columns are params (`{ key, label, format, ... }`); rows are given (`rows`), loaded once (`load`), or
73
+ * loaded a page at a time (`load` with `paging="server"`, which sends the search, the sort, and the page). The user
74
+ * arranges the columns: the Columns menu shows and hides them, a heading drags into another place, its edge changes its
75
+ * width, and its menu pins it left or right; with a `stateKey` the table remembers all of it on the device.
76
+ *
77
+ * @example
78
+ * <DataTable
79
+ * aria-label="Guests"
80
+ * columns={[{ key: "name", label: "Name" }, { key: "visits", label: "Visits", format: "number" }]}
81
+ * load={(query, signal) => fetchGuests(query, signal)}
82
+ * paging="server"
83
+ * selectable
84
+ * massActions={[{ id: "email", label: "Email", onSelect: rows => email(rows) }]}
85
+ * rowActions={row => [{ id: "edit", label: "Edit", icon: <InlineIcon icon={iconPencil} />, inline: true, onSelect: edit }]}
86
+ * />
87
+ */
88
+ export function DataTable(props) {
89
+ const { columns, rows: givenRows, load, loadKey, rowKey = "id", rowLabel, table: givenTable, searchable = true, searchPlaceholder = "Search…", toolbar, actions, columnsMenu = true, selectable = false, maxSelection = DEFAULT_MAX_SELECTION, onSelectionChange, massActions, rowActions, onRowOpen, pinnedRows, pinnedLoading = false, pinnedLabel = "Pinned", pinnedMax, onReload, reorderable = true, resizable = true, pinnable = true, moreActions, exportable = true, printable = true, exportName, onImport, pageSize = DEFAULT_PAGE_SIZE, pageSizes = DEFAULT_PAGE_SIZES, defaultSort = null, stateKey, emptyMessage = "No records yet.", emptyAction, maxHeight, size = "md", id, className, style, ref, } = props;
90
+ const paging = givenRows ? "client" : (props.paging ?? "client");
91
+ const reloadable = props.reloadable ?? Boolean(load);
92
+ const searchDelay = props.searchDelay ?? (paging === "server" ? 300 : 0);
93
+ // A table with checkboxes numbers its rows: the number becomes the checkbox as soon as the row is under the pointer.
94
+ const numbered = props.numbered ?? selectable;
95
+ const [own] = useState(() => (givenTable ? null : new TableStore(pageSize)));
96
+ const table = givenTable ?? own;
97
+ table.configure({ columns, rowKey, rows: givenRows, load, paging, maxSelection, onSelectionChange });
98
+ useImperativeHandle(ref, () => table, [table]);
99
+ const state = useSyncExternalStore(table.subscribe, table.getState, table.getState);
100
+ const preferences = useDatePreferences();
101
+ const timeZone = useBrowserTimeZone();
102
+ const cellOptions = useMemo(() => ({ preferences, timeZone: timeZone ?? undefined }), [preferences, timeZone]);
103
+ const tableId = useId();
104
+ // The search box is uncontrolled: a new key clears it when a new list starts over.
105
+ const [searchGeneration, setSearchGeneration] = useState(0);
106
+ // The pinned section starts folded shut, then takes the choice this device remembers (a layout effect: no flicker).
107
+ const [pinsOpen, setPinsOpen] = useState(false);
108
+ const searchTimer = useRef(undefined);
109
+ const started = useRef(false);
110
+ const loadedKey = useRef(loadKey);
111
+ // First rows: saved settings first, then the rows given or the first load. A layout effect, so rows that are at hand
112
+ // (given, or a cached list) paint with the table, not a frame after it.
113
+ useLayoutEffect(() => {
114
+ if (!started.current) {
115
+ started.current = true;
116
+ const saved = readSaved(stateKey);
117
+ table.restore({
118
+ pageSize: saved?.pageSize && pageSizes.includes(saved.pageSize) ? saved.pageSize : pageSize,
119
+ sort: saved?.sort !== undefined ? saved.sort : defaultSort,
120
+ hidden: saved?.hidden ?? columns.filter(column => column.hidden).map(column => column.key),
121
+ order: saved?.order,
122
+ widths: saved?.widths,
123
+ pinned: saved?.pinned,
124
+ });
125
+ if (saved?.pinsOpen)
126
+ setPinsOpen(true);
127
+ if (givenRows)
128
+ table.setRows(givenRows);
129
+ else
130
+ void table.reload();
131
+ }
132
+ else
133
+ table.resume();
134
+ // Leaving stops a load that runs; coming back (Strict Mode's second mount) starts it again.
135
+ return () => table.dispose();
136
+ // eslint-disable-next-line react-hooks/exhaustive-deps
137
+ }, []);
138
+ // Rows given by the page, again after each change of the array.
139
+ useLayoutEffect(() => {
140
+ if (started.current && givenRows)
141
+ table.setRows(givenRows);
142
+ }, [table, givenRows]);
143
+ // Another list behind the same table: start over from the first page, with no search and no selection.
144
+ useLayoutEffect(() => {
145
+ if (loadedKey.current === loadKey)
146
+ return;
147
+ loadedKey.current = loadKey;
148
+ clearTimeout(searchTimer.current);
149
+ setSearchGeneration(count => count + 1);
150
+ void table.reload({ reset: true });
151
+ }, [table, loadKey]);
152
+ useEffect(() => () => clearTimeout(searchTimer.current), []);
153
+ // Saved settings follow the user's choices.
154
+ const hiddenList = useMemo(() => [...state.hidden].sort(), [state.hidden]);
155
+ useEffect(() => {
156
+ if (!stateKey || !started.current)
157
+ return;
158
+ writeSaved(stateKey, {
159
+ pageSize: state.query.pageSize,
160
+ sort: state.query.sort,
161
+ hidden: hiddenList,
162
+ order: state.order.length ? [...state.order] : undefined,
163
+ widths: Object.keys(state.widths).length ? state.widths : undefined,
164
+ pinned: Object.keys(state.pinned).length ? state.pinned : undefined,
165
+ });
166
+ }, [stateKey, state.query.pageSize, state.query.sort, hiddenList, state.order, state.widths, state.pinned]);
167
+ const visible = useMemo(() => columns.filter(column => !state.hidden.has(column.key)), [columns, state.hidden]);
168
+ const hideable = columns.filter(column => column.hideable !== false);
169
+ const labelOf = useCallback((row) => {
170
+ if (rowLabel)
171
+ return rowLabel(row);
172
+ const first = columns[0];
173
+ const text = first ? formatCellText(first, columnValue(first, row), cellOptions) : "";
174
+ return text || `row ${table.keyOf(row)}`;
175
+ }, [rowLabel, columns, cellOptions, table]);
176
+ const actionsOf = usePerRow(rowActions);
177
+ const hasActions = Boolean(rowActions);
178
+ const lastToggled = useRef(null);
179
+ const warnFull = useCallback(() => toast.warning(`You can select up to ${formatCount(maxSelection)} rows.`), [maxSelection]);
180
+ // Shift+click selects every row between the last one clicked and this one.
181
+ const onToggle = useCallback((row, on, event) => {
182
+ const rows = table.getState().rows;
183
+ const index = rows.indexOf(row);
184
+ const from = lastToggled.current;
185
+ const range = event.shiftKey && from !== null && from < rows.length && index >= 0 ? rows.slice(Math.min(from, index), Math.max(from, index) + 1) : [row];
186
+ lastToggled.current = index;
187
+ if (table.select(range, on) > 0)
188
+ warnFull();
189
+ }, [table, warnFull]);
190
+ const onAction = useCallback((row, action) => {
191
+ if (!action || action.disabled)
192
+ return;
193
+ const key = table.keyOf(row);
194
+ const result = action.onSelect(row);
195
+ if (result instanceof Promise) {
196
+ table.setBusy([key], true);
197
+ void result.finally(() => table.setBusy([key], false)).catch(() => undefined);
198
+ }
199
+ }, [table]);
200
+ const onSearch = (text) => {
201
+ clearTimeout(searchTimer.current);
202
+ if (searchDelay > 0 && text.trim())
203
+ searchTimer.current = setTimeout(() => table.setSearch(text), searchDelay);
204
+ else
205
+ table.setSearch(text);
206
+ };
207
+ const { rows, query, status, refreshing } = state;
208
+ const pageKeys = rows.map(row => table.keyOf(row));
209
+ const selectedOnPage = pageKeys.filter(key => state.selected.has(key)).length;
210
+ const allOnPage = rows.length > 0 && selectedOnPage === rows.length;
211
+ const selectionFull = state.selected.size >= maxSelection;
212
+ const selecting = selectable && state.selected.size > 0;
213
+ /* The columns as they are drawn: in the user's order, the pinned ones at their side, each with its width */
214
+ const rootRef = useRef(null);
215
+ const scrollRef = useRef(null);
216
+ const tableRef = useRef(null);
217
+ const headRef = useRef(null);
218
+ const colRefs = useMemo(() => new Map(), []);
219
+ const [offsets, setOffsets] = useState({});
220
+ const sized = Object.keys(state.widths).length > 0;
221
+ const views = useMemo(() => viewColumns(visible, { order: state.order, pinned: state.pinned, widths: state.widths, offsets, reorderable, resizable }), [visible, state.order, state.pinned, state.widths, offsets, reorderable, resizable]);
222
+ const indexOn = selectable || numbered;
223
+ // The shadow while columns scroll under a pinned side belongs to its last column, which may be one of the table's own.
224
+ const pinnedLeft = views.some(view => view.pin === "left");
225
+ const pinnedRight = views.some(view => view.pin === "right");
226
+ const frame = useMemo(() => ({
227
+ index: indexOn ? { offset: offsets[TABLE_INDEX_COLUMN] ?? 0, edge: !pinnedLeft } : null,
228
+ actions: hasActions ? { offset: offsets[TABLE_ACTIONS_COLUMN] ?? 0, edge: !pinnedRight } : null,
229
+ }), [indexOn, hasActions, offsets, pinnedLeft, pinnedRight]);
230
+ const cellCount = views.length;
231
+ const span = cellCount + (frame.index ? 1 : 0) + (frame.actions ? 1 : 0) + (sized ? 1 : 0);
232
+ // How far the pinned cells sit from their edge, measured from the headings before them. It runs after every render
233
+ // (before the browser paints) and settles at once, since nothing changes when the numbers are the same.
234
+ useLayoutEffect(() => {
235
+ const row = headRef.current;
236
+ const root = rootRef.current;
237
+ if (!row)
238
+ return;
239
+ const next = {};
240
+ let left = 0;
241
+ for (const cell of row.children) {
242
+ if (cell.getAttribute("data-pin") !== "left")
243
+ break;
244
+ const key = cell.getAttribute("data-col");
245
+ if (key)
246
+ next[key] = left;
247
+ left += cell.getBoundingClientRect().width;
248
+ }
249
+ let right = 0;
250
+ for (let index = row.children.length - 1; index >= 0; index--) {
251
+ const cell = row.children[index];
252
+ if (cell.getAttribute("data-pin") !== "right")
253
+ break;
254
+ const key = cell.getAttribute("data-col");
255
+ if (key)
256
+ next[key] = right;
257
+ right += cell.getBoundingClientRect().width;
258
+ }
259
+ // The bar of the selected rows starts after the checkbox column, whatever width it took.
260
+ root?.style.setProperty("--nx-table-index-w", `${Math.round(left)}px`);
261
+ setOffsets(previous => (sameNumbers(previous, next) ? previous : next));
262
+ });
263
+ // Shadows at the pinned columns while the others scroll under them: set on the frame, with no render.
264
+ useLayoutEffect(() => {
265
+ const scroller = scrollRef.current;
266
+ const frameElement = scroller?.parentElement;
267
+ if (!scroller || !frameElement)
268
+ return;
269
+ const update = () => {
270
+ const left = Math.abs(scroller.scrollLeft);
271
+ frameElement.toggleAttribute("data-scroll-start", left > 1);
272
+ frameElement.toggleAttribute("data-scroll-end", left < scroller.scrollWidth - scroller.clientWidth - 1);
273
+ };
274
+ update();
275
+ scroller.addEventListener("scroll", update, { passive: true });
276
+ const observer = new ResizeObserver(update);
277
+ observer.observe(scroller);
278
+ if (scroller.firstElementChild)
279
+ observer.observe(scroller.firstElementChild);
280
+ return () => {
281
+ scroller.removeEventListener("scroll", update);
282
+ observer.disconnect();
283
+ };
284
+ }, []);
285
+ const headerCheck = useRef(null);
286
+ useLayoutEffect(() => {
287
+ if (headerCheck.current)
288
+ headerCheck.current.indeterminate = selectedOnPage > 0 && !allOnPage;
289
+ });
290
+ /* Resizing a column: the table freezes the widths it has, then only the dragged edge moves */
291
+ const measureWidths = useCallback(() => {
292
+ const row = headRef.current;
293
+ const widths = {};
294
+ for (const cell of row?.children ?? []) {
295
+ const key = cell.getAttribute("data-col");
296
+ if (key)
297
+ widths[key] = Math.round(cell.getBoundingClientRect().width);
298
+ }
299
+ return widths;
300
+ }, []);
301
+ const freezeWidths = useCallback(() => {
302
+ if (Object.keys(table.getState().widths).length === 0)
303
+ table.setWidths(measureWidths());
304
+ }, [table, measureWidths]);
305
+ // A column never goes under the width its params ask for, whether the user drags its edge or fits its content.
306
+ const smallest = (heading) => Math.max(MIN_COLUMN_WIDTH, (heading && Number.parseFloat(getComputedStyle(heading).minWidth)) || 0);
307
+ const fitColumn = useCallback((key) => {
308
+ const element = tableRef.current;
309
+ if (!element)
310
+ return;
311
+ const heading = element.querySelector(`thead [data-col="${key}"]`);
312
+ const width = Math.max(fitColumnWidth(element, key), smallest(heading));
313
+ table.setWidths({ ...(Object.keys(table.getState().widths).length ? {} : measureWidths()), [key]: width });
314
+ }, [table, measureWidths]);
315
+ const onColumnPointerDown = (event, view) => {
316
+ if (event.button !== 0 || !event.isPrimary)
317
+ return;
318
+ const heading = event.target.closest("th");
319
+ if (!heading)
320
+ return;
321
+ const target = event.target;
322
+ if (target.closest(".nx-table__resizer, .nx-table__col-menu"))
323
+ return;
324
+ if (!view.movable)
325
+ return;
326
+ startColumnDrag(event.nativeEvent, {
327
+ heading,
328
+ scroller: scrollRef.current,
329
+ root: rootRef.current,
330
+ onDrop: (key, beforeKey) => table.moveColumn(key, beforeKey),
331
+ });
332
+ };
333
+ const onResizerPointerDown = (event, key) => {
334
+ if (event.button !== 0 || !event.isPrimary)
335
+ return;
336
+ const heading = event.target.closest("th");
337
+ if (!heading)
338
+ return;
339
+ event.stopPropagation();
340
+ // The widths freeze on the press, so the column element is there to write to on the first move.
341
+ freezeWidths();
342
+ startColumnResize(event.nativeEvent, {
343
+ heading,
344
+ root: rootRef.current,
345
+ min: smallest(heading),
346
+ onResize: width => {
347
+ const col = colRefs.get(key);
348
+ if (col)
349
+ col.style.width = `${width}px`;
350
+ },
351
+ onCommit: width => table.setWidth(key, width),
352
+ });
353
+ };
354
+ /* The bar above the table, and the options menu */
355
+ const outputName = exportName ?? props["aria-label"] ?? "Table";
356
+ const output = () => {
357
+ const shown = views.map(view => view.column);
358
+ const rowsOut = table.getViewRows();
359
+ const note = [`${formatCount(rowsOut.length)} ${rowsOut.length === 1 ? "row" : "rows"}`, query.search && `search: ${query.search}`].filter(Boolean).join(" · ");
360
+ return { columns: shown, rows: rowsOut, cellOptions, name: outputName, note };
361
+ };
362
+ const serverPage = paging === "server";
363
+ const optionItems = [
364
+ ...(exportable && status === "ready" && rows.length > 0
365
+ ? [{ id: "export", label: serverPage ? "Export this page (CSV)" : "Export as CSV", icon: _jsx(InlineIcon, { icon: iconFileExport }), onSelect: () => exportTableCsv(output()) }]
366
+ : []),
367
+ ...(printable && status === "ready" && rows.length > 0
368
+ ? [{ id: "print", label: serverPage ? "Print this page" : "Print", icon: _jsx(InlineIcon, { icon: iconPrinter }), onSelect: () => printTable(output()) }]
369
+ : []),
370
+ ...(onImport ? [{ id: "import", label: "Import", icon: _jsx(InlineIcon, { icon: iconFileImport }), onSelect: onImport }] : []),
371
+ ...(moreActions ?? []),
372
+ ...(table.hasColumnChanges() ? [{ id: "reset-columns", label: "Reset columns", description: "Their order, widths, and pins", icon: _jsx(InlineIcon, { icon: iconRestore }), separator: true, onSelect: () => table.resetColumns() }] : []),
373
+ ];
374
+ const optionEntries = optionItems.flatMap((action, index) => [
375
+ ...(action.separator && index > 0 ? [{ separator: true }] : []),
376
+ { value: action.id, label: action.label, icon: action.icon, description: action.description, tone: action.tone, disabled: action.disabled },
377
+ ]);
378
+ const pageSizePicker = _jsx(TablePageSize, { pageSize: query.pageSize, pageSizes: pageSizes, compact: searchable, onChange: value => table.setPageSize(value) });
379
+ let body;
380
+ if (status === "loading") {
381
+ body = _jsx(TableSkeletonRows, { count: Math.min(query.pageSize, 8), cells: cellCount, frame: frame, sized: sized });
382
+ }
383
+ else if (status === "error") {
384
+ body = (_jsx(TableMessageRow, { span: span, tone: "danger", icon: iconAlertCircle, title: "The rows could not be loaded.", detail: state.error, children: _jsx(Button, { size: "sm", icon: _jsx(InlineIcon, { icon: iconRefresh }), onClick: () => void table.reload({ quiet: false }), children: "Retry" }) }));
385
+ }
386
+ else if (rows.length === 0 && query.search) {
387
+ body = (_jsx(TableMessageRow, { span: span, icon: iconSearchOff, title: `Nothing matches “${query.search}”.`, detail: "Check the spelling, or search for something shorter.", children: _jsx(Button, { size: "sm", icon: _jsx(InlineIcon, { icon: iconX }), onClick: () => {
388
+ clearTimeout(searchTimer.current);
389
+ setSearchGeneration(count => count + 1);
390
+ table.setSearch("");
391
+ }, children: "Clear search" }) }));
392
+ }
393
+ else if (rows.length === 0) {
394
+ body = (_jsx(TableMessageRow, { span: span, icon: iconDatabase, title: emptyMessage, children: emptyAction }));
395
+ }
396
+ else {
397
+ body = rows.map((row, index) => {
398
+ const key = pageKeys[index];
399
+ const selected = state.selected.has(key);
400
+ return (_jsx(TableRowView, { row: row, rowKey: key, number: state.offset + index + 1, label: labelOf(row), columns: views, cellOptions: cellOptions, selectable: selectable, selected: selected, selectionFull: selectionFull && !selected, busy: state.busy.has(key), flashed: state.flashed.has(key), numbered: numbered, frame: frame, sized: sized, actions: hasActions ? (actionsOf(row) ?? []) : null, onToggle: onToggle, onAction: onAction, onOpen: onRowOpen }, key || index));
401
+ });
402
+ }
403
+ /* The pinned section above the page: the records the user pinned, folded open or shut, remembered on this device */
404
+ // A section with nothing in it and nothing on its way would be an empty heading, so it does not show at all.
405
+ const pins = pinnedRows && (pinnedRows.length > 0 || pinnedLoading) ? pinnedRows : null;
406
+ const togglePins = () => {
407
+ const next = !pinsOpen;
408
+ setPinsOpen(next);
409
+ if (stateKey)
410
+ writeSaved(stateKey, { pinsOpen: next });
411
+ };
412
+ const pinnedBody = pinsOpen &&
413
+ pins?.map((row, index) => {
414
+ const key = table.keyOf(row);
415
+ const selected = state.selected.has(key);
416
+ return (_jsx(TableRowView, { row: row, rowKey: key, number: index + 1, label: labelOf(row), columns: views, cellOptions: cellOptions, selectable: selectable, selected: selected, selectionFull: selectionFull && !selected, busy: state.busy.has(key), flashed: state.flashed.has(key), numbered: numbered, pinned: true, frame: frame, sized: sized, actions: hasActions ? (actionsOf(row) ?? []) : null, onToggle: onToggle, onAction: onAction, onOpen: onRowOpen }, `pin:${key || index}`));
417
+ });
418
+ const selectedRows = selecting ? [...state.selected.values()] : [];
419
+ const mass = selectedRows.length ? (typeof massActions === "function" ? massActions(selectedRows) : (massActions ?? [])) : [];
420
+ const columnItems = hideable.map(column => ({ value: column.key, label: column.label, checked: !state.hidden.has(column.key) }));
421
+ return (_jsxs("div", { ref: rootRef, id: id, className: cx("nx-table", `nx-table--${size}`, className), style: style, "data-status": status, "data-refreshing": refreshing || undefined, "data-selecting": selecting || undefined, "data-sized": sized || undefined, children: [_jsxs("div", { className: "nx-table__bar", children: [toolbar && _jsx("div", { className: "nx-table__bar-start", children: toolbar }), _jsxs("div", { className: "nx-table__bar-end", children: [searchable ? (_jsx(TextBox, { type: "search", size: "sm", className: "nx-table__search", actionsStart: pageSizePicker, icon: _jsx(InlineIcon, { icon: iconSearch }), placeholder: searchPlaceholder, "aria-label": searchPlaceholder.replace(/…$/, ""), "aria-controls": tableId, defaultValue: query.search, clearable: true, onValueChange: onSearch, onKeyDown: event => {
422
+ if (event.key === "Enter") {
423
+ clearTimeout(searchTimer.current);
424
+ table.setSearch(event.currentTarget.value);
425
+ }
426
+ } }, searchGeneration)) : (pageSizePicker), columnsMenu && hideable.length > 1 && (_jsx(Menu, { items: columnItems, kind: "checkbox", size: "sm", align: "end", closeOnSelect: false, "aria-label": "Columns", onSelect: value => {
427
+ const hide = !state.hidden.has(value);
428
+ // At least one column stays.
429
+ if (hide && visible.length <= 1)
430
+ return;
431
+ table.setHidden(value, hide);
432
+ }, children: _jsx(Button, { size: "sm", icon: _jsx(InlineIcon, { icon: iconColumns3 }), "aria-label": "Columns", title: "Show or hide columns" }) })), reloadable && (_jsx(Button, { size: "sm", icon: _jsx(InlineIcon, { icon: iconRefresh, className: refreshing ? "nx-spin" : undefined }), "aria-label": "Reload", title: "Reload", disabled: status === "loading", onClick: () => {
433
+ onReload?.();
434
+ void table.reload({ quiet: true });
435
+ } })), optionEntries.length > 0 && (_jsx(Menu, { items: optionEntries, size: "sm", align: "end", "aria-label": "More options", onSelect: value => optionItems.find(action => action.id === value)?.onSelect(), children: _jsx(Button, { size: "sm", icon: _jsx(InlineIcon, { icon: iconDotsVertical }), "aria-label": "More options", title: "More options", "data-table-options": true }) })), actions] })] }), _jsxs("div", { className: "nx-table__frame", children: [selectedRows.length > 0 && (_jsx(SelectionBar, { count: selectedRows.length, actions: mass, rows: selectedRows, onClear: () => table.clearSelection(), controls: tableId })), _jsx("div", { className: "nx-table__progress", "aria-hidden": "true" }), _jsx("div", { ref: scrollRef, className: "nx-table__scroll", style: { maxHeight: cssLength(maxHeight) }, children: _jsxs("table", { ref: tableRef, id: tableId, className: "nx-table__table", "aria-label": props["aria-label"], "aria-labelledby": props["aria-labelledby"], "aria-busy": status === "loading" || refreshing || undefined, children: [sized && _jsx(TableColumnGroup, { columns: views, widths: state.widths, frame: frame, colRefs: colRefs }), _jsx("thead", { children: _jsxs("tr", { ref: headRef, className: "nx-table__head", children: [frame.index && (_jsx(TableIndexCell, { heading: true, own: frame.index, number: "#", numbered: numbered, selectable: selectable, checkbox: selectable && (_jsx("label", { className: "nx-choice nx-choice--sm nx-table__check", children: _jsx("input", { ref: headerCheck, type: "checkbox", className: "nx-choice__control nx-choice__control--checkbox", checked: allOnPage, disabled: rows.length === 0 || status !== "ready", "aria-label": "Select every row on this page", onChange: event => {
436
+ lastToggled.current = null;
437
+ if (table.selectPage(event.currentTarget.checked) > 0)
438
+ warnFull();
439
+ } }) })) })), views.map(view => (_jsx(HeaderCell, { view: view, sized: sized, sort: query.sort, disabled: status !== "ready" || rows.length < 2, covered: selectedRows.length > 0, canPin: canPinColumn(view.column, { pinnable }), canHide: view.column.hideable !== false && visible.length > 1, neighbours: moveTargets(views, view.column.key), onSort: direction => table.setSort(direction ? { key: view.column.key, direction } : null), onPin: side => table.setPinned(view.column.key, side), onMove: beforeKey => table.moveColumn(view.column.key, beforeKey), onFit: () => fitColumn(view.column.key), onHide: () => table.setHidden(view.column.key, true), onPointerDown: event => onColumnPointerDown(event, view), onResizerDown: event => onResizerPointerDown(event, view.column.key) }, view.column.key))), sized && _jsx("td", { className: "nx-table__heading nx-table__cell--filler" }), frame.actions && (_jsx("th", { scope: "col", className: "nx-table__heading nx-table__cell--actions", "data-col": TABLE_ACTIONS_COLUMN, "data-pin": "right", "data-pin-edge": frame.actions.edge || undefined, style: { right: frame.actions.offset }, children: _jsx("span", { className: "nx-visually-hidden", children: "Actions" }) }))] }) }), pins && (_jsxs("tbody", { className: "nx-table__pins", "data-open": pinsOpen || undefined, children: [_jsx(TablePinnedHead, { span: span, label: pinnedLabel, count: pins.length, max: pinnedMax, loading: pinnedLoading, open: pinsOpen, onToggle: togglePins }), pinnedBody] })), _jsx("tbody", { children: body })] }) })] }), _jsx(TableFooter, { state: state, disabled: refreshing, onPage: page => table.setPage(page) })] }));
440
+ }
441
+ /** Where "Move left" and "Move right" put a column: the key it goes before, or null for the end of its side. */
442
+ function moveTargets(views, key) {
443
+ // A column moves among the columns on its own side, past the ones that stay put.
444
+ const pin = views.find(view => view.column.key === key)?.pin ?? null;
445
+ const group = views.filter(view => view.pin === pin && view.movable);
446
+ const at = group.findIndex(view => view.column.key === key);
447
+ if (at < 0)
448
+ return { left: undefined, right: undefined };
449
+ return {
450
+ left: at > 0 ? group[at - 1].column.key : undefined,
451
+ right: at < group.length - 1 ? (group[at + 2]?.column.key ?? null) : undefined,
452
+ };
453
+ }
454
+ /**
455
+ * A column's heading: a button that sorts (the direction as an arrow and in `aria-sort`), a menu that pins, moves,
456
+ * fits, and hides the column, and an edge to drag for its width. The heading itself drags into another place.
457
+ * While the selection bar covers the headings (`covered`), Tab skips what is under it.
458
+ */
459
+ function HeaderCell({ view, sized, sort, disabled, covered, canPin, canHide, neighbours, onSort, onPin, onMove, onFit, onHide, onPointerDown, onResizerDown }) {
460
+ const { column } = view;
461
+ const direction = sort?.key === column.key ? sort.direction : null;
462
+ const align = columnAlign(column);
463
+ const sortable = column.sortable !== false;
464
+ const entries = [];
465
+ if (sortable) {
466
+ // The heading's arrow says which way it is sorted now; the menu offers a direction straight away.
467
+ entries.push({ value: "asc", label: "Sort ascending", icon: _jsx(InlineIcon, { icon: iconSortAscending }), disabled: direction === "asc" });
468
+ entries.push({ value: "desc", label: "Sort descending", icon: _jsx(InlineIcon, { icon: iconSortDescending }), disabled: direction === "desc" });
469
+ if (direction)
470
+ entries.push({ value: "no-sort", label: "Clear sort", icon: _jsx(InlineIcon, { icon: iconArrowsSort }) });
471
+ }
472
+ if (canPin) {
473
+ if (entries.length)
474
+ entries.push({ separator: true });
475
+ if (view.pin !== "left")
476
+ entries.push({ value: "pin-left", label: "Pin left", icon: _jsx(InlineIcon, { icon: iconArrowBarToLeft }) });
477
+ if (view.pin !== "right")
478
+ entries.push({ value: "pin-right", label: "Pin right", icon: _jsx(InlineIcon, { icon: iconArrowBarToRight }) });
479
+ if (view.pin)
480
+ entries.push({ value: "unpin", label: "Unpin", icon: _jsx(InlineIcon, { icon: iconPinnedOff }) });
481
+ }
482
+ if (view.movable && (neighbours.left !== undefined || neighbours.right !== undefined)) {
483
+ if (entries.length)
484
+ entries.push({ separator: true });
485
+ entries.push({ value: "move-left", label: "Move left", icon: _jsx(InlineIcon, { icon: iconArrowLeft }), disabled: neighbours.left === undefined });
486
+ entries.push({ value: "move-right", label: "Move right", icon: _jsx(InlineIcon, { icon: iconArrowRight }), disabled: neighbours.right === undefined });
487
+ }
488
+ if (view.resizable || canHide) {
489
+ if (entries.length)
490
+ entries.push({ separator: true });
491
+ if (view.resizable)
492
+ entries.push({ value: "fit", label: "Fit to content", icon: _jsx(InlineIcon, { icon: iconArrowAutofitWidth }) });
493
+ if (canHide)
494
+ entries.push({ value: "hide", label: "Hide column", icon: _jsx(InlineIcon, { icon: iconEyeOff }) });
495
+ }
496
+ function choose(value) {
497
+ if (value === "asc" || value === "desc")
498
+ onSort(value);
499
+ else if (value === "no-sort")
500
+ onSort(null);
501
+ else if (value === "pin-left")
502
+ onPin("left");
503
+ else if (value === "pin-right")
504
+ onPin("right");
505
+ else if (value === "unpin")
506
+ onPin(null);
507
+ else if (value === "move-left")
508
+ onMove(neighbours.left ?? null);
509
+ else if (value === "move-right")
510
+ onMove(neighbours.right ?? null);
511
+ else if (value === "fit")
512
+ onFit();
513
+ else if (value === "hide")
514
+ onHide();
515
+ }
516
+ const label = _jsx("span", { className: "nx-table__heading-label", children: column.label });
517
+ return (_jsxs("th", { scope: "col", className: cx("nx-table__heading", `nx-table__cell--${align}`), "data-col": column.key, "data-pin": view.pin ?? undefined, "data-pin-edge": (view.pin && view.edge) || undefined, "data-movable": view.movable || undefined, style: columnStyle(view, sized), "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : undefined, onPointerDown: onPointerDown, children: [_jsxs("div", { className: "nx-table__heading-inner", children: [sortable ? (_jsxs("button", { type: "button", className: "nx-table__sort", "data-sorted": direction ?? undefined, disabled: disabled && !direction, tabIndex: covered ? -1 : undefined, onClick: () => onSort(direction === "asc" ? "desc" : direction === "desc" ? null : "asc"), title: `Sort by ${column.label}`, children: [label, _jsx(InlineIcon, { icon: direction === "asc" ? iconArrowUp : direction === "desc" ? iconArrowDown : iconArrowsSort, className: "nx-table__sort-icon" })] })) : (label), entries.length > 0 && (_jsx(Menu, { items: entries, size: "sm", align: "end", "aria-label": `${column.label}: column`, onSelect: choose, children: _jsx(Button, { variant: "ghost", size: "sm", className: "nx-table__col-menu", icon: _jsx(InlineIcon, { icon: iconChevronDown }), tabIndex: covered ? -1 : undefined, "aria-label": `${column.label}: column options`, title: "Column options" }) }))] }), view.resizable && (_jsx("span", { className: "nx-table__resizer", "aria-hidden": "true", "data-resize": column.key, title: `Drag to resize ${column.label}, double-click to fit`, onPointerDown: onResizerDown, onDoubleClick: onFit }))] }));
518
+ }
519
+ /** The bar over the column headings while rows are selected: the count, one button for the actions, and Clear. */
520
+ function SelectionBar({ count, actions, rows, onClear, controls }) {
521
+ const [running, setRunning] = useState(null);
522
+ const [chosen, setChosen] = useState(undefined);
523
+ const alive = useRef(true);
524
+ useEffect(() => () => {
525
+ alive.current = false;
526
+ }, []);
527
+ const usable = actions.filter(action => !action.disabled);
528
+ const current = actions.find(action => action.id === chosen && !action.disabled) ?? usable[0] ?? actions[0];
529
+ function run(action) {
530
+ if (!action || action.disabled)
531
+ return;
532
+ const result = action.onSelect(rows);
533
+ if (!(result instanceof Promise))
534
+ return;
535
+ setRunning(action.id);
536
+ void result.finally(() => alive.current && setRunning(null)).catch(() => undefined);
537
+ }
538
+ // Every action on the selected rows lives in one button: the one shown runs on a click, the menu holds the rest and
539
+ // makes the one picked the button, the way a split button works everywhere else.
540
+ const options = actions.map(action => ({ value: action.id, label: action.label, icon: action.icon, disabled: action.disabled }));
541
+ return (_jsxs("div", { className: "nx-table__selection", role: "toolbar", "aria-label": "Selected rows", "aria-controls": controls, children: [_jsxs("span", { className: "nx-table__selection-count", "aria-live": "polite", children: [formatCount(count), " selected"] }), _jsx("div", { className: "nx-table__selection-actions", children: actions.length > 1 ? (_jsx(SplitButton, { options: options, value: current?.id, size: "sm", variant: current?.tone === "danger" ? "danger" : "primary", loading: running !== null, menuLabel: "More actions on the selected rows", className: "nx-table__mass", onValueChange: value => setChosen(value), onAction: value => run(actions.find(action => action.id === value)) })) : (actions.map(action => (_jsx(Button, { variant: "primary", size: "sm", icon: action.icon, loading: running === action.id, disabled: action.disabled, "data-action": action.id, title: action.label, className: "nx-table__mass", onClick: () => run(action), children: action.label }, action.id)))) }), _jsx(Button, { variant: "ghost", size: "sm", icon: _jsx(InlineIcon, { icon: iconX }), className: "nx-table__selection-clear", disabled: running !== null, onClick: onClear, children: "Clear" })] }));
542
+ }