fix(nodes): fix the same backdrop containing-block bug in the shared .llm-modal popup

While verifying the dialog style convergence, found that generic-node's
"Edit <field>" popup and container-node's "Edit Name" popup have the
same bug just fixed in bias-annotations: they render inside the node
card, which rete.js positions with a CSS transform for pan/zoom, so
their fixed-position .llm-modal-backdrop was trapped inside the node's
own box instead of covering the page — confirmed live, the backdrop's
bounding rect matched the node's box (280x533) rather than the
viewport.

This popup is the primary way to edit any short parameter on any node
in the editor, so the bug was hit far more often than the bias one.

Same fix: promote the backdrop to a native <dialog> shown via
showModal(), move the dimming to ::backdrop, and guard showModal()
since jsdom doesn't implement it. Verified live: the backdrop's rect
now spans the full viewport for both generic-node and container-node,
and closing via a backdrop click still works.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lucio Lelii 2026-07-22 09:20:08 +02:00
parent 803e70e6e5
commit 46976b1f92
5 changed files with 60 additions and 6 deletions

View File

@ -76,7 +76,7 @@
</div>
@if (nameEditorOpen) {
<div class="llm-modal-backdrop" (pointerdown)="$event.stopPropagation()" (click)="cancelNameEditor($event)">
<dialog #nameEditorDialog class="llm-modal-backdrop" (pointerdown)="$event.stopPropagation()" (click)="onNameEditorDialogClick($event)" (cancel)="cancelNameEditor($event)">
<div class="llm-modal" (pointerdown)="$event.stopPropagation()" (click)="$event.stopPropagation()">
<div class="llm-modal-title">Edit Name</div>
<div class="llm-modal-field">
@ -94,7 +94,7 @@
<button type="button" class="llm-btn llm-btn-primary" [disabled]="!draftName.trim()" (pointerdown)="$event.stopPropagation()" (click)="saveNameEditor($event)">Save</button>
</div>
</div>
</div>
</dialog>
}
<div class="container-node__ports">

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, HostListener, Input, OnDestroy, inject } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, HostListener, Input, OnDestroy, effect, inject, viewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatTooltipModule } from '@angular/material/tooltip';
import { currentFlowPortValueKind, flowValueKindLabel, FlowData, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY } from '@models/flow';
@ -109,6 +109,29 @@ export class ContainerNodeComponent implements OnDestroy {
nameEditorOpen = false;
draftName = '';
/**
* This template renders inside a node card, which rete.js positions with a
* CSS `transform` for pan/zoom, so a plain fixed-position backdrop would be
* confined to the node's box. A native `<dialog>` shown via `showModal()`
* escapes that via the browser's top layer, same fix as bias-annotations.
*/
private readonly nameEditorDialog = viewChild<ElementRef<HTMLDialogElement>>('nameEditorDialog');
constructor() {
effect(() => {
const dialog = this.nameEditorDialog()?.nativeElement;
if (dialog && typeof dialog.showModal === 'function' && !dialog.open) {
dialog.showModal();
}
});
}
onNameEditorDialogClick(event: MouseEvent) {
if (event.target === this.nameEditorDialog()?.nativeElement) {
this.cancelNameEditor(event);
}
}
get isSchemaLoading() {
return this.schemaLoading;
}

View File

@ -534,7 +534,7 @@
(annotationsChange)="updateBiasAnnotations($event)" />
@if (localEditorOpen) {
<div class="llm-modal-backdrop" (pointerdown)="$event.stopPropagation()" (click)="closeSimpleParamEditor($event)">
<dialog #simpleEditorDialog class="llm-modal-backdrop" (pointerdown)="$event.stopPropagation()" (click)="onSimpleEditorDialogClick($event)" (cancel)="closeSimpleParamEditor($event)">
<div class="llm-modal" (pointerdown)="$event.stopPropagation()" (click)="$event.stopPropagation()">
<div class="llm-modal-title">Edit {{ localEditorLabel }}</div>
@if (localEditorBindableAsInput && localEditorPath !== 'name') {
@ -605,7 +605,7 @@
<button type="button" class="llm-btn llm-btn-primary" [disabled]="!canSaveLocalEditor()" (pointerdown)="$event.stopPropagation()" (click)="saveSimpleParamEditor($event)">Save</button>
</div>
</div>
</div>
</dialog>
}
</div>

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, HostBinding, HostListener, inject, Input, OnDestroy } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, HostBinding, HostListener, inject, Input, OnDestroy, viewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BiasAnnotation, BiasAnnotationsDescriptor, BlockType, currentFlowPortValueKind, flowValueKindLabel, FlowBlock, FlowData, FlowPort, FlowValueKind, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY, isProbeExecutable, normalizeFlowPortValueKinds } from '@models/flow';
@ -204,6 +204,14 @@ export class GenericNodeComponent implements OnDestroy {
private conditionalRequiredByPath = new Map<string, boolean>();
private refreshingConditionalRequirements = false;
/**
* This template renders inside a node card, which rete.js positions with a
* CSS `transform` for pan/zoom, so a plain fixed-position backdrop would be
* confined to the node's box. A native `<dialog>` shown via `showModal()`
* escapes that via the browser's top layer, same fix as bias-annotations.
*/
private readonly simpleEditorDialog = viewChild<ElementRef<HTMLDialogElement>>('simpleEditorDialog');
constructor() {
effect(() => {
const descriptorSignal = (this.blocksService as BlocksService & {
@ -214,6 +222,19 @@ export class GenericNodeComponent implements OnDestroy {
this.data.data.__biasAnnotationsProperty = property;
}
});
effect(() => {
const dialog = this.simpleEditorDialog()?.nativeElement;
if (dialog && typeof dialog.showModal === 'function' && !dialog.open) {
dialog.showModal();
}
});
}
onSimpleEditorDialogClick(event: MouseEvent) {
if (event.target === this.simpleEditorDialog()?.nativeElement) {
this.closeSimpleParamEditor(event);
}
}
ngOnInit() {

View File

@ -166,7 +166,17 @@ body.node-focus-modal-open {
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
margin: 0;
padding: 12px;
border: none;
background: transparent;
color: inherit;
}
.llm-modal-backdrop::backdrop {
background: rgba(15, 23, 42, 0.45);
}