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<[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.| 53| 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.| 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)) => 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)) => 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)) => 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)) => 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 | () => 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 | () => 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 | () => 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