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