fix(editor): connector not added as frame child (#12611)

Co-authored-by: L-Sun <zover.v@gmail.com>
This commit is contained in:
doufa
2025-05-30 13:29:42 +08:00
committed by GitHub
parent 5c96566dd8
commit 7b82dd656b
3 changed files with 72 additions and 13 deletions
@@ -241,20 +241,35 @@ export class EdgelessFrameManager extends GfxExtension {
surfaceModel.elementAdded.subscribe(({ id, local }) => {
const element = surfaceModel.getElementById(id);
if (element && local) {
const frame = this.getFrameFromPoint(element.elementBound.center);
// if the container created with a frame, skip it.
if (
isGfxGroupCompatibleModel(element) &&
frame &&
element.hasChild(frame)
) {
return;
}
// new element may intended to be added to other group
// so we need to wait for the next microtask to check if the element can be added to the frame
// The entire frame detection logic must be in microtask for timing reasons:
//
// 1. For connectors: When elementAdded fires, connectors have invalid bounds [0,0,0,0]
// because their path/bounds are calculated in a separate microtask of updateConnectorPath by connector-watcher.
// We need to wait for that calculation to complete before frame detection.
//
// 2. For shapes: Although they have valid bounds immediately, processing them in microtask
// ensures consistent timing and allows other initialization to complete first.
//
// 3. Group compatibility: Some elements may need to establish their group relationships
// before being considered for frame membership.
//
// By embedding the entire logic in microtask, we ensure:
// - Connectors have proper bounds calculated (not [0,0,0,0])
// - getFrameFromPoint() works correctly with valid element centers
// - All element initialization is complete before frame detection
queueMicrotask(() => {
const frame = this.getFrameFromPoint(element.elementBound.center);
// if the container created with a frame, skip it.
if (
isGfxGroupCompatibleModel(element) &&
frame &&
element.hasChild(frame)
) {
return;
}
// Only add elements that aren't already grouped and have a valid frame
if (!element.group && frame) {
this.addElementsToFrame(frame, [element]);
}
@@ -40,6 +40,7 @@ describe('basic', () => {
xywh: '[100, 0, 100, 100]',
index: service.generateIndex(),
})!;
await wait(0); // wait next frame
frameId = service.crud.addBlock(
'affine:frame',
{
@@ -478,3 +478,46 @@ test('undo/redo should work when change frame background', async ({ page }) => {
expect(await getFrameBackground()).not.toBe(prevBackground);
}
});
test('connector and shape created simultaneously with edgeless-auto-complete should both be added to frame', async ({
page,
}) => {
// Create a larger frame to ensure everything fits
const frameId = await createFrame(page, [50, 50], [650, 650]);
// Create first shape inside the frame (well within bounds)
const shape1Id = await createShapeElement(
page,
[150, 150],
[250, 250],
Shape.Square
);
// Click on the existing shape to start connection
await clickView(page, [200, 200]);
const autoComplete = page.locator('edgeless-auto-complete');
const rightArrowButton = autoComplete
.locator('.edgeless-auto-complete-arrow')
.nth(0);
await rightArrowButton.click();
// Wait for async processing
await waitNextFrame(page);
// Verify that the frame contains 3 children: original shape + new shape + connector
await assertContainerChildCount(page, frameId, 3);
// Verify by moving the frame - all elements should move together
const frameTitle = page.locator('affine-frame-title');
await frameTitle.click();
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
// Check that the original shape moved with the frame
await assertEdgelessElementBound(page, shape1Id, [200, 200, 100, 100]);
await assertEdgelessElementBound(page, frameId, [100, 100, 600, 600]);
// Frame should still contain all 3 elements after the move
await assertContainerChildCount(page, frameId, 3);
});