fix(editor): dragging area gets stuck when hovering some embed blocks (#11223)

Close [BS-2778](https://linear.app/affine-design/issue/BS-2778/[bug]-选区框选-html-block-卡顿)
This commit is contained in:
donteatfriedrice
2025-03-27 03:59:26 +00:00
parent b00584c4cc
commit e763061bd6
6 changed files with 43 additions and 123 deletions

View File

@@ -13,7 +13,7 @@ import { findAncestorModel } from '@blocksuite/affine-shared/utils';
import type { BlockService } from '@blocksuite/block-std'; import type { BlockService } from '@blocksuite/block-std';
import type { GfxCompatibleProps } from '@blocksuite/block-std/gfx'; import type { GfxCompatibleProps } from '@blocksuite/block-std/gfx';
import type { BlockModel } from '@blocksuite/store'; import type { BlockModel } from '@blocksuite/store';
import { computed, type ReadonlySignal } from '@preact/signals-core'; import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
import type { TemplateResult } from 'lit'; import type { TemplateResult } from 'lit';
import { html } from 'lit'; import { html } from 'lit';
import { query } from 'lit/decorators.js'; import { query } from 'lit/decorators.js';
@@ -31,6 +31,17 @@ export class EmbedBlockComponent<
}) })
); );
readonly isDraggingOnHost$ = signal(false);
readonly isResizing$ = signal(false);
// show overlay to prevent the iframe from capturing pointer events
// when the block is dragging, resizing, or not selected
readonly showOverlay$ = computed(
() =>
this.isDraggingOnHost$.value ||
this.isResizing$.value ||
!this.selected$.value
);
private _fetchAbortController = new AbortController(); private _fetchAbortController = new AbortController();
_cardStyle: EmbedCardStyle = 'horizontal'; _cardStyle: EmbedCardStyle = 'horizontal';
@@ -114,6 +125,24 @@ export class EmbedBlockComponent<
this._fetchAbortController = new AbortController(); this._fetchAbortController = new AbortController();
this.contentEditable = 'false'; this.contentEditable = 'false';
// subscribe the editor host global dragging event
// to show the overlay for the dragging area or other pointer events
this.handleEvent(
'dragStart',
() => {
this.isDraggingOnHost$.value = true;
},
{ global: true }
);
this.handleEvent(
'dragEnd',
() => {
this.isDraggingOnHost$.value = false;
},
{ global: true }
);
} }
override disconnectedCallback(): void { override disconnectedCallback(): void {

View File

@@ -24,12 +24,6 @@ export function toEdgelessEmbedBlock<
return class extends toGfxBlockComponent(block) { return class extends toGfxBlockComponent(block) {
override selectedStyle$ = null; override selectedStyle$ = null;
_isDragging = false;
_isResizing = false;
_showOverlay = false;
override [blockComponentSymbol] = true; override [blockComponentSymbol] = true;
override blockDraggable = false; override blockDraggable = false;
@@ -55,16 +49,13 @@ export function toEdgelessEmbedBlock<
this._disposables.add( this._disposables.add(
this.edgelessSlots.elementResizeStart.subscribe(() => { this.edgelessSlots.elementResizeStart.subscribe(() => {
this._isResizing = true; this.isResizing$.value = true;
this._showOverlay = true;
}) })
); );
this._disposables.add( this._disposables.add(
this.edgelessSlots.elementResizeEnd.subscribe(() => { this.edgelessSlots.elementResizeEnd.subscribe(() => {
this._isResizing = false; this.isResizing$.value = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
}) })
); );
} }

View File

@@ -5,7 +5,6 @@ import type {
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { BlockSelection } from '@blocksuite/block-std'; import { BlockSelection } from '@blocksuite/block-std';
import { html, nothing } from 'lit'; import { html, nothing } from 'lit';
import { state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
@@ -17,10 +16,6 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
override _cardStyle: (typeof EmbedFigmaStyles)[number] = 'figma'; override _cardStyle: (typeof EmbedFigmaStyles)[number] = 'figma';
protected _isDragging = false;
protected _isResizing = false;
open = () => { open = () => {
let link = this.model.props.url; let link = this.model.props.url;
if (!link.match(/^[a-zA-Z]+:\/\//)) { if (!link.match(/^[a-zA-Z]+:\/\//)) {
@@ -68,25 +63,6 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
} }
}) })
); );
// this is required to prevent iframe from capturing pointer events
this.disposables.add(
this.selected$.subscribe(selected => {
this._showOverlay = this._isResizing || this._isDragging || !selected;
})
);
// this is required to prevent iframe from capturing pointer events
this.handleEvent('dragStart', () => {
this._isDragging = true;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
this.handleEvent('dragEnd', () => {
this._isDragging = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
} }
override renderBlock() { override renderBlock() {
@@ -115,10 +91,11 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
loading="lazy" loading="lazy"
></iframe> ></iframe>
<!-- overlay to prevent the iframe from capturing pointer events -->
<div <div
class=${classMap({ class=${classMap({
'affine-embed-figma-iframe-overlay': true, 'affine-embed-figma-iframe-overlay': true,
hide: !this._showOverlay, hide: !this.showOverlay$.value,
})} })}
></div> ></div>
</div> </div>
@@ -150,7 +127,4 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
` `
); );
} }
@state()
protected accessor _showOverlay = true;
} }

