1# SelectionMenu 2 3 4The **SelectionMenu** component is a context menu designed for use with the [RichEditor](ts-basic-components-richeditor.md) component, allowing you to bind a custom context menu on selection through the [bindSelectionMenu](./ts-basic-components-richeditor.md#bindselectionmenu) API. This component is not intended for standalone use, and you are advised to display it by right-clicking or by selecting text with a mouse device. 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 12## Modules to Import 13 14``` 15import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI' 16``` 17 18## Child Components 19 20Not supported 21 22## SelectionMenu 23 24SelectionMenu(options: SelectionMenuOptions): void 25 26Defines a custom context menu on selection. When the input parameter is empty, the sizes of the menu and its content area are 0, making the menu invisible. In this case, for example, if a right-click context menu is bound to the **RichEditor** component, it will not be displayed when the component is right-clicked. 27 28**Decorator**: @Builder 29 30**Atomic service API**: This API can be used in atomic services since API version 12. 31 32**System capability**: SystemCapability.ArkUI.ArkUI.Full 33 34**Parameters** 35 36| Name| Type| Mandatory| Description| 37| -------- | -------- | -------- | -------- | 38| options | [SelectionMenuOptions](#selectionmenuoptions) | Yes| Options of the context menu on selection.| 39 40## SelectionMenuOptions 41 42Defines the options of the context menu on selection. 43 44**Atomic service API**: This API can be used in atomic services since API version 12. 45 46**System capability**: SystemCapability.ArkUI.ArkUI.Full 47 48| Name| Type| Mandatory| Description| 49| -------- | -------- | -------- | -------- | 50| editorMenuOptions | Array<[EditorMenuOptions](#editormenuoptions)> | 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.| 51| expandedMenuOptions | Array<[ExpandedMenuOptions](#expandedmenuoptions)> | 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.| 52| 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.| 53| onCopy | (event?: [EditorEventInfo](#editoreventinfo)) => 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.| 54| onPaste | (event?: [EditorEventInfo](#editoreventinfo)) => 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.| 55| onCut | (event?: [EditorEventInfo](#editoreventinfo)) => 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.| 56| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo)) => 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.| 57 58 59## EditorMenuOptions 60 61Describes the edit menu options. 62 63**Atomic service API**: This API can be used in atomic services since API version 12. 64 65**System capability**: SystemCapability.ArkUI.ArkUI.Full 66 67| Name| Type| Mandatory| Description| 68| -------- | -------- | -------- | -------- | 69| icon | [ResourceStr](ts-types.md#resourcestr) | Yes| Icon.| 70| 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.| 71| builder | () => void | No| Builder of the custom component displayed upon click. It must be used with @Builder for building the custom component.| 72| action | () => void | No| Action triggered when the menu option is clicked.| 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 | () => 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 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 760