A singleton-style global interaction layer for alerts, confirms, form dialogs, dropdown menus, and transient toasts.
Quickstart
Mount once near app root:
<rtgl-global-ui id="global-ui"></rtgl-global-ui>
<rtgl-button id="show-alert">Show Alert</rtgl-button>
<script>
const globalUi = document.getElementById("global-ui");
document.getElementById("show-alert").addEventListener("click", async () => {
await globalUi.transformedHandlers.handleShowAlert({
title: "Saved",
message: "Changes were saved successfully.",
confirmText: "OK",
});
});
</script>
Imperative API
Call through globalUiElement.transformedHandlers:
| Method | Payload | Result |
|---|---|---|
handleShowAlert(options) |
{ message, title?, status?, confirmText? } |
resolves when the alert closes |
handleShowConfirm(options) |
{ message, title?, status?, confirmText?, cancelText? } |
resolves true (confirm) or false (cancel / dismiss) |
handleShowFormDialog(options) |
{ form, defaultValues?, context?, disabled?, size?, onFieldEvent?, mount? } |
resolves { actionId, values }, { actionId, values, valid, errors }, or null on dismiss |
handleShowDropdownMenu(options) |
{ items, x, y, place?, dir?, ariaLabel? } |
resolves { index, indexPath, item } or null |
handleShowToast(options) |
{ message, size?, position? } |
fire-and-forget toast that auto-dismisses after 3 seconds |
handleCloseAll() |
none | closes any open global dialog/dropdown, clears visible toasts, and resolves the pending flow |
Multiline Alert and Confirm Messages
Alert and confirm message values are plain text. Newlines (\n), blank lines,
and spaces are preserved; long text wraps to fit the dialog, including long file
names. HTML and Markdown are not interpreted.
await globalUiElement.transformedHandlers.handleShowAlert({
title: "Warning",
message: "Could not load these assets:\n• Images: Image One\n• Fonts: Font One\n\nReplace their files with valid copies.",
});
The same formatting works with or without a title and in handleShowConfirm.
Form Dialog
handleShowFormDialog(options) renders an embedded rtgl-form inside the shared global dialog shell.
Form Dialog Options
| Field | Type | Notes |
|---|---|---|
form |
object | required rtgl-form schema |
defaultValues |
object | initial form values |
context |
object | extra jempl/context data for the form |
disabled |
boolean | disables the whole form |
size |
sm | md | lg | f |
dialog size token, defaults to md |
onFieldEvent |
({ detail, formEl }) => void |
optional hook for form-field-event forwarding |
mount |
(formEl) => void |
optional one-time mount hook for slot content or custom imperative setup |
<rtgl-global-ui id="global-ui"></rtgl-global-ui>
<rtgl-button id="invite-user">Invite User</rtgl-button>
<rtgl-text id="invite-result">No submission yet</rtgl-text>
<script>
const globalUi = document.getElementById("global-ui");
const result = document.getElementById("invite-result");
document.getElementById("invite-user").addEventListener("click", async () => {
const dialogResult = await globalUi.transformedHandlers.handleShowFormDialog({
size: "lg",
defaultValues: {
name: "Ada Lovelace",
email: "[email protected]",
role: "developer",
},
form: {
title: "Invite Team Member",
description: "Configure access before sending the invite.",
fields: [
{ name: "name", type: "input-text", label: "Name", required: true },
{
name: "email",
type: "input-text",
label: "Email",
required: true,
rules: [{ rule: "pattern", value: "email", message: "Must be a valid email" }],
},
{
name: "role",
type: "select",
label: "Role",
options: [
{ label: "Developer", value: "developer" },
{ label: "Designer", value: "designer" },
{ label: "Manager", value: "manager" },
],
},
],
actions: {
buttons: [
{ id: "cancel", label: "Cancel", variant: "se", align: "left" },
{ id: "invite", label: "Invite", variant: "pr", validate: true },
],
},
},
});
result.textContent = dialogResult
? `Resolved: ${dialogResult.actionId}`
: "Dismissed";
});
</script>
Dropdown Item Shape
handleShowDropdownMenu({ items }) accepts the same recursive item model as
rtgl-dropdown-menu. A non-empty items array on an enabled item makes it a
submenu trigger and takes precedence over its href, path, or leaf action.
const result = await globalUi.transformedHandlers.handleShowDropdownMenu({
x: event.clientX,
y: event.clientY,
items: [
{ type: "section", label: "Actions" },
{ id: "rename", label: "Rename" },
{
id: "export",
label: "Export",
items: [
{ id: "export-pdf", label: "PDF" },
{
id: "export-image",
label: "Image",
items: [
{ id: "export-png", label: "PNG" },
{ id: "export-webp", label: "WebP" },
],
},
],
},
{ type: "separator" },
{ id: "help", label: "Help", href: "/help" },
],
});
if (result) {
const { index, indexPath, item } = result;
console.log({ index, indexPath, item });
}
The supported row shapes are:
- Section:
{ type: "section", label: string }. Legacytype: "label"remains an alias. - Separator:
{ type: "separator" }. - Leaf item:
{ type?: "item", label: string, href?, path?, disabled?, ... }. - Submenu trigger:
{ type?: "item", label: string, items: DropdownItem[] }.
For a selection, item is the selected leaf, index is its index in the
immediate menu, and indexPath contains the root-to-leaf indexes. A legacy flat
selection at index 2 therefore resolves with both index: 2 and
indexPath: [2].
Nested menus support Up/Down, Home/End, printable-character typeahead,
Enter/Space, and direction-aware submenu arrows. Disabled rows remain
keyboard-focusable but cannot be activated. Escape closes the deepest submenu
first, then the root; Tab closes the complete dropdown and continues normal
focus navigation. Pass dir: "rtl" to set an explicit direction and
ariaLabel to name the root menu and dialog.
Toast
handleShowToast({ message, size?, position? }) renders a toast and removes it automatically after 3 seconds.
<rtgl-global-ui id="global-ui"></rtgl-global-ui>
<rtgl-button id="copy-button">Copy Link</rtgl-button>
<script>
const globalUi = document.getElementById("global-ui");
document.getElementById("copy-button").addEventListener("click", () => {
globalUi.transformedHandlers.handleShowToast({
message: "Link copied to clipboard.",
position: "bottom",
});
});
</script>
Behavior
Behavior & precedence
- New global UI calls close any existing open global UI first.
handleShowAlert,handleShowConfirm,handleShowFormDialog, andhandleShowDropdownMenuare awaitable.handleShowToast({ message, size?, position? })is non-blocking, stacks multiple messages, and auto-dismisses after 3 seconds.- Toast
positionacceptstoporbottomand defaults totop. handleShowFormDialoguses the existingrtgl-formaction payload:{ actionId, values }or{ actionId, values, valid, errors }.handleShowDropdownMenuresolves only after a leaf selection. Submenu triggers open their child menu instead of resolving the promise.- A validating form action only resolves when validation passes. Invalid submit keeps the dialog open and shows inline errors.
- Use
mount(formEl)for slotted/custom content andonFieldEvent({ detail, formEl })for image-picker or custom field workflows. - A dismissed confirm resolves
false. Dismissed alert/form/dropdown flows resolvenull/empty completion. - Keep a single mounted
rtgl-global-uiinstance per page/app shell.