• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SelectionMenu
2
3
4The **SelectionMenu** component serves as a context menu designed specifically for text selection in the [RichEditor](ts-basic-components-richeditor.md) or [Text](ts-basic-components-text.md) component. This component must be bound to either a **RichEditor** component through its [bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu) API or a **Text** component using its [bindSelectionMenu](ts-basic-components-text.md#bindselectionmenu11) API to work properly, as it cannot operate as an independent UI element. It is recommended that this component be displayed through mouse right-click or text selection events.
5
6
7> **NOTE**
8>
9> This component is supported since API version 11. Updates will be marked with a superscript to indicate their earliest API version.
10>
11> This component is not supported on wearables.
12
13
14## Modules to Import
15
16```ts
17import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI';
18```
19
20## Child Components
21
22Not supported
23
24## SelectionMenu
25
26SelectionMenu(options: SelectionMenuOptions): void
27
28Defines a **SelectionMenu** component. When the input parameter is empty, the sizes of the component and its content area are 0, making the component invisible. In this case, for example, if the **SelectionMenu** component activated through right clicks is bound to a **RichEditor** component, it will not be displayed when the **RichEditor** component is right-clicked.
29
30**Decorator**: @Builder
31
32**Atomic service API**: This API can be used in atomic services since API version 12.
33
34**System capability**: SystemCapability.ArkUI.ArkUI.Full
35
36**Parameters**
37
38| Name| Type| Mandatory| Description|
39| -------- | -------- | -------- | -------- |
40| options | [SelectionMenuOptions](#selectionmenuoptions) | Yes| Configuration options of the **SelectionMenu** component.|
41
42## SelectionMenuOptions
43
44Defines the configuration options of the **SelectionMenu** component.
45
46**Atomic service API**: This API can be used in atomic services since API version 12.
47
48**System capability**: SystemCapability.ArkUI.ArkUI.Full
49
50| Name| Type| Mandatory| Description|
51| -------- | -------- | -------- | -------- |
52| editorMenuOptions | Array&lt;[EditorMenuOptions](#editormenuoptions)&gt; | No| Edit menu.<br>If **editorMenuOptions** is not set, the edit menu is not displayed.<br>When both **action** and **builder** in **EditorMenuOptions** are configured, clicking the edit icon will trigger both.<br>By default, the context menu is not closed when the edit menu icon is clicked. You can configure **closeSelectionMenu** of **RichEditorController** in **action** to enable the menu to be closed.|
53| expandedMenuOptions | Array&lt;[ExpandedMenuOptions](#expandedmenuoptions)&gt; | No| Expanded drop-down menu options.<br>If this parameter is left empty, the expanded drop-down menu is not displayed.<br>The options configured for **ExpandedMenuOptions** are displayed in the **More** menu option, and clicking **More** shows the expanded drop-down menu.|
54| controller | [RichEditorController](ts-basic-components-richeditor.md#richeditorcontroller) | No| Rich text editor controller. If **controller** is set, the default system menu (including the cut, copy, and paste options) is displayed, and the preset menu features are provided.<br>If **controller** is left empty, the **More** menu option is not displayed. If **expandedMenuOptions** is not empty, the expanded drop-down menu is displayed.<br>By default, the copy and paste feature is only available for rich text. To use the feature for content that includes both text and images, define custom **onCopy** and **onPaste** APIs. If a custom **onCopy** \| **onPaste** API is defined, the default copy and paste feature is ineffective, and the custom API is called instead.<br>**NOTE**<br> When the preset copy option is selected, the custom context menu on selection is hidden, while the selected text is still highlighted.<br> When the preset select-all option is selected, the custom context menu on selection is hidden, while all text is highlighted.<br> When the preset paste option is selected, the style of the copied text is retained, whether the text is pasted to a blank area or not.<br> When the **copyOptions** attribute of the [RichEditor](ts-basic-components-richeditor.md) component is set to **CopyOptions.None**, the preset copy and cut features are not restricted.|
55| onCopy | (event?: [EditorEventInfo](#editoreventinfo)) =&gt; void | No| Event callback to take the place of the preset copy menu option.<br>It is effective only when the **controller** parameter is set and the preset menu is available.<br>**NOTE**<br> **event** indicates the returned information.|
56| onPaste | (event?: [EditorEventInfo](#editoreventinfo)) =&gt; void | No| Event callback to take the place of the preset paste menu option.<br>It is effective only when the **controller** parameter is set and the preset menu is available.<br>**NOTE**<br> **event** indicates the returned information.|
57| onCut | (event?: [EditorEventInfo](#editoreventinfo)) =&gt; void | No| Event callback to take the place of the preset cut menu option.<br>It is effective only when the **controller** parameter is set and the preset menu is available.<br>**NOTE**<br>**event** indicates the returned information.|
58| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo)) =&gt; void | No| Event callback to take the place of the preset select-all menu option.<br>It is effective only when the **controller** parameter is set and the preset menu is available.<br>**NOTE**<br>**event** indicates the returned information.|
59
60
61## EditorMenuOptions
62
63Describes the edit menu options.
64
65**System capability**: SystemCapability.ArkUI.ArkUI.Full
66
67| Name| Type| Mandatory| Description|
68| -------- | -------- | -------- | -------- |
69| icon | [ResourceStr](ts-types.md#resourcestr) | Yes| Icon.<br>**Atomic service API**: This API can be used in atomic services since API version 12.|
70| builder | () =&gt; void | No| Builder of the custom component displayed upon click. It must be used with @Builder for building the custom component.<br>**Atomic service API**: This API can be used in atomic services since API version 12.|
71| action | () =&gt; void | No| Action triggered when the menu option is clicked.<br>**Atomic service API**: This API can be used in atomic services since API version 12.|
72| symbolStyle<sup>18+</sup> | [SymbolGlyphModifier](ts-universal-attributes-attribute-modifier.md) | No| Symbol icon resource, which has higher priority than **icon**.<br>**Atomic service API**: This API can be used in atomic services since API version 18.|
73
74
75## ExpandedMenuOptions
76
77Describes the expanded drop-down menu options.
78
79Inherits from [MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions).
80
81**Atomic service API**: This API can be used in atomic services since API version 12.
82
83**System capability**: SystemCapability.ArkUI.ArkUI.Full
84
85| Name| Type| Mandatory| Description|
86| -------- | -------- | -------- | -------- |
87| action | () =&gt; void | No| Action triggered when the menu option is clicked.|
88
89## EditorEventInfo
90
91Provides the information about the selected content.
92
93**Atomic service API**: This API can be used in atomic services since API version 12.
94
95**System capability**: SystemCapability.ArkUI.ArkUI.Full
96
97| Name| Type| Mandatory| Description|
98| -------- | -------- | -------- | -------- |
99| content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | No| Information about the selected content.|
100
101## Attributes
102
103The [universal attributes](ts-component-general-attributes.md) are not supported. The default width is 224 vp, and the height is adaptive.
104
105## Events
106The [universal events](ts-component-general-events.md) are not supported.
107
108## Example
109### Example 1: Binding Context Menus on Selection with Different Trigger Methods
110
111This example demonstrates the effects of a custom context menu on selection bound to text with different triggering modes.
112
113```ts
114import {
115  SelectionMenu,
116  EditorMenuOptions,
117  ExpandedMenuOptions,
118  EditorEventInfo,
119  SelectionMenuOptions
120} from '@kit.ArkUI';
121
122@Entry
123@Component
124struct Index {
125  @State select: boolean = true;
126  controller: RichEditorController = new RichEditorController();
127  options: RichEditorOptions = { controller: this.controller };
128  @State message: string = 'Hello world';
129  @State textSize: number = 30;
130  @State fontWeight: FontWeight = FontWeight.Normal;
131  @State start: number = -1;
132  @State end: number = -1;
133  @State visibleValue: Visibility = Visibility.Visible;
134  @State colorTransparent: Color = Color.Transparent;
135  @State textStyle: RichEditorTextStyle = {};
136  private editorMenuOptions: Array<EditorMenuOptions> =
137    [
138      {
139        icon: $r("app.media.ic_notepad_textbold"), action: () => {
140        if (this.controller) {
141          let selection = this.controller.getSelection();
142          let spans = selection.spans;
143          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
144            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
145              let span = item as RichEditorTextSpanResult;
146              this.textStyle = span.textStyle;
147              let start = span.offsetInSpan[0];
148              let end = span.offsetInSpan[1];
149              let offset = span.spanPosition.spanRange[0];
150              if (this.textStyle.fontWeight != 11) {
151                this.textStyle.fontWeight = FontWeight.Bolder;
152              } else {
153                this.textStyle.fontWeight = FontWeight.Normal;
154              }
155              this.controller.updateSpanStyle({
156                start: offset + start,
157                end: offset + end,
158                textStyle: this.textStyle
159              })
160            }
161          })
162        }
163      }
164      },
165      {
166        icon: $r("app.media.ic_notepad_texttilt"), action: () => {
167        if (this.controller) {
168          let selection = this.controller.getSelection();
169          let spans = selection.spans;
170          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
171            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
172              let span = item as RichEditorTextSpanResult;
173              this.textStyle = span.textStyle;
174              let start = span.offsetInSpan[0];
175              let end = span.offsetInSpan[1];
176              let offset = span.spanPosition.spanRange[0];
177              if (this.textStyle.fontStyle == FontStyle.Italic) {
178                this.textStyle.fontStyle = FontStyle.Normal;
179              } else {
180                this.textStyle.fontStyle = FontStyle.Italic;
181              }
182              this.controller.updateSpanStyle({
183                start: offset + start,
184                end: offset + end,
185                textStyle: this.textStyle
186              })
187            }
188          })
189        }
190      }
191      },
192      {
193        icon: $r("app.media.ic_notepad_underline"),
194        action: () => {
195          if (this.controller) {
196            let selection = this.controller.getSelection();
197            let spans = selection.spans;
198            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
199              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
200                let span = item as RichEditorTextSpanResult;
201                this.textStyle = span.textStyle;
202                let start = span.offsetInSpan[0];
203                let end = span.offsetInSpan[1];
204                let offset = span.spanPosition.spanRange[0];
205                if (this.textStyle.decoration) {
206                  if (this.textStyle.decoration.type == TextDecorationType.Underline) {
207                    this.textStyle.decoration.type = TextDecorationType.None;
208                  } else {
209                    this.textStyle.decoration.type = TextDecorationType.Underline;
210                  }
211                } else {
212                  this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
213                }
214                this.controller.updateSpanStyle({
215                  start: offset + start,
216                  end: offset + end,
217                  textStyle: this.textStyle
218                })
219              }
220            })
221          }
222        }
223      },
224      {
225        icon: $r("app.media.ic_notepad_fontsize"), action: () => {
226      }, builder: (): void => this.sliderPanel()
227      },
228      {
229        icon: $r("app.media.ic_notepad_textcolor"), action: () => {
230        if (this.controller) {
231          let selection = this.controller.getSelection();
232          let spans = selection.spans;
233          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
234            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
235              let span = item as RichEditorTextSpanResult;
236              this.textStyle = span.textStyle;
237              let start = span.offsetInSpan[0];
238              let end = span.offsetInSpan[1];
239              let offset = span.spanPosition.spanRange[0];
240              if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
241                this.textStyle.fontColor = Color.Black;
242              } else {
243                this.textStyle.fontColor = Color.Orange;
244              }
245              this.controller.updateSpanStyle({
246                start: offset + start,
247                end: offset + end,
248                textStyle: this.textStyle
249              })
250            }
251          })
252        }
253      }
254      }]
255  private expandedMenuOptions: Array<ExpandedMenuOptions> =
256    [{
257      startIcon: $r("app.media.startIcon"), content: 'Dictionary', action: () => {
258      }
259    }, {
260      startIcon: $r("app.media.startIcon"), content: 'Translate', action: () => {
261      }
262    }, {
263      startIcon: $r("app.media.startIcon"), content: 'Search', action: () => {
264      }
265    }]
266  private expandedMenuOptions1: Array<ExpandedMenuOptions> = []
267  private editorMenuOptions1: Array<EditorMenuOptions> = []
268  private selectionMenuOptions: SelectionMenuOptions = {
269    editorMenuOptions: this.editorMenuOptions,
270    expandedMenuOptions: this.expandedMenuOptions,
271    controller: this.controller,
272    onCut: (event?: EditorEventInfo) => {
273      if (event && event.content) {
274        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
275          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
276            let span = item as RichEditorTextSpanResult;
277            console.info('test cut' + span.value);
278            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
279          }
280        })
281      }
282    },
283    onPaste: (event?: EditorEventInfo) => {
284      if (event && event.content) {
285        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
286          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
287            let span = item as RichEditorTextSpanResult;
288            console.info('test onPaste' + span.value);
289            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
290          }
291        })
292      }
293    },
294    onCopy: (event?: EditorEventInfo) => {
295      if (event && event.content) {
296        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
297          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
298            let span = item as RichEditorTextSpanResult;
299            console.info('test cut' + span.value);
300            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
301          }
302        })
303      }
304    },
305    onSelectAll: (event?: EditorEventInfo) => {
306      if (event && event.content) {
307        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
308          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
309            let span = item as RichEditorTextSpanResult;
310            console.info('test onPaste' + span.value);
311            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
312          }
313        })
314      }
315    }
316  }
317
318  @Builder
319  sliderPanel() {
320    Column() {
321      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
322        Text('A').fontSize(15)
323        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
324          .width(210)
325          .onChange((value: number, mode: SliderChangeMode) => {
326            if (this.controller) {
327              let selection = this.controller.getSelection();
328              if (mode == SliderChangeMode.End) {
329                if (this.textSize == undefined) {
330                  this.textSize = 0;
331                }
332                let spans = selection.spans;
333                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
334                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
335                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize);
336                  }
337                })
338              }
339              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
340                this.start = selection.selection[0];
341                this.end = selection.selection[1];
342                this.textSize = value;
343                this.controller.updateSpanStyle({
344                  start: this.start,
345                  end: this.end,
346                  textStyle: { fontSize: this.textSize }
347                })
348              }
349            }
350          })
351        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
352      }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
353    }
354    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
355    .backgroundColor(Color.White)
356    .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
357    .padding(15)
358    .height(48)
359  }
360
361  @Builder
362  MyMenu() {
363    Column() {
364      SelectionMenu(this.selectionMenuOptions)
365    }
366    .width(256)
367    .backgroundColor(Color.Transparent)
368  }
369
370  @Builder
371  MyMenu2() {
372    Column() {
373      SelectionMenu({
374        editorMenuOptions: this.editorMenuOptions,
375        expandedMenuOptions: this.expandedMenuOptions1,
376        controller: this.controller,
377      })
378    }
379    .width(256)
380    .backgroundColor(Color.Transparent)
381  }
382
383  @Builder
384  MyMenu3() {
385    Column() {
386      SelectionMenu({
387        editorMenuOptions: this.editorMenuOptions,
388        expandedMenuOptions: this.expandedMenuOptions,
389        controller: this.controller,
390      })
391    }
392    .width(256)
393    .backgroundColor(Color.Transparent)
394  }
395
396  build() {
397    Column() {
398      Button("SetSelection")
399        .onClick((event: ClickEvent) => {
400          if (this.controller) {
401            this.controller.setSelection(0, 2);
402          }
403        })
404
405      RichEditor(this.options)
406        .onReady(() => {
407          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } });
408          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } });
409        })
410        .onSelect((value: RichEditorSelection) => {
411          if (value.selection[0] == -1 && value.selection[1] == -1) {
412            return;
413          }
414          this.start = value.selection[0];
415          this.end = value.selection[1];
416        })
417        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
418        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
419        .borderWidth(1)
420        .borderColor(Color.Red)
421        .width(200)
422        .height(200)
423        .margin(10)
424    }
425  }
426}
427```
428> **NOTE**
429>
430> Icons in bold and italics are not preset in the system. The sample code uses the default icons. You need to replace the icons in **editorMenuOptions** with the desired icons.
431
432![selectionmenu](figures/selectionmenu.gif)
433
434### Example 2: Setting the Symbol Icon
435
436This example demonstrates how to use **symbolStyle** in **EditorMenuOptions** to set custom symbol icons.
437
438```ts
439import {
440  SelectionMenu,
441  EditorMenuOptions,
442  ExpandedMenuOptions,
443  EditorEventInfo,
444  SelectionMenuOptions,
445  SymbolGlyphModifier
446} from '@kit.ArkUI'
447
448@Entry
449@Component
450struct Index {
451  @State select: boolean = true;
452  controller: RichEditorController = new RichEditorController();
453  options: RichEditorOptions = { controller: this.controller };
454  @State message: string = 'Hello world';
455  @State textSize: number = 30;
456  @State fontWeight: FontWeight = FontWeight.Normal;
457  @State start: number = -1;
458  @State end: number = -1;
459  @State visibleValue: Visibility = Visibility.Visible;
460  @State colorTransparent: Color = Color.Transparent;
461  @State textStyle: RichEditorTextStyle = {};
462  private editorMenuOptions: Array<EditorMenuOptions> =
463    [
464      {
465        icon: $r("sys.media.wifi_router_fill"),
466        symbolStyle: new SymbolGlyphModifier($r('sys.symbol.save')),
467        action: () => {
468          if (this.controller) {
469            let selection = this.controller.getSelection();
470            let spans = selection.spans;
471            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
472              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
473                let span = item as RichEditorTextSpanResult;
474                this.textStyle = span.textStyle;
475                let start = span.offsetInSpan[0];
476                let end = span.offsetInSpan[1];
477                let offset = span.spanPosition.spanRange[0];
478                if (this.textStyle.fontWeight != 11) {
479                  this.textStyle.fontWeight = FontWeight.Bolder;
480                } else {
481                  this.textStyle.fontWeight = FontWeight.Normal;
482                }
483                this.controller.updateSpanStyle({
484                  start: offset + start,
485                  end: offset + end,
486                  textStyle: this.textStyle
487                })
488              }
489            })
490          }
491        }
492      },
493      {
494        icon: $r("sys.media.save_button_picture"),
495        symbolStyle: new SymbolGlyphModifier($r('sys.symbol.camera')),
496        action: () => {
497          if (this.controller) {
498            let selection = this.controller.getSelection();
499            let spans = selection.spans;
500            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
501              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
502                let span = item as RichEditorTextSpanResult;
503                this.textStyle = span.textStyle;
504                let start = span.offsetInSpan[0];
505                let end = span.offsetInSpan[1];
506                let offset = span.spanPosition.spanRange[0];
507                if (this.textStyle.fontStyle == FontStyle.Italic) {
508                  this.textStyle.fontStyle = FontStyle.Normal;
509                } else {
510                  this.textStyle.fontStyle = FontStyle.Italic;
511                }
512                this.controller.updateSpanStyle({
513                  start: offset + start,
514                  end: offset + end,
515                  textStyle: this.textStyle
516                })
517              }
518            })
519          }
520        }
521      },
522      {
523        icon: $r("sys.media.waveform_folder_fill"),
524        symbolStyle: new SymbolGlyphModifier($r('sys.symbol.car')),
525        action: () => {
526          if (this.controller) {
527            let selection = this.controller.getSelection();
528            let spans = selection.spans;
529            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
530              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
531                let span = item as RichEditorTextSpanResult;
532                this.textStyle = span.textStyle;
533                let start = span.offsetInSpan[0];
534                let end = span.offsetInSpan[1];
535                let offset = span.spanPosition.spanRange[0];
536                if (this.textStyle.decoration) {
537                  if (this.textStyle.decoration.type == TextDecorationType.Underline) {
538                    this.textStyle.decoration.type = TextDecorationType.None;
539                  } else {
540                    this.textStyle.decoration.type = TextDecorationType.Underline;
541                  }
542                } else {
543                  this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
544                }
545                this.controller.updateSpanStyle({
546                  start: offset + start,
547                  end: offset + end,
548                  textStyle: this.textStyle
549                })
550              }
551            })
552          }
553        }
554      },
555      {
556        icon: $r("app.media.app_icon"), action: () => {
557      }, builder: (): void => this.sliderPanel()
558      },
559      {
560        icon: $r("sys.media.thermometer_fill"), action: () => {
561        if (this.controller) {
562          let selection = this.controller.getSelection();
563          let spans = selection.spans;
564          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
565            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
566              let span = item as RichEditorTextSpanResult;
567              this.textStyle = span.textStyle;
568              let start = span.offsetInSpan[0];
569              let end = span.offsetInSpan[1];
570              let offset = span.spanPosition.spanRange[0];
571              if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
572                this.textStyle.fontColor = Color.Black;
573              } else {
574                this.textStyle.fontColor = Color.Orange;
575              }
576              this.controller.updateSpanStyle({
577                start: offset + start,
578                end: offset + end,
579                textStyle: this.textStyle
580              })
581            }
582          })
583        }
584      }
585      }]
586  private expandedMenuOptions: Array<ExpandedMenuOptions> =
587    [{
588      startIcon: $r("app.media.startIcon"), content: 'Dictionary', action: () => {
589      }
590    }, {
591      startIcon: $r("app.media.startIcon"), content: 'Translate', action: () => {
592      }
593    }, {
594      startIcon: $r("app.media.startIcon"), content: 'Search', action: () => {
595      }
596    }]
597  private expandedMenuOptions1: Array<ExpandedMenuOptions> = []
598  private editorMenuOptions1: Array<EditorMenuOptions> = []
599  private selectionMenuOptions: SelectionMenuOptions = {
600    editorMenuOptions: this.editorMenuOptions,
601    expandedMenuOptions: this.expandedMenuOptions,
602    controller: this.controller,
603    onCut: (event?: EditorEventInfo) => {
604      if (event && event.content) {
605        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
606          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
607            let span = item as RichEditorTextSpanResult;
608            console.info('test cut' + span.value);
609            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
610          }
611        })
612      }
613    },
614    onPaste: (event?: EditorEventInfo) => {
615      if (event && event.content) {
616        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
617          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
618            let span = item as RichEditorTextSpanResult;
619            console.info('test onPaste' + span.value);
620            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
621          }
622        })
623      }
624    },
625    onCopy: (event?: EditorEventInfo) => {
626      if (event && event.content) {
627        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
628          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
629            let span = item as RichEditorTextSpanResult;
630            console.info('test cut' + span.value);
631            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
632          }
633        })
634      }
635    },
636    onSelectAll: (event?: EditorEventInfo) => {
637      if (event && event.content) {
638        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
639          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
640            let span = item as RichEditorTextSpanResult;
641            console.info('test onPaste' + span.value);
642            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
643          }
644        })
645      }
646    }
647  }
648
649  @Builder
650  sliderPanel() {
651    Column() {
652      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
653        Text('A').fontSize(15)
654        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
655          .width(210)
656          .onChange((value: number, mode: SliderChangeMode) => {
657            if (this.controller) {
658              let selection = this.controller.getSelection();
659              if (mode == SliderChangeMode.End) {
660                if (this.textSize == undefined) {
661                  this.textSize = 0;
662                }
663                let spans = selection.spans;
664                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
665                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
666                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize);
667                  }
668                })
669              }
670              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
671                this.start = selection.selection[0];
672                this.end = selection.selection[1];
673                this.textSize = value;
674                this.controller.updateSpanStyle({
675                  start: this.start,
676                  end: this.end,
677                  textStyle: { fontSize: this.textSize }
678                })
679              }
680            }
681          })
682        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
683      }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
684    }
685    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
686    .backgroundColor(Color.White)
687    .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
688    .padding(15)
689    .height(48)
690  }
691
692  @Builder
693  MyMenu() {
694    Column() {
695      SelectionMenu(this.selectionMenuOptions)
696    }
697    .width(256)
698    .backgroundColor(Color.Transparent)
699  }
700
701  @Builder
702  MyMenu2() {
703    Column() {
704      SelectionMenu({
705        editorMenuOptions: this.editorMenuOptions,
706        expandedMenuOptions: this.expandedMenuOptions1,
707        controller: this.controller,
708      })
709    }
710    .width(256)
711    .backgroundColor(Color.Transparent)
712  }
713
714  @Builder
715  MyMenu3() {
716    Column() {
717      SelectionMenu({
718        editorMenuOptions: this.editorMenuOptions1,
719        expandedMenuOptions: this.expandedMenuOptions,
720        controller: this.controller,
721      })
722    }
723    .width(256)
724    .backgroundColor(Color.Transparent)
725  }
726
727  build() {
728    Column() {
729      Button("SetSelection")
730        .onClick((event: ClickEvent) => {
731          if (this.controller) {
732            this.controller.setSelection(0, 2);
733          }
734        })
735
736      RichEditor(this.options)
737        .onReady(() => {
738          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } });
739          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } });
740        })
741        .onSelect((value: RichEditorSelection) => {
742          if (value.selection[0] == -1 && value.selection[1] == -1) {
743            return;
744          }
745          this.start = value.selection[0];
746          this.end = value.selection[1];
747        })
748        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
749        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
750        .borderWidth(1)
751        .borderColor(Color.Red)
752        .width(200)
753        .height(200)
754    }
755  }
756}
757```
758
759![selectionmenu02](figures/selectionmenu02.jpg)
760