feat(project): per-project new-chat defaults (agent, workspace, approval, thinking, model) (#191)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,14 +30,34 @@ export interface ModalProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack of currently open Modals: Escape only closes the **topmost** one. When
|
||||
* dialogs are nested (e.g. a confirmation popped inside a settings modal), each
|
||||
* Modal registers its own window keydown->onClose; without checking the top of
|
||||
* the stack, a single Escape would close both layers at once and discard unsaved
|
||||
* edits in the outer modal. Pushed in mount order, so the top of the stack is the
|
||||
* visually topmost dialog.
|
||||
* Stack of currently open Escape-consuming layers: Escape only acts on the **topmost**
|
||||
* one. Modals AND popup menus (Dropdown) join the same stack. When dialogs are nested
|
||||
* (e.g. a confirmation popped inside a settings modal), each Modal registers its own
|
||||
* window keydown->onClose; without checking the top of the stack, a single Escape would
|
||||
* close both layers at once and discard unsaved edits in the outer modal. The same rule
|
||||
* gives menus-inside-dialogs the right order: a Dropdown opened inside a Modal pushes
|
||||
* above it, so the first Escape closes only the menu (the Modal's handler sees itself
|
||||
* not on top and stays), and the next one closes the dialog. Pushed in mount order, so
|
||||
* the top of the stack is the visually topmost layer.
|
||||
*/
|
||||
const modalStack: symbol[] = [];
|
||||
const escLayers: symbol[] = [];
|
||||
|
||||
/** Register an Escape-consuming layer (called when a modal/menu opens); pair with popEscLayer. */
|
||||
export function pushEscLayer(): symbol {
|
||||
const id = Symbol("esc-layer");
|
||||
escLayers.push(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
export function popEscLayer(id: symbol): void {
|
||||
const i = escLayers.lastIndexOf(id);
|
||||
if (i !== -1) escLayers.splice(i, 1);
|
||||
}
|
||||
|
||||
/** Whether this layer is the topmost one — the only layer an Escape press may act on. */
|
||||
export function isTopEscLayer(id: symbol): boolean {
|
||||
return escLayers[escLayers.length - 1] === id;
|
||||
}
|
||||
|
||||
export function Modal({
|
||||
open,
|
||||
@@ -50,16 +70,14 @@ export function Modal({
|
||||
}: ModalProps) {
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const id = Symbol("modal");
|
||||
modalStack.push(id);
|
||||
const id = pushEscLayer();
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" && modalStack[modalStack.length - 1] === id) onClose();
|
||||
if (e.key === "Escape" && isTopEscLayer(id)) onClose();
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKey);
|
||||
const i = modalStack.lastIndexOf(id);
|
||||
if (i !== -1) modalStack.splice(i, 1);
|
||||
popEscLayer(id);
|
||||
};
|
||||
}, [open, onClose]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user