fix(bias-annotations): backdrop no longer trapped inside node canvas transform
The bias annotation editor rendered its dimming backdrop as a plain position:fixed div. Since it sits inside a node card that rete.js positions with a CSS transform for pan/zoom, that transform becomes the containing block for fixed descendants, so the backdrop was confined to the node's own box instead of covering the page. Switch to a native <dialog> shown via showModal(), which promotes it to the browser's top layer regardless of ancestor transforms, and use ::backdrop for the dimming overlay. showModal() is guarded because jsdom does not implement it, so unit tests keep working. Confirmed both by unit tests and a live browser check: the backdrop's bounding rect now spans the full viewport instead of the node's box. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
af246dab65
commit
897f57808e
|
|
@ -19,7 +19,23 @@ button:disabled { cursor: not-allowed; opacity: .5; }
|
|||
.bias-actions { justify-content: flex-end; }
|
||||
.bias-actions .danger { color: #b91c1c; }
|
||||
.bias-error, label.invalid em { margin-top: 5px; color: #b91c1c; font-size: 11px; font-style: normal; }
|
||||
.bias-modal-backdrop { position: fixed; inset: 0; z-index: 10020; display: grid; place-items: center; background: rgb(15 23 42 / 55%); }
|
||||
.bias-modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10020;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
.bias-modal-backdrop::backdrop { background: rgb(15 23 42 / 55%); }
|
||||
.bias-modal { width: min(620px, 92vw); max-height: 86vh; overflow: auto; border-radius: 14px; background: white; padding: 20px; box-shadow: 0 20px 60px rgb(0 0 0 / 25%); }
|
||||
.bias-modal h3 { margin: 0 0 14px; font-size: 18px; }
|
||||
.bias-form { display: grid; gap: 12px; }
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
</section>
|
||||
|
||||
@if (editorOpen) {
|
||||
<div class="bias-modal-backdrop" (pointerdown)="$event.stopPropagation()" (click)="close($event)">
|
||||
<dialog #biasModalDialog class="bias-modal-backdrop" (pointerdown)="$event.stopPropagation()" (click)="onDialogClick($event)" (cancel)="close($event)">
|
||||
<form class="bias-modal" (pointerdown)="$event.stopPropagation()" (click)="$event.stopPropagation()" (submit)="save($event)">
|
||||
<h3>{{ editingIndex === null ? 'Add' : 'Edit' }} bias annotation</h3>
|
||||
<div class="bias-form">
|
||||
|
|
@ -75,6 +75,6 @@
|
|||
<button type="submit" class="primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,26 @@ describe('BiasAnnotationsComponent', () => {
|
|||
expect(component.serverError(1, 'category')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders the editor as a native <dialog> so it escapes the node canvas transform, and closing it via the backdrop click works', () => {
|
||||
(fixture.nativeElement.querySelector('.bias-add') as HTMLButtonElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const dialog = fixture.nativeElement.querySelector('dialog.bias-modal-backdrop') as HTMLDialogElement;
|
||||
expect(dialog).not.toBeNull();
|
||||
|
||||
component.onDialogClick({ target: dialog, stopPropagation: vi.fn() } as unknown as MouseEvent);
|
||||
expect(component.editorOpen).toBe(false);
|
||||
});
|
||||
|
||||
it('does not close when clicking inside the dialog content', () => {
|
||||
(fixture.nativeElement.querySelector('.bias-add') as HTMLButtonElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const form = fixture.nativeElement.querySelector('.bias-modal') as HTMLElement;
|
||||
component.onDialogClick({ target: form, stopPropagation: vi.fn() } as unknown as MouseEvent);
|
||||
expect(component.editorOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('maps typed mock-output probe errors to nested fields', () => {
|
||||
validationErrors.set([{
|
||||
code: 'BIAS_PROBE_MOCK_OUTPUT_TYPE_MISMATCH',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, effect, inject, viewChild } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import {
|
||||
BiasAnnotation,
|
||||
|
|
@ -55,6 +55,32 @@ export class BiasAnnotationsComponent {
|
|||
draft: BiasAnnotation = {};
|
||||
clientErrors: Record<string, string> = {};
|
||||
|
||||
/**
|
||||
* The editor renders inside a node card, which rete.js positions with a CSS
|
||||
* `transform` for pan/zoom. A `transform` on any ancestor turns it into the
|
||||
* containing block for `position: fixed` descendants, so a plain fixed-position
|
||||
* backdrop would be confined to the node's box instead of covering the page.
|
||||
* A native `<dialog>` opened via `showModal()` is promoted to the browser's
|
||||
* top layer, which sits above the whole document regardless of ancestor
|
||||
* transforms — no manual DOM reparenting needed.
|
||||
*/
|
||||
private readonly modalDialog = viewChild<ElementRef<HTMLDialogElement>>('biasModalDialog');
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const dialog = this.modalDialog()?.nativeElement;
|
||||
if (dialog && typeof dialog.showModal === 'function' && !dialog.open) {
|
||||
dialog.showModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDialogClick(event: MouseEvent) {
|
||||
if (event.target === this.modalDialog()?.nativeElement) {
|
||||
this.close(event);
|
||||
}
|
||||
}
|
||||
|
||||
get descriptor(): BiasAnnotationsDescriptor | null {
|
||||
const descriptorSignal = (this.blocks as BlocksService & {
|
||||
biasAnnotationsDescriptor?: () => BiasAnnotationsDescriptor | null
|
||||
|
|
|
|||
Loading…
Reference in New Issue