1# SelectionMenu
2
3
4文本选择菜单,适用于富文本组件通过[bindSelectionMenu](./ts-basic-components-richeditor.md#属性)绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。
5
6
7> **说明:**
8>
9> 该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
10
11
12## 导入模块
13
14```
15import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI'
16```
17
18## 子组件
19
20无。
21
22## SelectionMenu
23
24SelectionMenu(options: SelectionMenuOptions)
25
26入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor](ts-basic-components-richeditor.md)使用[bindSelectionMenu](ts-basic-components-richeditor.md#属性)接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。
27
28**装饰器类型:**\@Builder
29
30**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
31
32**系统能力:** SystemCapability.ArkUI.ArkUI.Full
33
34**参数:**
35
36| 参数名 | 类型 | 必填 | 说明 |
37| -------- | -------- | -------- | -------- |
38| options | [SelectionMenuOptions](#selectionmenuoptions) | 是 | 文本选择菜单可选项。 |
39
40## SelectionMenuOptions
41
42SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。
43
44**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
45
46**系统能力:** SystemCapability.ArkUI.ArkUI.Full
47
48| 名称 | 类型 | 必填 | 说明 |
49| -------- | -------- | -------- | -------- |
50| editorMenuOptions | Array&lt;[EditorMenuOptions](#editormenuoptions)&gt; | 否 | 编辑菜单。<br/>editorMenuOptions未配置时,不显示编辑菜单。<br/>同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。<br/>点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 |
51| expandedMenuOptions | Array&lt;[ExpandedMenuOptions](#expandedmenuoptions)&gt; | 否 | 扩展下拉菜单。<br/>expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。<br/>expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。 |
52| controller | [RichEditorController](ts-basic-components-richeditor.md#richeditorcontroller) | 否 | 富文本控制器不为空时显示默认系统菜单(包含剪切复制粘贴等部分)且默认菜单功能内置。<br/>controller为空时不显示更多按钮,expandedMenuOptions参数不为空则显示下拉菜单中。<br/>系统默认只支持复制粘贴富文本文本内容,图文混排需要应用自定义onCopy、onPaste接口。应用自行配置onCopy \| onPaste接口时,系统菜单默认复制粘贴失效,调用应用自定义函数。 <br/>**说明:**<br/> 点击自定义文本选择菜单内置复制功能选项后,自定义菜单消失选中文本高亮保留。<br/> 点击自定义文本选择菜单内置全选功能选项后,自定义菜单消失文本全选高亮。<br/> 点击自定义文本选择菜单内置粘贴功能选项后,空白处粘贴或者选中文本替换粘贴均是保留被复制文本的样式。<br/> 当富文本组件[RichEditor](ts-basic-components-richeditor.md)的copyOptions属性设置为`CopyOptions.None`时,内置的复制剪切功能不会被限制。|
53| onCopy | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单复制项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。<br/>**说明:**<br/> event为返回信息。|
54| onPaste | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单粘贴项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。<br/>**说明:**<br/> event为返回信息。 |
55| onCut | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单剪切项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。<br/>**说明:**<br/>event为返回信息。|
56| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单全选项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。<br/>**说明:**<br/>event为返回信息。|
57
58
59## EditorMenuOptions
60
61编辑菜单选项。
62
63**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
64
65**系统能力:** SystemCapability.ArkUI.ArkUI.Full
66
67| 名称 | 类型 | 必填 | 说明 |
68| -------- | -------- | -------- | -------- |
69| icon | [ResourceStr](ts-types.md#resourcestr) | 是 | 图标资源。 |
70| builder | ()&nbsp;=&gt;&nbsp;void | 否 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。 |
71| action | ()&nbsp;=&gt;&nbsp;void | 否 | 点击菜单项的事件回调。 |
72
73
74## ExpandedMenuOptions
75
76扩展下拉菜单。
77
78继承于[MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions对象说明)。
79
80**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
81
82**系统能力:** SystemCapability.ArkUI.ArkUI.Full
83
84| 名称 | 类型 | 必填 | 说明 |
85| -------- | -------- | -------- | -------- |
86| action | ()&nbsp;=&gt;&nbsp;void | 否 | 点击菜单项的事件回调。 |
87
88## EditorEventInfo
89
90选中内容信息。
91
92**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
93
94**系统能力:** SystemCapability.ArkUI.ArkUI.Full
95
96| 名称 | 类型 | 必填 | 说明 |
97| -------- | -------- | -------- | -------- |
98| content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | 否 | 选中内容信息。|
99
100## 属性
101
102不支持[通用属性](ts-universal-attributes-size.md),宽度默认256vp, 高度自适应内容。
103
104## 事件
105不支持[通用事件](ts-universal-events-click.md)。
106
107## 示例
108
109```ts
110import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI'
111
112@Entry
113@Component
114struct Index {
115  @State select: boolean = true
116  controller: RichEditorController = new RichEditorController();
117  options: RichEditorOptions = { controller: this.controller }
118  @State message: string = 'Hello word'
119  @State textSize: number = 30
120  @State fontWeight: FontWeight = FontWeight.Normal
121  @State start: number = -1
122  @State end: number = -1
123  @State visibleValue: Visibility = Visibility.Visible
124  @State colorTransparent: Color = Color.Transparent
125  @State textStyle: RichEditorTextStyle = {}
126  private editorMenuOptions: Array<EditorMenuOptions> =
127    [
128      { icon: $r("app.media.ic_notepad_textbold"), action: () => {
129        if (this.controller) {
130          let selection = this.controller.getSelection();
131          let spans = selection.spans
132          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
133            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
134              let span = item as RichEditorTextSpanResult
135              this.textStyle = span.textStyle
136              let start = span.offsetInSpan[0]
137              let end = span.offsetInSpan[1]
138              let offset = span.spanPosition.spanRange[0]
139              if (this.textStyle.fontWeight != 11) {
140                this.textStyle.fontWeight = FontWeight.Bolder
141              } else {
142                this.textStyle.fontWeight = FontWeight.Normal
143              }
144              this.controller.updateSpanStyle({
145                start: offset + start,
146                end: offset + end,
147                textStyle: this.textStyle
148              })
149            }
150          })
151        }
152      } },
153      { icon: $r("app.media.ic_notepad_texttilt"), action: () => {
154        if (this.controller) {
155          let selection = this.controller.getSelection();
156          let spans = selection.spans
157          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
158            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
159              let span = item as RichEditorTextSpanResult
160              this.textStyle = span.textStyle
161              let start = span.offsetInSpan[0]
162              let end = span.offsetInSpan[1]
163              let offset = span.spanPosition.spanRange[0]
164              if (this.textStyle.fontStyle == FontStyle.Italic) {
165                this.textStyle.fontStyle = FontStyle.Normal
166              } else {
167                this.textStyle.fontStyle = FontStyle.Italic
168              }
169              this.controller.updateSpanStyle({
170                start: offset + start,
171                end: offset + end,
172                textStyle: this.textStyle
173              })
174            }
175          })
176        }
177      } },
178      { icon: $r("app.media.ic_notepad_underline"),
179        action: () => {
180          if (this.controller) {
181            let selection = this.controller.getSelection();
182            let spans = selection.spans
183            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
184              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
185                let span = item as RichEditorTextSpanResult
186                this.textStyle = span.textStyle
187                let start = span.offsetInSpan[0]
188                let end = span.offsetInSpan[1]
189                let offset = span.spanPosition.spanRange[0]
190                if (this.textStyle.decoration) {
191                  if (this.textStyle.decoration.type == TextDecorationType.Underline) {
192                    this.textStyle.decoration.type = TextDecorationType.None
193                  } else {
194                    this.textStyle.decoration.type = TextDecorationType.Underline
195                  }
196                } else {
197                  this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
198                }
199                this.controller.updateSpanStyle({
200                  start: offset + start,
201                  end: offset + end,
202                  textStyle: this.textStyle
203                })
204              }
205            })
206          }
207        }
208      },
209      { icon: $r("app.media.app_icon"), action: () => {
210      }, builder: (): void => this.sliderPanel() },
211      { icon: $r("app.media.ic_notepad_textcolor"), action: () => {
212        if (this.controller) {
213          let selection = this.controller.getSelection();
214          let spans = selection.spans
215          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
216            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
217              let span = item as RichEditorTextSpanResult
218              this.textStyle = span.textStyle
219              let start = span.offsetInSpan[0]
220              let end = span.offsetInSpan[1]
221              let offset = span.spanPosition.spanRange[0]
222              if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
223                this.textStyle.fontColor = Color.Black
224              } else {
225                this.textStyle.fontColor = Color.Orange
226              }
227              this.controller.updateSpanStyle({
228                start: offset + start,
229                end: offset + end,
230                textStyle: this.textStyle
231              })
232            }
233          })
234        }
235      } }]
236  private expandedMenuOptions: Array<ExpandedMenuOptions> =
237    [{ startIcon: $r("app.media.icon"), content: '词典', action: () => {
238    } }, { startIcon: $r("app.media.icon"), content: '翻译', action: () => {
239    } }, { startIcon: $r("app.media.icon"), content: '搜索', action: () => {
240    } }]
241  private expandedMenuOptions1: Array<ExpandedMenuOptions> = []
242  private editorMenuOptions1: Array<EditorMenuOptions> = []
243  private selectionMenuOptions: SelectionMenuOptions = {
244    editorMenuOptions: this.editorMenuOptions,
245    expandedMenuOptions: this.expandedMenuOptions,
246    controller: this.controller,
247    onCut: (event?: EditorEventInfo) => {
248      if (event && event.content) {
249        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
250          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
251            let span = item as RichEditorTextSpanResult
252            console.info('test cut' + span.value)
253            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
254          }
255        })
256      }
257    },
258    onPaste: (event?: EditorEventInfo) => {
259      if (event && event.content) {
260        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
261          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
262            let span = item as RichEditorTextSpanResult
263            console.info('test onPaste' + span.value)
264            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
265          }
266        })
267      }
268    },
269    onCopy: (event?: EditorEventInfo) => {
270      if (event && event.content) {
271        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
272          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
273            let span = item as RichEditorTextSpanResult
274            console.info('test cut' + span.value)
275            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
276          }
277        })
278      }
279    },
280    onSelectAll: (event?: EditorEventInfo) => {
281      if (event && event.content) {
282        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
283          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
284            let span = item as RichEditorTextSpanResult
285            console.info('test onPaste' + span.value)
286            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
287          }
288        })
289      }
290    }
291  }
292
293  @Builder sliderPanel() {
294    Column() {
295      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
296        Text('A').fontSize(15)
297        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
298          .width(210)
299          .onChange((value: number, mode: SliderChangeMode) => {
300            if (this.controller) {
301              let selection = this.controller.getSelection();
302              if (mode == SliderChangeMode.End) {
303                if (this.textSize == undefined) {
304                  this.textSize = 0
305                }
306                let spans = selection.spans
307                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
308                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
309                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize)
310                  }
311                })
312              }
313              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
314                this.start = selection.selection[0]
315                this.end = selection.selection[1]
316                this.textSize = value
317                this.controller.updateSpanStyle({
318                  start: this.start,
319                  end: this.end,
320                  textStyle: { fontSize: this.textSize }
321                })
322              }
323            }
324          })
325        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
326      }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
327    }
328    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
329    .backgroundColor(Color.White)
330    .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
331    .padding(15)
332    .height(48)
333  }
334
335  @Builder
336  MyMenu() {
337    Column() {
338      SelectionMenu(this.selectionMenuOptions)
339    }
340    .width(256)
341    .backgroundColor(Color.Transparent)
342  }
343
344  @Builder
345  MyMenu2() {
346    Column() {
347      SelectionMenu({
348        editorMenuOptions: this.editorMenuOptions,
349        expandedMenuOptions: this.expandedMenuOptions1,
350        controller: this.controller,
351      })
352    }
353    .width(256)
354    .backgroundColor(Color.Transparent)
355  }
356
357  @Builder
358  MyMenu3() {
359    Column() {
360      SelectionMenu({
361        editorMenuOptions: this.editorMenuOptions1,
362        expandedMenuOptions: this.expandedMenuOptions,
363        controller: this.controller,
364      })
365    }
366    .width(256)
367    .backgroundColor(Color.Transparent)
368  }
369
370  build() {
371    Column() {
372      Button("SetSelection")
373        .onClick((event: ClickEvent) => {
374          if (this.controller) {
375            this.controller.setSelection(0, 2)
376          }
377        })
378
379      RichEditor(this.options)
380        .onReady(() => {
381          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
382          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } })
383        })
384        .onSelect((value: RichEditorSelection) => {
385          if (value.selection[0] == -1 && value.selection[1] == -1) {
386            return
387          }
388          this.start = value.selection[0]
389          this.end = value.selection[1]
390        })
391        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
392        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
393        .borderWidth(1)
394        .borderColor(Color.Red)
395        .width(200)
396        .height(200)
397    }
398  }
399}
400```
401> **说明:**
402>
403> 系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。
404
405![selectionmenu](figures/selectionmenu.jpeg)
406