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,71 @@
1
+ import type { ReactNode } from "react";
2
+ import { type ButtonVariant } from "../Documents/Button.tsx";
3
+ import { type TextBoxType } from "../Inputs/TextBox.tsx";
4
+ import type { PopupSize } from "./Popup.tsx";
5
+ export type MessageTone = "neutral" | "info" | "success" | "warning" | "danger" | "question";
6
+ export interface MessageBoxOptions {
7
+ title: ReactNode;
8
+ /** Text under the title. */
9
+ message?: ReactNode;
10
+ /** Colors the icon: `info`, `success`, `warning`, `danger`, `question`. `neutral` shows no icon. */
11
+ tone?: MessageTone;
12
+ /** Replaces the tone's icon; `null` shows none. */
13
+ icon?: ReactNode | null;
14
+ /** Default `sm`. */
15
+ size?: PopupSize;
16
+ /** The close button, Esc, and a click outside dismiss the box. Default true. */
17
+ closable?: boolean;
18
+ }
19
+ export interface MessageBoxButton<T extends string = string> {
20
+ value: T;
21
+ label: ReactNode;
22
+ /** Default: `primary` for the last button (`danger` when the tone is danger), `secondary` for the others. */
23
+ variant?: ButtonVariant;
24
+ /** Takes focus when the box opens. Default: the last button, or the first when the tone is danger or warning. */
25
+ autoFocus?: boolean;
26
+ }
27
+ export interface ShowMessageBoxOptions<T extends string> extends MessageBoxOptions {
28
+ buttons: readonly MessageBoxButton<T>[];
29
+ }
30
+ export interface ConfirmOptions extends MessageBoxOptions {
31
+ /** Default "OK". */
32
+ confirmLabel?: ReactNode;
33
+ /** Default "Cancel". */
34
+ cancelLabel?: ReactNode;
35
+ }
36
+ export interface AlertOptions extends MessageBoxOptions {
37
+ /** Default "OK". */
38
+ okLabel?: ReactNode;
39
+ }
40
+ export interface PromptOptions extends ConfirmOptions {
41
+ /** Label of the text box. */
42
+ label?: ReactNode;
43
+ defaultValue?: string;
44
+ placeholder?: string;
45
+ /** An empty answer is refused. Default true. */
46
+ required?: boolean;
47
+ inputType?: TextBoxType;
48
+ }
49
+ /**
50
+ * A message box with the given buttons. Resolves with the clicked button's value, or `null` when the box was
51
+ * dismissed with the close button, Esc, or a click outside.
52
+ */
53
+ export declare function showMessageBox<T extends string>(options: ShowMessageBoxOptions<T>): Promise<T | null>;
54
+ /** A message with one OK button. Resolves when it is closed. */
55
+ declare function alert(input: AlertOptions | string): Promise<void>;
56
+ /** A question with Cancel and OK. Resolves with true for OK; Esc, the close button, and a click outside mean false. */
57
+ declare function confirm(input: ConfirmOptions | string): Promise<boolean>;
58
+ /** A text box with Cancel and OK. Resolves with the text, or `null` when cancelled. */
59
+ declare function prompt(input: PromptOptions | string): Promise<string | null>;
60
+ /**
61
+ * Message boxes that return promises: `await messageBox.confirm({ title: "Delete the booking?", tone: "danger", confirmLabel: "Delete" })`,
62
+ * `messageBox.alert("Saved")`, `const name = await messageBox.prompt({ title: "Rename", defaultValue: room.name })`,
63
+ * `messageBox.show({ title, buttons: [...] })`. Needs one `<PopupHost />` on the page.
64
+ */
65
+ export declare const messageBox: {
66
+ show: typeof showMessageBox;
67
+ alert: typeof alert;
68
+ confirm: typeof confirm;
69
+ prompt: typeof prompt;
70
+ };
71
+ export {};
@@ -0,0 +1,104 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { iconAlertCircle, iconAlertTriangle, iconCircleCheck, iconHelpCircle, iconInfoCircle, InlineIcon } from "nexus-icons";
4
+ import { cx } from "../../Helpers/ClassHelpers.js";
5
+ import { Button } from "../Documents/Button.js";
6
+ import { TextBox } from "../Inputs/TextBox.js";
7
+ import { openPopup } from "./PopupHost.js";
8
+ const TONE_ICONS = {
9
+ neutral: null,
10
+ info: iconInfoCircle,
11
+ success: iconCircleCheck,
12
+ warning: iconAlertTriangle,
13
+ danger: iconAlertCircle,
14
+ question: iconHelpCircle,
15
+ };
16
+ let promptCounter = 0;
17
+ function toneIcon(tone, icon) {
18
+ if (icon !== undefined)
19
+ return icon;
20
+ const data = TONE_ICONS[tone];
21
+ return data ? _jsx(InlineIcon, { icon: data }) : null;
22
+ }
23
+ /**
24
+ * A message box with the given buttons. Resolves with the clicked button's value, or `null` when the box was
25
+ * dismissed with the close button, Esc, or a click outside.
26
+ */
27
+ export function showMessageBox(options) {
28
+ const { title, message, tone = "neutral", icon, size = "sm", closable = true, buttons } = options;
29
+ const cautious = tone === "danger" || tone === "warning";
30
+ const explicit = buttons.findIndex(button => button.autoFocus);
31
+ const focusIndex = explicit >= 0 ? explicit : cautious ? 0 : buttons.length - 1;
32
+ let result = null;
33
+ const popup = openPopup({
34
+ title,
35
+ icon: toneIcon(tone, icon),
36
+ size,
37
+ closable,
38
+ closeOnBackdrop: closable,
39
+ closeOnEscape: closable,
40
+ variant: "dialog",
41
+ minimizable: false,
42
+ maximizable: false,
43
+ className: cx("nx-popup--message", `nx-popup--tone-${tone}`),
44
+ initialFocus: "[data-nx-focus]",
45
+ content: message ? _jsx("div", { className: "nx-message__text", children: message }) : null,
46
+ footer: handle => buttons.map((button, index) => (_jsx(Button, { variant: button.variant ?? (index === buttons.length - 1 ? (tone === "danger" ? "danger" : "primary") : "secondary"), "data-nx-focus": index === focusIndex || undefined, onClick: () => {
47
+ result = button.value;
48
+ handle.close();
49
+ }, children: button.label }, button.value))),
50
+ });
51
+ return popup.closed.then(() => result);
52
+ }
53
+ function options(input) {
54
+ return typeof input === "string" ? { title: input } : input;
55
+ }
56
+ /** A message with one OK button. Resolves when it is closed. */
57
+ function alert(input) {
58
+ const given = options(input);
59
+ return showMessageBox({ tone: "info", ...given, buttons: [{ value: "ok", label: given.okLabel ?? "OK" }] }).then(() => undefined);
60
+ }
61
+ /** A question with Cancel and OK. Resolves with true for OK; Esc, the close button, and a click outside mean false. */
62
+ function confirm(input) {
63
+ const given = options(input);
64
+ return showMessageBox({
65
+ tone: "question",
66
+ ...given,
67
+ buttons: [
68
+ { value: "cancel", label: given.cancelLabel ?? "Cancel" },
69
+ { value: "confirm", label: given.confirmLabel ?? "OK" },
70
+ ],
71
+ }).then(value => value === "confirm");
72
+ }
73
+ /** A text box with Cancel and OK. Resolves with the text, or `null` when cancelled. */
74
+ function prompt(input) {
75
+ const { title, message, tone = "neutral", icon, size = "sm", closable = true, label, defaultValue, placeholder, required = true, inputType = "text", confirmLabel, cancelLabel } = options(input);
76
+ const formId = `nx-prompt-${++promptCounter}`;
77
+ let result = null;
78
+ const popup = openPopup({
79
+ title,
80
+ icon: toneIcon(tone, icon),
81
+ size,
82
+ closable,
83
+ closeOnBackdrop: closable,
84
+ closeOnEscape: closable,
85
+ variant: "dialog",
86
+ minimizable: false,
87
+ maximizable: false,
88
+ className: cx("nx-popup--message", `nx-popup--tone-${tone}`),
89
+ initialFocus: `#${formId} input`,
90
+ content: handle => (_jsxs("form", { id: formId, className: "nx-message__form", onSubmit: (event) => {
91
+ event.preventDefault();
92
+ result = String(new FormData(event.currentTarget).get("value") ?? "");
93
+ handle.close();
94
+ }, children: [message && _jsx("div", { className: "nx-message__text", children: message }), _jsx(TextBox, { name: "value", label: label, type: inputType, defaultValue: defaultValue, placeholder: placeholder, required: required })] })),
95
+ footer: handle => (_jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => handle.close(), children: cancelLabel ?? "Cancel" }), _jsx(Button, { type: "submit", form: formId, variant: "primary", children: confirmLabel ?? "OK" })] })),
96
+ });
97
+ return popup.closed.then(() => result);
98
+ }
99
+ /**
100
+ * Message boxes that return promises: `await messageBox.confirm({ title: "Delete the booking?", tone: "danger", confirmLabel: "Delete" })`,
101
+ * `messageBox.alert("Saved")`, `const name = await messageBox.prompt({ title: "Rename", defaultValue: room.name })`,
102
+ * `messageBox.show({ title, buttons: [...] })`. Needs one `<PopupHost />` on the page.
103
+ */
104
+ export const messageBox = { show: showMessageBox, alert, confirm, prompt };
@@ -0,0 +1,92 @@
1
+ import { type ReactNode } from "react";
2
+ export type PopupSize = "xs" | "sm" | "md" | "lg" | "xl" | "full";
3
+ /**
4
+ * `window`: a low title bar, as on a desktop window, with the window buttons filling its top right corner.
5
+ * `dialog`: a larger heading with the icon in a circle, as message boxes have.
6
+ */
7
+ export type PopupVariant = "window" | "dialog";
8
+ /**
9
+ * How a popup was closed: its close button, Esc, a click on the backdrop, code, or `replaced` (a popup opened with the
10
+ * same key took its place).
11
+ */
12
+ export type PopupCloseReason = "close" | "escape" | "backdrop" | "code" | "replaced";
13
+ /**
14
+ * What opening a popup again does while it is still open or minimized. `replace`: it closes, and a fresh one opens
15
+ * (content mounted again, centered, in its default window state). `restore`: it comes back as it was, with what was
16
+ * typed in it.
17
+ */
18
+ export type PopupReopen = "replace" | "restore";
19
+ /**
20
+ * A popup's window state: `normal` (its size, where it was moved to), `maximized` (the whole browser window), or
21
+ * `minimized` (a bar at the bottom right, while the page can be used).
22
+ */
23
+ export type PopupWindowState = "normal" | "maximized" | "minimized";
24
+ export interface PopupProps {
25
+ open: boolean;
26
+ /** Asked to close. The page sets `open` to false (or not, to keep it open). */
27
+ onClose: (reason: PopupCloseReason) => void;
28
+ /** After the exit animation, once the dialog is closed and its content unmounted. */
29
+ onClosed?: () => void;
30
+ title?: ReactNode;
31
+ /** Muted text after the title in the title bar (under it in a `dialog`). */
32
+ description?: ReactNode;
33
+ /** Before the title, and on its bar when minimized. */
34
+ icon?: ReactNode;
35
+ /**
36
+ * `window` (default): a low title bar with the icon, the title, and the description on one line, and the window
37
+ * buttons filling its top right corner. `dialog`: a larger heading, the icon in a circle, the description under the
38
+ * title, as message boxes have.
39
+ */
40
+ variant?: PopupVariant;
41
+ /** Width: `xs` 360, `sm` 440, `md` 560 (default), `lg` 720, `xl` 960, or `full`. */
42
+ size?: PopupSize;
43
+ /** Buttons at the end, right-aligned. */
44
+ footer?: ReactNode;
45
+ /** Shows the close button. Default true. */
46
+ closable?: boolean;
47
+ /** Esc closes. Default true. */
48
+ closeOnEscape?: boolean;
49
+ /** A click on the backdrop closes. Default true; turn off for forms that must be saved or cancelled. */
50
+ closeOnBackdrop?: boolean;
51
+ /**
52
+ * 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.
55
+ */
56
+ minimizable?: boolean;
57
+ /** The maximize button, and a double-click on the title bar: the popup fills the window. Default true. */
58
+ maximizable?: boolean;
59
+ /** The title bar moves the popup, kept inside the window. Default true. */
60
+ movable?: boolean;
61
+ /**
62
+ * What opening it again does while it is still open or minimized. `replace` (default): a fresh one, as a new opening.
63
+ * `restore`: it comes back as it was, with what was typed in it. A page popup opens again through the button that
64
+ * opened it (while minimized); a popup from `openPopup` through its `key`.
65
+ */
66
+ reopen?: PopupReopen;
67
+ /** The window state, when the page controls it; pair it with `onWindowStateChange`. */
68
+ windowState?: PopupWindowState;
69
+ /** The window state each time the popup opens, when the page does not control it. Default `normal`. */
70
+ defaultWindowState?: Exclude<PopupWindowState, "minimized">;
71
+ /** When the buttons, a double-click on the title bar, a drag, or the minimized bar change the window state. */
72
+ onWindowStateChange?: (state: PopupWindowState) => void;
73
+ /** A body without padding, for tables and lists that reach the edges. */
74
+ flush?: boolean;
75
+ /** A selector for the element that takes focus when the popup opens. Default: the first control in the body or footer. */
76
+ initialFocus?: string;
77
+ /** Name for screen readers when there is no title. */
78
+ "aria-label"?: string;
79
+ className?: string;
80
+ children?: ReactNode;
81
+ }
82
+ /**
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
86
+ * waits as a bar at the bottom right with its content kept, while the page can be used. The body scrolls while the
87
+ * title and the footer stay. The content mounts while the popup is open, so a form inside starts fresh every time.
88
+ *
89
+ * @example
90
+ * <Popup open={open} onClose={() => setOpen(false)} title="Edit booking" footer={<Button variant="primary">Save</Button>}>…</Popup>
91
+ */
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;
@@ -0,0 +1,376 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { iconMinus, iconSquare, iconSquares, iconX, InlineIcon } from "nexus-icons";
4
+ import { useContext, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
5
+ import { flushSync } from "react-dom";
6
+ import { afterAnimation } from "../../Helpers/AnimationHelpers.js";
7
+ import { cx } from "../../Helpers/ClassHelpers.js";
8
+ import { cssVariablePosition, DRAG_CANCEL_SELECTOR, moveWithin, startDrag, viewportBounds } from "../../Helpers/DragHelpers.js";
9
+ import { HostedPopupContext } from "./PopupContext.js";
10
+ import { dockWindow, undockWindow, useDockReady } from "./PopupDock.js";
11
+ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
12
+ // Where a minimized popup flies to: the newest bar of the dock, at the bottom right.
13
+ const DOCK_EDGE = 16;
14
+ const DOCK_BAR = { width: 300, height: 44 };
15
+ function motionAllowed() {
16
+ return !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
17
+ }
18
+ function popupPosition(dialog) {
19
+ return cssVariablePosition(dialog, "--nx-popup-x", "--nx-popup-y");
20
+ }
21
+ // Each popup's opener and the popup that held it, so focus can find its way back through popups that have gone.
22
+ const openers = new WeakMap();
23
+ /**
24
+ * A modal window on the native <dialog>, with the buttons of a desktop window: minimize, maximize, and close. It sits
25
+ * in the top layer above everything, dims and locks the page behind it, traps Tab, closes with Esc or a click outside,
26
+ * and returns focus to where it came from. The title bar moves it; a double-click there maximizes it. Minimized, it
27
+ * waits as a bar at the bottom right with its content kept, while the page can be used. The body scrolls while the
28
+ * title and the footer stay. The content mounts while the popup is open, so a form inside starts fresh every time.
29
+ *
30
+ * @example
31
+ * <Popup open={open} onClose={() => setOpen(false)} title="Edit booking" footer={<Button variant="primary">Save</Button>}>…</Popup>
32
+ */
33
+ 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, }) {
34
+ const id = useId();
35
+ const dialogRef = useRef(null);
36
+ // Content stays mounted through the exit animation, and while minimized.
37
+ const [shown, setShown] = useState(open);
38
+ const [closing, setClosing] = useState(false);
39
+ const [ownState, setOwnState] = useState(defaultWindowState);
40
+ // Counts fresh openings in place of a minimized popup (`reopen="replace"`): the content mounts again for each.
41
+ const [round, setRound] = useState(0);
42
+ const windowState = windowStateProp ?? ownState;
43
+ const minimized = windowState === "minimized";
44
+ const maximized = windowState === "maximized";
45
+ const dockReady = useDockReady();
46
+ const hosted = useContext(HostedPopupContext);
47
+ const canMinimize = (minimizable ?? closable) && dockReady;
48
+ const downOnBackdropRef = useRef(false);
49
+ // The element that opened the popup, where focus goes back when the browser has nowhere better.
50
+ const openerRef = useRef(null);
51
+ // True from an opening until the popup has closed, so the opener is taken once per opening (popups from code mount open).
52
+ const openingRef = useRef(false);
53
+ // The control that had focus when the popup was minimized, focused again when it comes back.
54
+ const savedFocusRef = useRef(null);
55
+ // Set before the popup closes its own dialog (to minimize or after the exit animation), so the close event is not taken for the browser's.
56
+ const selfCloseRef = useRef(false);
57
+ const dockedRef = useRef(false);
58
+ // The state a minimized popup comes back to.
59
+ const restoreToRef = useRef(defaultWindowState);
60
+ // The box before a maximize or a restore, for the animation between the two.
61
+ const flipFromRef = useRef(null);
62
+ const dockId = `nx-window${id}`;
63
+ function changeWindowState(next) {
64
+ if (next === latest.current.windowState)
65
+ return;
66
+ if (windowStateProp === undefined)
67
+ setOwnState(next);
68
+ onWindowStateChange?.(next);
69
+ }
70
+ // The latest props and callbacks, for the dock bar and the drag, which outlive a render.
71
+ const latest = useRef({ windowState, defaultWindowState, reopen, title, icon, closable, ariaLabel, onClose, changeWindowState });
72
+ useLayoutEffect(() => {
73
+ latest.current = { windowState, defaultWindowState, reopen, title, icon, closable, ariaLabel, onClose, changeWindowState };
74
+ if (!minimized)
75
+ restoreToRef.current = windowState;
76
+ });
77
+ function dock() {
78
+ const current = latest.current;
79
+ dockedRef.current = true;
80
+ dockWindow({
81
+ id: dockId,
82
+ title: current.title ?? current.ariaLabel ?? "Window",
83
+ label: typeof current.title === "string" ? current.title : current.ariaLabel,
84
+ icon: current.icon,
85
+ restore: () => latest.current.changeWindowState(restoreToRef.current),
86
+ close: current.closable ? () => latest.current.onClose("close") : undefined,
87
+ });
88
+ }
89
+ function undock() {
90
+ if (!dockedRef.current)
91
+ return;
92
+ dockedRef.current = false;
93
+ undockWindow(dockId);
94
+ }
95
+ // Focus goes back to the opener when the browser left it nowhere (its own target was a dock bar that is gone), or
96
+ // still in the closed dialog, which Chrome reports until its next focus fixup. An opener inside a popup that has
97
+ // closed since (one this popup replaced) is gone too: that popup's opener takes focus.
98
+ function returnFocus() {
99
+ const active = document.activeElement;
100
+ const dialog = dialogRef.current;
101
+ if (!dialog || (active && active !== document.body && !dialog.contains(active)))
102
+ return;
103
+ let link = openers.get(dialog);
104
+ for (let hops = 0; link && !link.opener?.isConnected && link.within && hops < 10; hops++)
105
+ link = openers.get(link.within);
106
+ if (link?.opener?.isConnected)
107
+ link.opener.focus({ preventScroll: true });
108
+ }
109
+ function focusInside(dialog) {
110
+ const target = initialFocus ? dialog.querySelector(initialFocus) : dialog.querySelector(`:is(.nx-popup__body, .nx-popup__foot) :is(${FOCUSABLE})`);
111
+ (target ?? dialog).focus();
112
+ }
113
+ function showDialog(dialog) {
114
+ // The first modal locks the page; the space of its scrollbar is kept as padding, so nothing shifts.
115
+ if (!document.querySelector(".nx-popup:modal"))
116
+ document.documentElement.style.setProperty("--nx-scrollbar-gap", `${window.innerWidth - document.documentElement.clientWidth}px`);
117
+ dialog.showModal();
118
+ }
119
+ function closeDialog(dialog) {
120
+ selfCloseRef.current = true;
121
+ dialog.close();
122
+ returnFocus();
123
+ }
124
+ // After the popup has closed: the content unmounts, and the next opening starts centered, in its default state.
125
+ function finishClose() {
126
+ const dialog = dialogRef.current;
127
+ setClosing(false);
128
+ setShown(false);
129
+ undock();
130
+ openingRef.current = false;
131
+ savedFocusRef.current = null;
132
+ if (dialog) {
133
+ dialog.style.removeProperty("--nx-popup-x");
134
+ dialog.style.removeProperty("--nx-popup-y");
135
+ }
136
+ if (windowStateProp === undefined)
137
+ setOwnState(defaultWindowState);
138
+ onClosed?.();
139
+ }
140
+ // Minimizing: the popup flies to the bottom right and fades, then the dialog closes (the page is usable again) and
141
+ // its bar appears on the dock. Without motion it goes at once. Returns a function that stops it, for a quick restore.
142
+ function leaveToDock(dialog) {
143
+ let stopped = false;
144
+ let animation = null;
145
+ const hide = () => {
146
+ if (stopped)
147
+ return;
148
+ stopped = true;
149
+ const active = document.activeElement;
150
+ savedFocusRef.current = active instanceof HTMLElement && dialog.contains(active) ? active : null;
151
+ closeDialog(dialog);
152
+ dialog.removeAttribute("data-nx-minimizing");
153
+ animation?.cancel();
154
+ dock();
155
+ };
156
+ if (!motionAllowed()) {
157
+ hide();
158
+ return () => undefined;
159
+ }
160
+ const box = dialog.getBoundingClientRect();
161
+ const view = viewportBounds();
162
+ const barWidth = Math.min(DOCK_BAR.width, view.width - 2 * DOCK_EDGE);
163
+ const dx = view.width - DOCK_EDGE - barWidth / 2 - (box.left + box.width / 2);
164
+ const dy = view.height - DOCK_EDGE - DOCK_BAR.height / 2 - (box.top + box.height / 2);
165
+ const scale = Math.max(0.2, Math.min(1, barWidth / box.width));
166
+ dialog.setAttribute("data-nx-minimizing", "");
167
+ animation = dialog.animate([{ transform: "none", opacity: 1 }, { transform: `translate(${dx}px, ${dy}px) scale(${scale})`, opacity: 0 }], { duration: 200, easing: "cubic-bezier(0.3, 0, 0.8, 0.15)", fill: "forwards" });
168
+ animation.finished.then(hide, () => undefined);
169
+ return () => {
170
+ if (stopped)
171
+ return;
172
+ stopped = true;
173
+ animation?.cancel();
174
+ dialog.removeAttribute("data-nx-minimizing");
175
+ };
176
+ }
177
+ useLayoutEffect(() => {
178
+ const dialog = dialogRef.current;
179
+ if (!dialog)
180
+ return;
181
+ if (!open) {
182
+ if (dialog.open)
183
+ setClosing(true);
184
+ // Closed while minimized: there is nothing to animate.
185
+ else if (shown)
186
+ finishClose();
187
+ return;
188
+ }
189
+ if (!openingRef.current) {
190
+ openingRef.current = true;
191
+ const active = document.activeElement;
192
+ openerRef.current = active instanceof HTMLElement && active !== document.body ? active : null;
193
+ openers.set(dialog, { opener: openerRef.current, within: openerRef.current?.closest("dialog.nx-popup") ?? null });
194
+ }
195
+ setShown(true);
196
+ setClosing(false);
197
+ if (minimized) {
198
+ if (dialog.open)
199
+ return leaveToDock(dialog);
200
+ if (!dockedRef.current)
201
+ dock();
202
+ return;
203
+ }
204
+ undock();
205
+ if (dialog.open)
206
+ return;
207
+ showDialog(dialog);
208
+ // Back from the dock: the control that had focus takes it again. A first opening focuses once the content has rendered.
209
+ if (shown) {
210
+ const saved = savedFocusRef.current;
211
+ savedFocusRef.current = null;
212
+ if (saved?.isConnected && dialog.contains(saved))
213
+ saved.focus({ preventScroll: true });
214
+ else
215
+ focusInside(dialog);
216
+ }
217
+ // eslint-disable-next-line react-hooks/exhaustive-deps
218
+ }, [open, minimized]);
219
+ // Closing: the exit animation plays, then the dialog closes and the content unmounts. Without the animation
220
+ // (reduced motion), it closes in the same frame.
221
+ useLayoutEffect(() => {
222
+ const dialog = dialogRef.current;
223
+ if (!closing || !dialog)
224
+ return;
225
+ return afterAnimation(dialog, "nx-popup-out", () => {
226
+ closeDialog(dialog);
227
+ finishClose();
228
+ });
229
+ // eslint-disable-next-line react-hooks/exhaustive-deps
230
+ }, [closing]);
231
+ // Focus moves in once the content has rendered: the asked-for element, else the first control in the body or footer.
232
+ useLayoutEffect(() => {
233
+ const dialog = dialogRef.current;
234
+ if (!open || !shown || !dialog || !dialog.open)
235
+ return;
236
+ focusInside(dialog);
237
+ // eslint-disable-next-line react-hooks/exhaustive-deps
238
+ }, [open, shown]);
239
+ // Maximize and restore grow or shrink the popup from its old box to its new one.
240
+ useLayoutEffect(() => {
241
+ const from = flipFromRef.current;
242
+ flipFromRef.current = null;
243
+ const dialog = dialogRef.current;
244
+ if (!from || !dialog?.open || minimized)
245
+ return;
246
+ const to = dialog.getBoundingClientRect();
247
+ if (!to.width || !to.height)
248
+ return;
249
+ const animation = dialog.animate([
250
+ { transformOrigin: "0 0", transform: `translate(${from.left - to.left}px, ${from.top - to.top}px) scale(${from.width / to.width}, ${from.height / to.height})` },
251
+ { transformOrigin: "0 0", transform: "none" },
252
+ ], { duration: 200, easing: "cubic-bezier(0.2, 0, 0, 1)" });
253
+ return () => animation.cancel();
254
+ }, [windowState, minimized]);
255
+ // A moved popup stays inside the window when the window gets smaller or the popup grows.
256
+ useEffect(() => {
257
+ const dialog = dialogRef.current;
258
+ if (!open || minimized || maximized || !movable || !dialog)
259
+ return;
260
+ const position = popupPosition(dialog);
261
+ const keepInside = () => {
262
+ const { x, y } = position.get();
263
+ if (dialog.open && (x || y))
264
+ moveWithin(dialog, { x: 0, y: 0 }, { position });
265
+ };
266
+ const observer = new ResizeObserver(keepInside);
267
+ observer.observe(dialog);
268
+ window.addEventListener("resize", keepInside);
269
+ return () => {
270
+ observer.disconnect();
271
+ window.removeEventListener("resize", keepInside);
272
+ };
273
+ }, [open, minimized, maximized, movable]);
274
+ // Opened again while minimized, with `reopen="replace"`: a fresh opening in its place, its content mounted again,
275
+ // centered, in its default window state.
276
+ function reopenFresh() {
277
+ const dialog = dialogRef.current;
278
+ savedFocusRef.current = null;
279
+ dialog?.style.removeProperty("--nx-popup-x");
280
+ dialog?.style.removeProperty("--nx-popup-y");
281
+ setRound(value => value + 1);
282
+ latest.current.changeWindowState(latest.current.defaultWindowState);
283
+ }
284
+ // A page's popup comes back from the dock when the button that opened it is pressed again (fresh, or as it was, by
285
+ // `reopen`), since setting `open` to true again changes nothing. Popups opened from code come back through their key.
286
+ useEffect(() => {
287
+ const opener = openerRef.current;
288
+ if (hosted || !open || !minimized || !opener?.isConnected)
289
+ return;
290
+ const bringBack = () => (latest.current.reopen === "replace" ? reopenFresh() : latest.current.changeWindowState(restoreToRef.current));
291
+ opener.addEventListener("click", bringBack);
292
+ return () => opener.removeEventListener("click", bringBack);
293
+ }, [hosted, open, minimized]);
294
+ // The dock bar follows a new title or icon; it goes when the popup unmounts.
295
+ useEffect(() => {
296
+ if (dockedRef.current)
297
+ dock();
298
+ // eslint-disable-next-line react-hooks/exhaustive-deps
299
+ }, [title, icon, closable, ariaLabel]);
300
+ useEffect(() => () => undock(),
301
+ // eslint-disable-next-line react-hooks/exhaustive-deps
302
+ []);
303
+ // Esc: the browser asks to close; the page decides through onClose.
304
+ function onCancel(event) {
305
+ event.preventDefault();
306
+ if (closeOnEscape && !closing)
307
+ onClose("escape");
308
+ }
309
+ // Closed by the browser itself, e.g. a form with method="dialog": tell the page.
310
+ function onNativeClose() {
311
+ if (selfCloseRef.current) {
312
+ selfCloseRef.current = false;
313
+ return;
314
+ }
315
+ if (open)
316
+ onClose("code");
317
+ }
318
+ // A click counts as "outside" only when it started and ended on the backdrop, so dragging a selection out of
319
+ // the popup does not close it.
320
+ function onPointerDown(event) {
321
+ downOnBackdropRef.current = event.target === event.currentTarget;
322
+ }
323
+ function onClick(event) {
324
+ const onBackdrop = event.target === event.currentTarget && downOnBackdropRef.current;
325
+ downOnBackdropRef.current = false;
326
+ if (onBackdrop && closeOnBackdrop && !closing)
327
+ onClose("backdrop");
328
+ }
329
+ // A mouse click on minimize or maximize leaves focus in the field being typed in, as a desktop window's buttons do.
330
+ function keepFocus(event) {
331
+ event.preventDefault();
332
+ }
333
+ function toggleMaximize() {
334
+ const dialog = dialogRef.current;
335
+ if (dialog && motionAllowed())
336
+ flipFromRef.current = dialog.getBoundingClientRect();
337
+ changeWindowState(maximized ? "normal" : "maximized");
338
+ }
339
+ // The title bar moves the popup. Dragging a maximized popup restores it under the pointer, as desktop windows do.
340
+ function onHeadPointerDown(event) {
341
+ const dialog = dialogRef.current;
342
+ if (!movable || !dialog || closing || event.button !== 0 || !event.isPrimary)
343
+ return;
344
+ if (event.target instanceof Element && event.target.closest(DRAG_CANCEL_SELECTOR))
345
+ return;
346
+ const position = popupPosition(dialog);
347
+ startDrag(event.nativeEvent, {
348
+ target: dialog,
349
+ handle: event.currentTarget,
350
+ position,
351
+ onStart: ({ start, pointer }) => {
352
+ if (latest.current.windowState !== "maximized")
353
+ return;
354
+ const before = dialog.getBoundingClientRect();
355
+ const along = (start.x - before.left) / before.width;
356
+ const down = start.y - before.top;
357
+ position.set({ x: 0, y: 0 });
358
+ flushSync(() => latest.current.changeWindowState("normal"));
359
+ if (latest.current.windowState === "maximized")
360
+ return false;
361
+ // Keep the pressed spot of the title bar under the pointer; the drag adds the distance moved so far.
362
+ const after = dialog.getBoundingClientRect();
363
+ position.set({ x: pointer.x - along * after.width - after.left - (pointer.x - start.x), y: pointer.y - down - after.top - (pointer.y - start.y) });
364
+ },
365
+ });
366
+ }
367
+ function onHeadDoubleClick(event) {
368
+ if (!maximizable || closing || (event.target instanceof Element && event.target.closest(DRAG_CANCEL_SELECTOR)))
369
+ return;
370
+ toggleMaximize();
371
+ }
372
+ const titleId = `${id}-title`;
373
+ const descriptionId = `${id}-description`;
374
+ const hasControls = canMinimize || maximizable || closable;
375
+ 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)) }));
376
+ }
@@ -0,0 +1,2 @@
1
+ /** True inside `<PopupHost />`: its popups are opened from code and come back through their `key`, not their opener. Internal. */
2
+ export declare const HostedPopupContext: import("react").Context<boolean>;
@@ -0,0 +1,4 @@
1
+ "use client";
2
+ import { createContext } from "react";
3
+ /** True inside `<PopupHost />`: its popups are opened from code and come back through their `key`, not their opener. Internal. */
4
+ export const HostedPopupContext = createContext(false);