feat(mobile): auto show text keyboard subtoolbar when selecting text (#9256)
Close [BS-1924](https://linear.app/affine-design/issue/BS-1924/文本选中后-切换toolbar) https://github.com/user-attachments/assets/2361dfb9-82ae-4be3-808c-6ea33a70c84d
This commit is contained in:
@@ -9,6 +9,7 @@ import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
|||||||
import {
|
import {
|
||||||
createDefaultDoc,
|
createDefaultDoc,
|
||||||
openFileOrFiles,
|
openFileOrFiles,
|
||||||
|
type Signal,
|
||||||
} from '@blocksuite/affine-shared/utils';
|
} from '@blocksuite/affine-shared/utils';
|
||||||
import type { BlockStdScope } from '@blocksuite/block-std';
|
import type { BlockStdScope } from '@blocksuite/block-std';
|
||||||
import { viewPresets } from '@blocksuite/data-view/view-presets';
|
import { viewPresets } from '@blocksuite/data-view/view-presets';
|
||||||
@@ -54,6 +55,7 @@ import {
|
|||||||
YesterdayIcon,
|
YesterdayIcon,
|
||||||
YoutubeDuotoneIcon,
|
YoutubeDuotoneIcon,
|
||||||
} from '@blocksuite/icons/lit';
|
} from '@blocksuite/icons/lit';
|
||||||
|
import { computed } from '@preact/signals-core';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import type { TemplateResult } from 'lit';
|
import type { TemplateResult } from 'lit';
|
||||||
|
|
||||||
@@ -113,6 +115,10 @@ export type KeyboardToolbarActionItem = {
|
|||||||
export type KeyboardSubToolbarConfig = {
|
export type KeyboardSubToolbarConfig = {
|
||||||
icon: KeyboardIconType;
|
icon: KeyboardIconType;
|
||||||
items: KeyboardToolbarItem[];
|
items: KeyboardToolbarItem[];
|
||||||
|
/**
|
||||||
|
* It will enter this sub-toolbar when the condition is met.
|
||||||
|
*/
|
||||||
|
autoShow?: (ctx: KeyboardToolbarContext) => Signal<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type KeyboardToolbarContext = {
|
export type KeyboardToolbarContext = {
|
||||||
@@ -868,6 +874,13 @@ const textSubToolbarConfig: KeyboardSubToolbarConfig = {
|
|||||||
},
|
},
|
||||||
highlightToolPanel,
|
highlightToolPanel,
|
||||||
],
|
],
|
||||||
|
autoShow: ({ std }) => {
|
||||||
|
return computed(() => {
|
||||||
|
const selection =
|
||||||
|
std.command.exec('getTextSelection').currentTextSelection;
|
||||||
|
return selection ? !selection.isCollapsed() : false;
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
|
export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||||
import { ArrowLeftBigIcon, KeyboardIcon } from '@blocksuite/icons/lit';
|
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 { html } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
import { repeat } from 'lit/directives/repeat.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<boolean> }[] =
|
||||||
|
[];
|
||||||
|
|
||||||
|
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() {
|
override disconnectedCallback() {
|
||||||
|
|||||||
Reference in New Issue
Block a user