← Files DataARCHIVED FILE
templates/data-app/base/src/components/contained-ui.jsx
5.78 KB · Sep 30, 2026 · 23:19 UTC
import React, { useCallback, useId, useLayoutEffect, useRef } from "react";
import { Icon } from "./Icon.jsx";
function deepActiveElement(root) {
let active = root?.activeElement;
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
return active;
}
function dialogFocusableElements(element) {
return [
...element.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])',
),
].filter(
(candidate) =>
candidate.tabIndex >= 0 &&
!candidate.closest('[inert], [hidden], [aria-hidden="true"]') &&
candidate.getClientRects().length > 0,
);
}
/**
* An intrinsic, host-contained dialog. It never portals the layer, locks the
* document body, or listens for keys outside its own content.
*/
export function ContainedDialog({
title,
initialFocusSelector,
expanded = false,
className = "",
headerActions,
onClose,
children,
showClose = true,
layerClassName = "",
contentRef,
onKeyDown,
}) {
const ownContentRef = useRef(null);
const titleId = useId();
const setContentRef = useCallback(
(element) => {
ownContentRef.current = element;
if (typeof contentRef === "function") contentRef(element);
else if (contentRef) contentRef.current = element;
},
[contentRef],
);
useLayoutEffect(() => {
const element = ownContentRef.current;
if (!element) return undefined;
const ownerDocument = element.ownerDocument;
const focusRoot = element.getRootNode();
const previousFocus = deepActiveElement(focusRoot) ?? deepActiveElement(ownerDocument);
const initial = initialFocusSelector ? element.querySelector(initialFocusSelector) : element;
(initial ?? element).focus({ preventScroll: true });
return () =>
queueMicrotask(() => {
const active = deepActiveElement(focusRoot) ?? deepActiveElement(ownerDocument);
if (
active?.isConnected &&
active !== ownerDocument.body &&
active !== focusRoot.host &&
active !== previousFocus &&
!element.contains(active)
)
return;
if (previousFocus?.isConnected) previousFocus.focus({ preventScroll: true });
});
}, [initialFocusSelector]);
return (
<div className={["dialog-layer", "dialog-layer--contained", layerClassName].filter(Boolean).join(" ")}>
<section
ref={setContentRef}
role="dialog"
aria-labelledby={titleId}
aria-modal={false}
tabIndex={-1}
className={["dialog", expanded ? "expanded" : "source-dialog", className].filter(Boolean).join(" ")}
onKeyDown={(event) => {
onKeyDown?.(event);
if (event.defaultPrevented) return;
if (event.key === "Escape") {
event.preventDefault();
event.stopPropagation();
onClose();
return;
}
if (event.key !== "Tab") return;
const element = ownContentRef.current;
const focusable = dialogFocusableElements(element);
const active = deepActiveElement(element.getRootNode()) ?? deepActiveElement(element.ownerDocument);
const first = focusable[0];
const last = focusable.at(-1);
if (
!first ||
(event.shiftKey && (active === first || !focusable.includes(active))) ||
(!event.shiftKey && (active === last || !focusable.includes(active)))
) {
event.preventDefault();
(event.shiftKey ? last : first)?.focus({ preventScroll: true });
if (!first) element.focus({ preventScroll: true });
}
}}
>
<header className="dialog-header" tabIndex={initialFocusSelector === ".dialog-header" ? -1 : undefined}>
<div className="dialog-title-group">
<h2 id={titleId}>{title}</h2>
</div>
<div className="dialog-header-actions">
{headerActions}
{showClose && (
<button type="button" className="icon-button" aria-label="Close" onClick={onClose}>
<Icon name="cross" size={20} />
</button>
)}
</div>
</header>
<div className="dialog-content">{children}</div>
</section>
</div>
);
}
/** The shared Select value/choice contract without a portaled menu runtime. */
export function NativeSelect({
label,
value,
choices = [],
onChange,
showLabel = false,
allLabel = "All",
formatChoice,
triggerClassName = "",
disabled = false,
groups,
}) {
const display = (choice) => (choice === "all" ? allLabel : choice ? formatChoice?.(choice) ?? choice : "No series");
const select = (
<select
className={["select-trigger", "native-select", triggerClassName].filter(Boolean).join(" ")}
aria-label={label}
value={value ?? ""}
disabled={disabled}
onChange={(event) => onChange?.(event.currentTarget.value)}
onKeyDown={(event) => {
// Let the browser dismiss its own picker without closing the editor.
if (event.key === "Escape") event.stopPropagation();
}}
>
{(groups ?? [{ choices }]).map((group, index) => {
const options = group.choices.map((choice) => (
<option key={choice} value={choice}>
{display(choice)}
</option>
));
return group.label ? (
<optgroup key={group.label} label={group.label}>
{options}
</optgroup>
) : (
<React.Fragment key={index}>{options}</React.Fragment>
);
})}
</select>
);
return showLabel ? (
<label className="native-select-field">
<span className="filter-label">{label}</span>
{select}
</label>
) : (
select
);
}
SHA-256: da4daa991f8df9421a4290ca33709fb8bb055280c0558a4de1153b698472a789