fix(editor): invalid caret in note-edgeless-block on focus (#14229)

### Problem
●In edgeless mode, when starting to edit, `note-block` exhibits two
types of invalid caret behavior:
(1)**Title Region Misalignment**: Clicking on the title region
incorrectly generates the caret in the first line of the note content,
rather than in the title itself.
(2)**Vanishing Caret at Line End**: When clicking in the empty space
beyond the end of a text section, the caret appears momentarily at the
line's end but disappears immediately.
●The following video demonstrates these issues:


https://github.com/user-attachments/assets/db9c2c50-709f-4d32-912c-0f01841d2024


### Solution
●**Title Click Interception**: Added a check to determine if the click
coordinates fall in the title region. If so, the caret positioning is
now handled by a dedicated logic path. Otherwise, it falls back to the
existing note-content logic as before.
●**Range Normalization**: When the generated `range.startContainer` is
not a `TextNode`, try to find a most appropriate `TextNode` and update
the `range` accordingly.

### After
●The video below shows the behavior after this fix.


https://github.com/user-attachments/assets/b2f70b64-1fc6-4049-8379-8bcf3a488a05



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Clicking a page block title no longer creates unwanted paragraphs and
reliably focuses the title.
* Paragraph creation now occurs only when needed and focus is applied
only after successful creation.
* Click coordinates are clamped to container bounds to prevent misplaced
cursors or focus.

* **Improvements**
* Caret normalization: clicks place the caret at the last meaningful
text position for consistent single-cursor behavior.

* **Tests**
  * Added end-to-end coverage for caret placement and focus transitions.
* New ratio-based click/double-click test utilities and a helper for
double-clicking note bodies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
congzhou09
2026-03-02 18:51:23 +08:00
committed by GitHub
parent 5464d1a9ce
commit 478138493a
8 changed files with 305 additions and 5 deletions
+7
View File
@@ -6,6 +6,8 @@ import type { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph
import type { BlockComponent } from '@blocksuite/std';
import { expect, type Locator, type Page } from '@playwright/test';
import { dblclickLocatorByRatio } from './utils';
const EDGELESS_TOOLBAR_WIDGET = 'edgeless-toolbar-widget';
export const ZERO_WIDTH_FOR_EMPTY_LINE =
process.env.BROWSER === 'webkit' ? '\u200C' : '\u200B';
@@ -65,6 +67,11 @@ export function locateEditorContainer(page: Page, editorIndex = 0) {
return page.locator('[data-affine-editor-container]').nth(editorIndex);
}
export async function dblclickNoteBody(page: Page) {
const note = page.locator('affine-edgeless-note');
await dblclickLocatorByRatio(page, note, { yRatio: 0.7 });
}
export function locateDocTitle(page: Page, editorIndex = 0) {
return locateEditorContainer(page, editorIndex).locator('doc-title');
}
+46
View File
@@ -1,6 +1,7 @@
import { setTimeout } from 'node:timers/promises';
import type { Locator, Page } from '@playwright/test';
import { expect } from '@playwright/test';
import fs from 'fs-extra';
export async function waitForLogMessage(
@@ -70,3 +71,48 @@ export async function isContainedInBoundingBox(
}
return true;
}
/**
* Click at a specific position relative to a locator's bounding box.
* * Ratios are NOT clamped:
* - 0 ~ 1 : inside the bounding box
* - < 0 : outside (left / top of the box)
* - > 1 : outside (right / bottom of the box)
*
* @param locator The locator to click
* @param options Optional click position ratios
* @param options.xRatio Horizontal ratio relative to box width (not clamped), default is 0.5 (center)
* @param options.yRatio Vertical ratio relative to box height (not clamped), default is 0.5 (center)
*/
export async function clickLocatorByRatio(
page: Page,
locator: Locator,
{ xRatio = 0.5, yRatio = 0.5 } = {}
) {
const box = await getLocatorBox(locator);
await page.mouse.click(
box.x + box.width * xRatio,
box.y + box.height * yRatio
);
}
export async function dblclickLocatorByRatio(
page: Page,
locator: Locator,
{ xRatio = 0.5, yRatio = 0.5 } = {}
) {
const box = await getLocatorBox(locator);
await page.mouse.dblclick(
box.x + box.width * xRatio,
box.y + box.height * yRatio
);
}
async function getLocatorBox(locator: Locator) {
await expect(locator).toBeVisible();
const box = await locator.boundingBox();
if (!box) throw new Error(`error getting locator's bounding box`);
return box;
}