diff --git a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/config.ts b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/config.ts index f2a4efecda..3f7fafae81 100644 --- a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/config.ts +++ b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/config.ts @@ -9,6 +9,7 @@ import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; import { createDefaultDoc, openFileOrFiles, + type Signal, } from '@blocksuite/affine-shared/utils'; import type { BlockStdScope } from '@blocksuite/block-std'; import { viewPresets } from '@blocksuite/data-view/view-presets'; @@ -54,6 +55,7 @@ import { YesterdayIcon, YoutubeDuotoneIcon, } from '@blocksuite/icons/lit'; +import { computed } from '@preact/signals-core'; import { cssVarV2 } from '@toeverything/theme/v2'; import type { TemplateResult } from 'lit'; @@ -113,6 +115,10 @@ export type KeyboardToolbarActionItem = { export type KeyboardSubToolbarConfig = { icon: KeyboardIconType; items: KeyboardToolbarItem[]; + /** + * It will enter this sub-toolbar when the condition is met. + */ + autoShow?: (ctx: KeyboardToolbarContext) => Signal; }; export type KeyboardToolbarContext = { @@ -868,6 +874,13 @@ const textSubToolbarConfig: KeyboardSubToolbarConfig = { }, highlightToolPanel, ], + autoShow: ({ std }) => { + return computed(() => { + const selection = + std.command.exec('getTextSelection').currentTextSelection; + return selection ? !selection.isCollapsed() : false; + }); + }, }; export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = { diff --git a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts index 0950954fd9..dda5337e21 100644 --- a/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts +++ b/blocksuite/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts @@ -9,7 +9,7 @@ import { } from '@blocksuite/block-std'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils'; import { ArrowLeftBigIcon, KeyboardIcon } from '@blocksuite/icons/lit'; -import { effect, signal } from '@preact/signals-core'; +import { effect, type Signal, signal } from '@preact/signals-core'; import { html } from 'lit'; import { property } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; @@ -283,6 +283,48 @@ export class AffineKeyboardToolbar extends SignalWatcher( }); }) ); + + this._watchAutoShow(); + } + + private _watchAutoShow() { + const autoShowSubToolbars: { path: number[]; signal: Signal }[] = + []; + + const traverse = (item: KeyboardToolbarItem, path: number[]) => { + if (isKeyboardSubToolBarConfig(item) && item.autoShow) { + autoShowSubToolbars.push({ + path, + signal: item.autoShow(this._context), + }); + + item.items.forEach((subItem, index) => { + traverse(subItem, [...path, index]); + }); + } + }; + this.config.items.forEach((item, index) => { + traverse(item, [index]); + }); + + const samePath = (a: number[], b: number[]) => + a.length === b.length && a.every((v, i) => v === b[i]); + + let prevPath = this._path$.peek(); + this.disposables.add( + effect(() => { + autoShowSubToolbars.forEach(({ path, signal }) => { + if (signal.value) { + if (samePath(this._path$.peek(), path)) return; + + prevPath = this._path$.peek(); + this._path$.value = path; + } else { + this._path$.value = prevPath; + } + }); + }) + ); } override disconnectedCallback() {