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
@@ -40,6 +40,11 @@ export interface PopupProps {
40
40
  variant?: PopupVariant;
41
41
  /** Width: `xs` 360, `sm` 440, `md` 560 (default), `lg` 720, `xl` 960, or `full`. */
42
42
  size?: PopupSize;
43
+ /**
44
+ * Height, pixels or a CSS length: the body scrolls inside it. Default: as tall as the content, up to the window. A
45
+ * docked window is `32rem` tall by default, never taller than the window.
46
+ */
47
+ height?: number | string;
43
48
  /** Buttons at the end, right-aligned. */
44
49
  footer?: ReactNode;
45
50
  /** Shows the close button. Default true. */
@@ -50,8 +55,9 @@ export interface PopupProps {
50
55
  closeOnBackdrop?: boolean;
51
56
  /**
52
57
  * The minimize button: the popup steps aside to a bar at the bottom right, keeping everything typed in it, and the
53
- * page can be used again. Default: when `closable`, since a popup that must be answered cannot be put aside.
54
- * Needs `<PopupHost />`, which shows the bars.
58
+ * page can be used again. Default false: only a popup that asks has it. A CRUD page's Add and Edit forms and its row
59
+ * forms ask (a form put aside while the list behind it is read); message boxes, viewers, galleries, histories and
60
+ * the rest do not. Needs `<PopupHost />`, which shows the bars.
55
61
  */
56
62
  minimizable?: boolean;
57
63
  /** The maximize button, and a double-click on the title bar: the popup fills the window. Default true. */
@@ -66,27 +72,48 @@ export interface PopupProps {
66
72
  reopen?: PopupReopen;
67
73
  /** The window state, when the page controls it; pair it with `onWindowStateChange`. */
68
74
  windowState?: PopupWindowState;
75
+ /**
76
+ * A window that leaves the page usable: it opens without a backdrop at the bottom right, beside the other docked
77
+ * windows (the first on the right, each next one to its left; on a screen too narrow for two, over each other), the
78
+ * one last used on top. Esc inside it closes it, Tab leaves it for the page. A mail client's chat popups and compose
79
+ * window are docked. Minimize and maximize work as on any popup. Default false: a popup locks the page.
80
+ */
81
+ docked?: boolean;
82
+ /**
83
+ * A drawer: the window's whole height at its end edge (the right, in a left-to-right page), its width from `size`
84
+ * (all of it on a phone). Modal as any popup; it neither moves nor maximizes. A board's Add widget list is one.
85
+ */
86
+ drawer?: boolean;
69
87
  /** The window state each time the popup opens, when the page does not control it. Default `normal`. */
70
88
  defaultWindowState?: Exclude<PopupWindowState, "minimized">;
71
89
  /** When the buttons, a double-click on the title bar, a drag, or the minimized bar change the window state. */
72
90
  onWindowStateChange?: (state: PopupWindowState) => void;
73
91
  /** A body without padding, for tables and lists that reach the edges. */
74
92
  flush?: boolean;
75
- /** A selector for the element that takes focus when the popup opens. Default: the first control in the body or footer. */
93
+ /**
94
+ * A selector for the element that takes focus when the popup opens. Default: an element of the body or footer marked
95
+ * `data-nx-focus` (with `tabIndex={-1}` when it is no control, such as a heading), else the first control there.
96
+ */
76
97
  initialFocus?: string;
77
98
  /** Name for screen readers when there is no title. */
78
99
  "aria-label"?: string;
100
+ /**
101
+ * Ids of elements inside the popup that describe it to screen readers, after its `description`: a message box names
102
+ * its message and its details, so they are read with the title as the focus enters.
103
+ */
104
+ "aria-describedby"?: string;
79
105
  className?: string;
80
106
  children?: ReactNode;
81
107
  }
82
108
  /**
83
- * A modal window on the native <dialog>, with the buttons of a desktop window: minimize, maximize, and close. It sits
84
- * in the top layer above everything, dims and locks the page behind it, traps Tab, closes with Esc or a click outside,
85
- * and returns focus to where it came from. The title bar moves it; a double-click there maximizes it. Minimized, it
109
+ * A modal window on the native <dialog>, with the buttons of a desktop window: maximize and close, and minimize when
110
+ * it asks (`minimizable`: a CRUD page's Add and Edit). It sits in the top layer above everything, dims and locks the
111
+ * page behind it, traps Tab, closes with Esc or a click outside, and returns focus to where it came from. The title
112
+ * bar moves it; a double-click there maximizes it. Minimized, it
86
113
  * waits as a bar at the bottom right with its content kept, while the page can be used. The body scrolls while the
87
114
  * title and the footer stay. The content mounts while the popup is open, so a form inside starts fresh every time.
88
115
  *
89
116
  * @example
90
117
  * <Popup open={open} onClose={() => setOpen(false)} title="Edit booking" footer={<Button variant="primary">Save</Button>}>…</Popup>
91
118
  */
92
- export declare function Popup({ open, onClose, onClosed, title, description, icon, variant, size, footer, closable, closeOnEscape, closeOnBackdrop, minimizable, maximizable, movable, reopen, windowState: windowStateProp, defaultWindowState, onWindowStateChange, flush, initialFocus, "aria-label": ariaLabel, className, children, }: PopupProps): import("react").JSX.Element;
119
+ export declare function Popup({ open, onClose, onClosed, title, description, icon, variant, size, height, footer, closable, closeOnEscape, closeOnBackdrop, minimizable, maximizable: maximizableProp, movable: movableProp, reopen, docked, drawer, windowState: windowStateProp, defaultWindowState, onWindowStateChange, flush, initialFocus, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, className, children, }: PopupProps): import("react").JSX.Element | null;
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { iconMinus, iconSquare, iconSquares, iconX, InlineIcon } from "nexus-icons";
4
4
  import { useContext, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
5
- import { flushSync } from "react-dom";
5
+ import { createPortal, flushSync } from "react-dom";
6
6
  import { afterAnimation, motionAllowed } from "../../Helpers/AnimationHelpers.js";
7
7
  import { cx } from "../../Helpers/ClassHelpers.js";
8
8
  import { cssVariablePosition, DRAG_CANCEL_SELECTOR, moveWithin, startDrag, viewportBounds } from "../../Helpers/DragHelpers.js";
@@ -12,22 +12,79 @@ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]):not([t
12
12
  // Where a minimized popup flies to: the newest bar of the dock, at the bottom right.
13
13
  const DOCK_EDGE = 16;
14
14
  const DOCK_BAR = { width: 300, height: 44 };
15
+ // Docked windows (`docked`), in the order they opened (each next one to the left of the one before), and in the order
16
+ // they were last used (the last on top).
17
+ const DOCKED_GAP = 12;
18
+ let dockedOrder = [];
19
+ let dockedStack = [];
20
+ let dockedObserver = null;
21
+ /** Lays the open docked windows out along the bottom edge, and stacks them by use. */
22
+ function layoutDocked() {
23
+ const view = document.documentElement.clientWidth;
24
+ let right = DOCK_EDGE;
25
+ for (const dialog of dockedOrder) {
26
+ if (!dialog.open)
27
+ continue;
28
+ const width = dialog.offsetWidth;
29
+ // A window with no room left of the others stands at the right again, over them: the last used shows on top.
30
+ const fits = right + width + DOCK_EDGE <= view;
31
+ dialog.style.setProperty("--nx-docked-right", `${fits ? right : DOCK_EDGE}px`);
32
+ if (fits)
33
+ right += width + DOCKED_GAP;
34
+ }
35
+ dockedStack.forEach((dialog, index) => dialog.style.setProperty("--nx-docked-z", String(index + 1)));
36
+ }
37
+ function dockedJoin(dialog) {
38
+ if (!dockedOrder.includes(dialog))
39
+ dockedOrder = [...dockedOrder, dialog];
40
+ dockedRaise(dialog);
41
+ if (!dockedObserver) {
42
+ dockedObserver = new ResizeObserver(layoutDocked);
43
+ window.addEventListener("resize", layoutDocked);
44
+ }
45
+ dockedObserver.observe(dialog);
46
+ layoutDocked();
47
+ }
48
+ function dockedLeave(dialog) {
49
+ dockedOrder = dockedOrder.filter(one => one !== dialog);
50
+ dockedStack = dockedStack.filter(one => one !== dialog);
51
+ dockedObserver?.unobserve(dialog);
52
+ if (dockedOrder.length === 0 && dockedObserver) {
53
+ dockedObserver.disconnect();
54
+ dockedObserver = null;
55
+ window.removeEventListener("resize", layoutDocked);
56
+ }
57
+ layoutDocked();
58
+ }
59
+ /** The docked window last used comes on top of the others. */
60
+ function dockedRaise(dialog) {
61
+ if (dockedStack[dockedStack.length - 1] === dialog)
62
+ return;
63
+ dockedStack = [...dockedStack.filter(one => one !== dialog), dialog];
64
+ layoutDocked();
65
+ }
15
66
  function popupPosition(dialog) {
16
67
  return cssVariablePosition(dialog, "--nx-popup-x", "--nx-popup-y");
17
68
  }
18
69
  // Each popup's opener and the popup that held it, so focus can find its way back through popups that have gone.
19
70
  const openers = new WeakMap();
71
+ // How each popup puts the focus inside itself (its `initialFocus`), for a popup closed over it that left the focus nowhere.
72
+ const homes = new WeakMap();
20
73
  /**
21
- * A modal window on the native <dialog>, with the buttons of a desktop window: minimize, maximize, and close. It sits
22
- * in the top layer above everything, dims and locks the page behind it, traps Tab, closes with Esc or a click outside,
23
- * and returns focus to where it came from. The title bar moves it; a double-click there maximizes it. Minimized, it
74
+ * A modal window on the native <dialog>, with the buttons of a desktop window: maximize and close, and minimize when
75
+ * it asks (`minimizable`: a CRUD page's Add and Edit). It sits in the top layer above everything, dims and locks the
76
+ * page behind it, traps Tab, closes with Esc or a click outside, and returns focus to where it came from. The title
77
+ * bar moves it; a double-click there maximizes it. Minimized, it
24
78
  * waits as a bar at the bottom right with its content kept, while the page can be used. The body scrolls while the
25
79
  * title and the footer stay. The content mounts while the popup is open, so a form inside starts fresh every time.
26
80
  *
27
81
  * @example
28
82
  * <Popup open={open} onClose={() => setOpen(false)} title="Edit booking" footer={<Button variant="primary">Save</Button>}>…</Popup>
29
83
  */
30
- export function Popup({ open, onClose, onClosed, title, description, icon, variant = "window", size = "md", footer, closable = true, closeOnEscape = true, closeOnBackdrop = true, minimizable, maximizable = true, movable = true, reopen = "replace", windowState: windowStateProp, defaultWindowState = "normal", onWindowStateChange, flush = false, initialFocus, "aria-label": ariaLabel, className, children, }) {
84
+ export function Popup({ open, onClose, onClosed, title, description, icon, variant = "window", size = "md", height, footer, closable = true, closeOnEscape = true, closeOnBackdrop = true, minimizable, maximizable: maximizableProp = true, movable: movableProp = true, reopen = "replace", docked = false, drawer = false, windowState: windowStateProp, defaultWindowState = "normal", onWindowStateChange, flush = false, initialFocus, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, className, children, }) {
85
+ // A drawer stands at its edge: it neither moves nor maximizes.
86
+ const maximizable = maximizableProp && !drawer;
87
+ const movable = movableProp && !drawer;
31
88
  const id = useId();
32
89
  const dialogRef = useRef(null);
33
90
  // Content stays mounted through the exit animation, and while minimized.
@@ -41,7 +98,8 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
41
98
  const maximized = windowState === "maximized";
42
99
  const dockReady = useDockReady();
43
100
  const hosted = useContext(HostedPopupContext);
44
- const canMinimize = (minimizable ?? closable) && dockReady;
101
+ // Only a popup that asks: a CRUD page's Add and Edit, and its row forms.
102
+ const canMinimize = minimizable === true && dockReady;
45
103
  const downOnBackdropRef = useRef(false);
46
104
  // The element that opened the popup, where focus goes back when the browser has nowhere better.
47
105
  const openerRef = useRef(null);
@@ -57,6 +115,13 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
57
115
  // The box before a maximize or a restore, for the animation between the two.
58
116
  const flipFromRef = useRef(null);
59
117
  const dockId = `nx-window${id}`;
118
+ // A docked window is drawn on the page's body, never inside what opened it: a scroller, a container or a transform
119
+ // there would carry it along or cut it off. Only once the page is in the browser, where a window can be opened.
120
+ const [portalReady, setPortalReady] = useState(false);
121
+ useEffect(() => {
122
+ if (docked)
123
+ setPortalReady(true);
124
+ }, [docked]);
60
125
  function changeWindowState(next) {
61
126
  if (next === latest.current.windowState)
62
127
  return;
@@ -65,19 +130,22 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
65
130
  onWindowStateChange?.(next);
66
131
  }
67
132
  // The latest props and callbacks, for the dock bar and the drag, which outlive a render.
68
- const latest = useRef({ windowState, defaultWindowState, reopen, title, icon, closable, ariaLabel, onClose, changeWindowState });
133
+ const latest = useRef({ windowState, defaultWindowState, reopen, title, description, icon, closable, ariaLabel, onClose, changeWindowState });
69
134
  useLayoutEffect(() => {
70
- latest.current = { windowState, defaultWindowState, reopen, title, icon, closable, ariaLabel, onClose, changeWindowState };
135
+ latest.current = { windowState, defaultWindowState, reopen, title, description, icon, closable, ariaLabel, onClose, changeWindowState };
71
136
  if (!minimized)
72
137
  restoreToRef.current = windowState;
73
138
  });
74
139
  function dock() {
75
140
  const current = latest.current;
76
141
  dockedRef.current = true;
142
+ const label = typeof current.title === "string" ? current.title : current.ariaLabel;
77
143
  dockWindow({
78
144
  id: dockId,
79
145
  title: current.title ?? current.ariaLabel ?? "Window",
80
- label: typeof current.title === "string" ? current.title : current.ariaLabel,
146
+ // Under the title, so two bars of one kind (two Edit forms, two Adds) read apart: the record, the place.
147
+ description: current.description,
148
+ label: label && typeof current.description === "string" ? `${label} · ${current.description}` : label,
81
149
  icon: current.icon,
82
150
  restore: () => latest.current.changeWindowState(restoreToRef.current),
83
151
  close: current.closable ? () => latest.current.onClose("close") : undefined,
@@ -102,12 +170,37 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
102
170
  link = openers.get(link.within);
103
171
  if (link?.opener?.isConnected)
104
172
  link.opener.focus({ preventScroll: true });
173
+ // An opener that cannot take the focus - hidden in a menu that closed, disabled while its action runs - or none at
174
+ // all never leaves it on the page while another popup is still open: the popup now on top takes it, where it puts
175
+ // it on opening (a record's workspace: its chosen tab).
176
+ const now = document.activeElement;
177
+ if (now && now !== document.body && !dialog.contains(now))
178
+ return;
179
+ const below = Array.from(document.querySelectorAll("dialog.nx-popup")).filter(other => other !== dialog && other.open);
180
+ const top = below[below.length - 1];
181
+ if (top)
182
+ (homes.get(top) ?? (() => top.focus()))();
105
183
  }
184
+ // Where the focus starts: the asked-for element; else what the content marked as its start (`data-nx-focus`, such as a
185
+ // mail thread's subject, so a long read opens at its top and not on its first link or scroller); else the first control.
106
186
  function focusInside(dialog) {
107
- const target = initialFocus ? dialog.querySelector(initialFocus) : dialog.querySelector(`:is(.nx-popup__body, .nx-popup__foot) :is(${FOCUSABLE})`);
187
+ const target = initialFocus
188
+ ? dialog.querySelector(initialFocus)
189
+ : (dialog.querySelector(":is(.nx-popup__body, .nx-popup__foot) [data-nx-focus]") ?? dialog.querySelector(`:is(.nx-popup__body, .nx-popup__foot) :is(${FOCUSABLE})`));
108
190
  (target ?? dialog).focus();
109
191
  }
192
+ useLayoutEffect(() => {
193
+ const dialog = dialogRef.current;
194
+ if (dialog)
195
+ homes.set(dialog, () => focusInside(dialog));
196
+ });
110
197
  function showDialog(dialog) {
198
+ // A docked window leaves the page as it is: no backdrop, no lock, laid out beside the others.
199
+ if (docked) {
200
+ dialog.show();
201
+ dockedJoin(dialog);
202
+ return;
203
+ }
111
204
  // The first modal locks the page; the space of its scrollbar is kept as padding, so nothing shifts.
112
205
  if (!document.querySelector(".nx-popup:modal"))
113
206
  document.documentElement.style.setProperty("--nx-scrollbar-gap", `${window.innerWidth - document.documentElement.clientWidth}px`);
@@ -116,6 +209,8 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
116
209
  function closeDialog(dialog) {
117
210
  selfCloseRef.current = true;
118
211
  dialog.close();
212
+ if (docked)
213
+ layoutDocked();
119
214
  returnFocus();
120
215
  }
121
216
  // After the popup has closed: the content unmounts, and the next opening starts centered, in its default state.
@@ -212,7 +307,7 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
212
307
  focusInside(dialog);
213
308
  }
214
309
  // eslint-disable-next-line react-hooks/exhaustive-deps
215
- }, [open, minimized]);
310
+ }, [open, minimized, portalReady]);
216
311
  // Closing: the exit animation plays, then the dialog closes and the content unmounts. Without the animation
217
312
  // (reduced motion), it closes in the same frame.
218
313
  useLayoutEffect(() => {
@@ -297,6 +392,28 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
297
392
  useEffect(() => () => undock(),
298
393
  // eslint-disable-next-line react-hooks/exhaustive-deps
299
394
  []);
395
+ // A docked window leaves the row of docked windows when it goes.
396
+ useEffect(() => {
397
+ const dialog = dialogRef.current;
398
+ if (!docked || !dialog)
399
+ return;
400
+ return () => dockedLeave(dialog);
401
+ }, [docked, portalReady]);
402
+ // Esc inside a docked window closes it: the browser asks only a modal one. A menu or a list inside that took the key
403
+ // first keeps it.
404
+ function onKeyDown(event) {
405
+ if (!docked || event.key !== "Escape" || event.defaultPrevented || closing)
406
+ return;
407
+ event.preventDefault();
408
+ if (closeOnEscape)
409
+ onClose("escape");
410
+ }
411
+ // The docked window used last comes on top.
412
+ function raise() {
413
+ const dialog = dialogRef.current;
414
+ if (docked && dialog)
415
+ dockedRaise(dialog);
416
+ }
300
417
  // Esc: the browser asks to close; the page decides through onClose.
301
418
  function onCancel(event) {
302
419
  event.preventDefault();
@@ -317,10 +434,17 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
317
434
  function onPointerDown(event) {
318
435
  downOnBackdropRef.current = event.target === event.currentTarget;
319
436
  }
437
+ // A press outside a popup that stays open through it (`closeOnBackdrop: false`) leaves the focus where it was, as
438
+ // the press does nothing: a question's Confirm keeps it, so Enter still answers, and a field keeps its caret.
439
+ function onMouseDown(event) {
440
+ if (event.target === event.currentTarget && !closeOnBackdrop)
441
+ event.preventDefault();
442
+ }
320
443
  function onClick(event) {
321
444
  const onBackdrop = event.target === event.currentTarget && downOnBackdropRef.current;
322
445
  downOnBackdropRef.current = false;
323
- if (onBackdrop && closeOnBackdrop && !closing)
446
+ // A docked window has no backdrop: a press on its own edge is not outside it.
447
+ if (onBackdrop && closeOnBackdrop && !closing && !docked)
324
448
  onClose("backdrop");
325
449
  }
326
450
  // A mouse click on minimize or maximize leaves focus in the field being typed in, as a desktop window's buttons do.
@@ -369,5 +493,11 @@ export function Popup({ open, onClose, onClosed, title, description, icon, varia
369
493
  const titleId = `${id}-title`;
370
494
  const descriptionId = `${id}-description`;
371
495
  const hasControls = canMinimize || maximizable || closable;
372
- return (_jsx("dialog", { ref: dialogRef, className: cx("nx-popup", `nx-popup--${variant}`, `nx-popup--${size}`, flush && "nx-popup--flush", maximized && "nx-popup--maximized", movable && "nx-popup--movable", closing && "nx-popup--closing", className), "data-window-state": windowState, "aria-labelledby": title ? titleId : undefined, "aria-label": title ? undefined : ariaLabel, "aria-describedby": description ? descriptionId : undefined, onCancel: onCancel, onClose: onNativeClose, onPointerDown: onPointerDown, onClick: onClick, children: shown && (_jsxs(HostedPopupContext, { value: false, children: [(title || icon || hasControls) && (_jsxs("header", { className: "nx-popup__head", onPointerDown: onHeadPointerDown, onDoubleClick: onHeadDoubleClick, children: [icon && _jsx("span", { className: "nx-popup__icon", children: icon }), _jsxs("div", { className: "nx-popup__heading", children: [title && (_jsx("h2", { id: titleId, className: "nx-popup__title", children: title })), description && (_jsx("p", { id: descriptionId, className: "nx-popup__description", children: description }))] }), hasControls && (_jsxs("div", { className: "nx-popup__controls", children: [canMinimize && (_jsx("button", { type: "button", className: "nx-popup__control nx-popup__minimize", "aria-label": "Minimize", title: "Minimize", onMouseDown: keepFocus, onClick: () => changeWindowState("minimized"), children: _jsx(InlineIcon, { icon: iconMinus }) })), maximizable && (_jsx("button", { type: "button", className: "nx-popup__control nx-popup__maximize", "aria-label": maximized ? "Restore down" : "Maximize", title: maximized ? "Restore down" : "Maximize", onMouseDown: keepFocus, onClick: toggleMaximize, children: _jsx(InlineIcon, { icon: maximized ? iconSquares : iconSquare }) })), closable && (_jsx("button", { type: "button", className: "nx-popup__control nx-popup__close", "aria-label": "Close", title: "Close", onClick: () => onClose("close"), children: _jsx(InlineIcon, { icon: iconX }) }))] }))] })), _jsx("div", { className: "nx-popup__body", children: children }), footer && _jsx("footer", { className: "nx-popup__foot", children: footer })] }, round)) }));
496
+ const element = (_jsx("dialog", { ref: dialogRef, className: cx("nx-popup", `nx-popup--${variant}`, `nx-popup--${size}`, docked && "nx-popup--docked", drawer && "nx-popup--drawer", flush && "nx-popup--flush", maximized && "nx-popup--maximized", movable && "nx-popup--movable", closing && "nx-popup--closing", className), "data-window-state": windowState, style: height === undefined ? undefined : { "--nx-popup-height": typeof height === "number" ? `${height}px` : height }, "aria-labelledby": title ? titleId : undefined, "aria-label": title ? undefined : ariaLabel, "aria-describedby": cx(description ? descriptionId : undefined, ariaDescribedBy) || undefined, onCancel: onCancel, onClose: onNativeClose, onPointerDown: event => {
497
+ raise();
498
+ onPointerDown(event);
499
+ }, onFocus: raise, onKeyDown: onKeyDown, onMouseDown: onMouseDown, onClick: onClick, children: shown && (_jsxs(HostedPopupContext, { value: false, children: [(title || icon || hasControls) && (_jsxs("header", { className: "nx-popup__head", onPointerDown: onHeadPointerDown, onDoubleClick: onHeadDoubleClick, children: [icon && _jsx("span", { className: "nx-popup__icon", children: icon }), _jsxs("div", { className: "nx-popup__heading", children: [title && (_jsx("h2", { id: titleId, className: "nx-popup__title", children: title })), description && (_jsx("p", { id: descriptionId, className: "nx-popup__description", children: description }))] }), hasControls && (_jsxs("div", { className: "nx-popup__controls", children: [canMinimize && (_jsx("button", { type: "button", className: "nx-popup__control nx-popup__minimize", "aria-label": "Minimize", title: "Minimize", onMouseDown: keepFocus, onClick: () => changeWindowState("minimized"), children: _jsx(InlineIcon, { icon: iconMinus }) })), maximizable && (_jsx("button", { type: "button", className: "nx-popup__control nx-popup__maximize", "aria-label": maximized ? "Restore down" : "Maximize", title: maximized ? "Restore down" : "Maximize", onMouseDown: keepFocus, onClick: toggleMaximize, children: _jsx(InlineIcon, { icon: maximized ? iconSquares : iconSquare }) })), closable && (_jsx("button", { type: "button", className: "nx-popup__control nx-popup__close", "aria-label": "Close", title: "Close", onClick: () => onClose("close"), children: _jsx(InlineIcon, { icon: iconX }) }))] }))] })), _jsx("div", { className: "nx-popup__body", children: children }), footer && _jsx("footer", { className: "nx-popup__foot", children: footer })] }, round)) }));
500
+ if (!docked)
501
+ return element;
502
+ return portalReady ? createPortal(element, document.body) : null;
373
503
  }
@@ -3,7 +3,9 @@ import { type ReactNode } from "react";
3
3
  export interface DockedWindow {
4
4
  id: string;
5
5
  title: ReactNode;
6
- /** Plain text of the title, for the tooltip. */
6
+ /** A second line under the title: the record or the place a window is about, so two of one kind read apart. */
7
+ description?: ReactNode;
8
+ /** Plain text of the title, and of the description, for the tooltip. */
7
9
  label?: string;
8
10
  /** The window's icon; a window icon when left out. */
9
11
  icon?: ReactNode;
@@ -113,9 +113,10 @@ export function PopupDock() {
113
113
  function DockBar({ item, onRestore }) {
114
114
  const id = useId();
115
115
  const titleId = `${id}-title`;
116
+ const descriptionId = `${id}-description`;
116
117
  const closeId = `${id}-close`;
117
118
  return (_jsxs("li", { className: "nx-dock__item", children: [_jsxs("button", { type: "button", className: "nx-dock__window", title: item.label ? `Restore: ${item.label}` : "Restore", onClick: () => {
118
119
  onRestore();
119
120
  item.restore();
120
- }, children: [_jsx("span", { className: "nx-dock__icon", "aria-hidden": "true", children: item.icon ?? _jsx(InlineIcon, { icon: iconAppWindow }) }), _jsx("span", { className: "nx-visually-hidden", children: "Restore " }), _jsx("span", { id: titleId, className: "nx-dock__title", children: item.title }), _jsx(InlineIcon, { className: "nx-dock__restore", icon: iconChevronUp })] }), item.close && (_jsxs("button", { type: "button", className: "nx-dock__close", "aria-labelledby": `${closeId} ${titleId}`, title: "Close", onClick: item.close, children: [_jsx("span", { id: closeId, hidden: true, children: "Close" }), _jsx(InlineIcon, { icon: iconX })] }))] }));
121
+ }, children: [_jsx("span", { className: "nx-dock__icon", "aria-hidden": "true", children: item.icon ?? _jsx(InlineIcon, { icon: iconAppWindow }) }), _jsx("span", { className: "nx-visually-hidden", children: "Restore " }), _jsxs("span", { className: "nx-dock__text", children: [_jsx("span", { id: titleId, className: "nx-dock__title", children: item.title }), item.description ? (_jsx("span", { id: descriptionId, className: "nx-dock__description", children: item.description })) : null] }), _jsx(InlineIcon, { className: "nx-dock__restore", icon: iconChevronUp })] }), item.close && (_jsxs("button", { type: "button", className: "nx-dock__close", "aria-labelledby": item.description ? `${closeId} ${titleId} ${descriptionId}` : `${closeId} ${titleId}`, title: "Close", onClick: item.close, children: [_jsx("span", { id: closeId, hidden: true, children: "Close" }), _jsx(InlineIcon, { icon: iconX })] }))] }));
121
122
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useLayoutEffect, useSyncExternalStore } from "react";
4
4
  import { Popup } from "./Popup.js";
5
5
  import { HostedPopupContext } from "./PopupContext.js";
6
+ import { PageLeaveGuardContext } from "../Forms/FormLeaveGuard.js";
6
7
  import { PopupDock } from "./PopupDock.js";
7
8
  import { withPopupRule } from "./PopupRules.js";
8
9
  const EMPTY = [];
@@ -156,8 +157,8 @@ export function PopupHost() {
156
157
  for (const entry of list)
157
158
  rendered.add(entry.id);
158
159
  });
159
- return (_jsxs(HostedPopupContext, { value: true, children: [list.map(entry => {
160
- const { content, footer, onClose: _onClose, beforeClose: _beforeClose, onWindowStateChange: _onWindowStateChange, key: _key, ...rest } = entry.options;
161
- return (_jsx(Popup, { ...rest, open: entry.open, windowState: entry.windowState, onWindowStateChange: state => setWindowState(entry.id, state), footer: typeof footer === "function" ? footer(entry.handle) : footer, onClose: reason => requestClose(entry.id, reason), onClosed: () => removeEntry(entry.id), children: typeof content === "function" ? content(entry.handle) : content }, entry.id));
162
- }), _jsx(PopupDock, {})] }));
160
+ return (_jsx(PageLeaveGuardContext, { value: null, children: _jsxs(HostedPopupContext, { value: true, children: [list.map(entry => {
161
+ const { content, footer, onClose: _onClose, beforeClose: _beforeClose, onWindowStateChange: _onWindowStateChange, key: _key, ...rest } = entry.options;
162
+ return (_jsx(Popup, { ...rest, open: entry.open, windowState: entry.windowState, onWindowStateChange: state => setWindowState(entry.id, state), footer: typeof footer === "function" ? footer(entry.handle) : footer, onClose: reason => requestClose(entry.id, reason), onClosed: () => removeEntry(entry.id), children: typeof content === "function" ? content(entry.handle) : content }, entry.id));
163
+ }), _jsx(PopupDock, {})] }) }));
163
164
  }