View File

@@ -1,7 +1,7 @@
import type { EmbedHtmlModel, EmbedHtmlStyles } from '@blocksuite/affine-model'; import type { EmbedHtmlModel, EmbedHtmlStyles } from '@blocksuite/affine-model';
import { BlockSelection } from '@blocksuite/block-std'; import { BlockSelection } from '@blocksuite/block-std';
import { html } from 'lit'; import { html } from 'lit';
import { query, state } from 'lit/decorators.js'; import { query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js'; import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
@@ -13,10 +13,6 @@ export class EmbedHtmlBlockComponent extends EmbedBlockComponent<EmbedHtmlModel>
override _cardStyle: (typeof EmbedHtmlStyles)[number] = 'html'; override _cardStyle: (typeof EmbedHtmlStyles)[number] = 'html';
protected _isDragging = false;
protected _isResizing = false;
close = () => { close = () => {
document.exitFullscreen().catch(console.error); document.exitFullscreen().catch(console.error);
}; };
@@ -50,25 +46,6 @@ export class EmbedHtmlBlockComponent extends EmbedBlockComponent<EmbedHtmlModel>
override connectedCallback() { override connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this._cardStyle = this.model.props.style; this._cardStyle = this.model.props.style;
// this is required to prevent iframe from capturing pointer events
this.disposables.add(
this.selected$.subscribe(selected => {
this._showOverlay = this._isResizing || this._isDragging || !selected;
})
);
// this is required to prevent iframe from capturing pointer events
this.handleEvent('dragStart', () => {
this._isDragging = true;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
this.handleEvent('dragEnd', () => {
this._isDragging = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
} }
override renderBlock(): unknown { override renderBlock(): unknown {
@@ -112,10 +89,11 @@ export class EmbedHtmlBlockComponent extends EmbedBlockComponent<EmbedHtmlModel>
></embed-html-fullscreen-toolbar> ></embed-html-fullscreen-toolbar>
</div> </div>
<!-- overlay to prevent the iframe from capturing pointer events -->
<div <div
class=${classMap({ class=${classMap({
'affine-embed-html-iframe-overlay': true, 'affine-embed-html-iframe-overlay': true,
hide: !this._showOverlay, hide: !this.showOverlay$.value,
})} })}
></div> ></div>
</div> </div>
@@ -131,9 +109,6 @@ export class EmbedHtmlBlockComponent extends EmbedBlockComponent<EmbedHtmlModel>
}); });
} }
@state()
protected accessor _showOverlay = true;
@query('.embed-html-block-iframe-wrapper') @query('.embed-html-block-iframe-wrapper')
accessor iframeWrapper!: HTMLDivElement; accessor iframeWrapper!: HTMLDivElement;
} }

