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.
- package/LICENSE +21 -0
- package/README.md +903 -0
- package/dist/Client.Index.d.ts +44 -0
- package/dist/Client.Index.js +49 -0
- package/dist/Components/Documents/Button.d.ts +29 -0
- package/dist/Components/Documents/Button.js +11 -0
- package/dist/Components/Documents/Menu.d.ts +102 -0
- package/dist/Components/Documents/Menu.js +238 -0
- package/dist/Components/Documents/SplitButton.d.ts +64 -0
- package/dist/Components/Documents/SplitButton.js +127 -0
- package/dist/Components/Documents/TabButtons.d.ts +62 -0
- package/dist/Components/Documents/TabButtons.js +19 -0
- package/dist/Components/Forms/ApiForm.d.ts +21 -0
- package/dist/Components/Forms/ApiForm.js +99 -0
- package/dist/Components/Forms/Crud.d.ts +17 -0
- package/dist/Components/Forms/Crud.js +556 -0
- package/dist/Components/Forms/CrudParts.d.ts +15 -0
- package/dist/Components/Forms/CrudParts.js +36 -0
- package/dist/Components/Forms/Form.d.ts +52 -0
- package/dist/Components/Forms/Form.js +319 -0
- package/dist/Components/Forms/SubmitForm.d.ts +65 -0
- package/dist/Components/Forms/SubmitForm.js +198 -0
- package/dist/Components/Inputs/Calendar.d.ts +69 -0
- package/dist/Components/Inputs/Calendar.js +258 -0
- package/dist/Components/Inputs/CheckBox.d.ts +53 -0
- package/dist/Components/Inputs/CheckBox.js +87 -0
- package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
- package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
- package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
- package/dist/Components/Inputs/ChoiceField.js +169 -0
- package/dist/Components/Inputs/DatePicker.d.ts +54 -0
- package/dist/Components/Inputs/DatePicker.js +246 -0
- package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
- package/dist/Components/Inputs/DateRangePicker.js +283 -0
- package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
- package/dist/Components/Inputs/DateTimePicker.js +399 -0
- package/dist/Components/Inputs/Dropdown.d.ts +117 -0
- package/dist/Components/Inputs/Dropdown.js +839 -0
- package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
- package/dist/Components/Inputs/DropdownParts.js +102 -0
- package/dist/Components/Inputs/EditorShell.d.ts +45 -0
- package/dist/Components/Inputs/EditorShell.js +214 -0
- package/dist/Components/Inputs/Field.d.ts +53 -0
- package/dist/Components/Inputs/Field.js +16 -0
- package/dist/Components/Inputs/GroupForm.d.ts +20 -0
- package/dist/Components/Inputs/GroupForm.js +170 -0
- package/dist/Components/Inputs/InputField.d.ts +59 -0
- package/dist/Components/Inputs/InputField.js +169 -0
- package/dist/Components/Inputs/InputRenderer.d.ts +35 -0
- package/dist/Components/Inputs/InputRenderer.js +99 -0
- package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
- package/dist/Components/Inputs/MarkdownEditor.js +289 -0
- package/dist/Components/Inputs/NumberBox.d.ts +53 -0
- package/dist/Components/Inputs/NumberBox.js +228 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
- package/dist/Components/Inputs/RadioGroup.js +43 -0
- package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
- package/dist/Components/Inputs/ReadOnlyNotice.js +116 -0
- package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
- package/dist/Components/Inputs/RichTextEditor.js +1170 -0
- package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
- package/dist/Components/Inputs/RichTextParts.js +157 -0
- package/dist/Components/Inputs/RowsInput.d.ts +104 -0
- package/dist/Components/Inputs/RowsInput.js +259 -0
- package/dist/Components/Inputs/Slider.d.ts +87 -0
- package/dist/Components/Inputs/Slider.js +216 -0
- package/dist/Components/Inputs/TabularForm.d.ts +23 -0
- package/dist/Components/Inputs/TabularForm.js +283 -0
- package/dist/Components/Inputs/TextArea.d.ts +43 -0
- package/dist/Components/Inputs/TextArea.js +31 -0
- package/dist/Components/Inputs/TextBox.d.ts +52 -0
- package/dist/Components/Inputs/TextBox.js +58 -0
- package/dist/Components/Inputs/TextField.d.ts +31 -0
- package/dist/Components/Inputs/TextField.js +35 -0
- package/dist/Components/Inputs/TimePanel.d.ts +34 -0
- package/dist/Components/Inputs/TimePanel.js +288 -0
- package/dist/Components/Inputs/TimePicker.d.ts +54 -0
- package/dist/Components/Inputs/TimePicker.js +239 -0
- package/dist/Components/Inputs/TreeView.d.ts +160 -0
- package/dist/Components/Inputs/TreeView.js +947 -0
- package/dist/Components/Layouts/MessageBox.d.ts +71 -0
- package/dist/Components/Layouts/MessageBox.js +104 -0
- package/dist/Components/Layouts/Popup.d.ts +92 -0
- package/dist/Components/Layouts/Popup.js +376 -0
- package/dist/Components/Layouts/PopupContext.d.ts +2 -0
- package/dist/Components/Layouts/PopupContext.js +4 -0
- package/dist/Components/Layouts/PopupDock.d.ts +22 -0
- package/dist/Components/Layouts/PopupDock.js +121 -0
- package/dist/Components/Layouts/PopupHost.d.ts +56 -0
- package/dist/Components/Layouts/PopupHost.js +163 -0
- package/dist/Components/Layouts/PopupRules.d.ts +30 -0
- package/dist/Components/Layouts/PopupRules.js +63 -0
- package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
- package/dist/Components/Layouts/ThemePicker.js +22 -0
- package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
- package/dist/Components/Layouts/ThemeScript.js +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.js +29 -0
- package/dist/Components/Layouts/Toaster.d.ts +76 -0
- package/dist/Components/Layouts/Toaster.js +172 -0
- package/dist/Components/Viewers/DataTable.d.ts +25 -0
- package/dist/Components/Viewers/DataTable.js +542 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +134 -0
- package/dist/Components/Viewers/DataTableParts.js +145 -0
- package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
- package/dist/Components/Viewers/MarkdownView.js +79 -0
- package/dist/Components/Viewers/RichTextView.d.ts +18 -0
- package/dist/Components/Viewers/RichTextView.js +17 -0
- package/dist/Helpers/AnimationHelpers.d.ts +6 -0
- package/dist/Helpers/AnimationHelpers.js +35 -0
- package/dist/Helpers/ApiClient.d.ts +74 -0
- package/dist/Helpers/ApiClient.js +197 -0
- package/dist/Helpers/ApiFormHelpers.d.ts +26 -0
- package/dist/Helpers/ApiFormHelpers.js +56 -0
- package/dist/Helpers/ApiModules.d.ts +135 -0
- package/dist/Helpers/ApiModules.js +194 -0
- package/dist/Helpers/ApiResponses.d.ts +41 -0
- package/dist/Helpers/ApiResponses.js +159 -0
- package/dist/Helpers/ApiRoutes.d.ts +150 -0
- package/dist/Helpers/ApiRoutes.js +191 -0
- package/dist/Helpers/ApiToasts.d.ts +21 -0
- package/dist/Helpers/ApiToasts.js +22 -0
- package/dist/Helpers/ClassHelpers.d.ts +3 -0
- package/dist/Helpers/ClassHelpers.js +4 -0
- package/dist/Helpers/CrudBackend.d.ts +56 -0
- package/dist/Helpers/CrudBackend.js +119 -0
- package/dist/Helpers/CrudHelpers.d.ts +76 -0
- package/dist/Helpers/CrudHelpers.js +202 -0
- package/dist/Helpers/DateHelpers.d.ts +88 -0
- package/dist/Helpers/DateHelpers.js +363 -0
- package/dist/Helpers/DatePreferences.d.ts +14 -0
- package/dist/Helpers/DatePreferences.js +94 -0
- package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
- package/dist/Helpers/DateTimeHelpers.js +286 -0
- package/dist/Helpers/DragHelpers.d.ts +129 -0
- package/dist/Helpers/DragHelpers.js +281 -0
- package/dist/Helpers/DropdownHelpers.d.ts +56 -0
- package/dist/Helpers/DropdownHelpers.js +180 -0
- package/dist/Helpers/FormDrafts.d.ts +18 -0
- package/dist/Helpers/FormDrafts.js +76 -0
- package/dist/Helpers/FormStore.d.ts +231 -0
- package/dist/Helpers/FormStore.js +734 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
- package/dist/Helpers/InputParamsHelpers.js +388 -0
- package/dist/Helpers/MarkdownEditing.d.ts +27 -0
- package/dist/Helpers/MarkdownEditing.js +250 -0
- package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
- package/dist/Helpers/MarkdownHelpers.js +775 -0
- package/dist/Helpers/MessageBuilder.d.ts +74 -0
- package/dist/Helpers/MessageBuilder.js +254 -0
- package/dist/Helpers/NepaliCalendar.d.ts +25 -0
- package/dist/Helpers/NepaliCalendar.js +107 -0
- package/dist/Helpers/NumberHelpers.d.ts +71 -0
- package/dist/Helpers/NumberHelpers.js +202 -0
- package/dist/Helpers/Permissions.d.ts +62 -0
- package/dist/Helpers/Permissions.js +133 -0
- package/dist/Helpers/PopoverHelpers.d.ts +43 -0
- package/dist/Helpers/PopoverHelpers.js +91 -0
- package/dist/Helpers/RichTextHelpers.d.ts +77 -0
- package/dist/Helpers/RichTextHelpers.js +1072 -0
- package/dist/Helpers/RowsStore.d.ts +160 -0
- package/dist/Helpers/RowsStore.js +584 -0
- package/dist/Helpers/TableColumns.d.ts +40 -0
- package/dist/Helpers/TableColumns.js +254 -0
- package/dist/Helpers/TableExport.d.ts +30 -0
- package/dist/Helpers/TableExport.js +97 -0
- package/dist/Helpers/TableHelpers.d.ts +102 -0
- package/dist/Helpers/TableHelpers.js +281 -0
- package/dist/Helpers/TableStore.d.ts +142 -0
- package/dist/Helpers/TableStore.js +530 -0
- package/dist/Helpers/ThemeHelpers.d.ts +15 -0
- package/dist/Helpers/ThemeHelpers.js +16 -0
- package/dist/Helpers/TimeHelpers.d.ts +73 -0
- package/dist/Helpers/TimeHelpers.js +253 -0
- package/dist/Helpers/TreeStore.d.ts +188 -0
- package/dist/Helpers/TreeStore.js +577 -0
- package/dist/Helpers/ValidationHelpers.d.ts +124 -0
- package/dist/Helpers/ValidationHelpers.js +301 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +149 -0
- package/dist/Interfaces/ApiInterfaces.js +24 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +171 -0
- package/{src/sso-client.tsx → dist/Interfaces/CrudInterfaces.js} +1 -1
- package/dist/Interfaces/DateInterfaces.d.ts +154 -0
- package/dist/Interfaces/DateInterfaces.js +29 -0
- package/dist/Interfaces/FormInterfaces.d.ts +352 -0
- package/dist/Interfaces/FormInterfaces.js +1 -0
- package/dist/Interfaces/InputInterfaces.d.ts +141 -0
- package/dist/Interfaces/InputInterfaces.js +1 -0
- package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
- package/dist/Interfaces/MessageInterfaces.js +5 -0
- package/dist/Interfaces/TableInterfaces.d.ts +291 -0
- package/dist/Interfaces/TableInterfaces.js +1 -0
- package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
- package/dist/Interfaces/ThemeInterfaces.js +17 -0
- package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
- package/dist/Interfaces/TypeInterfaces.js +1 -0
- package/dist/Server.Index.d.ts +2 -0
- package/dist/Server.Index.js +5 -0
- package/dist/Services/ApiProxy.d.ts +32 -0
- package/dist/Services/ApiProxy.js +111 -0
- package/dist/Services/BrowserApi.d.ts +78 -0
- package/dist/Services/BrowserApi.js +111 -0
- package/dist/Services/ServerApi.d.ts +49 -0
- package/dist/Services/ServerApi.js +75 -0
- package/dist/Services/ThemeService.d.ts +17 -0
- package/dist/Services/ThemeService.js +83 -0
- package/dist/Shared.Index.d.ts +43 -0
- package/dist/Shared.Index.js +47 -0
- package/package.json +82 -33
- package/src/Styles/Nexus.Base.css +70 -0
- package/src/Styles/Nexus.Button.css +151 -0
- package/src/Styles/Nexus.Calendar.css +275 -0
- package/src/Styles/Nexus.Choice.css +345 -0
- package/src/Styles/Nexus.Crud.css +84 -0
- package/src/Styles/Nexus.DateTime.css +81 -0
- package/src/Styles/Nexus.Dropdown.css +774 -0
- package/src/Styles/Nexus.Editor.css +297 -0
- package/src/Styles/Nexus.Form.css +298 -0
- package/src/Styles/Nexus.Index.css +34 -0
- package/src/Styles/Nexus.Input.css +637 -0
- package/src/Styles/Nexus.Markdown.css +265 -0
- package/src/Styles/Nexus.Menu.css +249 -0
- package/src/Styles/Nexus.Popup.css +731 -0
- package/src/Styles/Nexus.RichText.css +463 -0
- package/src/Styles/Nexus.Rows.css +566 -0
- package/src/Styles/Nexus.Slider.css +278 -0
- package/src/Styles/Nexus.Tab.Buttons.css +364 -0
- package/src/Styles/Nexus.Table.css +1014 -0
- package/src/Styles/Nexus.Theme.Picker.css +159 -0
- package/src/Styles/Nexus.Themes.css +194 -0
- package/src/Styles/Nexus.Time.css +147 -0
- package/src/Styles/Nexus.Toast.css +265 -0
- package/src/Styles/Nexus.Tokens.css +72 -0
- package/src/Styles/Nexus.Tree.css +435 -0
- package/src/api-services/authentication-service.tsx +0 -23
- package/src/api-services/preference-service.tsx +0 -5
- package/src/api-services/system-service.tsx +0 -34
- package/src/client.ts +0 -31
- package/src/components/documents/button.tsx +0 -142
- package/src/components/documents/icon-box.tsx +0 -93
- package/src/components/documents/page-title.tsx +0 -7
- package/src/components/documents/tab-button.tsx +0 -172
- package/src/components/documents/tag.tsx +0 -30
- package/src/components/index.js +0 -0
- package/src/components/inputs/checkbox-input.tsx +0 -66
- package/src/components/inputs/input-box.tsx +0 -48
- package/src/components/inputs/input-element.tsx +0 -65
- package/src/components/inputs/input-form.tsx +0 -164
- package/src/components/inputs/input.tsx +0 -181
- package/src/components/inputs/number-input.tsx +0 -108
- package/src/components/inputs/radiobox-input.tsx +0 -53
- package/src/components/inputs/textarea-input.tsx +0 -47
- package/src/components/inputs/textbox-input.tsx +0 -45
- package/src/components/layouts/global-dialogbox.tsx +0 -431
- package/src/components/layouts/global-layout.tsx +0 -59
- package/src/components/layouts/layout-helpers.tsx +0 -20
- package/src/components/layouts/panels/user-panel.tsx +0 -112
- package/src/components/layouts/utility-menu.tsx +0 -49
- package/src/components/panels/theme-panel.tsx +0 -46
- package/src/helpers/bitwise-helpers.tsx +0 -11
- package/src/helpers/browser-helpers.tsx +0 -156
- package/src/helpers/datasource-helpers.tsx +0 -99
- package/src/helpers/element-helpers.tsx +0 -57
- package/src/helpers/input-helpers.tsx +0 -74
- package/src/helpers/string-helpers.tsx +0 -28
- package/src/helpers/utility-helpers.tsx +0 -45
- package/src/helpers/validation-helpers.tsx +0 -302
- package/src/index.ts +0 -26
- package/src/interface.ts +0 -19
- package/src/interfaces/auth-token-interfaces.tsx +0 -6
- package/src/interfaces/browser-interfaces.tsx +0 -36
- package/src/interfaces/button-interfaces.tsx +0 -63
- package/src/interfaces/datasource-interfaces.tsx +0 -22
- package/src/interfaces/datatable-interfaces.tsx +0 -25
- package/src/interfaces/dialogbox-interfaces.tsx +0 -5
- package/src/interfaces/exception-interfaces.tsx +0 -99
- package/src/interfaces/http-interfaces.tsx +0 -129
- package/src/interfaces/icon-interfaces.tsx +0 -129
- package/src/interfaces/input-interfaces.tsx +0 -410
- package/src/interfaces/layout-interfaces.tsx +0 -191
- package/src/interfaces/menu-interfaces.tsx +0 -49
- package/src/interfaces/message-interfaces.tsx +0 -32
- package/src/interfaces/permission-interfaces.tsx +0 -9
- package/src/interfaces/storage-interfaces.tsx +0 -5
- package/src/interfaces/system-interfaces.tsx +0 -22
- package/src/interfaces/theme-interfaces.tsx +0 -123
- package/src/interfaces/type-interfaces.tsx +0 -28
- package/src/interfaces/user-interfaces.tsx +0 -47
- package/src/nexus-client.tsx +0 -21
- package/src/nexus.environments.tsx +0 -66
- package/src/proxy-api/proxy-backend.tsx +0 -60
- package/src/proxy-api/proxy-constants.tsx +0 -28
- package/src/proxy-api/proxy-helpers.tsx +0 -91
- package/src/proxy-api-client.tsx +0 -1
- package/src/proxy-api-server.tsx +0 -2
- package/src/services/http-client-services.tsx +0 -75
- package/src/services/http-services.tsx +0 -158
- package/src/services/loader-service.tsx +0 -185
- package/src/services/localstorage-service.tsx +0 -119
- package/src/services/message-services.tsx +0 -383
- package/src/services/theme-service.tsx +0 -161
- package/src/services/user-services.tsx +0 -10
- package/src/sso-config/auth-token-validation.ts +0 -16
- package/src/sso-config/callback-route.tsx +0 -35
- package/src/sso-config/cookie-encryption.ts +0 -51
- package/src/sso-config/cookie-helpers.tsx +0 -102
- package/src/sso-config/forgot-password-route.tsx +0 -36
- package/src/sso-config/oauth-callback-state.ts +0 -61
- package/src/sso-config/pkce-helpers.ts +0 -9
- package/src/sso-config/provider-complete-route.tsx +0 -18
- package/src/sso-config/redirect-context-actions.tsx +0 -45
- package/src/sso-config/redirect-context-helpers.tsx +0 -159
- package/src/sso-config/redirect-context-persist.tsx +0 -18
- package/src/sso-config/redirect-context-route.tsx +0 -45
- package/src/sso-config/refresh-route.tsx +0 -23
- package/src/sso-config/sign-in-route.tsx +0 -74
- package/src/sso-config/sign-out-route.tsx +0 -57
- package/src/sso-config/sign-up-route.tsx +0 -57
- package/src/sso-config/sso-backend-flow.ts +0 -38
- package/src/sso-config/sso-complete-route.tsx +0 -50
- package/src/sso-config/sso-config.ts +0 -59
- package/src/sso-config/sso-interfaces.tsx +0 -99
- package/src/sso-config/sso-response-helpers.tsx +0 -20
- package/src/sso-server.tsx +0 -21
- package/src/styles/nexus.animation.css +0 -269
- package/src/styles/nexus.core.css +0 -119
- package/src/styles/nexus.dialog.css +0 -144
- package/src/styles/nexus.icon.css +0 -51
- package/src/styles/nexus.input.css +0 -207
- package/src/styles/nexus.loader.css +0 -11
- package/src/styles/nexus.logic.css +0 -47
- 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
|
+
}
|