diff --git a/blocksuite/affine/block-embed/src/common/render-linked-doc.ts b/blocksuite/affine/block-embed/src/common/render-linked-doc.ts index 7b65858b514be..1c312ca664ad2 100644 --- a/blocksuite/affine/block-embed/src/common/render-linked-doc.ts +++ b/blocksuite/affine/block-embed/src/common/render-linked-doc.ts @@ -23,6 +23,12 @@ import { render, type TemplateResult } from 'lit'; import type { EmbedLinkedDocBlockComponent } from '../embed-linked-doc-block/index.js'; import type { EmbedSyncedDocCard } from '../embed-synced-doc-block/components/embed-synced-doc-card.js'; +// Throttle delay for block updates to reduce unnecessary re-renders +// - Prevents rapid-fire updates when multiple blocks are updated in quick succession +// - Ensures UI remains responsive while maintaining performance +// - Small enough to feel instant to users, large enough to batch updates effectively +export const RENDER_CARD_THROTTLE_MS = 60; + export function renderLinkedDocInCard( card: EmbedLinkedDocBlockComponent | EmbedSyncedDocCard ) { diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts index 4c830ff00d175..625ec5041d7df 100644 --- a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts @@ -23,7 +23,7 @@ import { matchFlavours, referenceToNode, } from '@blocksuite/affine-shared/utils'; -import { Bound } from '@blocksuite/global/utils'; +import { Bound, throttle } from '@blocksuite/global/utils'; import { DocCollection } from '@blocksuite/store'; import { computed } from '@preact/signals-core'; import { html, nothing } from 'lit'; @@ -33,7 +33,10 @@ import { styleMap } from 'lit/directives/style-map.js'; import { when } from 'lit/directives/when.js'; import { EmbedBlockComponent } from '../common/embed-block-element.js'; -import { renderLinkedDocInCard } from '../common/render-linked-doc.js'; +import { + RENDER_CARD_THROTTLE_MS, + renderLinkedDocInCard, +} from '../common/render-linked-doc.js'; import { SyncedDocErrorIcon } from '../embed-synced-doc-block/styles.js'; import { type EmbedLinkedDocBlockConfig, @@ -301,24 +304,28 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent { - if ( - payload.type === 'update' && - ['', 'caption', 'xywh'].includes(payload.props.key) - ) { - return; - } - - if (payload.type === 'add' && payload.init) { - return; - } - - this._load().catch(e => { - console.error(e); - this.isError = true; - }); - }) + linkedDoc.slots.blockUpdated.on( + throttle(payload => { + if ( + payload.type === 'update' && + ['', 'caption', 'xywh'].includes(payload.props.key) + ) { + return; + } + + if (payload.type === 'add' && payload.init) { + return; + } + + this._load().catch(e => { + console.error(e); + this.isError = true; + }); + }, RENDER_CARD_THROTTLE_MS) + ) ); this._setDocUpdatedAt(); diff --git a/blocksuite/affine/block-embed/src/embed-synced-doc-block/components/embed-synced-doc-card.ts b/blocksuite/affine/block-embed/src/embed-synced-doc-block/components/embed-synced-doc-card.ts index fb96b90485493..adaf26217acdd 100644 --- a/blocksuite/affine/block-embed/src/embed-synced-doc-block/components/embed-synced-doc-card.ts +++ b/blocksuite/affine/block-embed/src/embed-synced-doc-block/components/embed-synced-doc-card.ts @@ -1,11 +1,14 @@ import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { isGfxBlockComponent, ShadowlessElement } from '@blocksuite/block-std'; -import { WithDisposable } from '@blocksuite/global/utils'; +import { throttle, WithDisposable } from '@blocksuite/global/utils'; import { html, nothing } from 'lit'; import { property, queryAsync } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { renderLinkedDocInCard } from '../../common/render-linked-doc.js'; +import { + RENDER_CARD_THROTTLE_MS, + renderLinkedDocInCard, +} from '../../common/render-linked-doc.js'; import type { EmbedSyncedDocBlockComponent } from '../embed-synced-doc-block.js'; import { cardStyles } from '../styles.js'; import { getSyncedDocIcons } from '../utils.js'; @@ -101,19 +104,23 @@ export class EmbedSyncedDocCard extends WithDisposable(ShadowlessElement) { renderLinkedDocInCard(this); }) ); + // Should throttle the blockUpdated event to avoid too many re-renders + // Because the blockUpdated event is triggered too frequently at some cases this.disposables.add( - syncedDoc.slots.blockUpdated.on(payload => { - if (this._dragging) { - return; - } - if ( - payload.type === 'update' && - ['', 'caption', 'xywh'].includes(payload.props.key) - ) { - return; - } - renderLinkedDocInCard(this); - }) + syncedDoc.slots.blockUpdated.on( + throttle(payload => { + if (this._dragging) { + return; + } + if ( + payload.type === 'update' && + ['', 'caption', 'xywh'].includes(payload.props.key) + ) { + return; + } + renderLinkedDocInCard(this); + }, RENDER_CARD_THROTTLE_MS) + ) ); } }