diff --git a/frontend/src/components/simulator/PartInspectorDialog.css b/frontend/src/components/simulator/PartInspectorDialog.css index 427f87e0..aa43afc3 100644 --- a/frontend/src/components/simulator/PartInspectorDialog.css +++ b/frontend/src/components/simulator/PartInspectorDialog.css @@ -353,20 +353,6 @@ background: #d70015; /* --red-600 */ } -.pid-confirm-label { - font-size: 12px; - color: #ffb4ab; - display: flex; - align-items: center; - gap: 6px; -} - -.pid-confirm-hint { - color: #b58b86; - font-style: normal; - font-size: 11px; -} - /* Buy in the action bar: solid brand blue (Velxio buttons are solid, and the buy link is the primary action of the datasheet). */ .pid-buy-button { diff --git a/frontend/src/components/simulator/PartInspectorDialog.tsx b/frontend/src/components/simulator/PartInspectorDialog.tsx index 54897d36..61db789c 100644 --- a/frontend/src/components/simulator/PartInspectorDialog.tsx +++ b/frontend/src/components/simulator/PartInspectorDialog.tsx @@ -46,6 +46,7 @@ import { type InspectorPinInput, type PinLayoutResult, } from '../../utils/pinInspectorLayout'; +import { showConfirmDialog } from '../../store/useMessageDialogStore'; // The keybind chips and the SD Card panel keep their existing class names and // styles — both live in ComponentPropertyDialog.css, imported here so the old // dialog file can eventually retire without orphaning them. @@ -94,8 +95,9 @@ interface PartInspectorDialogProps { extraActions?: InspectorAction[]; /** Overrides the Delete button's label (boards say "Remove board"). */ deleteLabel?: string; - /** Extra line under the delete confirmation (e.g. "3 wires will be removed"). */ - deleteHint?: string; + /** The caller confirms deletion itself (boards open the remove-board modal + with its wire count), so Delete fires onDelete without asking here. */ + skipDeleteConfirm?: boolean; /** Tag rendered live in the preview when the element is not the metadata's. */ previewTagName?: string; /** Attributes to set on the preview element (boards need board-kind). */ @@ -122,7 +124,7 @@ export const PartInspectorDialog: React.FC = ({ wireInProgress, extraActions, deleteLabel, - deleteHint, + skipDeleteConfirm, previewTagName, previewAttributes, }) => { @@ -134,7 +136,6 @@ export const PartInspectorDialog: React.FC = ({ // The pin the pointer is on — its leader line and dot light up together, so // a name in the gutter can be traced to the pad it belongs to. const [hoverPin, setHoverPin] = useState(null); - const [confirmingDelete, setConfirmingDelete] = useState(false); const [layout, setLayout] = useState(null); const [previewFailed, setPreviewFailed] = useState(false); @@ -319,10 +320,37 @@ export const PartInspectorDialog: React.FC = ({ }; }, [position]); + // ── Delete goes through the global confirm modal ───────────────────────── + // Same pattern as FileExplorer / BoardOptionsModal. The old inline + // confirmation swapped the action bar's contents in place, which made the + // buttons jump around. While the modal is open the dialog's Escape and + // click-outside close handlers must stand down — the modal is portaled to + // document.body, so interacting with it looks like "outside" to them. + const confirmOpenRef = useRef(false); + const handleDeleteClick = async () => { + if (skipDeleteConfirm) { + onDelete(componentId); + return; + } + confirmOpenRef.current = true; + const confirmed = await showConfirmDialog( + t('editor.componentProps.confirmDelete', { name: componentMetadata.name }), + { + kind: 'error', + title: t('editor.componentProps.confirmDeleteTitle'), + confirmLabel: deleteLabel ?? t('editor.componentProps.delete'), + cancelLabel: t('editor.componentProps.cancel'), + danger: true, + }, + ); + confirmOpenRef.current = false; + if (confirmed) onDelete(componentId); + }; + // ── Close behaviours (unchanged semantics) ─────────────────────────────── useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') onClose(); + if (e.key === 'Escape' && !confirmOpenRef.current) onClose(); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); @@ -330,6 +358,7 @@ export const PartInspectorDialog: React.FC = ({ useEffect(() => { const handleClickOutside = (e: MouseEvent) => { + if (confirmOpenRef.current) return; if (dialogRef.current && !dialogRef.current.contains(e.target as Node)) onClose(); }; // Delay to avoid immediate close from the click that opened the dialog. @@ -637,63 +666,35 @@ export const PartInspectorDialog: React.FC = ({
- {confirmingDelete ? ( - <> - - {t('editor.componentProps.confirmDelete', { name: componentMetadata.name })} - {deleteHint && {deleteHint}} - - - - - ) : ( - <> - {/* Boards pass no onRotate (they do not rotate); they pass - Board Options / Flash as extraActions instead. */} - {onRotate && ( - - )} - {extraActions?.map((a) => ( - - ))} - - + {/* Boards pass no onRotate (they do not rotate); they pass + Board Options / Flash as extraActions instead. */} + {onRotate && ( + )} + {extraActions?.map((a) => ( + + ))} + {/* Buy lives here, right-aligned, so it is reachable from BOTH tabs — it used to sit at the end of the datasheet body, which diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx index 5b72be08..c7fd0a5b 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.tsx +++ b/frontend/src/components/simulator/SimulatorCanvas.tsx @@ -3378,11 +3378,6 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { const board = boards.find((b) => b.id === boardContextMenu.boardId); if (!board) return null; const name = boardDisplayName(board); - const connectedWires = wires.filter( - (w) => - w.start.componentId === boardContextMenu.boardId || - w.end.componentId === boardContextMenu.boardId, - ).length; const element = document.getElementById(boardContextMenu.boardId); const boardPins = element ? (element as any).pinInfo : []; const actions: InspectorAction[] = []; @@ -3441,11 +3436,9 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { previewAttributes={{ 'board-kind': board.boardKind }} extraActions={actions} deleteLabel={t('editor.canvas.removeBoard')} - deleteHint={ - connectedWires > 0 - ? t('editor.canvas.wireCount', { count: connectedWires }) - : undefined - } + // The remove-board modal (boardToRemove) is the confirmation for + // boards — it knows the wire count; a second ask here would stack. + skipDeleteConfirm wireInProgress={Boolean(wireInProgress)} onClose={() => setBoardContextMenu(null)} onPropertyChange={(id, propName, value) => {