mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-24 05:07:06 +08:00
fix(core): fix menu not close when click outside (#9535)
This commit is contained in:
@@ -5,6 +5,7 @@ import { PageViewportService } from '@blocksuite/affine-shared/services';
|
|||||||
import type { Viewport } from '@blocksuite/affine-shared/types';
|
import type { Viewport } from '@blocksuite/affine-shared/types';
|
||||||
import {
|
import {
|
||||||
focusTitle,
|
focusTitle,
|
||||||
|
getClosestBlockComponentByPoint,
|
||||||
getDocTitleInlineEditor,
|
getDocTitleInlineEditor,
|
||||||
getScrollContainer,
|
getScrollContainer,
|
||||||
matchFlavours,
|
matchFlavours,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
BlockSelection,
|
BlockSelection,
|
||||||
TextSelection,
|
TextSelection,
|
||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
|
import { Point } from '@blocksuite/global/utils';
|
||||||
import type { BlockModel, Text } from '@blocksuite/store';
|
import type { BlockModel, Text } from '@blocksuite/store';
|
||||||
import { css, html } from 'lit';
|
import { css, html } from 'lit';
|
||||||
import { query } from 'lit/decorators.js';
|
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');
|
const event = ctx.get('pointerState');
|
||||||
if (
|
if (
|
||||||
event.raw.target !== this &&
|
event.raw.target !== this &&
|
||||||
@@ -312,7 +314,6 @@ export class PageRootBlockComponent extends BlockComponent<
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { paddingLeft, paddingRight } = window.getComputedStyle(
|
const { paddingLeft, paddingRight } = window.getComputedStyle(
|
||||||
this.rootElementContainer
|
this.rootElementContainer
|
||||||
);
|
);
|
||||||
@@ -325,8 +326,53 @@ export class PageRootBlockComponent extends BlockComponent<
|
|||||||
parseFloat(paddingLeft),
|
parseFloat(paddingLeft),
|
||||||
parseFloat(paddingRight)
|
parseFloat(paddingRight)
|
||||||
);
|
);
|
||||||
if (isClickOnBlankArea) {
|
if (!isClickOnBlankArea) {
|
||||||
this.host.selection.clear(['block']);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -206,6 +206,11 @@ export class AffinePageDraggingAreaWidget extends WidgetComponent<
|
|||||||
const container = this.block.rootElementContainer;
|
const container = this.block.rootElementContainer;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
const currentFocus = document.activeElement;
|
||||||
|
if (!container.contains(currentFocus)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const containerRect = container.getBoundingClientRect();
|
const containerRect = container.getBoundingClientRect();
|
||||||
const containerStyles = window.getComputedStyle(container);
|
const containerStyles = window.getComputedStyle(container);
|
||||||
const paddingLeft = parseFloat(containerStyles.paddingLeft);
|
const paddingLeft = parseFloat(containerStyles.paddingLeft);
|
||||||
|
|||||||
@@ -8,5 +8,4 @@ export * from './scope/index.js';
|
|||||||
export * from './selection/index.js';
|
export * from './selection/index.js';
|
||||||
export * from './service/index.js';
|
export * from './service/index.js';
|
||||||
export * from './spec/index.js';
|
export * from './spec/index.js';
|
||||||
export * from './utils/index.js';
|
|
||||||
export * from './view/index.js';
|
export * from './view/index.js';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { signal } from '@preact/signals-core';
|
|||||||
|
|
||||||
import { TextSelection } from '../selection/index.js';
|
import { TextSelection } from '../selection/index.js';
|
||||||
import type { BlockComponent } from '../view/element/block-component.js';
|
import type { BlockComponent } from '../view/element/block-component.js';
|
||||||
|
import { isActiveInEditor } from './active.js';
|
||||||
|
|
||||||
export const getInlineRangeProvider: (
|
export const getInlineRangeProvider: (
|
||||||
element: BlockComponent
|
element: BlockComponent
|
||||||
@@ -87,6 +88,8 @@ export const getInlineRangeProvider: (
|
|||||||
|
|
||||||
editorHost.disposables.add(
|
editorHost.disposables.add(
|
||||||
selectionManager.slots.changed.on(selections => {
|
selectionManager.slots.changed.on(selections => {
|
||||||
|
if (!isActiveInEditor(editorHost)) return;
|
||||||
|
|
||||||
const textSelection = selections.find(s => s.type === 'text') as
|
const textSelection = selections.find(s => s.type === 'text') as
|
||||||
| TextSelection
|
| TextSelection
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { BaseSelection, BlockModel } from '@blocksuite/store';
|
|||||||
import { TextSelection } from '../selection/index.js';
|
import { TextSelection } from '../selection/index.js';
|
||||||
import type { BlockComponent } from '../view/element/block-component.js';
|
import type { BlockComponent } from '../view/element/block-component.js';
|
||||||
import { BLOCK_ID_ATTR } from '../view/index.js';
|
import { BLOCK_ID_ATTR } from '../view/index.js';
|
||||||
|
import { isActiveInEditor } from './active.js';
|
||||||
import { RANGE_SYNC_EXCLUDE_ATTR } from './consts.js';
|
import { RANGE_SYNC_EXCLUDE_ATTR } from './consts.js';
|
||||||
import type { RangeManager } from './range-manager.js';
|
import type { RangeManager } from './range-manager.js';
|
||||||
|
|
||||||
@@ -169,6 +170,7 @@ export class RangeBinding {
|
|||||||
private readonly _onNativeSelectionChanged = async () => {
|
private readonly _onNativeSelectionChanged = async () => {
|
||||||
if (this.isComposing) return;
|
if (this.isComposing) return;
|
||||||
if (!this.host) return; // Unstable when switching views, card <-> embed
|
if (!this.host) return; // Unstable when switching views, card <-> embed
|
||||||
|
if (!isActiveInEditor(this.host)) return;
|
||||||
|
|
||||||
await this.host.updateComplete;
|
await this.host.updateComplete;
|
||||||
|
|
||||||
@@ -247,6 +249,7 @@ export class RangeBinding {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private readonly _onStdSelectionChanged = (selections: BaseSelection[]) => {
|
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');
|
const closestHost = document.activeElement?.closest('editor-host');
|
||||||
if (closestHost && closestHost !== this.host) return;
|
if (closestHost && closestHost !== this.host) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './path-finder.js';
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user