View File

@@ -3,7 +3,7 @@ import type { EmbedLoomModel, EmbedLoomStyles } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { BlockSelection } from '@blocksuite/block-std'; import { BlockSelection } from '@blocksuite/block-std';
import { html } from 'lit'; import { html } from 'lit';
import { property, state } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
@@ -22,10 +22,6 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
override _cardStyle: (typeof EmbedLoomStyles)[number] = 'video'; override _cardStyle: (typeof EmbedLoomStyles)[number] = 'video';
protected _isDragging = false;
protected _isResizing = false;
open = () => { open = () => {
let link = this.model.props.url; let link = this.model.props.url;
if (!link.match(/^[a-zA-Z]+:\/\//)) { if (!link.match(/^[a-zA-Z]+:\/\//)) {
@@ -89,25 +85,6 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
} }
}) })
); );
// this is required to prevent iframe from capturing pointer events
this.disposables.add(
this.selected$.subscribe(selected => {
this._showOverlay = this._isResizing || this._isDragging || !selected;
})
);
// this is required to prevent iframe from capturing pointer events
this.handleEvent('dragStart', () => {
this._isDragging = true;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
this.handleEvent('dragEnd', () => {
this._isDragging = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
} }
override renderBlock() { override renderBlock() {
@@ -152,10 +129,11 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
loading="lazy" loading="lazy"
></iframe> ></iframe>
<!-- overlay to prevent the iframe from capturing pointer events -->
<div <div
class=${classMap({ class=${classMap({
'affine-embed-loom-video-iframe-overlay': true, 'affine-embed-loom-video-iframe-overlay': true,
hide: !this._showOverlay, hide: !this.showOverlay$.value,
})} })}
></div> ></div>
</div> </div>
@@ -188,9 +166,6 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
); );
} }
@state()
protected accessor _showOverlay = true;
@property({ attribute: false }) @property({ attribute: false })
accessor loading = false; accessor loading = false;
} }

View File

@@ -25,10 +25,6 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
override _cardStyle: (typeof EmbedYoutubeStyles)[number] = 'video'; override _cardStyle: (typeof EmbedYoutubeStyles)[number] = 'video';
protected _isDragging = false;
protected _isResizing = false;
open = () => { open = () => {
let link = this.model.props.url; let link = this.model.props.url;
if (!link.match(/^[a-zA-Z]+:\/\//)) { if (!link.match(/^[a-zA-Z]+:\/\//)) {
@@ -93,25 +89,6 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
}) })
); );
// this is required to prevent iframe from capturing pointer events
this.disposables.add(
this.selected$.subscribe(selected => {
this._showOverlay = this._isResizing || this._isDragging || !selected;
})
);
// this is required to prevent iframe from capturing pointer events
this.handleEvent('dragStart', () => {
this._isDragging = true;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
this.handleEvent('dragEnd', () => {
this._isDragging = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
});
matchMedia('print').addEventListener('change', () => { matchMedia('print').addEventListener('change', () => {
this._showImage = matchMedia('print').matches; this._showImage = matchMedia('print').matches;
}); });
@@ -177,10 +154,12 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
allowfullscreen allowfullscreen
loading="lazy" loading="lazy"
></iframe> ></iframe>
<!-- overlay to prevent the iframe from capturing pointer events -->
<div <div
class=${classMap({ class=${classMap({
'affine-embed-youtube-video-iframe-overlay': true, 'affine-embed-youtube-video-iframe-overlay': true,
hide: !this._showOverlay, hide: !this.showOverlay$.value,
})} })}
></div> ></div>
<img <img
@@ -242,9 +221,6 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
@state() @state()
private accessor _showImage = false; private accessor _showImage = false;
@state()
protected accessor _showOverlay = true;
@property({ attribute: false }) @property({ attribute: false })
accessor loading = false; accessor loading = false;
} }