import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { DisposableGroup, SignalWatcher } from '@blocksuite/global/utils'; import { effect, signal } from '@preact/signals-core'; import { html, LitElement } from 'lit'; import { property } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import { ZERO_WIDTH_SPACE } from '../consts.js'; import type { InlineEditor } from '../inline-editor.js'; import type { DeltaInsert } from '../types.js'; import type { BaseTextAttributes } from '../utils/base-attributes.js'; import { isInlineRangeIntersect } from '../utils/inline-range.js'; export class VElement< T extends BaseTextAttributes = BaseTextAttributes, > extends SignalWatcher(LitElement) { readonly disposables = new DisposableGroup(); readonly selected = signal(false); override connectedCallback(): void { super.connectedCallback(); this.disposables.add( effect(() => { const inlineRange = this.inlineEditor.inlineRange$.value; this.selected.value = !!inlineRange && isInlineRangeIntersect(inlineRange, { index: this.startOffset, length: this.endOffset - this.startOffset, }); }) ); } override createRenderRoot() { return this; } override async getUpdateComplete(): Promise { const result = await super.getUpdateComplete(); const span = this.querySelector('[data-v-element="true"]') as HTMLElement; const el = span.firstElementChild as LitElement; await el.updateComplete; const vTexts = Array.from(this.querySelectorAll('v-text')); await Promise.all(vTexts.map(vText => vText.updateComplete)); return result; } override render() { const inlineEditor = this.inlineEditor; const attributeRenderer = inlineEditor.attributeService.attributeRenderer; const renderProps: Parameters[0] = { delta: this.delta, selected: this.selected.value, startOffset: this.startOffset, endOffset: this.endOffset, lineIndex: this.lineIndex, editor: inlineEditor, }; const isEmbed = inlineEditor.isEmbed(this.delta); if (isEmbed) { if (this.delta.insert.length !== 1) { throw new BlockSuiteError( ErrorCode.InlineEditorError, `The length of embed node should only be 1. This seems to be an internal issue with inline editor. Please go to https://github.com/toeverything/blocksuite/issues to report it.` ); } return html`${attributeRenderer(renderProps)}`; } // we need to avoid \n appearing before and after the span element, which will // cause the unexpected space return html`${attributeRenderer(renderProps)}`; } @property({ type: Object }) accessor delta: DeltaInsert = { insert: ZERO_WIDTH_SPACE, }; @property({ attribute: false }) accessor endOffset!: number; @property({ attribute: false }) accessor inlineEditor!: InlineEditor; @property({ attribute: false }) accessor lineIndex!: number; @property({ attribute: false }) accessor startOffset!: number; } declare global { interface HTMLElementTagNameMap { 'v-element': VElement; } }