From 18ff7500c8eef392ad0ebb9510a0b58516eedf44 Mon Sep 17 00:00:00 2001 From: EYHN Date: Fri, 10 Jan 2025 08:04:48 +0000 Subject: [PATCH] fix(core): fix menu not close when click outside (#9535) --- .../src/root-block/page/page-root-block.ts | 54 +++++++++++++++++-- .../page-dragging-area/page-dragging-area.ts | 5 ++ blocksuite/framework/block-std/src/index.ts | 1 - .../framework/block-std/src/range/active.ts | 14 +++++ .../src/range/inline-range-provider.ts | 3 ++ .../block-std/src/range/range-binding.ts | 3 ++ .../framework/block-std/src/utils/index.ts | 1 - .../block-std/src/utils/path-finder.ts | 30 ----------- 8 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 blocksuite/framework/block-std/src/range/active.ts delete mode 100644 blocksuite/framework/block-std/src/utils/index.ts delete mode 100644 blocksuite/framework/block-std/src/utils/path-finder.ts diff --git a/blocksuite/blocks/src/root-block/page/page-root-block.ts b/blocksuite/blocks/src/root-block/page/page-root-block.ts index 5ebcae60c9..cdfdbcdbd4 100644 --- a/blocksuite/blocks/src/root-block/page/page-root-block.ts +++ b/blocksuite/blocks/src/root-block/page/page-root-block.ts @@ -5,6 +5,7 @@ import { PageViewportService } from '@blocksuite/affine-shared/services'; import type { Viewport } from '@blocksuite/affine-shared/types'; import { focusTitle, + getClosestBlockComponentByPoint, getDocTitleInlineEditor, getScrollContainer, matchFlavours, @@ -15,6 +16,7 @@ import { BlockSelection, TextSelection, } from '@blocksuite/block-std'; +import { Point } from '@blocksuite/global/utils'; import type { BlockModel, Text } from '@blocksuite/store'; import { css, html } from 'lit'; import { query } from 'lit/decorators.js'; @@ -303,7 +305,7 @@ export class PageRootBlockComponent extends BlockComponent< }, }); - this.handleEvent('click', ctx => { + this.handleEvent('pointerDown', ctx => { const event = ctx.get('pointerState'); if ( event.raw.target !== this && @@ -312,7 +314,6 @@ export class PageRootBlockComponent extends BlockComponent< ) { return; } - const { paddingLeft, paddingRight } = window.getComputedStyle( this.rootElementContainer ); @@ -325,8 +326,53 @@ export class PageRootBlockComponent extends BlockComponent< parseFloat(paddingLeft), parseFloat(paddingRight) ); - if (isClickOnBlankArea) { - this.host.selection.clear(['block']); + if (!isClickOnBlankArea) { + return; + } + + const hostRect = this.host.getBoundingClientRect(); + const x = hostRect.width / 2 + hostRect.left; + const point = new Point(x, event.raw.clientY); + const side = event.raw.clientX < x ? 'left' : 'right'; + + const nearestBlock = getClosestBlockComponentByPoint(point); + event.raw.preventDefault(); + if (nearestBlock) { + const text = nearestBlock.model.text; + if (text) { + this.host.selection.setGroup('note', [ + this.host.selection.create(TextSelection, { + from: { + blockId: nearestBlock.model.id, + index: side === 'left' ? 0 : text.length, + length: 0, + }, + to: null, + }), + ]); + } else { + this.host.selection.setGroup('note', [ + this.host.selection.create(BlockSelection, { + blockId: nearestBlock.model.id, + }), + ]); + } + } else { + if (this.host.selection.find(BlockSelection)) { + this.host.selection.clear(['block']); + } + } + + return; + }); + + this.handleEvent('click', ctx => { + const event = ctx.get('pointerState'); + if ( + event.raw.target !== this && + event.raw.target !== this.viewportElement && + event.raw.target !== this.rootElementContainer + ) { return; } diff --git a/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts b/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts index fb785f50b0..5d52ddc9e3 100644 --- a/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts +++ b/blocksuite/blocks/src/root-block/widgets/page-dragging-area/page-dragging-area.ts @@ -206,6 +206,11 @@ export class AffinePageDraggingAreaWidget extends WidgetComponent< const container = this.block.rootElementContainer; if (!container) return; + const currentFocus = document.activeElement; + if (!container.contains(currentFocus)) { + return; + } + const containerRect = container.getBoundingClientRect(); const containerStyles = window.getComputedStyle(container); const paddingLeft = parseFloat(containerStyles.paddingLeft); diff --git a/blocksuite/framework/block-std/src/index.ts b/blocksuite/framework/block-std/src/index.ts index 28169939b5..1167b51d01 100644 --- a/blocksuite/framework/block-std/src/index.ts +++ b/blocksuite/framework/block-std/src/index.ts @@ -8,5 +8,4 @@ export * from './scope/index.js'; export * from './selection/index.js'; export * from './service/index.js'; export * from './spec/index.js'; -export * from './utils/index.js'; export * from './view/index.js'; diff --git a/blocksuite/framework/block-std/src/range/active.ts b/blocksuite/framework/block-std/src/range/active.ts new file mode 100644 index 0000000000..8afbea1795 --- /dev/null +++ b/blocksuite/framework/block-std/src/range/active.ts @@ -0,0 +1,14 @@ +/** + * Check if the active element is in the editor host. + * TODO(@mirone): this is a trade-off, we need to use separate awareness store for every store to make sure the selection is isolated. + * + * @param editorHost - The editor host element. + * @returns Whether the active element is in the editor host. + */ +export function isActiveInEditor(editorHost: HTMLElement) { + const currentActiveElement = document.activeElement; + if (!currentActiveElement) return false; + const currentEditorHost = currentActiveElement?.closest('editor-host'); + if (!currentEditorHost) return false; + return currentEditorHost === editorHost; +} diff --git a/blocksuite/framework/block-std/src/range/inline-range-provider.ts b/blocksuite/framework/block-std/src/range/inline-range-provider.ts index 5466d0fa96..50cde7a888 100644 --- a/blocksuite/framework/block-std/src/range/inline-range-provider.ts +++ b/blocksuite/framework/block-std/src/range/inline-range-provider.ts @@ -3,6 +3,7 @@ import { signal } from '@preact/signals-core'; import { TextSelection } from '../selection/index.js'; import type { BlockComponent } from '../view/element/block-component.js'; +import { isActiveInEditor } from './active.js'; export const getInlineRangeProvider: ( element: BlockComponent @@ -87,6 +88,8 @@ export const getInlineRangeProvider: ( editorHost.disposables.add( selectionManager.slots.changed.on(selections => { + if (!isActiveInEditor(editorHost)) return; + const textSelection = selections.find(s => s.type === 'text') as | TextSelection | undefined; diff --git a/blocksuite/framework/block-std/src/range/range-binding.ts b/blocksuite/framework/block-std/src/range/range-binding.ts index b3eee6ff2a..f0393d151b 100644 --- a/blocksuite/framework/block-std/src/range/range-binding.ts +++ b/blocksuite/framework/block-std/src/range/range-binding.ts @@ -4,6 +4,7 @@ import type { BaseSelection, BlockModel } from '@blocksuite/store'; import { TextSelection } from '../selection/index.js'; import type { BlockComponent } from '../view/element/block-component.js'; import { BLOCK_ID_ATTR } from '../view/index.js'; +import { isActiveInEditor } from './active.js'; import { RANGE_SYNC_EXCLUDE_ATTR } from './consts.js'; import type { RangeManager } from './range-manager.js'; @@ -169,6 +170,7 @@ export class RangeBinding { private readonly _onNativeSelectionChanged = async () => { if (this.isComposing) return; if (!this.host) return; // Unstable when switching views, card <-> embed + if (!isActiveInEditor(this.host)) return; await this.host.updateComplete; @@ -247,6 +249,7 @@ export class RangeBinding { }; private readonly _onStdSelectionChanged = (selections: BaseSelection[]) => { + // TODO(@mirone): this is a trade-off, we need to use separate awareness store for every store to make sure the selection is isolated. const closestHost = document.activeElement?.closest('editor-host'); if (closestHost && closestHost !== this.host) return; diff --git a/blocksuite/framework/block-std/src/utils/index.ts b/blocksuite/framework/block-std/src/utils/index.ts deleted file mode 100644 index f3a720d4f7..0000000000 --- a/blocksuite/framework/block-std/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './path-finder.js'; diff --git a/blocksuite/framework/block-std/src/utils/path-finder.ts b/blocksuite/framework/block-std/src/utils/path-finder.ts deleted file mode 100644 index 1083dea56c..0000000000 --- a/blocksuite/framework/block-std/src/utils/path-finder.ts +++ /dev/null @@ -1,30 +0,0 @@ -export class PathFinder { - static equals = (path1: readonly string[], path2: readonly string[]) => { - return PathFinder.pathToKey(path1) === PathFinder.pathToKey(path2); - }; - - static id = (path: readonly string[]) => { - return path[path.length - 1]; - }; - - // check if path1 includes path2 - static includes = (path1: string[], path2: string[]) => { - return PathFinder.pathToKey(path1).startsWith(PathFinder.pathToKey(path2)); - }; - - static keyToPath = (key: string) => { - return key.split('|'); - }; - - static parent = (path: readonly string[]) => { - return path.slice(0, path.length - 1); - }; - - static pathToKey = (path: readonly string[]) => { - return path.join('|'); - }; - - private constructor() { - // this is a static class - } -}