@@ -0,0 +1,50 @@
1
+ import { type ReactNode } from "react";
2
+ import { type MailClientView } from "../../Helpers/MailClientHelpers.ts";
3
+ import type { ChatId, ChatSource } from "../../Interfaces/ChatInterfaces.ts";
4
+ import type { MailDomain, MailInboxRow, MessageSource } from "../../Interfaces/MailInterfaces.ts";
5
+ import type { MailDomainContext } from "./MailThread.tsx";
6
+ export interface MailClientProps {
7
+ /** Where the mailbox's data comes from: one object for the page (the bell polls on it too). */
8
+ source: MessageSource;
9
+ /** Who is reading. */
10
+ userId: ChatId;
11
+ /**
12
+ * The bar's heading. Default "Message". `false` draws none, for a page whose own heading names the client (an admin
13
+ * page's title from its menu row): the search then starts the bar, and the page has one heading.
14
+ */
15
+ title?: ReactNode | false;
16
+ /** A sentence beside the title, clipped to the rail's width, the whole of it on hover: what a page's own heading would say. */
17
+ description?: ReactNode;
18
+ /** The title's heading level: 1 when the client takes the page's own title. Default 2. */
19
+ headingLevel?: 1 | 2 | 3;
20
+ /** The folder and type shown, when the page controls them (a URL); pair with `onViewChange`. */
21
+ view?: MailClientView;
22
+ /** The folder and type shown first, when the page does not control them. Default the Inbox, every type. */
23
+ defaultView?: MailClientView;
24
+ onViewChange?: (view: MailClientView) => void;
25
+ /**
26
+ * The conversation open in the pane, when the page controls it (`?thread=`); pair with `onThreadChange`. A chat named
27
+ * here (the bell's link, an e-mail copy's) opens its chat window too, as a click on its row does.
28
+ */
29
+ threadId?: ChatId | null;
30
+ onThreadChange?: (threadId: ChatId | null) => void;
31
+ /** At the end of the bar: the app's settings, a gear `Button` that links to its admin pages. */
32
+ settings?: ReactNode;
33
+ /** Conversations per page of the list. Default 50. */
34
+ pageSize?: number;
35
+ /** A chat's lines, when they do not come from the source's `threadSource`. */
36
+ chatSourceFor?: (row: MailInboxRow) => ChatSource;
37
+ /** Draws the record a mail is about. Default its `DomainCard`. */
38
+ renderDomain?: (domain: MailDomain, context: MailDomainContext) => ReactNode;
39
+ /**
40
+ * After anything that changes what is unread or where a conversation is - a read mark once the server answered it, a
41
+ * mark, an archive: a page's bell reads again (`refreshMessageUnread`). The client's own counts wait for every read
42
+ * mark on its way, so a page needs no wrapper of its own around the source for them.
43
+ */
44
+ onChanged?: () => void;
45
+ /** Milliseconds between quiet reads of the list and its counts while the page is in front. 0 turns it off. Default 60,000. */
46
+ pollInterval?: number;
47
+ className?: string;
48
+ }
49
+ /** A mailbox as a mail client: rail, list and reading pane, with chat popups and compose windows docked at the bottom right. */
50
+ export declare function MailClient({ source, userId, title, description, headingLevel, view: viewProp, defaultView, onViewChange, threadId: threadProp, onThreadChange, settings, pageSize, chatSourceFor, renderDomain, onChanged, pollInterval, className, }: MailClientProps): import("react").JSX.Element;