mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
fix: rewrite selection logic and frame selection handling logic (#12421)
Fixes [BS-3528](https://linear.app/affine-design/issue/BS-3528) Fixes [BS-3331](https://linear.app/affine-design/issue/BS-3331/frame-移动逻辑很奇怪) ### Changed - Remove `onSelected` method from gfx view, use `handleSelection` provided by `GfxViewInteraction` instead. - Add `selectable` to allow model to filter out itself from selection. - Frame can be selected by body only if it's locked or its background is not transparent. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced selection behavior for frames, edgeless text, notes, and mind map elements with refined control based on lock state and background transparency. - Introduced group-aware selection logic promoting selection of appropriate group ancestors. - Added support for element selection events in interactivity extensions. - **Bug Fixes** - Resolved frame selection issues by enabling selection via title clicks and restricting body selection to locked frames or those with non-transparent backgrounds. - **Documentation** - Added clarifying comments for group retrieval methods. - **Tests** - Updated and added end-to-end tests for frame and lock selection reflecting new selection conditions. - **Refactor** - Unified and simplified selection handling by moving logic from component methods to interaction handlers and removing deprecated selection methods. - Streamlined selection candidate processing with extension-driven target suggestion. - Removed legacy group element retrieval and selection helper methods to simplify interaction logic. - **Style** - Renamed types and improved type signatures for selection context and interaction configurations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -16,8 +16,6 @@ import {
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
@@ -57,21 +55,6 @@ test.describe('frame selection', () => {
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
});
|
||||
|
||||
test('frame can selected by click blank area of frame if it has not title', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await page.locator('affine-frame-title').dblclick();
|
||||
await pressBackspace(page);
|
||||
await pressEnter(page);
|
||||
|
||||
await clickView(page, [100, 100]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
});
|
||||
|
||||
test('frame can be selected by click frame title', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
@@ -84,6 +67,59 @@ test.describe('frame selection', () => {
|
||||
await assertSelectedBound(page, [50, 50, 100, 100]);
|
||||
});
|
||||
|
||||
test('frame can be selected by body only if frame is locked or its background is not transparent', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await frameTitle.click();
|
||||
|
||||
const getButtons = (page: Page) => {
|
||||
const toolbar = page.locator('affine-toolbar-widget');
|
||||
return {
|
||||
lock: toolbar.getByTestId('lock'),
|
||||
unlock: toolbar.getByTestId('unlock'),
|
||||
};
|
||||
};
|
||||
|
||||
const { lock, unlock } = await getButtons(page);
|
||||
await lock.click();
|
||||
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
// click body should select
|
||||
await clickView(page, [100, 100]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
|
||||
await unlock.click();
|
||||
// set frame background to non-transparent value
|
||||
await page.evaluate(() => {
|
||||
const edgelessRoot = document.querySelector('affine-edgeless-root');
|
||||
|
||||
if (!edgelessRoot) {
|
||||
throw new Error('Not edgeless root found');
|
||||
}
|
||||
|
||||
const gfx = edgelessRoot.gfx;
|
||||
|
||||
gfx.std.store.transact(() => {
|
||||
// @ts-expect-error set frame block background
|
||||
gfx.selection.selectedElements[0].props.background = '#fb7081';
|
||||
});
|
||||
});
|
||||
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
// click body should select
|
||||
await clickView(page, [100, 100]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
});
|
||||
|
||||
test('frame can be selected by click frame title when a note overlap on it', async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -112,8 +148,10 @@ test.describe('frame selection', () => {
|
||||
expect(selectedIds[0]).toBe(frame);
|
||||
});
|
||||
|
||||
test('shape inside frame can be selected and edited', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
test('shape inside frame can be directly selected and edited', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frame = await createFrame(page, [50, 50], [250, 250]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
@@ -121,6 +159,30 @@ test.describe('frame selection', () => {
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [100, 100, 100, 100]);
|
||||
|
||||
// select frame to change its background
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await frameTitle.click();
|
||||
// set frame background to non-transparent value
|
||||
await page.evaluate(() => {
|
||||
const edgelessRoot = document.querySelector('affine-edgeless-root');
|
||||
|
||||
if (!edgelessRoot) {
|
||||
throw new Error('Not edgeless root found');
|
||||
}
|
||||
|
||||
const gfx = edgelessRoot.gfx;
|
||||
|
||||
gfx.std.store.transact(() => {
|
||||
// @ts-expect-error set frame block background
|
||||
gfx.selection.selectedElements[0].props.background = '#fb7081';
|
||||
});
|
||||
});
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
await clickView(page, [150, 150]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [100, 100, 100, 100]);
|
||||
|
||||
await dblclickView(page, [150, 150]);
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
|
||||
@@ -194,7 +194,7 @@ test.describe('lock', () => {
|
||||
await lock.click();
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 125, 125]); // frame outline and shape
|
||||
await pressEscape(page);
|
||||
await clickView(page, [100, 100]);
|
||||
await clickView(page, [90, 90]);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 125, 125]);
|
||||
|
||||
await unlock.click();
|
||||
|
||||
Reference in New Issue
Block a user