# SelectionMenu
文本选择菜单,适用于富文本组件通过[bindSelectionMenu](./ts-basic-components-richeditor.md#bindselectionmenu)绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。
> **说明:**
>
> 该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
## 导入模块
```
import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI'
```
## 子组件
无。
## SelectionMenu
SelectionMenu(options: SelectionMenuOptions): void
入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor](ts-basic-components-richeditor.md)使用[bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu)接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。
**装饰器类型:**\@Builder
**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
**系统能力:** SystemCapability.ArkUI.ArkUI.Full
**参数:**
| 参数名 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| options | [SelectionMenuOptions](#selectionmenuoptions) | 是 | 文本选择菜单可选项。 |
## SelectionMenuOptions
SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。
**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
**系统能力:** SystemCapability.ArkUI.ArkUI.Full
| 名称 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| editorMenuOptions | Array<[EditorMenuOptions](#editormenuoptions)> | 否 | 编辑菜单。
editorMenuOptions未配置时,不显示编辑菜单。
同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。
点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 |
| expandedMenuOptions | Array<[ExpandedMenuOptions](#expandedmenuoptions)> | 否 | 扩展下拉菜单。
expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。
expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。 |
| controller | [RichEditorController](ts-basic-components-richeditor.md#richeditorcontroller) | 否 | 富文本控制器不为空时显示默认系统菜单(包含剪切复制粘贴等部分)且默认菜单功能内置。
controller为空时不显示更多按钮,expandedMenuOptions参数不为空则显示下拉菜单中。
系统默认只支持复制粘贴富文本文本内容,图文混排需要应用自定义onCopy、onPaste接口。应用自行配置onCopy \| onPaste接口时,系统菜单默认复制粘贴失效,调用应用自定义函数。
**说明:**
点击自定义文本选择菜单内置复制功能选项后,自定义菜单消失选中文本高亮保留。
点击自定义文本选择菜单内置全选功能选项后,自定义菜单消失文本全选高亮。
点击自定义文本选择菜单内置粘贴功能选项后,空白处粘贴或者选中文本替换粘贴均是保留被复制文本的样式。
当富文本组件[RichEditor](ts-basic-components-richeditor.md)的copyOptions属性设置为`CopyOptions.None`时,内置的复制剪切功能不会被限制。|
| onCopy | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单复制项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。
**说明:**
event为返回信息。|
| onPaste | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单粘贴项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。
**说明:**
event为返回信息。 |
| onCut | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单剪切项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。
**说明:**
event为返回信息。|
| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单全选项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。
**说明:**
event为返回信息。|
## EditorMenuOptions
编辑菜单选项。
**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
**系统能力:** SystemCapability.ArkUI.ArkUI.Full
| 名称 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| icon | [ResourceStr](ts-types.md#resourcestr) | 是 | 图标资源。 |
| builder | () => void | 否 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。 |
| action | () => void | 否 | 点击菜单项的事件回调。 |
## ExpandedMenuOptions
扩展下拉菜单。
继承于[MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions对象说明)。
**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
**系统能力:** SystemCapability.ArkUI.ArkUI.Full
| 名称 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| action | () => void | 否 | 点击菜单项的事件回调。 |
## EditorEventInfo
选中内容信息。
**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
**系统能力:** SystemCapability.ArkUI.ArkUI.Full
| 名称 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | 否 | 选中内容信息。|
## 属性
不支持[通用属性](ts-universal-attributes-size.md),宽度默认224vp, 高度自适应内容。
## 事件
不支持[通用事件](ts-universal-events-click.md)。
## 示例
该示例展示了文本绑定不同触发方式的自定义文本选择菜单的效果。
```ts
import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI'
@Entry
@Component
struct Index {
@State select: boolean = true
controller: RichEditorController = new RichEditorController();
options: RichEditorOptions = { controller: this.controller }
@State message: string = 'Hello world'
@State textSize: number = 30
@State fontWeight: FontWeight = FontWeight.Normal
@State start: number = -1
@State end: number = -1
@State visibleValue: Visibility = Visibility.Visible
@State colorTransparent: Color = Color.Transparent
@State textStyle: RichEditorTextStyle = {}
private editorMenuOptions: Array =
[
{ icon: $r("app.media.ic_notepad_textbold"), action: () => {
if (this.controller) {
let selection = this.controller.getSelection();
let spans = selection.spans
spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
this.textStyle = span.textStyle
let start = span.offsetInSpan[0]
let end = span.offsetInSpan[1]
let offset = span.spanPosition.spanRange[0]
if (this.textStyle.fontWeight != 11) {
this.textStyle.fontWeight = FontWeight.Bolder
} else {
this.textStyle.fontWeight = FontWeight.Normal
}
this.controller.updateSpanStyle({
start: offset + start,
end: offset + end,
textStyle: this.textStyle
})
}
})
}
} },
{ icon: $r("app.media.ic_notepad_texttilt"), action: () => {
if (this.controller) {
let selection = this.controller.getSelection();
let spans = selection.spans
spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
this.textStyle = span.textStyle
let start = span.offsetInSpan[0]
let end = span.offsetInSpan[1]
let offset = span.spanPosition.spanRange[0]
if (this.textStyle.fontStyle == FontStyle.Italic) {
this.textStyle.fontStyle = FontStyle.Normal
} else {
this.textStyle.fontStyle = FontStyle.Italic
}
this.controller.updateSpanStyle({
start: offset + start,
end: offset + end,
textStyle: this.textStyle
})
}
})
}
} },
{ icon: $r("app.media.ic_notepad_underline"),
action: () => {
if (this.controller) {
let selection = this.controller.getSelection();
let spans = selection.spans
spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
this.textStyle = span.textStyle
let start = span.offsetInSpan[0]
let end = span.offsetInSpan[1]
let offset = span.spanPosition.spanRange[0]
if (this.textStyle.decoration) {
if (this.textStyle.decoration.type == TextDecorationType.Underline) {
this.textStyle.decoration.type = TextDecorationType.None
} else {
this.textStyle.decoration.type = TextDecorationType.Underline
}
} else {
this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
}
this.controller.updateSpanStyle({
start: offset + start,
end: offset + end,
textStyle: this.textStyle
})
}
})
}
}
},
{ icon: $r("app.media.app_icon"), action: () => {
}, builder: (): void => this.sliderPanel() },
{ icon: $r("app.media.ic_notepad_textcolor"), action: () => {
if (this.controller) {
let selection = this.controller.getSelection();
let spans = selection.spans
spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
this.textStyle = span.textStyle
let start = span.offsetInSpan[0]
let end = span.offsetInSpan[1]
let offset = span.spanPosition.spanRange[0]
if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
this.textStyle.fontColor = Color.Black
} else {
this.textStyle.fontColor = Color.Orange
}
this.controller.updateSpanStyle({
start: offset + start,
end: offset + end,
textStyle: this.textStyle
})
}
})
}
} }]
private expandedMenuOptions: Array =
[{ startIcon: $r("app.media.icon"), content: '词典', action: () => {
} }, { startIcon: $r("app.media.icon"), content: '翻译', action: () => {
} }, { startIcon: $r("app.media.icon"), content: '搜索', action: () => {
} }]
private expandedMenuOptions1: Array = []
private editorMenuOptions1: Array = []
private selectionMenuOptions: SelectionMenuOptions = {
editorMenuOptions: this.editorMenuOptions,
expandedMenuOptions: this.expandedMenuOptions,
controller: this.controller,
onCut: (event?: EditorEventInfo) => {
if (event && event.content) {
event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
console.info('test cut' + span.value)
console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
}
})
}
},
onPaste: (event?: EditorEventInfo) => {
if (event && event.content) {
event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
console.info('test onPaste' + span.value)
console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
}
})
}
},
onCopy: (event?: EditorEventInfo) => {
if (event && event.content) {
event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
console.info('test cut' + span.value)
console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
}
})
}
},
onSelectAll: (event?: EditorEventInfo) => {
if (event && event.content) {
event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
let span = item as RichEditorTextSpanResult
console.info('test onPaste' + span.value)
console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
}
})
}
}
}
@Builder sliderPanel() {
Column() {
Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
Text('A').fontSize(15)
Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
.width(210)
.onChange((value: number, mode: SliderChangeMode) => {
if (this.controller) {
let selection = this.controller.getSelection();
if (mode == SliderChangeMode.End) {
if (this.textSize == undefined) {
this.textSize = 0
}
let spans = selection.spans
spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize)
}
})
}
if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
this.start = selection.selection[0]
this.end = selection.selection[1]
this.textSize = value
this.controller.updateSpanStyle({
start: this.start,
end: this.end,
textStyle: { fontSize: this.textSize }
})
}
}
})
Text('A').fontSize(20).fontWeight(FontWeight.Medium)
}.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
}
.shadow(ShadowStyle.OUTER_DEFAULT_MD)
.backgroundColor(Color.White)
.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
.padding(15)
.height(48)
}
@Builder
MyMenu() {
Column() {
SelectionMenu(this.selectionMenuOptions)
}
.width(256)
.backgroundColor(Color.Transparent)
}
@Builder
MyMenu2() {
Column() {
SelectionMenu({
editorMenuOptions: this.editorMenuOptions,
expandedMenuOptions: this.expandedMenuOptions1,
controller: this.controller,
})
}
.width(256)
.backgroundColor(Color.Transparent)
}
@Builder
MyMenu3() {
Column() {
SelectionMenu({
editorMenuOptions: this.editorMenuOptions1,
expandedMenuOptions: this.expandedMenuOptions,
controller: this.controller,
})
}
.width(256)
.backgroundColor(Color.Transparent)
}
build() {
Column() {
Button("SetSelection")
.onClick((event: ClickEvent) => {
if (this.controller) {
this.controller.setSelection(0, 2)
}
})
RichEditor(this.options)
.onReady(() => {
this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } })
})
.onSelect((value: RichEditorSelection) => {
if (value.selection[0] == -1 && value.selection[1] == -1) {
return
}
this.start = value.selection[0]
this.end = value.selection[1]
})
.bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
.bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
.borderWidth(1)
.borderColor(Color.Red)
.width(200)
.height(200)
}
}
}
```
> **说明:**
>
> 系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。
