RichEditor

RichEditor is a component that supports interactive text editing and mixture of text and imagery.

NOTE

  • This component is supported since API version 10. Updates will be marked with a superscript to indicate their earliest API version.

  • This component supports WithTheme since API version 26.0.0.

Child Components

Not supported

APIs

RichEditor

RichEditor(value: RichEditorOptions)

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorOptions Yes Options for initializing the component.

RichEditor12+

RichEditor(options: RichEditorStyledStringOptions)

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
options RichEditorStyledStringOptions Yes Options for initializing the component.

Attributes

In addition to the universal attributes, the following attributes are supported.

NOTE

  • The align attribute supports only the start, center, and end options.
  • The borderImage attribute is not supported.
  • The default horizontal padding of the component is 16 vp, and the default vertical padding is 8 vp.

customKeyboard

customKeyboard(value: CustomBuilder | ComponentContent | undefined, options?: KeyboardOptions | undefined)

Sets a custom keyboard.

When a custom keyboard is set, activating the text box opens the specified custom component, instead of the system input method.

The custom keyboard's height can be set through the height attribute of the custom component's root node, and its width is fixed at the default value.

The custom keyboard cannot obtain the focus, but it blocks gesture events.

By default, the custom keyboard is closed when the input component loses the focus.

The custom keyboard supports the continue function. You can call the setCustomKeyboardContinueFeature API to set whether the custom keyboard remains persistent during input field switches.

NOTE

This API can be called within attributeModifier since API version 23.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value CustomBuilder | ComponentContent23+ | undefined23+ Yes Custom keyboard.
When undefined is passed, the system keyboard is used by default.
options12+ KeyboardOptions | undefined23+ No Whether to support keyboard avoidance.
When undefined is passed, keyboard avoidance is not supported by default.

bindSelectionMenu

bindSelectionMenu(spanType: RichEditorSpanType, content: CustomBuilder, responseType: ResponseType | RichEditorResponseType, options?: SelectionMenuOptions)

Sets the custom context menu on text selection. If the custom menu is too long, embed a Scroll component to prevent the keyboard from being blocked.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
spanType RichEditorSpanType Yes Menu type.
Default value:
RichEditorSpanType.TEXT
content CustomBuilder Yes Menu content.
responseType  ResponseType  |  RichEditorResponseType Yes Response type of the menu.
Default value:
ResponseType.LongPress
options SelectionMenuOptions No Menu options.

copyOptions

copyOptions(value: CopyOptions)

Specifies whether copy and paste operations are allowed for text content.

Since API version 20, copied or cut text from the RichEditor component includes HTML-formatted content in the pasteboard.

  • Only TextSpan and ImageSpan allow HTML content to be added to the pasteboard. This capability is unavailable for other span types, including BuilderSpan, SymbolSpan, and CustomSpan.

  • For styled strings, refer to toHtml for supported HTML conversion scope.

When copyOptions is not set to CopyOptions.None, long-pressing the component content brings up the text selection menu. If a custom context menu is defined through bindSelectionMenu or other approaches, it will be displayed.

When copyOptions is set to CopyOptions.None, the copy, cut, translate, share, search, and Celia Writer features are disabled, and drag-and-drop operations are not supported.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value CopyOptions Yes Whether copy and paste operations are allowed for text content.
Default value: CopyOptions.LocalDevice

enableDataDetector11+

enableDataDetector(enable: boolean)

Enables recognition for special entities within the text.

This API only works on devices that provide text recognition.

When enableDataDetector is set to true and the dataDetectorConfig attribute is not specified, the system recognizes all types of entities by default, and changes the color and decoration of these entities to the preset style.

color: '#ff007dff'
decoration:{
  type: TextDecorationType.Underline,
  color: '#ff007dff',
  style: TextDecorationStyle.SOLID
}

Touching or right-clicking an entity opens a context menu with actions based on entity type, while left-clicking triggers the first menu option directly.

This API does not work for the node text of addBuilderSpan.

When copyOptions is set to CopyOptions.None, the menu displayed after an entity is clicked does not provide the text selection or copy functionality.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enable boolean Yes Whether to enable text recognition.
true to enable, false otherwise.
Default value: false.

dataDetectorConfig11+

dataDetectorConfig(config: TextDataDetectorConfig)

Configures text special entity recognition settings, including detectable entity types, entity display styles, and long-press preview availability.

This API must be used together with enableDataDetector. It takes effect only when enableDataDetector is set to true.

When entities A and B overlap, the following rules are followed:

  1. If A ⊂ B, retain B. Otherwise, retain A.

  2. When A ⊄ B and B ⊄ A: If A.start < B.start, retain A; otherwise, retain B.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
config TextDataDetectorConfig Yes Text recognition configuration.

enableSelectedDataDetector22+

enableSelectedDataDetector(enable: boolean | undefined)

Sets whether to enable the AI menu feature for text selection. After this feature is enabled, the entities such as email address, phone number, website URL, date, and address in the selection area can be recognized, and the corresponding AI menu items can be displayed in the text selection menu. By default, the AI menu feature is enabled.

When the AI menu feature is enabled, selecting text in the component allows the text selection menu to display corresponding AI menu items, including url (opening a link), email (creating an email), phoneNumber (making a call), address (navigating), and dateTime (creating a new event) in TextMenuItemId.

When the AI menu is active, the corresponding menu item is displayed only if the selected range contains exactly one complete AI entity. This menu item does not appear at the same time as the askAI menu item in TextMenuItemId.

This feature takes effect only when copyOptions is set to CopyOptions.LocalDevice or CopyOptions.CROSS_DEVICE.

This API depends on the text recognition capability of the device; otherwise, the setting does not take effect.

Atomic service API: This API can be used in atomic services since API version 22.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enable boolean | undefined Yes Whether to enable text recognition. The value true means to enable text recognition, and false means the opposite.
If undefined or null is passed, the attribute is reset to the default value.

enablePreviewText12+

enablePreviewText(enable: boolean)

Sets whether to enable preview text.

NOTE

This API can be called within attributeModifier since API version 18.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enable boolean Yes Whether to enable preview text.
true to enable, false otherwise.
Default value: true

This API is disabled by default in C API scenarios. To enable preview text in such scenarios, set metadata in the module.json5 file of the project as follows:

"metadata": [
  {
    "name": "can_preview_text",
    "value": "true"
  }
]

placeholder12+

placeholder(value: ResourceStr, style?: PlaceholderStyle)

Text displayed when there is no input.

NOTE

This API can be called within attributeModifier since API version 18.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value ResourceStr Yes Placeholder text.
style PlaceholderStyle No Style of the placeholder text.
By default, the style follows the theme.

caretColor12+

caretColor(value: ResourceColor)

Sets the color of the caret and selection handle in the text box.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value ResourceColor Yes Color of the caret and selection handle in the text box.
Default value: '#007DFF'

selectedBackgroundColor12+

selectedBackgroundColor(value: ResourceColor)

Sets the background color of the selected text. If the opacity is not set, a 20% opacity will be used.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value ResourceColor Yes Background color of the selected text.
By default, a 20% opacity is applied.

editMenuOptions12+

editMenuOptions(editMenu: EditMenuOptions)

Sets the extended options for the default system menu, including text content, icons, and callback methods.

When disableMenuItems or disableSystemServiceMenuItems is used to disable system service menu items in the text selection menu, the disabled menu options will be excluded from the parameter list in the onCreateMenu callback of editMenuOptions.

NOTE

This API can be called within attributeModifier since API version 18.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
editMenu EditMenuOptions Yes Extended options of the custom menu.

enterKeyType12+

enterKeyType(value: EnterKeyType)

Sets the Enter key type of the soft keyboard.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value EnterKeyType Yes Type of the Enter key.
Default value: EnterKeyType.NEW_LINE

enableKeyboardOnFocus12+

enableKeyboardOnFocus(isEnabled: boolean)

Sets whether to enable the input method when the RichEditor component obtains focus in a way other than clicking.

NOTE

This API can be called within attributeModifier since API version 18.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
isEnabled boolean Yes Whether to pop up the soft keyboard when the TextInput component obtains focus in a way other than clicking.
true: yes; false: no
Default value: true

barState13+

barState(state: BarState)

Sets the display mode of the RichEditor scrollbar.

NOTE

This API can be called within attributeModifier since API version 18.

Atomic service API: This API can be used in atomic services since API version 13.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
state BarState Yes Scrollbar display mode.
Default value: BarState.Auto

maxLength18+

maxLength(maxLength: Optional<number>)

Sets the maximum length of the component content. When the total length of the content (including text, images, symbols, and builders) reaches this value, no more content can be added.

Atomic service API: This API can be used in atomic services since API version 18.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
maxLength Optional<number> Yes Maximum number of characters for text input.
Default value: Infinity, which means unlimited input. The undefined type is supported.
NOTE
If this attribute is not set or is set to an invalid value, the default value is used. If a decimal number is specified, the integer part is used.

maxLines18+

maxLines(maxLines: Optional<number>)

Sets the maximum number of lines the rich text can display. When maxLines is set, content that exceeds the specified number of lines can be scrolled to display. If both the component height and maxLines are set, the component height takes precedence.

Atomic service API: This API can be used in atomic services since API version 18.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
maxLines Optional<number> Yes Maximum number of lines the rich text can display. When maxLines is set, content that exceeds the specified number of lines can be scrolled to display. If both the component height and maxLines are set, the component height takes precedence.
Default value: UINT32_MAX, which means unlimited input. The undefined type is supported.
Value range: (0, UINT32_MAX]

enableHapticFeedback13+

enableHapticFeedback(isEnabled: boolean)

Sets whether to enable haptic feedback.

NOTE

This API can be called within attributeModifier since API version 20.

Atomic service API: This API can be used in atomic services since API version 13.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
isEnabled boolean Yes Whether to enable haptic feedback.
Default value: true true to enable; false otherwise.
NOTE
Haptic feedback takes effect only when the app has the ohos.permission.VIBRATE permission, the user has enabled haptic feedback, and the system hardware supports it.

keyboardAppearance15+

keyboardAppearance(appearance: Optional<KeyboardAppearance>)

Sets the keyboard appearance.

Atomic service API: This API can be used in atomic services since API version 15.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
appearance Optional<KeyboardAppearance> Yes Keyboard appearance.
Default value: KeyboardAppearance.NONE_IMMERSIVE

stopBackPress18+

stopBackPress(isStopped: Optional<boolean>)

Sets whether to prevent the back key event from being propagated.

Atomic service API: This API can be used in atomic services since API version 18.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
isStopped Optional<boolean> Yes Whether to prevent the back key event from being propagated.
true: Propagation is prevented. false: Propagation is allowed.
Default value: true Invalid values are treated as the default value.

undoStyle20+

undoStyle(style: Optional<UndoStyle>)

Sets whether to retain the original content style upon undo operations.

When the RichEditorStyledStringOptions is used to build the RichEditor component, the original content style is retained by default upon undo operations, and is not affected by the attribute set by this API.

Atomic service API: This API can be used in atomic services since API version 20.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
style Optional<UndoStyle> Yes Whether to retain the original style upon undo operations. Default value: UndoStyle.CLEAR_STYLE.

enableAutoSpacing20+

enableAutoSpacing(enable: Optional<boolean>)

Sets whether to enable automatic spacing between Chinese and Western characters.

Atomic service API: This API can be used in atomic services since API version 20.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enable Optional<boolean> Yes Whether to enable automatic spacing between Chinese and Western characters.
true to enable, false otherwise.
Default value: false

scrollBarColor21+

scrollBarColor(color: Optional<ColorMetrics>)

Sets the color of the scrollbar.

Atomic service API: This API can be used in atomic services since API version 21.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
color Optional<ColorMetrics> Yes Color of the scrollbar.
Default value: '#66182431', displayed as gray.
Note: Invalid values are treated as the default value.

includeFontPadding23+

includeFontPadding(include: Optional<boolean>)

Sets whether to add spacing to the first and last lines to avoid text truncation. If this attribute is not set, no spacing is added by default.

Atomic service API: This API can be used in atomic services since API version 23.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
include Optional<boolean> Yes Whether to add spacing to the first and last lines to avoid text truncation.
true: Spacing is added to the first and last lines. false: Spacing is not added to the first and last lines.

fallbackLineSpacing23+

fallbackLineSpacing(enabled: Optional<boolean>)

Adapts the line height to the actual text height for overlapped multi-line text. If this API is not set, the line height does not adapt to the actual text height by default.

This API depends on the lineHeight property of RichEditorTextStyle. When the value of lineHeight is less than the actual height of the text rendered under the current font size, the fallbackLineSpacing attribute takes effect.

Atomic service API: This API can be used in atomic services since API version 23.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enabled Optional<boolean> Yes Whether the line height adapts to the actual text height.
true: Line height adapts to the actual text height. false: Line height does not adapt to the actual text height.

compressLeadingPunctuation23+

compressLeadingPunctuation(enabled: Optional<boolean>)

Sets whether to enable leading punctuation compression.

NOTE

Leading punctuation is not compressed by default.

For the range of punctuation marks that support leading compression, see ParagraphStyle.

Atomic service API: This API can be used in atomic services since API version 23.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enabled Optional<boolean> Yes Whether to enable leading punctuation compression.
true: Leading punctuation compression is enabled. false: Leading punctuation compression is disabled.

selectedDragPreviewStyle23+

selectedDragPreviewStyle(value: SelectedDragPreviewStyle | undefined)

Sets the drag preview style.

Atomic service API: This API can be used in atomic services since API version 23.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value SelectedDragPreviewStyle | undefined Yes Drag preview style. If it is set to undefined, the style will be reset.

singleLine23+

singleLine(isEnable: boolean | undefined)

Sets whether to enable single-line mode. The single-line mode is disabled by default when this API is not specified.

NOTE

The scrollbar is not displayed in single-line mode.

Line breaks are rendered as spaces in single-line mode.

Atomic service API: This API can be used in atomic services since API version 23.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
isEnable boolean | undefined Yes Whether to enable single-line mode.
true: The single-line mode is enabled. false: The single-line mode is disabled.
When the parameter is set to undefined or null, the value is handled as false and single-line mode is disabled.

orphanCharOptimization

orphanCharOptimization(enabled: Optional<boolean>)

Sets whether to enable orphan character optimization during text typesetting. If this attribute is not set, orphan character optimization is disabled by default.

Orphan character optimization improves the text layout by handling the orphan character (the first Chinese character of the last line of a paragraph) more efficiently. When enabled, it adjusts line breaks to avoid orphan characters as much as possible. Orphan character optimization takes effect only when the wordBreak property of RichEditorParagraphStyle is not BREAK_ALL and locale of the first TextStyle of the text to be typeset is either "zh-Hans" or "zh-Hant".

Since: 26.0.0

Atomic service API: This API can be used in atomic services since API version 26.0.0.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enabled Optional<boolean> Yes Whether to enable orphan character optimization for the last line of the paragraph.
true: Orphan character optimization is enabled. false: Orphan character optimization is disabled. When the value is undefined or null, orphan character optimization is disabled.

horizontalScrolling

horizontalScrolling(enabled: Optional<boolean>)

Sets whether to enable horizontal scrolling for text when its width exceeds the width of the content area. If this API is not specified, horizontal scrolling is disabled by default.

Since: 26.0.0

Atomic service API: This API can be used in atomic services since API version 26.0.0.

Model restriction: This API can be used only in the stage model.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enabled Optional<boolean> Yes Whether to enable horizontal scrolling.
true: Horizontal scrolling is enabled. false: Horizontal scrolling is disabled and the text automatically wraps. If this parameter is set to undefined or null, horizontal scrolling is disabled.

Events

In addition to the universal events, OnDidChangeCallback, StyledStringChangedListener, StyledStringChangeValue, and the following events are supported.

onReady

onReady(callback:Callback<void>)

Triggered after the RichEditor component is initialized.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<void> Yes Callback invoked when the initialization of the RichEditor component is complete.

onSelect

onSelect(callback:Callback<RichEditorSelection>)

Triggered when content is selected via left mouse button double-click and triggered again upon left mouse button release.

Triggered when content is selected via long press, and triggered again upon finger release.

The onSelect callback is not invoked during continuous selection adjustment with mouse or touch gestures, or during triple-click paragraph selection.

If the selection area needs to be detected in real time or the RichEditor component is built with RichEditorStyledStringOptions, use the onSelectionChange API.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<RichEditorSelection> Yes RichEditorSelection indicates information about all the selected spans.
Callback invoked when content is selected.

aboutToIMEInput

aboutToIMEInput(callback:Callback<RichEditorInsertValue, boolean>)

Triggered when content is about to be entered in the input method.

This callback is not supported when the RichEditor component built with RichEditorStyledStringOptions is used.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<RichEditorInsertValue, boolean> Yes RichEditorInsertValue indicates whether content will be entered in the input method.
true: The component adds the content.
false: The component does not add the content.
Callback invoked when content is about to be entered in the input method.

onDidIMEInput12+

onDidIMEInput(callback:Callback<TextRange>)

Triggered when text input is completed via the input method editor.

This callback is not supported when the RichEditor component built with RichEditorStyledStringOptions is used.

NOTE

This API can be called within attributeModifier since API version 20.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<TextRange> Yes TextRange indicates the text range for the current input.
Callback invoked when IME input is completed.

onIMEInputComplete

onIMEInputComplete(callback:Callback<RichEditorTextSpanResult>)

Triggered when text input is completed via the input method editor.

This API can return information about only one text span. You are advised to use the onDidIMEInput API if the edit operation involves returning information about multiple text spans.

This callback is not supported when the RichEditor component built with RichEditorStyledStringOptions is used.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<RichEditorTextSpanResult> Yes RichEditorTextSpanResult indicates the text span information after text input is complete.
Callback invoked after IME input is completed.

aboutToDelete

aboutToDelete(callback:Callback<RichEditorDeleteValue, boolean>)

Triggered when content is about to be deleted via the IME.

This callback is not supported when the RichEditor component built with RichEditorStyledStringOptions is used.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<RichEditorDeleteValue, boolean> Yes RichEditorDeleteValue indicates the text or image span where the content to be deleted is located.
true: Content is deleted.
false: Content is not deleted.
Callback invoked when content is about to be deleted in the input method. It is executed when a candidate word is touched in preview text.

onDeleteComplete

onDeleteComplete(callback:Callback<void>)

Triggered when content is deleted via the IME.

This callback is not supported when the RichEditor component built with RichEditorStyledStringOptions is used.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<void> Yes Triggered when deletion in the input method is completed.

onPaste11+

onPaste(callback: PasteEventCallback )

Triggered when a paste operation is performed. You can use this API to override the default system behavior so that both images and text can be pasted.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback PasteEventCallback Yes Callback used to subscribe to the pasted content.

onSelectionChange12+

onSelectionChange(callback:Callback<RichEditorRange>)

Triggered when the selection area or caret position changes in the editing state. When the caret position changes, the start and end positions of the selection area are the same.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<RichEditorRange> Yes RichEditorRange indicates the start and end positions of the content selection area.
Callback invoked when the content selection area changes or the caret position changes in the editing state.

onEditingChange12+

onEditingChange(callback: Callback<boolean>)

Triggered when the content editing state in the component changes.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<boolean> Yes Callback invoked when the editing state of all content in the component changes. The value true indicates the editing state, and false indicates the non-editing state.

onSubmit12+

onSubmit(callback: SubmitCallback)

Triggered when the Enter key on the soft keyboard is pressed.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback SubmitCallback Yes Callback used to return the subscription event.

onWillChange12+

onWillChange(callback: Callback<RichEditorChangeValue , boolean>)

Invoked when an addition or deletion operation is about to be performed on the component.

This callback is not supported when the RichEditor component built with RichEditorStyledStringOptions is used.

NOTE

This API can be called within attributeModifier since API version 18.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<RichEditorChangeValue , boolean> Yes RichEditorChangeValue indicates the image and text change information. The boolean value indicates whether the image and text can be modified. true: The image and text can be modified. false: The image and text cannot be modified.

onDidChange12+

onDidChange(callback: OnDidChangeCallback)

Triggered after an addition or deletion operation is performed on the component. This callback is not executed if there is no actual addition or deletion of text.

This callback is not supported when the RichEditor component built with RichEditorStyledStringOptions is used.

NOTE

This API can be called within attributeModifier since API version 18.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback OnDidChangeCallback Yes Content range before and after the text and image change.

onCut12+

onCut(callback: Callback<CutEvent>)

Triggered on cut operations. You can use this method to override the system's default behavior and implement the cutting of text and images.

The RichEditor component built with RichEditorStyledStringOptions supports text and image cutting by default.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<CutEvent> Yes Defines a custom cut event.

onCopy12+

onCopy(callback: Callback<CopyEvent>)

Triggered on copy operations. You can use this method to override the system's default behavior and implement the copying of text and images.

The RichEditor component built with RichEditorStyledStringOptions supports text and image copying by default.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<CopyEvent> Yes User copy event.

onWillAttachIME22+

onWillAttachIME(callback: Callback<IMEClient> | undefined)

Triggered before the component is bound to the IME.

Call the setExtraConfig method of IMEClient to set input method extension information. After the input method is bound, it receives this extension information which can be used to implement custom functionality.

Atomic service API: This API can be used in atomic services since API version 22.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
callback Callback<IMEClient> | undefined Yes Callback invoked before the component is bound to the IME.
If the value is undefined, the bound callback event is cleared.

RichEditorInsertValue

Defines information about the text to be inserted.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
insertOffset number No No Offset of the text to be inserted.
Atomic service API: This API can be used in atomic services since API version 11.
insertValue string No No Content of the text to be inserted.
Atomic service API: This API can be used in atomic services since API version 11.
previewText12+ string No Yes Content of the preview text to be inserted.
Atomic service API: This API can be used in atomic services since API version 12.

RichEditorDeleteValue

Defines information about the deletion operation and the content to be deleted.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
offset number No No Offset of the content to be deleted.
direction RichEditorDeleteDirection No No Direction of the delete operation.
length number No No Length of the content to be deleted.
richEditorDeleteSpans Array<RichEditorTextSpanResult | RichEditorImageSpanResult> No No Information about the text or image spans to be deleted.

RichEditorDeleteDirection

Defines the deletion direction.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Description
BACKWARD Backward deletion.
FORWARD Forward deletion.

RichEditorTextSpanResult

Defines text span information.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
spanPosition RichEditorSpanPosition No No Span position.
Atomic service API: This API can be used in atomic services since API version 11.
value string No No Content of the text span or symbol ID.
Atomic service API: This API can be used in atomic services since API version 11.
textStyle RichEditorTextStyleResult No No Text span style.
Atomic service API: This API can be used in atomic services since API version 11.
offsetInSpan [number, number] No No Start and end positions of the valid content in the text span.
Atomic service API: This API can be used in atomic services since API version 11.
valueResource11+ Resource No Yes Content of the SymbolSpan component.
Atomic service API: This API can be used in atomic services since API version 12.
symbolSpanStyle11+ RichEditorSymbolSpanStyle No Yes Style of the SymbolSpan component.
Atomic service API: This API can be used in atomic services since API version 12.
paragraphStyle12+ RichEditorParagraphStyle No Yes Paragraph style.
Atomic service API: This API can be used in atomic services since API version 12.
previewText12+ string No Yes Content of the preview text.
Atomic service API: This API can be used in atomic services since API version 12.
urlStyle19+ RichEditorUrlStyle No Yes URL information.
Default value: undefined
Atomic service API: This API can be used in atomic services since API version 19.

RichEditorSpanPosition

Defines span position information.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
spanIndex number No No Span index.
spanRange [number, number] No No Start and end positions of the span content in the RichEditor component.

RichEditorSpanType

Enumerates span types.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Value Description
TEXT 0 Text span.
Atomic service API: This API can be used in atomic services since API version 11.
IMAGE 1 Image span.
Atomic service API: This API can be used in atomic services since API version 11.
MIXED 2 Mixed text and image span.
Atomic service API: This API can be used in atomic services since API version 11.
BUILDER12+ 3 Builder span.
Atomic service API: This API can be used in atomic services since API version 12.
DEFAULT15+ 4 When a menu of this type is registered while the TEXT, IMAGE, MIXED, or BUILDER types are not registered, this menu will be triggered and displayed for those unregistered types.
Atomic service API: This API can be used in atomic services since API version 15.

RichEditorResponseType11+

Enumerates the response types of the menu.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Value Description
RIGHT_CLICK 0 The menu is displayed when the component is right-clicked.
Atomic service API: This API can be used in atomic services since API version 12.
LONG_PRESS 1 The menu is displayed when the component is long-pressed.
Atomic service API: This API can be used in atomic services since API version 12.
SELECT 2 The menu is displayed when the component is selected.
Atomic service API: This API can be used in atomic services since API version 12.
DEFAULT15+ 3 When a menu of this type is registered while RIGHT_CLICK, LONG_PRESS, and SELECT menus are not registered, the menu will be displayed for those unregistered types.
Atomic service API: This API can be used in atomic services since API version 15.

UndoStyle20+

Enumerates the options for whether to retain the original style upon undo operations.

Atomic service API: This API can be used in atomic services since API version 20.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Value Description
CLEAR_STYLE 0 The original style is not retained upon undo operations.
KEEP_STYLE 1 The original style is retained upon undo operations.

RichEditorTextStyleResult

Provides the text span style information returned by the backend.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
fontColor ResourceColor No No Font color.
Atomic service API: This API can be used in atomic services since API version 11.
fontSize number No No Font size. The default unit is fp.
Atomic service API: This API can be used in atomic services since API version 11.
fontStyle FontStyle No No Font style.
Atomic service API: This API can be used in atomic services since API version 11.
fontWeight number No No Font weight.
Atomic service API: This API can be used in atomic services since API version 11.
fontFamily string No No Font family.
Atomic service API: This API can be used in atomic services since API version 11.
decoration DecorationStyleResult No No Text decoration.
Atomic service API: This API can be used in atomic services since API version 11.
textShadow12+  Array<ShadowOptions> No Yes Text shadow.
NOTE
Only the shadow blur radius, shadow color, and shadow offset can be queried.
Atomic service API: This API can be used in atomic services since API version 12.
lineHeight12+ number No Yes Line height. The default unit is fp.
Atomic service API: This API can be used in atomic services since API version 12.
letterSpacing12+ number No Yes Letter spacing. The default unit is fp.
Atomic service API: This API can be used in atomic services since API version 12.
fontFeature12+ string No Yes Font feature.
Atomic service API: This API can be used in atomic services since API version 12.
halfLeading18+ boolean No Yes Whether half leading is enabled.
true: Half leading is enabled. false: Half leading is not enabled.
Default value: false
Atomic service API: This API can be used in atomic services since API version 18.
textBackgroundStyle18+ TextBackgroundStyle No Yes Text background style.
Atomic service API: This API can be used in atomic services since API version 18.
strokeWidth23+ number No Yes Text stroke width.
The unit is vp.
Atomic service API: This API can be used in atomic services since API version 23.
strokeColor23+ ResourceColor No Yes Text stroke color.
Atomic service API: This API can be used in atomic services since API version 23.

While fontWeight in RichEditorTextStyle sets the font weight,

fontWeight in RichEditorTextStyleResult returns the set font weight after conversion to digits.

The table below lists the conversion mappings.

fontWeight in RichEditorTextStyle fontWeight in RichEditorTextStyleResult
100 0
200 1
300 2
400 3
500 4
600 5
700 6
800 7
900 8
Lighter 12
Normal 10
Regular 14
Medium 13
Bold 9
Bolder 11

The mapping of fontWeight between RichEditorSymbolSpanStyle and RichEditorSymbolSpanStyleResult is the same as that of fontWeight between RichEditorTextStyle and RichEditorTextStyleResult.

RichEditorSymbolSpanStyleResult11+

Provides the symbol span style information returned by the backend.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
fontColor Array<ResourceColor> No No Color of the symbol span.
Default value: depending on the rendering strategy
fontSize number | string | Resource No No Size of the symbol span. The default unit is fp.
The default value follows the theme.
fontWeight number | FontWeight | string No No Weight of the symbol span.
For the number type, the value ranges from 100 to 900, at an interval of 100. A larger value indicates a heavier font weight. The default value is 400.
For the string type, only strings of the number type are supported, for example, "400", "bold", "bolder", "lighter", "regular", and "medium", which correspond to the enumerated values in FontWeight.
Default value: FontWeight.Normal
renderingStrategy SymbolRenderingStrategy No No Rendering strategy of the symbol span.
Default value: SymbolRenderingStrategy.SINGLE
effectStrategy SymbolEffectStrategy No No Effect strategy of the symbol span.
Default value: SymbolEffectStrategy.NONE

RichEditorImageSpanResult

Provides the image information returned by the backend.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
spanPosition RichEditorSpanPosition No No Span position.
valuePixelMap PixelMap No Yes Image content.
valueResourceStr ResourceStr No Yes Image resource ID.
imageStyle RichEditorImageSpanStyleResult No No Image style.
offsetInSpan [number, number] No No Start and end positions of the image in the span.

RichEditorImageSpanStyleResult

Provides the image span style information returned by the backend.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
size [number, number] No No Width and height of the image, in px. Default value depends on the objectFit setting. If the value of objectFit is Cover, the image height is the component height minus the top and bottom paddings, and the image width is the component width minus the left and right paddings.
Atomic service API: This API can be used in atomic services since API version 11.
verticalAlign ImageSpanAlignment No No Vertical alignment mode of the image.
Atomic service API: This API can be used in atomic services since API version 11.
objectFit ImageFit No No Scale mode of the image.
Atomic service API: This API can be used in atomic services since API version 11.
layoutStyle12+ RichEditorLayoutStyle No Yes Image layout style.
Atomic service API: This API can be used in atomic services since API version 12.

RichEditorLayoutStyle11+

Defines image layout information.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
margin Dimension | Margin No Yes Margins in different directions of the component.
When the parameter is of the Dimension type, the four margins take effect.
borderRadius Dimension | BorderRadiuses No Yes Radius of the rounded corners of the component.
If of the Dimension type, this parameter cannot be set in percentage.

RichEditorOptions

Defines the options for initializing the RichEditor component.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
controller RichEditorController No No Controller for the RichEditor component.

RichEditorStyledStringOptions12+

Defines the options for initializing the RichEditor component.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
controller RichEditorStyledStringController No No Controller for the RichEditor component.

RichEditorChangeValue12+

Defines image and text change information.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
rangeBefore TextRange No No Start and end indexes of the content to be replaced.
replacedSpans Array<RichEditorTextSpanResult> No No Information about the text span after the change.
replacedImageSpans Array<RichEditorImageSpanResult> No No Information about the image span after the change.
replacedSymbolSpans Array<RichEditorTextSpanResult> No No Information about the symbol span after the change.

RichEditorBaseController12+

Represents the base class of the RichEditor component controller.

getCaretOffset10+

getCaretOffset(): number

Obtains the current caret position.

If the caret position cannot be obtained (for example, when the controller is not bound to the component), the return value is -1.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
number Position of the caret.

setCaretOffset10+

setCaretOffset(offset: number): boolean

Sets the caret position.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
offset number Yes Offset of the caret. If it exceeds the range of all content, the setting will fail.

Return value

Type Description
boolean Whether the caret offset is set successfully.
true if the caret offset is set successfully; false otherwise.

closeSelectionMenu10+

closeSelectionMenu(): void

Closes the custom or default context menu on selection.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

getTypingStyle11+

getTypingStyle(): RichEditorTextStyle

Obtains the preset text style of a user.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
RichEditorTextStyle Preset typing style.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

setTypingStyle11+

setTypingStyle(value: RichEditorTextStyle): void

Sets the preset typing style.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorTextStyle Yes Preset typing style.

setTypingParagraphStyle20+

setTypingParagraphStyle(style: RichEditorParagraphStyle): void

Sets the preset paragraph style for text input. The style takes effect only when the component content is empty or a line break is appended at the end of the component.

Atomic service API: This API can be used in atomic services since API version 20.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
style RichEditorParagraphStyle Yes Preset paragraph style.

setSelection11+

setSelection(selectionStart: number, selectionEnd: number, options?: SelectionOptions): void

Sets the range of content selection. The selected content is highlighted.

If both selectionStart and selectionEnd are set to -1, all content is selected. If both selectionStart and selectionEnd are set to 0, the current selection is cleared.

If this API is called when the text box is not focused, the selected effect is not displayed.

Since API version 12, on 2-in-1 devices, regardless of the value of options, calling the setSelection API will not display the menu. In addition, if there is already a menu present within the component, calling the setSelection API will close the menu.

On non-2-in-1 devices, when options is set to MenuPolicy.DEFAULT, the following rules apply:

  1. If the component has a selection handle menu, calling the API will not close the menu, and the menu position will be adjusted.

  2. If the component has a menu without a selection handle, calling the API will not close the menu, and the menu position will remain unchanged.

  3. If there is no menu within the component, calling the API will not display the menu.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
selectionStart number Yes Start position of the selection.
selectionEnd number Yes End position of the selection.
options12+ SelectionOptions No Configuration of options.

isEditing12+

isEditing(): boolean

Obtains the editing state of this RichEditor component.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
boolean Editing state. The value true indicates the editing state, and false indicates the non-editing state.

stopEditing12+

stopEditing(): void

Exits the editing state.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

getLayoutManager12+

getLayoutManager(): LayoutManager

Obtains the LayoutManager object.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
LayoutManager LayoutManager object.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

getPreviewText12+

getPreviewText(): PreviewText

Obtains the preview text.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
PreviewText Preview text.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

getCaretRect18+

getCaretRect(): RectResult | undefined

Obtains the relative position of the caret in the RichEditor component. If the caret is not blinking, the API returns undefined.

Atomic service API: This API can be used in atomic services since API version 18.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
RectResult | undefined Relative position of the caret in the RichEditor component.

deleteBackward23+

deleteBackward(): void

Provides the capability of deleting characters. If no content is selected, one character before the current caret position is deleted. If content is selected, the selected content is deleted.

This API is not supported in preview display scenarios.

Atomic service API: This API can be used in atomic services since API version 23.

System capability: SystemCapability.ArkUI.ArkUI.Full

setStyledPlaceholder24+

setStyledPlaceholder(styledString: StyledString): void

Sets the placeholder text of the styled string when there is no input.

Atomic service API: This API can be used in atomic services since API version 24.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
styledString StyledString Yes Sets the placeholder text of the styled string. It takes higher priority than the placeholder text set by the placeholder attribute.
The placeholder text does not support gesture events bound to the GestureStyle of the styled string, or hyperlink navigation provided by UrlStyle.

RichEditorController

Implements the RichEditor component controller. Inherits from RichEditorBaseController.

NOTE

When the content length exceeds the height of the component's display area, the insertion APIs (such as addTextSpan, addImageSpan, addBuilderSpan, and addSymbolSpan) are called. The component automatically scrolls to keep the end of the inserted content visible.

Objects to Import

controller: RichEditorController = new RichEditorController();

addTextSpan

addTextSpan(content: ResourceStr, options?: RichEditorTextSpanOptions): number

Adds a text span. If the caret in the component is blinking, the caret position is updated to be after the inserted text span.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
content ResourceStr Yes Text content.
The Resource type is supported since API version 20.
options RichEditorTextSpanOptions No Text options.

Return value

Type Description
number Index of the added TextSpan among all spans.

addImageSpan

addImageSpan(value: PixelMap | ResourceStr, options?: RichEditorImageSpanOptions): number

Adds an image span. If the caret in the component is blinking, the caret position is updated to be after the inserted image span.

This API is a synchronous API. Adding network images directly under poor network conditions may block the UI thread and result in screen freezing. To avoid potential loading issues, do not directly add a network image.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value PixelMap | ResourceStr Yes Image content.
options RichEditorImageSpanOptions No Image options.

Return value

Type Description
number Index of the added ImageSpan among all spans.

addBuilderSpan11+

addBuilderSpan(value: CustomBuilder, options?: RichEditorBuilderSpanOptions): number

Adds a custom layout (BuilderSpan) to RichEditor.

NOTE

  • When added to RichEditor, the placeholder span calls the system measure method to calculate its actual width, height and position.
  • You can use RichEditorBuilderSpanOptions to set the index of the builder in the RichEditor component (with one character as the unit).
  • This builder span is unfocusable, draggable, and equipped with certain universal attributes. It behaves similarly to an image span in terms of placeholder and deletion functionality, and it is treated as a single character in length.
  • Custom menus can be set using bindSelectionMenu.
  • The information about the builder span cannot be obtained through getSpans, getSelection, onSelect, or aboutToDelete.
  • The builder span cannot be updated using updateSpanStyle or updateParagraphStyle.
  • Copying or pasting the builder span does not take effect.
  • The layout constraints of the builder span are passed in from the RichEditor component. If the size of the outermost component in the builder span is not set, the size of the RichEditor is used as the value of maxSize.
  • The gesture event mechanism of the builder span is the same as the universal gesture event mechanism. If gesture propagation is not enabled for the builder, only the child components in the builder respond.
  • If the caret in the component is blinking, the caret position is updated to be after the inserted builder span.

Only the following universal attributes are supported: size, padding, margin, aspectRatio, borderStyle, borderWidth, borderColor, borderRadius, backgroundColor, backgroundBlurStyle, opacity, blur, backdropBlur, shadow, grayscale, brightness, saturate, contrast, invert, sepia, hueRotate, colorBlend, linearGradientBlur, clip, mask, foregroundBlurStyle, accessibilityGroup, accessibilityText, accessibilityDescription, accessibilityLevel, sphericalEffect, lightUpEffect, pixelStretchEffect.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value CustomBuilder Yes Custom component.
options RichEditorBuilderSpanOptions No Builder options.

Return value

Type Description
number Index of the added builderSpan among all spans.

addSymbolSpan11+

addSymbolSpan(value: Resource, options?: RichEditorSymbolSpanOptions ): number

Adds a symbol span. If the caret in the component is blinking, the caret position is updated to be after the inserted symbol span.

Currently, gestures, copying, and dragging are not supported.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value Resource Yes Symbol resource object.
options RichEditorSymbolSpanOptions No Symbol options.

Return value

Type Description
number Index of the added SymbolSpan among all spans.

updateSpanStyle

updateSpanStyle(value: RichEditorUpdateTextSpanStyleOptions | RichEditorUpdateImageSpanStyleOptions | RichEditorUpdateSymbolSpanStyleOptions): void

Updates the text, image, or symbol span style.
If only part of a span is updated, the span is split into multiple spans based on the updated part and the non-updated part.

Calling this API will not close the custom context menu on selection by default.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorUpdateTextSpanStyleOptions | RichEditorUpdateImageSpanStyleOptions | RichEditorUpdateSymbolSpanStyleOptions Yes Style options of the text, image, or symbol span.

NOTE

If the value of start is greater than that of end, the value 0 will be used as start and infinity as end.

updateParagraphStyle11+

updateParagraphStyle(value: RichEditorParagraphStyleOptions): void

Updates the paragraph style.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorParagraphStyleOptions Yes Paragraph style options.

getSpans

getSpans(value?: RichEditorRange): Array<RichEditorImageSpanResult | RichEditorTextSpanResult>

Obtains span information.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorRange No Range of the target spans.

Return value

Type Description
Array<RichEditorImageSpanResult | RichEditorTextSpanResult> Text and image span information.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

deleteSpans

deleteSpans(value?: RichEditorRange): void

Deletes the text and image spans in a specified range.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorRange No Range of the target spans. If this parameter is omitted, all text and image spans are deleted.

getParagraphs11+

getParagraphs(value?: RichEditorRange): Array<RichEditorParagraphResult>

Obtains the paragraph information within a specified range.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorRange No Range of the paragraphs.

Return value

Type Description
Array<RichEditorParagraphResult> Information about the selected paragraphs.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

getSelection11+

getSelection(): RichEditorSelection

Obtains the range and span information of the selection. If no text is selected, this API returns the information about the span where the caret is located.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
RichEditorSelection Selection information.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

fromStyledString12+

fromStyledString(value: StyledString): Array<RichEditorSpan>

Converts a styled string to a span.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value StyledString Yes Styled string before conversion.

Return value

Type Description
Array<RichEditorSpan> Text and image span information.

Error codes

For details about the error codes, see Universal Error Codes.

ID Error Message
401 The parameter check failed.

toStyledString12+

toStyledString(value: RichEditorRange): StyledString

Converts the component content within the given range to a styled string. SymbolSpan and BuilderSpan cannot be converted.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
value RichEditorRange Yes Source range.

Return value

Type Description
StyledString Styled string after conversion.

Error codes

For details about the error codes, see Universal Error Codes.

ID Error Message
401 The parameter check failed.

RichEditorStyledStringController12+

Represents the controller of the RichEditor component built with the styled string. Inherits from RichEditorBaseController.

Objects to Import

controller: RichEditorStyledStringController = new RichEditorStyledStringController();

getSelection12+

getSelection(): RichEditorRange

Obtains the current selection range of the RichEditor component.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
RichEditorRange Selection range.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

setStyledString12+

setStyledString(styledString: StyledString): void

Sets the styled string displayed in the RichEditor component.

NOTE

  • When this API is called, the StyledString of the RichEditor component is fully replaced and re-rendered.
  • When the content exceeds the component area, the component automatically scrolls up until the end of the content is visible.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
styledString StyledString Yes Styled string.
NOTE
The child class MutableStyledString of StyledString can also serve as the argument.

getStyledString12+

getStyledString(): MutableStyledString

Obtains the styled string displayed in the RichEditor component.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Return value

Type Description
MutableStyledString Styled string displayed in the rich text component.
If no component is bound to the controller or the component bound to the controller is released, undefined is returned.

onContentChanged12+

onContentChanged(listener: StyledStringChangedListener): void

Registers a callback for the text content change. This callback is triggered only when the text content is changed by backend programs, and is not triggered when setStyledString is called.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
listener StyledStringChangedListener Yes Callback listener for text content changes.

RichEditorSelection

Defines information about the selected content.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
selection [number, number] No No Range of the selection.
spans Array<RichEditorTextSpanResult | RichEditorImageSpanResult> No No Span information.

RichEditorRange

Defines the range of the RichEditor.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
start number No Yes Start position of the span whose style needs to be updated. If this parameter is left empty or set to a negative value, the value 0 will be used.
end number No Yes End position of the span whose style needs to be updated. If this parameter is left empty or set to a value beyond the range, it indicates infinity.

RichEditorSpanStyleOptions

Defines the text span style options.

Inherits RichEditorRange.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

RichEditorUpdateTextSpanStyleOptions

Defines the text span style options.

Inherits RichEditorSpanStyleOptions.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
textStyle RichEditorTextStyle No No Text style.
Atomic service API: This API can be used in atomic services since API version 11.
urlStyle19+ RichEditorUrlStyle No Yes URL information.
Default value: undefined
Atomic service API: This API can be used in atomic services since API version 19.

RichEditorUpdateImageSpanStyleOptions

Defines the image span style options.

Inherits RichEditorSpanStyleOptions.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
imageStyle RichEditorImageSpanStyle No No Image style.

RichEditorUpdateSymbolSpanStyleOptions11+

Defines the symbol span style options.

Inherits RichEditorSpanStyleOptions.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
symbolStyle RichEditorSymbolSpanStyle No No Style of the symbol span.

RichEditorParagraphStyleOptions11+

Defines the paragraph style options.

Inherits RichEditorRange.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
style RichEditorParagraphStyle No No Paragraph style.

NOTE

Applicable scope of the API: spans involved in the specified range.

RichEditorParagraphStyle11+

Defines the paragraph style.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
textAlign TextAlign No Yes Horizontal alignment of the text paragraph.
Default value: TextAlign.START
Atomic service API: This API can be used in atomic services since API version 12.
leadingMargin Dimension | LeadingMarginPlaceholder No Yes Indent of the paragraph. It has no effect if the paragraph starts with an image or builder span. If of the Dimension type, this parameter cannot be set in percentage. Default value: {"size":["0.00px","0.00px"]}
Atomic service API: This API can be used in atomic services since API version 12.
wordBreak12+ WordBreak No Yes Sets the word break rule.
Default value: WordBreak.BREAK_WORD
Atomic service API: This API can be used in atomic services since API version 12.
lineBreakStrategy12+ LineBreakStrategy No Yes Sets the line break rule.
Default value: LineBreakStrategy.GREEDY
This parameter takes effect when wordBreak is not set to breakAll. Hyphens are not supported.
Atomic service API: This API can be used in atomic services since API version 12.
paragraphSpacing19+ number No Yes Spacing between paragraphs.
Unit: fp
Default value: 0.
Atomic service API: This API can be used in atomic services since API version 19.
textVerticalAlign20+ TextVerticalAlign No Yes Vertical alignment mode of text paragraphs.
Default value: TextVerticalAlign.BASELINE.
Atomic service API: This API can be used in atomic services since API version 20.
textDirection23+ TextDirection No Yes Text direction.
Default value: TextDirection.DEFAULT
Atomic service API: This API can be used in atomic services since API version 23.

LeadingMarginPlaceholder11+

Describes the leading margin placeholder, which dictates the distance between the left edges of the paragraph and the component.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
pixelMap PixelMap No No Image content.
Atomic service API: This API can be used in atomic services since API version 12.
size [Dimension, Dimension] No No Image size. This parameter cannot be set in percentage.
Atomic service API: This API can be used in atomic services since API version 12.

RichEditorParagraphResult11+

Describes the returned paragraph information.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
style RichEditorParagraphStyle No No Paragraph style.
range [number, number] No No Start and end positions of the paragraph.

RichEditorTextSpanOptions

Defines the options for adding a text span.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
offset number No Yes Position of the text span to be added. If this parameter is omitted, the span is added to the end of all content.
If the value specified is less than 0, the span is placed at the beginning of all content. If the value is greater than the length of all content, the span is placed at the end of all content.
Atomic service API: This API can be used in atomic services since API version 11.
style RichEditorTextStyle No Yes Style of the text span to be added. If this parameter is omitted, the default text style is used.
Atomic service API: This API can be used in atomic services since API version 11.
paragraphStyle11+ RichEditorParagraphStyle No Yes Paragraph style.
Atomic service API: This API can be used in atomic services since API version 12.
gesture11+ RichEditorGesture No Yes Gesture event that triggers a callback. If this parameter is omitted, only the default system behavior is supported.
Atomic service API: This API can be used in atomic services since API version 12.
urlStyle19+ RichEditorUrlStyle No Yes URL information.
Default value: undefined
Atomic service API: This API can be used in atomic services since API version 19.

RichEditorTextStyle

Provides text style information.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
fontColor ResourceColor No Yes Font color.
Default value: $r('sys.color.font_primary')
Atomic service API: This API can be used in atomic services since API version 11.
fontSize Length | number No Yes Font size. If Length is of the number type, the unit fp is used. The default value is 16. The value cannot be a percentage. If the font size is set to 0, the default font size is used.
Atomic service API: This API can be used in atomic services since API version 11.
fontStyle FontStyle No Yes Font style.
Default value: FontStyle.Normal
Atomic service API: This API can be used in atomic services since API version 11.
fontWeight number | FontWeight | string No Yes Font weight.
For the number type, the value ranges from 100 to 900, at an interval of 100. A larger value indicates a heavier font weight. The default value is 400.
For the string type, only strings of the number type are supported, for example, "400", "bold", "bolder", "lighter", "regular", and "medium", which correspond to the enumerated values in FontWeight.
Default value: FontWeight.Normal
Atomic service API: This API can be used in atomic services since API version 11.
fontFamily ResourceStr No Yes Sets the font family. The HarmonyOS Sans font and register custom fonts are supported.
Default font: 'HarmonyOS Sans'
Atomic service API: This API can be used in atomic services since API version 11.
decoration DecorationStyleInterface No Yes Style, color, and thickness of text decoration.
Default value of type: TextDecorationType.None
Default value of color: same as the font color
Default value of style: TextDecorationStyle.SOLID
Default value of thicknessScale: 1.0
Atomic service API: This API can be used in atomic services since API version 11.
textShadow11+ ShadowOptions | Array<ShadowOptions> No Yes Sets the text shadow. It supports input parameters in an array to implement multiple text shadows.
NOTE
Only the shadow blur radius, color, and offset can be set. The coloring strategy is not supported.
Atomic service API: This API can be used in atomic services since API version 12.
lineHeight12+ number | string | Resource No Yes Text line height. If the value is less than or equal to 0, the line height is not limited and the font size is adaptive. When the value is of the number type, the unit is fp and percentage strings are not supported.
Atomic service API: This API can be used in atomic services since API version 12.
letterSpacing12+ number | string No Yes Letter spacing. If the value is negative, text compression is applied. An overly small negative value may compress the text to 0 and cause the content to disappear. If the value is of the number type, the unit fp is used and percentage strings are not supported.
Atomic service API: This API can be used in atomic services since API version 12.
fontFeature12+ string No Yes Sets the font feature, for example, monospaced digits. If this parameter is not specified, proportional digits are used by default. Invalid characters are disregarded, and the default is preserved.
Format: normal | <feature-tag-value>
Format of <feature-tag-value>: <string> [ <integer> | on | off ]
There can be multiple <feature-tag-value> values, which are separated by commas (,).
For example, the input format for monospaced clock fonts is "ss01" on.
For details about the supported font features, see Font Feature List.
Font features are advanced typographic features, such as ligatures and monospace, for OpenType fonts. They are typically used in custom fonts and require the support of the font itself.
For more information about the font features, see Low-level font feature settings control: the font-feature-settings property and The Complete CSS Demo for OpenType Features.
Atomic service API: This API can be used in atomic services since API version 12.
halfLeading18+ boolean No Yes Whether half leading is enabled.
true: Half leading is enabled. false: Half leading is not enabled.
Default value: false
Atomic service API: This API can be used in atomic services since API version 18.
textBackgroundStyle18+ TextBackgroundStyle No Yes Text background style.
Default value:
{
color: Color.Transparent,
radius: 0
}
Atomic service API: This API can be used in atomic services since API version 18.
strokeWidth23+ LengthMetrics | number No Yes Text stroke width. If the unit of LengthMetrics is PERCENT, the current setting does not take effect and is treated as 0.
A negative value results in solid text. A positive value results in outlined text. A value of 0 applies no stroke effect.
Default value: 0vp
Unit: follows LengthMetrics for the LengthMetrics type; vp for the number type.
Value range: (-∞, +∞)
Atomic service API: This API can be used in atomic services since API version 23.
Model restriction: This API can be used only in the stage model.
strokeColor23+ ResourceColor No Yes Text stroke color.
Default value: follows the font color.
When the value is invalid, it follows the font color.
Atomic service API: This API can be used in atomic services since API version 23.

PlaceholderStyle12+

Sets the style of the placeholder text.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
font Font No Yes Style of the placeholder text.
The default value follows the theme.
fontColor ResourceColor No Yes Sets the placeholder text color.
The default value follows the theme.

RichEditorImageSpanOptions

Sets the offset and style of an image span.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
offset number No Yes Position of the image span to be added. If this parameter is omitted, the span is added to the end of all content.
If the value specified is less than 0, the span is placed at the beginning of all content. If the value is greater than the length of all content, the span is placed at the end of all content.
Atomic service API: This API can be used in atomic services since API version 11.
imageStyle RichEditorImageSpanStyle No Yes Image style. If this parameter is omitted, the default image style is used.
Atomic service API: This API can be used in atomic services since API version 11.
gesture11+ RichEditorGesture No Yes Gesture event that triggers a callback. If this parameter is omitted, only the default system behavior is supported.
Atomic service API: This API can be used in atomic services since API version 12.
onHover14+ OnHoverCallback No Yes Callback triggered on mouse hover. If this parameter is omitted, no corresponding action is taken.
Atomic service API: This API can be used in atomic services since API version 14.

RichEditorImageSpanStyle

Image style.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
size [Dimension, Dimension] No Yes Width and height of the image. Default value: subject to the value of objectFit. If the value of objectFit is Cover, the image height is the component height minus the top and bottom paddings, and the image width is the component width minus the left and right paddings. Values using percentage notation are not supported.
Atomic service API: This API can be used in atomic services since API version 11.
verticalAlign ImageSpanAlignment No Yes Vertical alignment mode of the image.
Default value: ImageSpanAlignment.BOTTOM
Atomic service API: This API can be used in atomic services since API version 11.
objectFit ImageFit No Yes Scale mode of the image.
Default value: ImageFit.Cover
Atomic service API: This API can be used in atomic services since API version 11.
layoutStyle11+ RichEditorLayoutStyle No Yes Image layout style. Default value: {"borderRadius":"","margin":""}

Atomic service API: This API can be used in atomic services since API version 12.

RichEditorSymbolSpanOptions11+

Sets the offset and style of the SymbolSpan component.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
offset number No Yes Position of the symbol span to be added. If this parameter is omitted, the span is added to the end of all content.
If the value is less than 0, the span is added to the beginning of all content. If the value is greater than the length of all content, the span is added to the end of all content.
style RichEditorSymbolSpanStyle No Yes Style of the symbol span. If this parameter is omitted, the default style is used.

RichEditorSymbolSpanStyle11+

Sets the symbol span style.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
fontColor Array<ResourceColor> No Yes Color of the symbol span.
Default value: depending on the rendering strategy
fontSize number | string | Resource No Yes Size of the symbol span. The default unit is fp.
The default value follows the theme.
fontWeight number | FontWeight | string No Yes Font weight of the symbol span.
For the number type, the value ranges from 100 to 900, at an interval of 100. A larger value indicates a heavier font weight. The default value is 400.
For the string type, only strings of the number type are supported, for example, "400", "bold", "bolder", "lighter", "regular", and "medium", which correspond to the enumerated values in FontWeight.
Default value: FontWeight.Normal
renderingStrategy SymbolRenderingStrategy No Yes Rendering strategy of the symbol span.
Default value: SymbolRenderingStrategy.SINGLE
effectStrategy SymbolEffectStrategy No Yes Effect strategy of the symbol span.
Default value: SymbolEffectStrategy.NONE

RichEditorBuilderSpanOptions11+

Sets the offset and style of the builder.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
offset number No Yes Position of the builder span to be added. If this parameter is omitted or set to an invalid value, the span is added to the end of all content.
Atomic service API: This API can be used in atomic services since API version 12.
accessibilitySpanOptions23+ AccessibilitySpanOptions No Yes Accessibility settings. By default, the default value of AccessibilitySpanOptions is used.
Atomic service API: This API can be used in atomic services since API version 23.
Model restriction: This API can be used only in the stage model.

RichEditorSpan12+

type RichEditorSpan = RichEditorImageSpanResult | RichEditorTextSpanResult

Provides the span information of the RichEditor component.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Type Description
RichEditorImageSpanResult Returned image information.
RichEditorTextSpanResult Describes the returned text information.

SelectionMenuOptions

Sets menu options.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
onAppear MenuOnAppearCallback No Yes Callback invoked when the custom context menu on selection appears.
Atomic service API: This API can be used in atomic services since API version 11.
onDisappear Callback<void> No Yes Callback invoked when the custom context menu on selection disappears.
Atomic service API: This API can be used in atomic services since API version 11.
menuType13+ MenuType No Yes Type of the custom context menu on selection.
Atomic service API: This API can be used in atomic services since API version 13.
Default value: MenuType.SELECTION_MENU
onMenuShow15+ MenuCallback No Yes Callback invoked when the custom context menu on selection is shown.
Atomic service API: This API can be used in atomic services since API version 15.
onMenuHide15+ MenuCallback No Yes Callback invoked when the custom context menu on selection is hidden.
Atomic service API: This API can be used in atomic services since API version 15.
previewMenuOptions18+ PreviewMenuOptions No Yes Options of the preview menu. This parameter is valid only in RichEditor.
Atomic service API: This API can be used in atomic services since API version 18.

PreviewMenuOptions18+

Defines the options of the preview menu.

Atomic service API: This API can be used in atomic services since API version 18.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
hapticFeedbackMode HapticFeedbackMode No Yes Vibration effect when the menu is displayed. This parameter takes effect when ImageSpan or BuilderSpan is bound to the preview menu.
Default value: HapticFeedbackMode.DISABLED (no vibration when the menu is displayed)
Note: The settings take effect only when the app has the ohos.permission.VIBRATE permission and the user has enabled haptic feedback.

PasteEvent11+

Defines a user paste event.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
preventDefault Callback<void> No Yes Prevents the default paste event.

CutEvent12+

Defines a custom cut event.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
preventDefault Callback<void> No Yes Prevents the default cut event.

CopyEvent12+

User copy event.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
preventDefault Callback<void> No Yes Prevents the default copy operation of the component.

RichEditorGesture11+

Defines a user gesture event.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
onClick Callback<ClickEvent> No Yes Triggered when a click event occurs.
It is executed on completion of a single click.
For a double-click scenario, the first click triggers this callback.
onLongPress Callback<GestureEvent> No Yes Triggered when a long press event occurs.
It is executed on completion of a long press.

KeyboardOptions12+

Whether to support keyboard avoidance.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
supportAvoidance boolean No Yes Whether to support keyboard avoidance. true to support, false otherwise. Default value: false.

SubmitCallback12+

type SubmitCallback = (enterKey: EnterKeyType, event: SubmitEvent) => void

Represents the callback invoked when the Enter key on the soft keyboard is pressed.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
enterKey EnterKeyType Yes Type of the Enter key. For details, see EnterKeyType.
event SubmitEvent Yes Submit event, which provides a method to keep the component in editing state. When EnterKeyType is set to NEW_LINE, the editing state is retained by default.

type MenuOnAppearCallback = (start: number, end: number) => void

Represents the callback invoked when the custom context menu on selection appears.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
start number Yes Start position of the selected content.
end number Yes End position of the selected content.

type MenuCallback = (start: number, end: number) => void

Represents the callback invoked when the custom context menu on selection is shown or hidden.

Atomic service API: This API can be used in atomic services since API version 15.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
start number Yes Start position of the selected content.
end number Yes End position of the selected content.

PasteEventCallback12+

type PasteEventCallback = (event?: PasteEvent) => void

Represents the callback invoked when a paste operation is about to complete.

Atomic service API: This API can be used in atomic services since API version 12.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
event PasteEvent No User paste event.

OnHoverCallback14+

type OnHoverCallback = (status: boolean, event: HoverEvent) => void

Defines the callback triggered on hover.

Atomic service API: This API can be used in atomic services since API version 14.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Name Type Mandatory Description
status boolean Yes Whether the mouse pointer is hovering over the component. The value true means that the mouse pointer enters the component, and false means that the mouse pointer leaves the component.
event HoverEvent Yes Hover event.

RichEditorTextSpan

Defines text span information.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
spanPosition RichEditorSpanPosition No No Span position.
value string No No Text span content.
textStyle RichEditorTextStyle No Yes Text span style.

RichEditorImageSpan

Image span information.

Atomic service API: This API can be used in atomic services since API version 11.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
spanPosition RichEditorSpanPosition No No Span position.
value PixelMap | ResourceStr No No Image content.
imageStyle RichEditorImageSpanStyle No Yes Image style.

RichEditorUrlStyle19+

URL information.

Atomic service API: This API can be used in atomic services since API version 19.

System capability: SystemCapability.ArkUI.ArkUI.Full

Name Type Read-Only Optional Description
url ResourceStr No Yes URL.
Default value: undefined

Example

Example 1: Updating the Text Style

This example demonstrates how to update the text style using the updateSpanStyle API. After modifying the style, you can use getSpans to obtain the updated style information of the text.

// xxx.ets
@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  private start: number = -1;
  private end: number = -1;
  @State message: string = "[-1, -1]";
  @State content: string = "";

  build() {
    Column() {
      Column() {
        Text("selection range:").width("100%")
        Text() {
          Span(this.message)
        }.width("100%")
        Text("selection content:").width("100%")
        Text() {
          Span(this.content)
        }.width("100%")
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("20%")

      Row() {
        Button("Update Style: Bold").onClick(() => {
          this.controller.updateSpanStyle({
            start: this.start,
            end: this.end,
            textStyle:
            {
              fontWeight: FontWeight.Bolder
            }
          })
        })
        Button("Obtain Selection").onClick(() => {
          this.content = "";
          this.controller.getSpans({
            start: this.start,
            end: this.end
          }).forEach(item => {
            if(typeof(item as RichEditorImageSpanResult)['imageStyle'] != 'undefined'){
              this.content += (item as RichEditorImageSpanResult).valueResourceStr;
              this.content += "\n";
            } else {
              if(typeof(item as RichEditorTextSpanResult)['symbolSpanStyle'] != 'undefined') {
                this.content += (item as RichEditorTextSpanResult).symbolSpanStyle?.fontSize;
                this.content += "\n";
              }else {
                this.content += (item as RichEditorTextSpanResult).value;
                this.content += "\n";
              }
            }
          })
        })
        Button("Delete Selection").onClick(() => {
          this.controller.deleteSpans({
            start: this.start,
            end: this.end
          })
          this.start = -1;
          this.end = -1;
          this.message = "[" + this.start + ", " + this.end + "]";
        })
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("10%")

      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan("012345",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30
                }
              })
            this.controller.addSymbolSpan($r("sys.symbol.ohos_trash"),
              {
                style:
                {
                  fontSize: 30
                }
              })
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["57px", "57px"]
                }
              })
            this.controller.addTextSpan("56789",
              {
                style:
                {
                  fontColor: Color.Black,
                  fontSize: 30
                }
              })
          })
          .onSelect((value: RichEditorSelection) => {
            this.start = value.selection[0];
            this.end = value.selection[1];
            this.message = "[" + this.start + ", " + this.end + "]";
          })
          .aboutToIMEInput((value: RichEditorInsertValue) => {
            console.info("---------------------- aboutToIMEInput ----------------------");
            console.info("insertOffset:" + value.insertOffset);
            console.info("insertValue:" + value.insertValue);
            return true;
          })
          .onIMEInputComplete((value: RichEditorTextSpanResult) => {
            console.info("---------------------- onIMEInputComplete ---------------------");
            console.info("spanIndex:" + value.spanPosition.spanIndex);
            console.info("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]");
            console.info("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]");
            console.info("value:" + value.value);
          })
          .aboutToDelete((value: RichEditorDeleteValue) => {
            console.info("---------------------- aboutToDelete --------------------------");
            console.info("offset:" + value.offset);
            console.info("direction:" + value.direction);
            console.info("length:" + value.length);
            value.richEditorDeleteSpans.forEach(item => {
              console.info("---------------------- item --------------------------");
              console.info("spanIndex:" + item.spanPosition.spanIndex);
              console.info("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]");
              console.info("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]");
              if (typeof(item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
                console.info("image:" + (item as RichEditorImageSpanResult).valueResourceStr);
              } else {
                console.info("text:" + (item as RichEditorTextSpanResult).value);
              }
            })
            return true;
          })
          .onDeleteComplete(() => {
            console.info("---------------------- onDeleteComplete ------------------------");
          })
          .placeholder("input...", {
            fontColor: Color.Gray,
            font: {
              size: 16,
              weight: FontWeight.Normal,
              family: "HarmonyOS Sans",
              style: FontStyle.Normal
            }
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height("30%")
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("70%")
    }
  }
}

richeditor

Example 2: Binding a Custom Keyboard

This example shows how to bind a custom keyboard to the component using customKeyboard.

// xxx.ets
@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();

  // Create a custom keyboard component.
  @Builder
  CustomKeyboardBuilder() {
    Column() {
      Grid() {
        ForEach(['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'], (item: string) => {
          GridItem() {
            Button(item).width(110).onClick(() => {
              this.controller.addTextSpan(item + '', {
                offset: this.controller.getCaretOffset(),
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30
                }
              })
            })
          }
        })
      }.maxCount(3).columnsGap(10).rowsGap(10).padding(5)
    }.backgroundColor(Color.Gray)
  }

  build() {
    Column() {
      RichEditor({ controller: this.controller })// Bind the custom keyboard.
        .customKeyboard(this.CustomKeyboardBuilder())
        .border({ width: 1 })
        .borderWidth(1)
        .borderColor(Color.Red)
        .margin(10)
        .height(200)
        .width("100%")
    }
  }
}

customKeyboard

Example 3: Binding a Custom Menu

This example illustrates how to bind a custom menu to the component using bindSelectionMenu.

The paste menu item in this example involves reading pasteboard data. Therefore, you need to request permissions to access the pasteboard as required.

// xxx.ets
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';

export interface SelectionMenuTheme {
  imageSize: number;
  buttonSize: number;
  menuSpacing: number;
  editorOptionMargin: number;
  expandedOptionPadding: number;
  defaultMenuWidth: number;
  imageFillColor: Resource;
  backGroundColor: Resource;
  iconBorderRadius: Resource;
  containerBorderRadius: Resource;
  cutIcon: Resource;
  copyIcon: Resource;
  pasteIcon: Resource;
  selectAllIcon: Resource;
  shareIcon: Resource;
  translateIcon: Resource;
  searchIcon: Resource;
  arrowDownIcon: Resource;
  iconPanelShadowStyle: ShadowStyle;
  iconFocusBorderColor: Resource;
}

export const defaultTheme: SelectionMenuTheme = {
  imageSize: 24,
  buttonSize: 48,
  menuSpacing: 8,
  editorOptionMargin: 1,
  expandedOptionPadding: 3,
  defaultMenuWidth: 256,
  imageFillColor: $r('sys.color.ohos_id_color_primary'),
  backGroundColor: $r('sys.color.ohos_id_color_dialog_bg'),
  iconBorderRadius: $r('sys.float.ohos_id_corner_radius_default_m'),
  containerBorderRadius: $r('sys.float.ohos_id_corner_radius_card'),
  cutIcon: $r("sys.media.ohos_ic_public_cut"),
  copyIcon: $r("sys.media.ohos_ic_public_copy"),
  pasteIcon: $r("sys.media.ohos_ic_public_paste"),
  selectAllIcon: $r("sys.media.ohos_ic_public_select_all"),
  shareIcon: $r("sys.media.ohos_ic_public_share"),
  translateIcon: $r("sys.media.ohos_ic_public_translate_c2e"),
  searchIcon: $r("sys.media.ohos_ic_public_search_filled"),
  arrowDownIcon: $r("sys.media.ohos_ic_public_arrow_down"),
  iconPanelShadowStyle: ShadowStyle.OUTER_DEFAULT_MD,
  iconFocusBorderColor: $r('sys.color.ohos_id_color_focused_outline')
}

@Entry
@Component
struct SelectionMenu {
  @State message: string = 'Hello World';
  @State textSize: number = 40;
  @State sliderShow: boolean = false;
  @State start: number = -1;
  @State end: number = -1;
  @State colorTransparent: Color = Color.Transparent;
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  // Replace $r('app.media.startIcon') with the image resource file you use.
  private iconArr: Array<Resource> =
    [$r('app.media.startIcon'), $r('app.media.startIcon'), $r('app.media.startIcon'),
    $r('app.media.startIcon'), $r('app.media.startIcon')];
  @State iconBgColor: ResourceColor[] = new Array(this.iconArr.length).fill(this.colorTransparent);
  @State pasteEnable: boolean = false;
  @State visibilityValue: Visibility = Visibility.Visible;
  @State textStyle: RichEditorTextStyle = {};
  private fontWeightTable: string[] = ["100", "200", "300", "400", "500", "600", "700", "800", "900", "bold", "normal", "bolder", "lighter", "medium", "regular"];
  private theme: SelectionMenuTheme = defaultTheme;

  aboutToAppear() {
    if (this.controller) {
      let richEditorSelection = this.controller.getSelection();
      if (richEditorSelection) {
        let start = richEditorSelection.selection[0];
        let end = richEditorSelection.selection[1];
        if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) {
          this.visibilityValue = Visibility.None;
        } else {
          this.visibilityValue = Visibility.Visible;
        }
      }
    }
    let sysBoard = pasteboard.getSystemPasteboard()
    try {
      if (sysBoard && sysBoard.hasDataSync()) {
        this.pasteEnable = true
      } else {
        this.pasteEnable = false
      }
    } catch (err) {
      console.error('Failed to check the PasteData. Cause:' + err.message)
    }
  }

  build() {
    Column() {
      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
          })
          .onSelect((value: RichEditorSelection) => {
            if (value.selection[0] == -1 && value.selection[1] == -1) {
              return;
            }
            this.start = value.selection[0];
            this.end = value.selection[1];
          })
          .bindSelectionMenu(RichEditorSpanType.TEXT, this.panel, ResponseType.LongPress, { onDisappear: () => {
            this.sliderShow = false;
          }})
          .bindSelectionMenu(RichEditorSpanType.TEXT, this.panel, ResponseType.RightClick, { onDisappear: () => {
            this.sliderShow = false;
          }})
          .bindSelectionMenu(RichEditorSpanType.IMAGE, this.panel, ResponseType.LongPress, { 
            menuType : MenuType.PREVIEW_MENU,
            previewMenuOptions : {
              hapticFeedbackMode : HapticFeedbackMode.ENABLED
            }
          })
          .borderWidth(1)
          .borderColor(Color.Red)
          .width(200)
          .height(200)
      }.width('100%').backgroundColor(Color.White)
    }.height('100%')
  }

  PushDataToPasteboard(richEditorSelection: RichEditorSelection) {
    let sysBoard = pasteboard.getSystemPasteboard();
    let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '');
    if (richEditorSelection.spans && richEditorSelection.spans.length > 0) {
      let count = richEditorSelection.spans.length;
      for (let i = count - 1; i >= 0; i--) {
        let item = richEditorSelection.spans[i]
        if ((item as RichEditorTextSpanResult)?.textStyle) {
          let span = item as RichEditorTextSpanResult;
          let style = span.textStyle;
          let data = pasteboard.createRecord(pasteboard.MIMETYPE_TEXT_PLAIN, span.value.substring(span.offsetInSpan[0], span.offsetInSpan[1]));
          let prop = pasteData.getProperty();
          let temp: Record<string, Object> = {
            'color': style.fontColor,
            'size': style.fontSize,
            'style': style.fontStyle,
            'weight': this.fontWeightTable[style.fontWeight],
            'fontFamily': style.fontFamily,
            'decorationType': style.decoration.type,
            'decorationColor': style.decoration.color
          };
          prop.additions[i] = temp;
          pasteData.addRecord(data)
          pasteData.setProperty(prop)
        }
      }
    }
    sysBoard.clearData()
    sysBoard.setData(pasteData).then(() => {
      console.info('SelectionMenu copy option, Succeeded in setting PasteData.');
      this.pasteEnable = true;
    }).catch((err: BusinessError) => {
      console.error('SelectionMenu copy option, Failed to set PasteData. Cause:' + err.message);
    })
  }

  PopDataFromPasteboard(richEditorSelection: RichEditorSelection) {
    let start = richEditorSelection.selection[0];
    let end = richEditorSelection.selection[1];
    if (start == end && this.controller) {
      start = this.controller.getCaretOffset();
      end = this.controller.getCaretOffset();
    }
    let moveOffset = 0;
    let sysBoard = pasteboard.getSystemPasteboard();
    sysBoard.getData((err, data) => {
      if (err) {
        return;
      }
      let count = data.getRecordCount();
      for (let i = 0; i < count; i++) {
        const element = data.getRecord(i);
        let tex: RichEditorTextStyle = {
          fontSize: 16,
          fontColor: Color.Black,
          fontWeight: FontWeight.Normal,
          fontFamily: "HarmonyOS Sans",
          fontStyle: FontStyle.Normal,
          decoration: { type: TextDecorationType.None, color: "#FF000000", style: TextDecorationStyle.SOLID }
        }
        if (data.getProperty() && data.getProperty().additions[i]) {
          const tmp = data.getProperty().additions[i] as Record<string, Object | undefined>;
          if (tmp.color) {
            tex.fontColor = tmp.color as ResourceColor;
          }
          if (tmp.size) {
            tex.fontSize = tmp.size as Length | number;
          }
          if (tmp.style) {
            tex.fontStyle = tmp.style as FontStyle;
          }
          if (tmp.weight) {
            tex.fontWeight = tmp.weight as number | FontWeight | string;
          }
          if (tmp.fontFamily) {
            tex.fontFamily = tmp.fontFamily as ResourceStr;
          }
          if (tmp.decorationType && tex.decoration) {
            tex.decoration.type = tmp.decorationType as TextDecorationType;
          }
          if (tmp.decorationColor && tex.decoration) {
            tex.decoration.color = tmp.decorationColor as ResourceColor;
          }
          if (tex.decoration) {
            tex.decoration = { type: tex.decoration.type, color: tex.decoration.color };
          }
        }
        if (element && element.plainText && element.mimeType === pasteboard.MIMETYPE_TEXT_PLAIN && this.controller) {
          this.controller.addTextSpan(element.plainText,
            {
              style: tex,
              offset: start + moveOffset
            }
          )
          moveOffset += element.plainText.length;
        }
      }
      if (this.controller) {
        this.controller.setCaretOffset(start + moveOffset)
        this.controller.closeSelectionMenu()
      }
      if (start != end && this.controller) {
        this.controller.deleteSpans({ start: start + moveOffset, end: end + moveOffset })
      }
    })
  }

  @Builder
  panel() {
    Column() {
      this.iconPanel()
      if (!this.sliderShow) {
        this.SystemMenu()
      } else {
        this.sliderPanel()
      }
    }.width(256)
  }

  @Builder iconPanel() {
    Column() {
      Row({ space: 2 }) {
        ForEach(this.iconArr, (item:Resource, index ?: number) => {
          Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
            Image(item).fillColor(this.theme.imageFillColor).width(24).height(24).focusable(true).draggable(false)
          }
          .borderRadius(this.theme.iconBorderRadius)
          .width(this.theme.buttonSize)
          .height(this.theme.buttonSize)
          .onClick(() => {
            if (index as number == 0) {
              this.sliderShow = false;
              if (this.controller) {
                let selection = this.controller.getSelection();
                let spans = selection.spans;
                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
                    let span = item as RichEditorTextSpanResult;
                    this.textStyle = span.textStyle;
                    let start = span.offsetInSpan[0];
                    let end = span.offsetInSpan[1];
                    let offset = span.spanPosition.spanRange[0];
                    if (this.textStyle.fontWeight != 11) {
                      this.textStyle.fontWeight = FontWeight.Bolder;
                    } else {
                      this.textStyle.fontWeight = FontWeight.Normal;
                    }
                    this.controller.updateSpanStyle({
                      start: offset + start,
                      end: offset + end,
                      textStyle: this.textStyle
                    })
                  }
                })
              }
            } else if (index as number == 1) {
              this.sliderShow = false;
              if (this.controller) {
                let selection = this.controller.getSelection();
                let spans = selection.spans;
                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
                    let span = item as RichEditorTextSpanResult;
                    this.textStyle = span.textStyle;
                    let start = span.offsetInSpan[0];
                    let end = span.offsetInSpan[1];
                    let offset = span.spanPosition.spanRange[0];
                    if (this.textStyle.fontStyle == FontStyle.Italic) {
                      this.textStyle.fontStyle = FontStyle.Normal;
                    } else {
                      this.textStyle.fontStyle = FontStyle.Italic;
                    }
                    this.controller.updateSpanStyle({
                      start: offset + start,
                      end: offset + end,
                      textStyle: this.textStyle
                    })
                  }
                })
              }
            } else if (index as number == 2) {
              this.sliderShow = false;
              if (this.controller) {
                let selection = this.controller.getSelection();
                let spans = selection.spans;
                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
                    let span = item as RichEditorTextSpanResult;
                    this.textStyle = span.textStyle;
                    let start = span.offsetInSpan[0];
                    let end = span.offsetInSpan[1];
                    let offset = span.spanPosition.spanRange[0];
                    if (this.textStyle.decoration) {
                      if (this.textStyle.decoration.type == TextDecorationType.Underline) {
                        this.textStyle.decoration.type = TextDecorationType.None;
                      } else {
                        this.textStyle.decoration.type = TextDecorationType.Underline;
                      }
                    } else {
                      this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black, style: TextDecorationStyle.SOLID };
                    }
                    this.controller.updateSpanStyle({
                      start: offset + start,
                      end: offset + end,
                      textStyle: this.textStyle 
                    })
                  }
                })
              }
            } else if (index as number == 3) {
              this.sliderShow = !this.sliderShow;
            } else if (index as number == 4) {
              this.sliderShow = false;
              if (this.controller) {
                let selection = this.controller.getSelection();
                let spans = selection.spans;
                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
                    let span = item as RichEditorTextSpanResult;
                    this.textStyle = span.textStyle;
                    let start = span.offsetInSpan[0];
                    let end = span.offsetInSpan[1];
                    let offset = span.spanPosition.spanRange[0];
                    if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
                      this.textStyle.fontColor = Color.Black;
                    } else {
                      this.textStyle.fontColor = Color.Orange;
                    }
                    this.controller.updateSpanStyle({
                      start: offset + start,
                      end: offset + end,
                      textStyle: this.textStyle
                    })
                  }
                })
              }
            }
          })
          .onTouch((event?: TouchEvent | undefined) => {
            if(event != undefined){
              if (event.type === TouchType.Down) {
                this.iconBgColor[index as number] = $r('sys.color.ohos_id_color_click_effect');
              }
              if (event.type === TouchType.Up) {
                this.iconBgColor[index as number] = this.colorTransparent;
              }
            }
          })
          .onHover((isHover?: boolean, event?: HoverEvent) => {
            this.iconBgColor.forEach((icon:ResourceColor, index1) => {
              this.iconBgColor[index1] = this.colorTransparent;
            })
            if(isHover != undefined) {
              this.iconBgColor[index as number] = $r('sys.color.ohos_id_color_hover');
            }
          })
          .backgroundColor(this.iconBgColor[index as number])
        })
      }
    }
    .clip(true)
    .width(this.theme.defaultMenuWidth)
    .padding(this.theme.expandedOptionPadding)
    .borderRadius(this.theme.containerBorderRadius)
    .margin({ bottom: this.theme.menuSpacing })
    .backgroundColor(this.theme.backGroundColor)
    .shadow(this.theme.iconPanelShadowStyle)
  }

  @Builder
  SystemMenu() {
    Column() {
      Menu() {
        if (this.controller) {
          MenuItemGroup() {
            MenuItem({ startIcon: this.theme.cutIcon, content: "Cut", labelInfo: "Ctrl+X" })
              .onClick(() => {
                if (!this.controller) {
                  return
                }
                let richEditorSelection = this.controller.getSelection();
                this.PushDataToPasteboard(richEditorSelection);
                this.controller.deleteSpans({
                  start: richEditorSelection.selection[0],
                  end: richEditorSelection.selection[1]
                })
              })
            MenuItem({ startIcon: this.theme.copyIcon, content: "Copy", labelInfo: "Ctrl+C" })
              .onClick(() => {
                if (!this.controller) {
                  return;
                }
                let richEditorSelection = this.controller.getSelection();
                this.PushDataToPasteboard(richEditorSelection)
                this.controller.closeSelectionMenu()
              })
            MenuItem({ startIcon: this.theme.pasteIcon, content: "Paste", labelInfo: "Ctrl+V" })
              .enabled(this.pasteEnable)
              .onClick(() => {
                if (!this.controller) {
                  return;
                }
                let richEditorSelection = this.controller.getSelection();
                this.PopDataFromPasteboard(richEditorSelection)
              })
            MenuItem({ startIcon: this.theme.selectAllIcon, content: "Select all", labelInfo: "Ctrl+A" })
              .visibility(this.visibilityValue)
              .onClick(() => {
                if (!this.controller) {
                  return;
                }
                this.controller.setSelection(-1, -1)
                this.visibilityValue = Visibility.None;
              })
            MenuItem({ startIcon: this.theme.shareIcon, content: "Share", labelInfo: "" })
              .enabled(false)
            MenuItem({ startIcon: this.theme.translateIcon, content: "Translate", labelInfo: "" })
              .enabled(false)
            MenuItem({ startIcon: this.theme.searchIcon, content: "Search", labelInfo: "" })
              .enabled(false)
          }
        }
      }
      .onVisibleAreaChange([0.0, 1.0], () => {
        if (!this.controller) {
          return;
        }
        let richEditorSelection = this.controller.getSelection();
        let start = richEditorSelection.selection[0];
        let end = richEditorSelection.selection[1];
        if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) {
          this.visibilityValue = Visibility.None;
        } else {
          this.visibilityValue = Visibility.Visible;
        }
      })
      .radius(this.theme.containerBorderRadius)
      .clip(true)
      .backgroundColor(Color.White)
      .width(this.theme.defaultMenuWidth)
    }
    .width(this.theme.defaultMenuWidth)
  }

  @Builder sliderPanel() {
    Column() {
      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
        Text('A').fontSize(15)
        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
          .width(210)
          .onChange((value: number, mode: SliderChangeMode) => {
            if (this.controller) {
              let selection = this.controller.getSelection();
              if (mode == SliderChangeMode.End) {
                if (this.textSize == undefined) {
                  this.textSize = 0;
                }
                let spans = selection.spans;
                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize);
                  }
                })
              }
              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
                this.start = selection.selection[0];
                this.end = selection.selection[1];
                this.textSize = value;
                this.controller.updateSpanStyle({
                  start: this.start,
                  end: this.end,
                  textStyle: { fontSize: this.textSize }
                })
              }
            }
          })
        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
      }.borderRadius(this.theme.containerBorderRadius)
    }
    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
    .backgroundColor(Color.White)
    .borderRadius(this.theme.containerBorderRadius)
    .padding(15)
    .height(48)
  }
}

NOTE

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 iconArr with the desired icons.

selectionMenu

Example 4: Updating the Image Style

This example demonstrates how to update the image style using the updateSpanStyle API.

// xxx.ets
@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  private start: number = -1;
  private end: number = -1;
  @State message: string = "[-1, -1]";
  @State content: string = "";
  @State paddingVal: number = 5;
  @State borderRad: number = 4;

  build() {
    Column() {
      Column() {
        Text("selection range:").width("100%")
        Text() {
          Span(this.message)
        }.width("100%")
        Text("selection content:").width("100%")
        Text() {
          Span(this.content)
        }.width("100%")
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("20%")

      Row() {
        Button("updateSpanStyle1")
          .fontSize(12)
          .onClick(() => {
            this.controller.updateSpanStyle({
              start: this.start,
              textStyle:
              {
                fontWeight: FontWeight.Bolder,
                fontSize:15
              },
              imageStyle: {
                size: ["80px", "80px"],
                layoutStyle: {
                  borderRadius: undefined,
                  margin: undefined
                }
              }
            })
          })

        Button("updateSpanStyle2")
          .fontSize(12)
          .onClick(() => {
            this.controller.updateSpanStyle({
              start: this.start,
              textStyle:
              {
                fontWeight: FontWeight.Bolder,
                fontSize:15
              },
              imageStyle: {
                size: ["70px", "70px"],
                layoutStyle: {
                  borderRadius: { topLeft: '100px', topRight: '20px', bottomLeft: '100px', bottomRight: '20px' },
                  margin: { left: '30px', top: '20px', right: '20px', bottom: '20px' }
                }
              }
            })
          })

        Button("updateSpanStyle3")
          .fontSize(12)
          .onClick(() => {
            this.controller.updateSpanStyle({
              start: this.start,
              textStyle:
              {
                fontWeight: FontWeight.Bolder,
                fontSize:15
              },
              imageStyle: {
                size: ["60px", "60px"],
                layoutStyle: {
                  borderRadius: '-10px',
                  margin: '-10px'
                }
              }
            })
          })
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("10%")

      Row() {
        Button('addImageSpan1')
          .fontSize(12)
          .onClick(() => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'), {
              imageStyle: {
                size: ["80px", "80px"],
                layoutStyle: {
                  borderRadius: '50px',
                  margin: '40px'
                }
              }
            })
          })

        Button('addImageSpan2')
          .fontSize(12)
          .onClick(() => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'), {
              imageStyle: {
                size: ["100px", "100px"],
                verticalAlign: ImageSpanAlignment.BOTTOM,
                layoutStyle: {
                  borderRadius: undefined,
                  margin: undefined
                }
              }
            })
          })

        Button('addImageSpan3')
          .fontSize(12)
          .onClick(() => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'), {
              imageStyle: {
                size: ["60px", "60px"],
                verticalAlign: ImageSpanAlignment.BOTTOM,
                layoutStyle: {
                  borderRadius: { topLeft: '10px', topRight: '20px', bottomLeft: '30px', bottomRight: '40px' },
                  margin: { left: '10px', top: '20px', right: '30px', bottom: '40px' }
                }
              }
            })
          })
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("10%")

      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan("0123456789",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30
                }
              })

            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["60px", "60px"],
                  verticalAlign: ImageSpanAlignment.BOTTOM,
                  layoutStyle: {
                    borderRadius: { topLeft: '10px', topRight: '20px', bottomLeft: '30px', bottomRight: '40px' },
                    margin: { left: '10px', top: '20px', right: '30px', bottom: '40px' }
                  }
                }
              })

            this.controller.addTextSpan("0123456789",
              {
                style:
                {
                  fontColor: Color.Black,
                  fontSize: 30
                }
              })
          })
          .onSelect((value: RichEditorSelection) => {
            this.start = value.selection[0];
            this.end = value.selection[1];
            this.message = "[" + this.start + ", " + this.end + "]";
          })
          .aboutToIMEInput((value: RichEditorInsertValue) => {
            console.info("---------------------- aboutToIMEInput ----------------------");
            console.info("insertOffset:" + value.insertOffset);
            console.info("insertValue:" + value.insertValue);
            return true;
          })
          .onIMEInputComplete((value: RichEditorTextSpanResult) => {
            console.info("---------------------- onIMEInputComplete ---------------------");
            console.info("spanIndex:" + value.spanPosition.spanIndex);
            console.info("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]");
            console.info("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]");
            console.info("value:" + value.value);
          })
          .aboutToDelete((value: RichEditorDeleteValue) => {
            console.info("---------------------- aboutToDelete --------------------------");
            console.info("offset:" + value.offset);
            console.info("direction:" + value.direction);
            console.info("length:" + value.length);
            value.richEditorDeleteSpans.forEach(item => {
              console.info("---------------------- item --------------------------");
              console.info("spanIndex:" + item.spanPosition.spanIndex);
              console.info("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]");
              console.info("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]");
              if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
                console.info("image:" + (item as RichEditorImageSpanResult).valueResourceStr);
              } else {
                console.info("text:" + (item as RichEditorTextSpanResult).value);
              }
            })
            return true;
          })
          .onDeleteComplete(() => {
            console.info("---------------------- onDeleteComplete ------------------------");
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height('80.00%')
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("70%")
    }
  }
}

ImageSpanStyle

Example 5: Binding a Gesture Event to a Span

This example shows how to bind a gesture callback to a span.

// xxx.ets
@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State textFlag: string = "TextFlag";

  build() {
    Column() {
      Column() {
        Text(this.textFlag)
          .copyOption(CopyOptions.InApp)
          .fontSize(50)
          .height(150)
      }
      Divider()
      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan('Area1\n', {
              style:
              {
                fontColor: Color.Orange,
                fontSize: 50,
              },
              gesture:
              {
                onClick: () => {
                  this.textFlag = "Area1 is onClick.";
                },
                onLongPress: () => {
                  this.textFlag = "Area1 is onLongPress.";
                }
              }
            })

            this.controller.addTextSpan('Area2\n', {
              style:
              {
                fontColor: Color.Blue,
                fontSize: 50
              },
              gesture:
              {
                onClick: () => {
                  this.textFlag = "Area2 is onClick.";
                },
                onLongPress: () => {
                  this.textFlag = "Area2 is onLongPress.";
                }
              }
            })

            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["100px", "100px"],
                  layoutStyle: {
                    margin: 5,
                    borderRadius: 15
                  }
                },
                gesture:
                {
                  onClick: () => {
                    this.textFlag = "ImageSpan is onClick.";
                  },
                  onLongPress: () => {
                    this.textFlag = "ImageSpan is onLongPress.";
                  }
                },
                onHover : (status) => {
                  this.textFlag = "ImageSpan is onHover :" + status;
                }
              })
          })
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("70%")
    }
  }
}

OnClickAndLongPress

Example 6: Updating and Obtaining Paragraph Styles

This example demonstrates how to update paragraph styles using the updateParagraphStyle API and obtain paragraph information within a specified range using the getParagraphs API.

// xxx.ets
@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  private spanParagraphs: RichEditorParagraphResult[] = [];

  build() {
    Column() {
      RichEditor({ controller: this.controller })
        .onReady(() => {
          this.controller.addTextSpan("0123456789\n", {
            style: {
              fontColor: Color.Pink,
              fontSize: "32"
            },
            paragraphStyle: {
              textAlign: TextAlign.Start,
              textVerticalAlign: TextVerticalAlign.BASELINE,
              leadingMargin: 16
            }
          })
          this.controller.addTextSpan("0123456789")
        })
        .width("80%")
        .height("30%")
        .border({ width: 1, radius: 5 })
        .draggable(false)

      Column({ space: 5 }) {
        Button("Align Left").onClick(() => {
          this.controller.updateParagraphStyle({ start: -1, end: -1,
            style: {
              textAlign: TextAlign.Start
            }
          })
        })

        Button("Align Right").onClick(() => {
          this.controller.updateParagraphStyle({ start: -1, end: -1,
            style: {
              textAlign: TextAlign.End
            }
          })
        })

        Button("Align Center").onClick(() => {
          this.controller.updateParagraphStyle({ start: -1, end: -1,
            style: {
              textAlign: TextAlign.Center
            }
          })
        })

        Button("Apply Paragraph Spacing (50)").onClick (() => {
          this.controller.updateParagraphStyle({ start: -1, end: -1,
            style: {
              paragraphSpacing: 50
            }
          })
        })
        Divider()
        Button("getParagraphs").onClick(() => {
          this.spanParagraphs = this.controller.getParagraphs({ start: -1, end: -1 });
          console.info("RichEditor getParagraphs:" + JSON.stringify(this.spanParagraphs));
        })

        Button("UpdateSpanStyle1").onClick(() => {
          this.controller.updateSpanStyle({ start: -1, end: -1,
            textStyle: {
              fontColor: Color.Brown,
              fontSize: 20
            }
          })
        })

        Button("UpdateSpanStyle2").onClick(() => {
          this.controller.updateSpanStyle({ start: -1, end: -1,
            textStyle: {
              fontColor: Color.Green,
              fontSize: 30
            }
          })
        })
      }
    }
  }
}

TextAlignAndGetParagraphInfo

Example 7: Updating the Preset Style and Indent

This example demonstrates how to update the preset text style using the setTypingStyle API and set paragraph indents using the updateParagraphStyle API.

// xxx.ets

const canvasWidth = 1000;
const canvasHeight = 100;
const Indentation = 40;
class LeadingMarginCreator {
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private offscreenCanvas: OffscreenCanvas = new OffscreenCanvas(canvasWidth, canvasHeight);
  private offContext: OffscreenCanvasRenderingContext2D = this.offscreenCanvas.getContext("2d", this.settings);
  public static instance: LeadingMarginCreator = new LeadingMarginCreator();

  // Obtain the font size level, which ranges from 0 to 4.
  public getFontSizeLevel(fontSize: number) {
    const fontScaled: number = Number(fontSize) / 16;

    enum FontSizeScaleThreshold {
      SMALL = 0.9,
      NORMAL = 1.1,
      LEVEL_1_LARGE = 1.2,
      LEVEL_2_LARGE = 1.4,
      LEVEL_3_LARGE = 1.5
    }

    let fontSizeLevel: number = 1;

    if (fontScaled < FontSizeScaleThreshold.SMALL) {
      fontSizeLevel = 0;
    } else if (fontScaled < FontSizeScaleThreshold.NORMAL) {
      fontSizeLevel = 1;
    } else if (fontScaled < FontSizeScaleThreshold.LEVEL_1_LARGE) {
      fontSizeLevel = 2;
    } else if (fontScaled < FontSizeScaleThreshold.LEVEL_2_LARGE) {
      fontSizeLevel = 3;
    } else if (fontScaled < FontSizeScaleThreshold.LEVEL_3_LARGE) {
      fontSizeLevel = 4;
    } else {
      fontSizeLevel = 1;
    }

    return fontSizeLevel;
  }
  // Obtain the font size level, which ranges from 0 to 4.
  public getmarginLevel(Width: number) {
    let marginlevel: number = 1;
    if (Width == 40) {
      marginlevel = 2.0;
    } else if (Width == 80) {
      marginlevel = 1.0;
    } else if (Width == 120) {
      marginlevel = 2/3;
    } else if (Width == 160) {
      marginlevel = 0.5;
    } else if (Width == 200) {
      marginlevel = 0.4;
    }
    return marginlevel;
  }

  public genStrMark(fontSize: number, str: string): PixelMap {
    this.offContext = this.offscreenCanvas.getContext("2d", this.settings);
    this.clearCanvas()
    this.offContext.font = fontSize + 'vp sans-serif';
    this.offContext.fillText(str + '.', 0, fontSize * 0.9)
    return this.offContext.getPixelMap(0, 0, fontSize * (str.length + 1) / 1.75, fontSize)
  }

  public genSquareMark(fontSize: number): PixelMap {
    this.offContext = this.offscreenCanvas.getContext("2d", this.settings);
    this.clearCanvas()
    const coordinate = fontSize * (1 - 1 / 1.5) / 2;
    const sideLength = fontSize / 1.5;
    this.offContext.fillRect(coordinate, coordinate, sideLength, sideLength)
    return this.offContext.getPixelMap(0, 0, fontSize, fontSize)
  }

  // Generate a circle symbol.
  public genCircleMark(fontSize: number, width: number, level?: number ): PixelMap {
    const indentLevel = level ?? 1;
    const offsetLevel = [22, 28, 32, 34, 38];
    const fontSizeLevel = this.getFontSizeLevel(fontSize);
    const marginlevel = this.getmarginLevel(width);
    const newCanvas = new OffscreenCanvas(canvasWidth, canvasHeight);
    const newOffContext: OffscreenCanvasRenderingContext2D = newCanvas.getContext("2d", this.settings);
    const centerCoordinate = 50;
    const radius = 10;
    this.clearCanvas()
    newOffContext.ellipse(100 * (indentLevel + 1) - centerCoordinate * marginlevel, offsetLevel[fontSizeLevel], radius * marginlevel, radius, 0, 0, 2 * Math.PI)
    newOffContext.fillStyle = '66FF0000';
    newOffContext.fill()
    return newOffContext.getPixelMap(0, 0, 100 + 100 * indentLevel, 100)
  }

  private clearCanvas() {
    this.offContext.clearRect(0, 0, canvasWidth, canvasHeight)
  }
}

@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  private leadingMarkCreatorInstance = LeadingMarginCreator.instance;
  private fontNameRawFile: string = 'MiSans-Bold';
  @State fs: number = 30;
  @State cl: number = Color.Black;
  private leftMargin: Dimension = 0;
  private richEditorTextStyle: RichEditorTextStyle = {};

  aboutToAppear() {
    this.getUIContext().getFont().registerFont({
      familyName: 'MiSans-Bold',
      familySrc: '/font/MiSans-Bold.ttf'
    })
  }

  build() {
    Scroll() {
      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan("0123456789\n",
              {
                style:
                {
                  fontWeight: 'medium',
                  fontFamily: this.fontNameRawFile,
                  fontColor: Color.Red,
                  fontSize: 50,
                  fontStyle: FontStyle.Italic,
                  decoration: { type: TextDecorationType.Underline, color: Color.Green }
                }
              })

            this.controller.addTextSpan("abcdefg",
              {
                style:
                {
                  fontWeight: FontWeight.Lighter,
                  fontFamily: 'HarmonyOS Sans',
                  fontColor: 'rgba(0,128,0,0.5)',
                  fontSize: 30,
                  fontStyle: FontStyle.Normal,
                  decoration: { type: TextDecorationType.Overline, color: 'rgba(169, 26, 246, 0.50)' }
                }
              })
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height("50%")

        Row({ space: 5 }) {
          Button('setTypingStyle1')
            .fontSize(10)
            .onClick(() => {
              this.controller.setTypingStyle(
                {
                  fontWeight: 'medium',
                  fontFamily: this.fontNameRawFile,
                  fontColor: Color.Blue,
                  fontSize: 50,
                  fontStyle: FontStyle.Italic,
                  decoration: { type: TextDecorationType.Underline, color: Color.Green }
                })
            })

          Button('setTypingStyle2')
            .fontSize(10)
            .onClick(() => {
              this.controller.setTypingStyle(
                {
                  fontWeight: FontWeight.Lighter,
                  fontFamily: 'HarmonyOS Sans',
                  fontColor: Color.Green,
                  fontSize: '30',
                  fontStyle: FontStyle.Normal,
                  decoration: { type: TextDecorationType.Overline, color: 'rgba(169, 26, 246, 0.50)' }
                })
            })
        }
        Divider()
        Button("getTypingStyle").onClick(() => {
          this.richEditorTextStyle = this.controller.getTypingStyle();
          console.info("RichEditor getTypingStyle:" + JSON.stringify(this.richEditorTextStyle));
        })
        Divider()
        Row({ space: 5 }) {
          Button("Increase List Indent").onClick(() => {
            let margin = Number(this.leftMargin);
            if (margin < 200) {
              margin += Indentation;
              this.leftMargin = margin;
            }
            this.controller.updateParagraphStyle({
              start: -10,
              end: -10,
              style: {
                leadingMargin : {
                  pixelMap : this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1),
                  size: [margin, 40]
                }
              }
            })
          })

          Button("Decrease List Indent").onClick(() => {
            let margin = Number(this.leftMargin);
            if (margin > 0) {
              margin -= Indentation;
              this.leftMargin = margin;
            }
            this.controller.updateParagraphStyle({
              start: -10,
              end: -10,
              style: {
                leadingMargin : {
                  pixelMap : this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1),
                  size: [margin, 40]
                }
              }
            })
          })
        }
        Divider()
        Row({ space: 5 }) {
          Button("Increase Paragraph Indent").onClick(() => {
            let margin = Number(this.leftMargin);
            if (margin < 200) {
              margin += Indentation;
              this.leftMargin = margin;
            }
            this.controller.updateParagraphStyle({
              start: -10,
              end: -10,
              style: {
                leadingMargin: margin
              }
            })
          })

          Button("Decrease Paragraph Indent").onClick(() => {
            let margin = Number(this.leftMargin)
            if (margin > 0) {
              margin -= Indentation;
              this.leftMargin = margin;
            }
            this.controller.updateParagraphStyle({
              start: -10,
              end: -10,
              style: {
                leadingMargin: margin
              }
            })
          })
        }
      }.borderWidth(1).borderColor(Color.Red)
    }
  }
}

UpdateParagraphAndTypingStyle

Example 8: Setting Text Weight and Shadow

This example demonstrates how to set text weight and shadow using the updateParagraphStyle API.

@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  private start: number = -1;
  private end: number = -1;
  @State message: string = "[-1, -1]"
  @State content: string = ""
  @State textShadows : Array<ShadowOptions> = [
    { radius: 10, color: Color.Red, offsetX: 10, offsetY: 0 },
    { radius: 10, color: Color.Black, offsetX: 20, offsetY: 0 },
    { radius: 10, color: Color.Brown, offsetX: 30, offsetY: 0 },
    { radius: 10, color: Color.Green, offsetX: 40, offsetY: 0 },
    { radius: 10, color: Color.Yellow, offsetX: 100, offsetY: 0 }
  ];

  build() {
    Column() {
      Column() {
        Text("selection range:").width("100%")
        Text() {
          Span(this.message)
        }.width("100%")
        Text("selection content:").width("100%")
        Text() {
          Span(this.content)
        }.width("100%")
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("20%")
      Row() {
        Button("Update Style: Bold & Shadow").onClick(() => {
          this.controller.updateSpanStyle({
            start: this.start,
            end: this.end,
            textStyle:
            {
              fontWeight: FontWeight.Bolder,
              textShadow: this.textShadows
            }
          })
        })
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("10%")
      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan("0123456789",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30,
                  textShadow: { radius: 10, color: Color.Blue, offsetX: 10, offsetY: 0 }
                }
              })
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height("30%")
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("70%")
    }
  }
}

TextshadowExample

Example 9: Adding Custom Layout Spans

This example shows how to add custom layout spans using the addBuilderSpan API.

@Builder
function placeholderBuilder2() {
  Row({ space: 2 }) {
    // Replace $r('app.media.startIcon') with the image resource file you use.
    Image($r('app.media.startIcon')).width(24).height(24).margin({ left: -5 })
    Text('okokokok').fontSize(10)
  }.width('20%').height(50).padding(10).backgroundColor(Color.Red)
}

// xxx.ets
@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  option: RichEditorOptions = { controller: this.controller };
  private start: number = 2;
  private end: number = 4;
  @State message: string = "[-1, -1]";
  @State content: string = "";
  private my_offset: number | undefined = undefined;
  private my_builder: CustomBuilder = undefined;
  @BuilderParam my_builder2:() => void = placeholderBuilder2;

  @Builder
  placeholderBuilder() {
    Row({ space: 2 }) {
      // Replace $r('app.media.startIcon') with the image resource file you use.
      Image($r('app.media.startIcon')).width(24).height(24).margin({ left: -5 })
      Text('Custom Popup').fontSize(10)
    }.width(100).height(50).padding(5)
  }

  @Builder
  placeholderBuilder3() {
    Text("hello").padding('20').borderWidth(1).width('100%')
  }

  @Builder
  placeholderBuilder4() {
    Column() {
      Column({ space: 5 }) {
        Text('direction:Row').fontSize(9).fontColor(0xCCCCCC).width('90%')
        Flex({ direction: FlexDirection.Row }) { // Child components are laid out along the main axis in row order.
          Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
          Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
        }
        .height(70)
        .width('90%')
        .padding(10)
        .backgroundColor(0xAFEEEE)

        Text('direction:RowReverse').fontSize(9).fontColor(0xCCCCCC).width('90%')
        Flex({ direction: FlexDirection.RowReverse }) { // Child components are laid out along the main axis in reverse row order.
          Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
          Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
        }
        .height(70)
        .width('90%')
        .padding(10)
        .backgroundColor(0xAFEEEE)

        Text('direction:Column').fontSize(9).fontColor(0xCCCCCC).width('90%')
        Flex({ direction: FlexDirection.Column }) { // Child components are laid out along the main axis in column order.
          Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
          Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
        }
        .height(160)
        .width('90%')
        .padding(10)
        .backgroundColor(0xAFEEEE)

        Text('direction:ColumnReverse').fontSize(9).fontColor(0xCCCCCC).width('90%')
        Flex({ direction: FlexDirection.ColumnReverse }) { // Child components are laid out along the main axis in reverse column order.
          Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
          Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
          Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
        }
        .height(160)
        .width('90%')
        .padding(10)
        .backgroundColor(0xAFEEEE)
      }.width('100%').margin({ top: 5 })
    }.width('100%')
  }

  @Builder
  MyMenu() {
    Menu() {
      // Replace $r('app.media.startIcon') with the image resource file you use.
      MenuItem({ startIcon: $r('app.media.startIcon'), content: "Menu item 1" })
      MenuItem({ startIcon: $r('app.media.startIcon'), content: "Menu item 2" })
        .enabled(false)
    }
  }

  build() {
    Column() {
      Column() {
        Text("selection range:").width("100%")
        Text() {
          Span(this.message)
        }.width("100%")

        Text("selection content:").width("100%")
        Text() {
          Span(this.content)
        }.width("100%")
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("20%")

      Row() {
        Button("Get Span Info").onClick(() => {
          console.info('getSpans='+JSON.stringify(this.controller.getSpans({ start:1, end:5 })));
          console.info('getParagraphs='+JSON.stringify(this.controller.getParagraphs({ start:1, end:5 })));
          this.content = "";
          this.controller.getSpans({
            start: this.start,
            end: this.end
          }).forEach(item => {
            if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
              if ((item as RichEditorImageSpanResult).valueResourceStr == "") {
                console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " +
                  (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1])
              } else {
                console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " +
                  (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " +
                  (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1])
              }
            } else {
              this.content += (item as RichEditorTextSpanResult).value;
              this.content += "\n";
              console.info("text span: " + (item as RichEditorTextSpanResult).value);
            }
          })
        })
        Button("Get Selection").onClick(() => {
          this.content = "";
          let select = this.controller.getSelection();
          console.info("selection start " + select.selection[0] + " end " + select.selection[1]);
          select.spans.forEach(item => {
            if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
              if ((item as RichEditorImageSpanResult).valueResourceStr == "") {
                console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " +
                  (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1])
              } else {
                console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " +
                  (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " +
                  (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1])
              }
            } else {
              this.content += (item as RichEditorTextSpanResult).value;
              this.content += "\n";
              console.info("text span: " + (item as RichEditorTextSpanResult).value);
            }
          })
        })
        Button("Delete Selection").onClick(() => {
          this.controller.deleteSpans({
            start: this.start,
            end: this.end
          })
        })
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("10%")

      Column() {
        RichEditor(this.option)
          .onReady(() => {
            this.controller.addTextSpan("0123456789",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30
                }
              })
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["57px", "57px"]
                }
              })
          })
          .onSelect((value: RichEditorSelection) => {
            this.start = value.selection[0];
            this.end = value.selection[1];
            this.message = "[" + this.start + ", " + this.end + "]";
            console.info("onSelect="+JSON.stringify(value));
          })
          .aboutToIMEInput((value: RichEditorInsertValue) => {
            console.info("---------------------- aboutToIMEInput --------------------");
            console.info("aboutToIMEInput="+JSON.stringify(value));
            console.info("insertOffset:" + value.insertOffset);
            console.info("insertValue:" + value.insertValue);
            return true;
          })
          .onIMEInputComplete((value: RichEditorTextSpanResult) => {
            console.info("---------------------- onIMEInputComplete --------------------");
            console.info("onIMEInputComplete="+JSON.stringify(value));
            console.info("spanIndex:" + value.spanPosition.spanIndex);
            console.info("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]");
            console.info("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]");
            console.info("value:" + value.value);
          })
          .aboutToDelete((value: RichEditorDeleteValue) => {
            value.richEditorDeleteSpans.forEach(item => {
              console.info("---------------------- item --------------------");
              console.info("spanIndex=" + item.spanPosition.spanIndex);
              console.info("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]");
              console.info("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]");
              if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
                if ((item as RichEditorImageSpanResult).valueResourceStr == "") {
                  console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " +
                  (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1])
                } else {
                  console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " +
                  (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " +
                  (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1])
                }
              } else {
                console.info("delete text: " + (item as RichEditorTextSpanResult).value);
              }
            })
            return true;
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height("30%")

        Button("add span")
          .onClick(() => {
            let num = this.controller.addBuilderSpan(this.my_builder, 
              { 
                offset: this.my_offset, 
                accessibilitySpanOptions: { accessibilityText:"hello", accessibilityDescription:"world", accessibilityLevel:"yes" } 
              });
            console.info('addBuilderSpan return ' + num);
          })
        Button("add image")
          .onClick(() => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            let num = this.controller.addImageSpan($r('app.media.startIcon'), {
              imageStyle: {
                size: ["50px", "50px"],
                verticalAlign: ImageSpanAlignment.BOTTOM,
                layoutStyle: {
                  borderRadius: undefined,
                  margin: undefined
                }
              }
            })
            console.info('addImageSpan return' + num);
          })
        Row() {
          Button('builder1').onClick(() => {
            this.my_builder = () => {
              this.placeholderBuilder()
            };
          })
          Button('builder2').onClick(() => {
            this.my_builder = () => {
              this.my_builder2()
            };
          })
          Button('builder3').onClick(() => {
            this.my_builder = () => {
              this.placeholderBuilder3()
            };
          })
          Button('builder4').onClick(() => {
            this.my_builder = () => {
              this.placeholderBuilder4()
            };
          })
        }
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("70%")
    }
  }
}

AddBuilderSpanExample

Example 10: Using and Managing BuilderSpan in a Component

This example demonstrates how to add a custom layout span using the addBuilderSpan API. APIs, such as getSpans and onWillChange, do not return the internal information of BuilderSpan. You need to manage the BuilderSpan state and update it when the component content changes.

const TAG = 'BuilderSpanDemo';

class BuilderObject {
  content: string
  imageUri?: string
  type: string
  id?: string

  constructor(content: string, type: string, imageUri?: string, id?: string) {
    this.content = content
    this.imageUri = imageUri
    this.type = type
    this.id = id
  }
}

@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController()
  option: RichEditorOptions = { controller: this.controller }
  @State content: string = "";
  @State start: number = 0;
  @State end: number = 0;
  private customBuilder: CustomBuilder = undefined;
  private builderArray: BuilderObject[] = [];
  private indicesToRemove: number[] = [];
  private builderId: number = 0;

  @Builder
  imageTextBuilder(builder: BuilderObject) {
    Row({ space: 2 }) {
      Image($r(builder.imageUri)).width(24).height(24).margin({ left: -5 })
      Text(builder.content).fontSize(10)
    }.width(110).height(50).padding(5)
  }

  @Builder
  chipBuilder(builder: BuilderObject) {
    Row() {
      Text(builder.content)
        .fontSize(14)
        .fontColor(Color.Black)
        .fontFamily('HarmonyHeiTi')
        .margin({ right: 4 })

      SymbolGlyph($r('sys.symbol.xmark'))
        .width(16)
        .height(16)
        .id(builder.id)
        .onClick((event: ClickEvent) => {
          this.deleteChipBuilder(event.target.id)
        })
    }
    .width('auto')
    .height(28)
    .backgroundColor(Color.Gray)
    .borderRadius(10)
    .padding({
      top: 4,
      bottom: 4,
      left: 12,
      right: 12
    })
  }

  private deleteChipBuilder(builderId?: string) {
    if (builderId == null || builderId == "") {
      console.info(TAG, "delete chipBuilder error");
      return
    }
    let deleteRange: number[] = this.getTargetBuilderSpanRange(builderId)
    if (deleteRange.length == 0) {
      console.error(TAG, "getTargetBuilderSpanRange failed" + builderId);
      return
    }
    this.builderArray = this.builderArray.filter(item => item.id !== builderId);
    this.controller.deleteSpans({ start: deleteRange[0], end: deleteRange[1] });
    console.info(TAG, `deleteChipBuilder start = ${deleteRange[0]}, end = ${deleteRange[1]}`);
    console.info(TAG, `deleteChipBuilder builderArray + ${this.builderArray.length}`);
  }

  private getTargetBuilderSpanRange(builderId: string): number[] {
    let allSpans = this.controller.getSpans();
    let result: number[] = [];
    let chitBuilderIndex = 0;
    for (let spanIndex = 0; spanIndex < allSpans.length; spanIndex++) {
      if (!this.isBuilderSpanResult(allSpans[spanIndex])) {
        continue;
      }
      if (this.builderArray.length <= chitBuilderIndex) {
        break;
      }
      if (this.builderArray[chitBuilderIndex].id === builderId) {
        result = allSpans[spanIndex].spanPosition.spanRange;
        break;
      }
      chitBuilderIndex++;
    }
    return result;
  }

  private isTextSpanResult(item: RichEditorImageSpanResult | RichEditorTextSpanResult): boolean {
    return typeof (item as RichEditorImageSpanResult)['imageStyle'] == 'undefined';
  }

  private isBuilderSpanResult(item: RichEditorImageSpanResult | RichEditorTextSpanResult): boolean {
    return typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined'
      && ((item as RichEditorImageSpanResult).valueResourceStr == " "
        || (item as RichEditorImageSpanResult).valueResourceStr == "");
  }

  build() {
    Column() {
      Scroll() {
        Column() {
          Text("Builder Info:").width("100%")
          Text() {
            Span(this.content)
          }.width("100%")
        }
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("20%")

      // Record their relative sequence and information when adding builders.
      // Spans with valueResourceStr equal to " " or "" returned by the getSpans API are BuilderSpans. These builders are returned in sequence.
      // Restore the builder information during queries based on the preceding two points.
      Button("addImageTextBuilder")
        .onClick(() => {
          let insertOffset = this.controller.getCaretOffset();
          // Replace 'app.media.startIcon' with the image resource file you use.
          let builder = new BuilderObject('Custom PopUP ' + this.builderId, 'imageTextBuilder', 'app.media.startIcon');
          this.customBuilder = () => {
            this.imageTextBuilder(builder);
          }
          let addIndex = this.addBuilderByIndex(insertOffset);
          console.info(TAG, "add imageTextBuilder index = " + addIndex);
          this.builderArray.splice(addIndex, 0, builder);
          this.controller.addBuilderSpan(this.customBuilder, { offset: insertOffset });
          this.builderId++;
          console.info(TAG, "add imageTextBuilder success");
        })
      Button("addChipBuilder")
        .onClick(() => {
          let insertOffset = this.controller.getCaretOffset();
          let builder = new BuilderObject('Hello World ' + this.builderId, 'chipBuilder', '',
            'chipBuilder' + this.builderId);
          this.customBuilder = () => {
            this.chipBuilder(builder);
          }
          let addIndex = this.addBuilderByIndex(insertOffset);
          console.info(TAG, "add addChipBuilder index = " + addIndex);
          this.builderArray.splice(addIndex, 0, builder);
          this.controller.addBuilderSpan(this.customBuilder, { offset: insertOffset });
          this.builderId++;
          console.info(TAG, "add chipBuilder success");
        })

      Row() {
        Button("getSpans").onClick(() => {
          console.info(TAG, "getSpans = " + JSON.stringify(this.controller.getSpans()));
          this.content = "";
          let allSpans = this.controller.getSpans();
          let builderSpanIndex = 0;
          allSpans.forEach(item => {
            if (this.isTextSpanResult(item)) {
              console.info(TAG, "text span value: " + (item as RichEditorTextSpanResult).value);
            } else if (this.isBuilderSpanResult(item)) {
              let builderOrder = "This is builderSpan " + builderSpanIndex + ":"
              console.info(TAG, builderOrder);
              this.content += builderOrder + "\n";
              let builderResult = (item as RichEditorImageSpanResult);
              let builderIndex = "index: " + builderResult.spanPosition.spanIndex
                + ", range: " + builderResult.spanPosition.spanRange[0] + ", "
                + builderResult.spanPosition.spanRange[1];
              console.info(TAG, builderIndex);
              this.content += builderIndex + "\n";
              if (builderSpanIndex >= this.builderArray.length) {
                console.error(TAG, "getSpans error,  builderSpanIndex = " + builderSpanIndex
                  + ", builderArray.length = " + this.builderArray.length);
                return;
              }
              let builderInfo = "content: " + this.builderArray[builderSpanIndex].content
                + ", image uri: " + this.builderArray[builderSpanIndex].imageUri
                + ", id: " + this.builderArray[builderSpanIndex].id + "\n\n";
              console.info(TAG, builderInfo);
              this.content += builderInfo;
              builderSpanIndex++;
            } else {
              let imageResult = (item as RichEditorImageSpanResult);
              console.info(TAG, "image span " + imageResult.valueResourceStr + ", index: " +
              imageResult.spanPosition.spanIndex + ", range: " +
              imageResult.offsetInSpan[0] + ", " + imageResult.offsetInSpan[1] + ", size: " +
              imageResult.imageStyle.size[0] + ", " + imageResult.imageStyle.size[1]);
            }
          })
        })
        Button("deleteSelectedSpans")
          .onClick(() => {
            this.start = this.controller.getSelection().selection[0];
            this.end = this.controller.getSelection().selection[1];
            if (this.start == this.end) {
              return;
            }
            let allSpans = this.controller.getSpans();
            let needRemoveIndex = 0;
            for (let i = 0; i < allSpans.length; i++) {
              if (!this.isBuilderSpanResult(allSpans[i])) {
                continue;
              }
              let builderIndex = (allSpans[i] as RichEditorImageSpanResult).spanPosition.spanRange[0];
              if (builderIndex < this.start || builderIndex >= this.end) {
                needRemoveIndex++;
                continue;
              }
              this.indicesToRemove.push(needRemoveIndex);
              needRemoveIndex++;
            }
            console.info(TAG, "deleteSpans indicesToRemove = " + this.indicesToRemove.toString());
            this.deleteBuilderByIndices();
            console.info(TAG, "deleteSpans builderArray = " + this.builderArray.length);
            this.controller.deleteSpans({ start: this.start, end: this.end });
          })
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("5%")

      Column() {
        RichEditor(this.option)
          .onReady(() => {
            this.controller.addTextSpan("0123456789",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30
                }
              })
          })
          .aboutToDelete((value: RichEditorDeleteValue) => {
            console.info(TAG, "aboutToDelete = " + JSON.stringify(value));
            let isBuilderAboutToDelete = this.isBuilderAboutToDelete(value);
            console.info(TAG, "aboutToDelete isBuilderAboutToDelete = " + isBuilderAboutToDelete);
            this.getIndicesToRemove(value, isBuilderAboutToDelete);
            console.info(TAG, "indicesToRemove = " + this.indicesToRemove.toString());
            this.deleteBuilderByIndices();
            console.info(TAG, "builderArray = " + this.builderArray.length);
            return true;
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height("30%")
      }
      .margin({ top: 60 })
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("70%")
    }
  }

  private isBuilderAboutToDelete(value: RichEditorDeleteValue): boolean {
    let flag = false;
    for (let i = 0; i < value.richEditorDeleteSpans.length; i++) {
      if (this.isBuilderSpanResult(value.richEditorDeleteSpans[i])) {
        flag = true;
        break;
      }
    }
    return flag;
  }

  private getIndicesToRemove(value: RichEditorDeleteValue, isBuilderAboutToDelete: boolean): void {
    if (!isBuilderAboutToDelete) {
      return
    }
    let allSpans = this.controller.getSpans();
    for (let i = 0; i < value.richEditorDeleteSpans.length; i++) {
      let needRemoveIndex = 0;
      let item = value.richEditorDeleteSpans[i];
      if (!this.isBuilderSpanResult(item)) {
        continue;
      }
      let aboutToDeleteBuilderIndex = (item as RichEditorImageSpanResult).spanPosition.spanIndex
      for (let j = 0; j < allSpans.length; j++) {
        if (!this.isBuilderSpanResult(allSpans[j])) {
          continue;
        }
        let builderIndex = (allSpans[j] as RichEditorImageSpanResult).spanPosition.spanIndex
        if (builderIndex == aboutToDeleteBuilderIndex) {
          this.indicesToRemove.push(needRemoveIndex)
          break;
        }
        needRemoveIndex++;
      }
    }
  }

  private deleteBuilderByIndices(): void {
    let indicesSet: Set<number> = new Set(this.indicesToRemove);
    let newLength = 0;
    for (let i = 0; i < this.builderArray.length; i++) {
      if (!indicesSet.has(i)) {
        this.builderArray[newLength] = this.builderArray[i];
        newLength++;
      }
    }
    this.builderArray.length = newLength;
    this.indicesToRemove.length = 0;
  }

  private addBuilderByIndex(insertOffset: number): number {
    if (insertOffset == 0 || this.builderArray.length == 0) {
      return 0;
    }
    let allSpans = this.controller.getSpans();
    let addIndex = 0;
    for (let i = 0; i < allSpans.length; i++) {
      if (!this.isBuilderSpanResult(allSpans[i])) {
        continue;
      }
      let builderIndex = (allSpans[i] as RichEditorImageSpanResult).spanPosition.spanRange[0];
      if (builderIndex < insertOffset) {
        addIndex++;
        continue;
      }
      break;
    }
    return addIndex;
  }
}

BuilderSpanManagerExample

Example 11: Configuring Text Recognition

This example demonstrates how to enable text recognition by setting enableDataDetector to true and configuring text recognition settings using the dataDetectorConfig API.

@Entry
@Component
struct TextExample7 {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State phoneNumber: string = '(86) (755) ********';
  @State url: string = 'www.********.com';
  @State email: string = '***@example.com';
  @State address: string = 'XX (province) XX (city) XX (district) XXXX';
  @State enableDataDetector: boolean = true;
  @State enablePreviewText: boolean = false;
  @State types: TextDataDetectorType[] = [];

  build() {
    Row() {
      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan('Phone number:' + this.phoneNumber + '\n',
              {
                style:
                {
                  fontSize: 30
                }
              })
            this.controller.addTextSpan('URL:' + this.url + '\n',
              {
                style:
                {
                  fontSize: 30
                }
              })
            this.controller.addTextSpan('Email:' + this.email + '\n',
              {
                style:
                {
                  fontSize: 30
                }
              })
            this.controller.addTextSpan('Address:' + this.address,
              {
                style:
                {
                  fontSize: 30
                }
              })
          })
          .copyOptions(CopyOptions.InApp)
          .enableDataDetector(this.enableDataDetector)
          .dataDetectorConfig({types : this.types, onDetectResultUpdate: (result: string)=>{}})
          .enablePreviewText(this.enablePreviewText)
          .borderWidth(1)
          .padding(10)
          .width('100%')
      }
      .width('100%')
    }
  }
}

Example 12: Setting Caret, Selection Handle, and Background Colors

This example shows how to set the caret and selection handle color using the caretColor attribute and the text selection background color using the selectedBackgroundColor attribute.

@Entry
@Component
struct RichEditorDemo {
  @State color: Color = Color.Black;
  controller: RichEditorController = new RichEditorController();

  build() {
    Column() {
      Row() {
        Button("Change to Red").onClick(() => {
          this.color = Color.Red;
        })
      }.margin({ top: 50 })

      RichEditor({ controller: this.controller })
        .onReady(() => {
          this.controller.addTextSpan('Set the caret and selected background colors through the caretColor and selectedBackgroundColor attributes.');
        })
        .width("100%")
        .border({ width: 1, radius: 5 })
        .key('RichEditor')
        .caretColor(this.color) // Caret color.
        .selectedBackgroundColor(this.color) // Background color of the selected content.
        .margin({ top: 50 })
    }
    .width('100%')
  }
}

SetCaretAndSelectedBackgroundColorExample

Example 13: Setting Line Height and Letter Spacing

This example demonstrates how to configure text line height (lineHeight) and letter spacing (letterSpacing) using the updateSpanStyle API.

@Entry
@Component
struct RichEditorDemo03 {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State start: number = -1;
  @State end: number = -1;
  @State LH:number = 50;
  @State LS:number = 20;

  build() {
    Column() {
      Scroll(){
        Column(){
          Row() {
            Button("Line Height ++").onClick(()=>{
              this.LH = this.LH + 5;
              this.controller.updateSpanStyle({
                start: this.start,
                end: this.end,
                textStyle:
                {
                  lineHeight: this.LH
                }
              })
            })
            Button("Line Height --").onClick(()=>{
              this.LH = this.LH - 5;
              this.controller.updateSpanStyle({
                start: this.start,
                end: this.end,
                textStyle:
                {
                  lineHeight: this.LH
                }
              })
            })
            Button("Letter Spacing ++").onClick(()=>{
              this.LS = this.LS + 5
              this.controller.updateSpanStyle({
                start: this.start,
                end: this.end,
                textStyle:
                {
                  letterSpacing: this.LS
                }
              })
            })
            Button("Letter Spacing --").onClick(()=>{
              this.LS = this.LS - 5
              this.controller.updateSpanStyle({
                start: this.start,
                end: this.end,
                textStyle:
                {
                  letterSpacing: this.LS
                }
              })
            })
          }
        }
      }.borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("20%")
      .margin({top: 20})

      Scroll(){
        Column() {
          Text("LineHeight:" + this.LH).width("100%")
          Text("LetterSpacing:" + this.LS).width("100%")
        }
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("20%")
      .margin({bottom: 20})

      Column() {
        RichEditor(this.options).clip(true).padding(10)
          .onReady(() => {
            this.controller.addTextSpan("012345",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30,
                  lineHeight: this.LH,
                  letterSpacing: this.LS
                }
              })
            this.controller.addTextSpan("6789",
              {
                style:
                {
                  fontColor: Color.Black,
                  fontSize: 30,
                  lineHeight: this.LH,
                  letterSpacing: this.LS
                }
              })
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width(400)
          .height(400)
      }
      .borderWidth(1)
      .borderColor(Color.Red)
      .width("100%")
      .height("60%")
    }
  }
}

AddBuilderSpanExample

Example 14: Adding a Custom Paste Event

This example shows how to add a custom paste event to the component using the onPaste event and customize user paste behavior using the PasteEvent API.

@Entry
@Component
struct RichEditorDemo {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };

  build() {
    Column({ space: 2 }) {
      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan('RichEditor preventDefault')
        })
        .onPaste((event?: PasteEvent) => {
          if (event != undefined && event.preventDefault) {
            event.preventDefault();
          }
        })
        .borderWidth(1)
        .borderColor(Color.Green)
        .width('100%')
        .height('40%')
    }
  }
}

PreventDefaultExample

Example 15: Setting Text Feature Effects

This example demonstrates how to set text feature effects using the addTextSpan API and the fontFeature attribute. This example sets the FontFeature attribute to ss01, which changes the digit "0" from its original oval shape to a shape with rounded corners.

@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State enableDataDetector: boolean = true;
  @State types: TextDataDetectorType[] = [];
  build() {
    Row() {
      Column() {
        RichEditor(this.options)
          .onReady(() => {
            this.controller.addTextSpan('This is ss01 off :' + '0000' + '\n',
              {
                style:
                {
                  fontSize: 30
                }
              })
            this.controller.addTextSpan('This is ss01 on :' + '0000' + '\n',
              {
                style:
                {
                  fontSize: 30,
                  fontFeature: "\"ss01\" 1"
                }
              })
          })
          .copyOptions(CopyOptions.InApp)
          .enableDataDetector(this.enableDataDetector)
          .dataDetectorConfig({types : this.types, onDetectResultUpdate: (result: string)=>{}})
          .borderWidth(1)
          .padding(10)
          .width('100%')
      }
      .width('100%')
      .margin({top:150})
    }
  }
}

FontFeatureExample

Example 16: Setting Custom Keyboard Avoidance

This example shows how to bind a custom keyboard using the customKeyboard attribute and configure whether the custom keyboard supports keyboard avoidance using the KeyboardOptions parameter.

@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();
  @State height1: string | number = '80%';
  @State height2: number = 100;
  @State supportAvoidance: boolean = true;

  // Create a custom keyboard component.
  @Builder
  CustomKeyboardBuilder() {
    Column() {
      Row() {
        Button('Add Emoticon').onClick(() => {
          this.controller.addTextSpan("\uD83D\uDE0A",
            {
              style:
              {
                fontColor: Color.Orange
              }
            })
        })
      }

      Grid() {
        ForEach(['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'], (item: string) => {
          GridItem() {
            Button(item).width(110).onClick(() => {
              this.controller.addTextSpan(item, {
                offset: this.controller.getCaretOffset(),
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 30
                }
              })
              this.controller.setCaretOffset(this.controller.getCaretOffset() + item.toString().length)
            })
          }
        })
      }.maxCount(3).columnsGap(10).rowsGap(10).padding(5)
    }.backgroundColor(Color.Gray)
  }

  build() {
    Column() {
      Row() {
        Button("20%")
          .fontSize(24)
          .onClick(() => {
            this.height1 = "20%";
          })
        Button("80%")
          .fontSize(24)
          .margin({ left: 20 })
          .onClick(() => {
            this.height1 = "80%";
          })
      }
      .justifyContent(FlexAlign.Center)
      .alignItems(VerticalAlign.Bottom)
      .height(this.height1)
      .width("100%")
      .padding({ bottom: 50 })

      RichEditor({ controller: this.controller })// Bind the custom keyboard.
        .customKeyboard(this.CustomKeyboardBuilder(), { supportAvoidance: this.supportAvoidance })
        .margin(10)
        .border({ width: 1 })
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
    }
  }
}

CustomRichEditorType

Example 17: Viewing the Editing State

This example demonstrates how to obtain the current editing state of the rich text using the isEditing API. The onEditingChange event can be added to the component to log whether the component is currently in editing mode.

@Entry
@Component
struct RichEditor_onEditingChange {
  controller: RichEditorController = new RichEditorController();
  @State controllerIsEditing: boolean = false;
  @Builder

  build() {
    Column() {
      Row() {
        Button("View isEditing() Value:").onClick(() => {
          this.controllerIsEditing = this.controller.isEditing();
        })
          .padding(5)
        Text('' + this.controllerIsEditing)
          .width('100%')
          .padding(5)
          .fontColor(Color.Orange)
          .fontSize(20)
      }
      RichEditor({ controller: this.controller })
        .onEditingChange((isEditing: boolean) => {
          console.info("Current Editing Status:" + isEditing);
        })
        .height(400)
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
    }
  }
}

RichEditorOnEditingChange

Example 18: Configuring Text Change Callback

This example shows how to add the onWillChange event to the component, which triggers a callback before the component performs any insert or delete operations.

@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();
  build() {
    Column() {
      RichEditor({ controller: this.controller })
        .height(200)
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
        .onReady(() => {
          this.controller.addTextSpan('TestWord', { style: { fontColor: Color.Orange, fontSize: 30 } })
          this.controller.updateSpanStyle({
            start: -1,
            end: -1,
            textStyle:
            {
              fontWeight: FontWeight.Bolder
            }
          })
        })
        .onWillChange((value: RichEditorChangeValue) => {
          console.info('Test log: onWillChange');
          console.info('rangeBefore: ' + JSON.stringify(value.rangeBefore));
          console.info('print replacedSpans');
          value.replacedSpans.forEach((item: RichEditorTextSpanResult) => {
            console.info('spanPosition:' + JSON.stringify(item.spanPosition));
            console.info('value:' + item.value);
            console.info('textStyle:' + JSON.stringify(item.textStyle));
            console.info('offsetInSpan:' + item.offsetInSpan);
            console.info('valueResource:' + item.valueResource);
            console.info('paragraphStyle:' + JSON.stringify(item.paragraphStyle));
          })
          console.info('print replacedImageSpans');
          value.replacedImageSpans.forEach((item: RichEditorImageSpanResult) => {
            console.info('spanPosition:' + JSON.stringify(item.spanPosition));
            console.info('valuePixelMap:' + JSON.stringify(item.valuePixelMap));
            console.info('valueResourceStr:' + item.valueResourceStr);
            console.info('imageStyle:' + JSON.stringify(item.imageStyle));
            console.info('offsetInSpan:' + item.offsetInSpan);
          })
          console.info('print replacedSymbolSpans');
          value.replacedSymbolSpans.forEach((item: RichEditorTextSpanResult) => {
            console.info('spanPosition:' + JSON.stringify(item.spanPosition));
            console.info('value:' + item.value);
            console.info('offsetInSpan:' + item.offsetInSpan);
            console.info('symbolSpanStyle:' + JSON.stringify(item.symbolSpanStyle));
            console.info('valueResource:' + item.valueResource);
            console.info('paragraphStyle:' + JSON.stringify(item.paragraphStyle));
          })
          return true;
        })
        .onDidChange((rangeBefore: TextRange, rangeAfter: TextRange) => {
          console.info('Test log: onDidChange');
          console.info('rangeBefore:' + JSON.stringify(rangeBefore));
          console.info('rangeAfter:' + JSON.stringify(rangeAfter));
        })
        .onCut((event:CutEvent) => {
          event.preventDefault!()
          console.info('Test log: onCut');
        })
        .onCopy((event:CopyEvent) => {
          event.preventDefault!()
          console.info('Test log: onCopy');
        })
        .onPaste(()=>{
          console.info('Test log: onPaste');
        })
      Text('Test text Hello')
        .lineHeight(50)
        .fontSize(24)
        .draggable(true)
        .onDragStart(()=>{})
      TextInput({text:'Test text NiHao'})
        .draggable(true)
        .margin(20)
    }
  }
}

Example 19: Configuring the Enter Key Type

This example demonstrates how to set the Enter key type of the soft keyboard using the enterKeyType attribute.

@Entry
@Component
struct SoftKeyboardEnterTypeExample {
  controller: RichEditorController = new RichEditorController();

    build() {
    Column() {
      Button("Stop Editing").onClick(()=>{
        this.controller.stopEditing()
      })
      RichEditor({ controller: this.controller })
        .margin(10)
        .border({ width: 1 })
        .height(200)
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
        .enterKeyType(EnterKeyType.Search)
        .onSubmit((enterKey: EnterKeyType, event: SubmitEvent) => {
          console.info("trigger richeditor onsubmit" + enterKey);
          this.controller.addTextSpan(" type["+ enterKey +"] triggered")
          event.keepEditableState()
        })
    }.height("100%").justifyContent(FlexAlign.Center)
  }
}

SoftKeyboardEnterType

Example 20: Setting the Paragraph Line Break Rule

This example shows how to set the line break rule (lineBreakStrategy) using the updateParagraphStyle API and obtain the current line break rule using the getParagraphs API.

@Entry
@Component
struct LineBreakStrategyExample {
  controller: RichEditorController = new RichEditorController();
  private spanParagraphs: RichEditorParagraphResult[] = [];
  @State lineBreakOptionStr: string[] = ['GREEDY', 'HIGH_QUALITY', 'BALANCED'];
  @State attributeValue: string = "";
  @State testStr: string = "0123456789,0123456789,0123456789,0123456789,0123456789.";
  build() {
    Column() {
      RichEditor({ controller: this.controller })
        .onReady(() => {
          this.controller.addTextSpan(this.testStr, {
            style: {
              fontColor: Color.Black,
              fontSize: "32"
            },
            paragraphStyle: {
              textAlign: TextAlign.Start,
              lineBreakStrategy: LineBreakStrategy.GREEDY
            }
          })
        })
        .width(400)
        .height(300)
        .margin({bottom:20})
        .draggable(false)
      Column(){
        Text('linebreak value: ' + this.attributeValue).fontSize(20).fontColor(Color.Black)
      }.margin({bottom: 10})
      Column({ space: 10 }) {
        Button("Set LineBreakStrategy to GREEDY").onClick(() => {
          this.controller.updateParagraphStyle({ start: -1, end: -1,
            style: {
              lineBreakStrategy: LineBreakStrategy.GREEDY
            }
          })
        })
        Button("Set LineBreakStrategy to HIGH_QUALITY").onClick(() => {
          this.controller.updateParagraphStyle({ start: -1, end: -1,
            style: {
              lineBreakStrategy: LineBreakStrategy.HIGH_QUALITY
            }
          })
        })
        Button("Set LineBreakStrategy to BALANCED").onClick(() => {
          this.controller.updateParagraphStyle({ start: -1, end: -1,
            style: {
              lineBreakStrategy: LineBreakStrategy.BALANCED
            }
          })
        })
        Divider()
        Row(){
          Button("Get LineBreakStrategy Value").onClick(() => {
            this.spanParagraphs = this.controller.getParagraphs({ start: -1, end: -1 });
            console.info("RichEditor getParagraphs:" + JSON.stringify(this.spanParagraphs));
            this.spanParagraphs.forEach(item => {
              if(typeof(item as RichEditorParagraphResult)['style'] != 'undefined'){
                this.attributeValue = "";
                console.info('lineBreakStrategy:'+ JSON.stringify((item as RichEditorParagraphResult)['style']));
                this.attributeValue += this.lineBreakOptionStr[Number((item as RichEditorParagraphResult)['style'].lineBreakStrategy)];
              }
            })
          })
        }
      }
    }
  }
}

LineBreakStrategy

Example 21: Using Basic Functionality of Styled Strings

This example demonstrates how to bind a styled string to a RichEditor component using the setStyledString API in RichEditorStyledStringController. This feature is available since API version 20. The getStyledString API can be used to obtain the styled string displayed by the RichEditor component.

// xxx.ets
import { LengthMetrics } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  stringLength: number = 0;
  @State selection: string = "";
  @State content: string = "";
  @State range: string = "";
  @State replaceString: string = "";
  @State rangeBefore: string = "";
  @State rangeAfter: string = "";
  richEditorStyledString: MutableStyledString = new MutableStyledString("");
  textStyle: TextStyle = new TextStyle({
    fontWeight: FontWeight.Lighter,
    fontFamily: 'HarmonyOS Sans',
    fontColor: Color.Green,
    fontSize: LengthMetrics.vp(30),
    fontStyle: FontStyle.Normal
  })
  fontStyle1: TextStyle = new TextStyle({ fontColor: Color.Blue });
  fontStyle2: TextStyle = new TextStyle({
    fontWeight: FontWeight.Bolder,
    fontFamily: 'Arial',
    fontColor: Color.Orange,
    fontSize: LengthMetrics.vp(30),
    fontStyle: FontStyle.Italic
  })

  controller1: RichEditorController = new RichEditorController();
  options1: RichEditorOptions = { controller: this.controller1 };
  // Create a styled string object.
  mutableStyledString: MutableStyledString = new MutableStyledString("Initial styled string",
    [{ start: 0, length: 5, styledKey: StyledStringKey.FONT, styledValue: this.fontStyle1 }]);
  styledString: StyledString = new StyledString("Styled string to insert",
    [{ start: 2, length: 4, styledKey: StyledStringKey.FONT, styledValue: this.fontStyle2 }]);
  controller: RichEditorStyledStringController = new RichEditorStyledStringController();
  options: RichEditorStyledStringOptions = {controller: this.controller};
  // Text content change callback
  contentChangedListener: StyledStringChangedListener = {
    onWillChange: (value: StyledStringChangeValue) => {
      this.range = '[ ' + value.range.start + ' , ' + value.range.end + ' ]';
      this.replaceString = value.replacementString.getString();
      return true;
    },
    onDidChange: (rangeBefore, rangeAfter) => {
      this.rangeBefore = '[ ' + rangeBefore.start + ' , ' + rangeBefore.end + ' ]';
      this.rangeAfter = '[ ' + rangeAfter.start + ' , ' + rangeAfter.end + ' ]';
    }
  }

  build() {
    Column({space:6}) {
      Column() {
        Text("Selection information")
          .fontSize(20)
          .width("100%")
        Text("selection range: " + this.selection).width("100%")
        Text("selection content: " + this.content).width("100%")
      }
      .width("100%")
      .height("10%")

      Column() {
        Text("onWillChange callback")
          .fontSize(20)
          .width("100%")
        Text("range: " + this.range).width("100%")
        Text("replacementString: " + this.replaceString).width("100%")
        Text("onWillChange callback")
          .fontSize(20)
          .width("100%")
        Text("rangeBefore: " + this.rangeBefore).width("100%")
        Text("rangeAfter: " + this.rangeAfter).width("100%")
      }
      .borderWidth(1)
      .borderColor(Color.Black)
      .width("100%")
      .height("20%")

      RichEditor(this.options)
        .onReady(() => {
          // Register a text change callback.
          this.controller.onContentChanged(this.contentChangedListener);
          // Set the styled string displayed in the component.
          this.controller.setStyledString(this.mutableStyledString);
        })
        .height("20%")
        .width("100%")

      RichEditor(this.options1)
        .onReady(() => {
        this.controller1.addTextSpan("Convert the text into a styled string.");
      })
        .height("10%")
        .width("100%")
        .borderWidth(1)
        .borderColor(Color.Black)

        Row({space:2}) {
          Button("Insert Image")
            .stateEffect(true)
            .onClick(() => {
              // Replace $r('app.media.startIcon') with the image resource file you use.
              let imageStyledString = new MutableStyledString(new ImageAttachment({
                resourceValue: $r('app.media.startIcon'),
                size: { width: 50, height: 50 },
                layoutStyle: { borderRadius: LengthMetrics.vp(10) },
                verticalAlign: ImageSpanAlignment.BASELINE,
                objectFit: ImageFit.Contain,
                syncLoad: true
              }));
              // Obtain the styled string displayed in the component.
              this.richEditorStyledString = this.controller.getStyledString();
              this.richEditorStyledString.appendStyledString(imageStyledString)
              // Apply the styled string after the image is inserted to the component.
              this.controller.setStyledString(this.richEditorStyledString)
              this.controller.setCaretOffset(this.richEditorStyledString.length)
          })
          Button("Insert Text").onClick () => {
            // Obtain the styled string displayed in the component.
            this.richEditorStyledString = this.controller.getStyledString();
            this.richEditorStyledString.appendStyledString(this.styledString)
            // Apply the styled string after the text is inserted to the component.
            this.controller.setStyledString(this.richEditorStyledString)
            this.controller.setCaretOffset(this.richEditorStyledString.length)
          })
          Button("Delete Selection").onClick(() => {
            // Obtain the selection range.
            let richEditorSelection = this.controller.getSelection();
            let start = richEditorSelection.start ? richEditorSelection.start : 0;
            let end = richEditorSelection.end ? richEditorSelection.end : 0;
            if (start < 0 || end <= start) {
              return;
            }
            // Obtain the styled string displayed in the component.
            this.richEditorStyledString = this.controller.getStyledString();
            this.richEditorStyledString.removeString(start, end - start)
            // Apply the styled string after the content is deleted to the component.
            this.controller.setStyledString(this.richEditorStyledString)
          })
        }
        Row({space:2}) {
          Button("Get Selection").onClick(() => {
            // Obtain the selection range.
            let richEditorSelection = this.controller.getSelection();
            let start = richEditorSelection.start ? richEditorSelection.start : 0;
            let end = richEditorSelection.end ? richEditorSelection.end : 0;
            // Obtain the styled string displayed in the component.
            this.richEditorStyledString = this.controller.getStyledString();
            this.selection = '[ ' + start + ' , ' + end + ' ]';
            if (start == end) {
              this.content = "";
            } else {
              this.content = this.richEditorStyledString.subStyledString(start, end - start).getString();
            }
          })
          Button("Update Selection Style").onClick(() => {
            // Obtain the selection range.
            let richEditorSelection = this.controller.getSelection();
            let start = richEditorSelection.start ? richEditorSelection.start : 0;
            let end = richEditorSelection.end ? richEditorSelection.end : 0;
            if (start < 0 || end <= start) {
              return;
            }
            // Obtain the styled string displayed in the component.
            this.richEditorStyledString = this.controller.getStyledString();
            this.richEditorStyledString.setStyle({
              start: start,
              length: end - start,
              styledKey: StyledStringKey.FONT,
              styledValue: this.textStyle
            })
            // Apply the updated styled string to the component.
            this.controller.setStyledString(this.richEditorStyledString)
          })
        }
        Row({space:2}){
          // Convert a styled string into a span.
          Button("Call fromStyledString").onClick(() => {
            this.controller1.addTextSpan("Call fromStyledString: " +JSON.stringify(this.controller1.fromStyledString(this.mutableStyledString)))
          })
          // Convert the component content within the given range to a styled string.
          Button("Call toStyledString").onClick(() => {
            this.controller.setStyledString(this.controller1.toStyledString({start:0,end:13}))
          })
        }
    }
  }
}

StyledString

Example 22: Obtaining Layout Information

This example shows how to obtain layout information using the getLayoutManager API. It includes obtaining the total number of lines for the component content or placeholder using getLineCount, the glyph position closest to a given coordinate using getGlyphPositionAtCoordinate, and line metrics, text style information, and font properties using getLineMetrics.

@Entry
@Component
export struct Index {
  @State lineCount: string = ""
  @State glyphPositionAtCoordinate: string = ""
  @State lineMetrics: string = ""
  controller: RichEditorController = new RichEditorController();
  @State textStr: string =
    'Hello World!'

  build() {
    Scroll() {
      Column() {
        Text('getLayoutManager obtains the layout information relative to the component')
          .fontSize(9)
          .fontColor(0xCCCCCC)
          .width('90%')
          .padding(10)
        RichEditor({ controller: this.controller })
          .borderColor(Color.Red)
          .borderWidth(1)
          .onReady(() => {
            this.controller.addTextSpan(this.textStr)
          })
          .onAreaChange(() => {
            let layoutManager = this.controller.getLayoutManager();
            this.lineCount = "LineCount: " + layoutManager.getLineCount();
          })

        Text('LineCount').fontSize(9).fontColor(0xCCCCCC).width('90%').padding(10)
        Text(this.lineCount)

        Text('GlyphPositionAtCoordinate').fontSize(9).fontColor(0xCCCCCC).width('90%').padding(10)
        Button("Relative Component Coordinate [150, 50]")
          .onClick(() => {
            let layoutManager: LayoutManager = this.controller.getLayoutManager();
            let position = layoutManager.getGlyphPositionAtCoordinate(150, 50);
            this.glyphPositionAtCoordinate =
            "Relative component coordinate [150, 50] glyphPositionAtCoordinate position: " + position.position + " affinity: " +
            position.affinity;
          })
          .margin({ bottom: 20, top: 10 })
        Text(this.glyphPositionAtCoordinate)

        Text('LineMetrics').fontSize(9).fontColor(0xCCCCCC).width('90%').padding(10)
        Button("Line Metrics")
          .onClick(() => {
            let layoutManager: LayoutManager = this.controller.getLayoutManager();
            let lineMetrics = layoutManager.getLineMetrics(0);
            this.lineMetrics = "lineMetrics is " + JSON.stringify(lineMetrics) + '\n\n';
            let runMetrics = lineMetrics.runMetrics;
            runMetrics.forEach((value, key) => {
              this.lineMetrics += "runMetrics key is " + key + " " + JSON.stringify(value) + "\n\n";
            });
          })
          .margin({ bottom: 20, top: 10 })
        Text(this.lineMetrics)
      }
      .margin({ top: 100, left: 8, right: 8 })
    }
  }
}

LayoutManager

Example 23: Configuring Extended Options for the System Default Menu

This example demonstrates how to configure extended options for the system default menu via the editMenuOptions attribute. You can customize the text labels, icons, and callback methods of menu extended options. This feature is available since API version 20.

// xxx.ets
@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State endIndex: number | undefined = 0;
  onCreateMenu = (menuItems: Array<TextMenuItem>) => {
    const idsToFilter: TextMenuItemId[] = [
      TextMenuItemId.TRANSLATE,
      TextMenuItemId.SHARE,
      TextMenuItemId.SEARCH,
      TextMenuItemId.AI_WRITER,
      // TextMenuItemId.autoFill is supported since API version 23.
      TextMenuItemId.autoFill
    ]
    const items = menuItems.filter(item => !idsToFilter.some(id => id.equals(item.id)))
    // Replace $r('app.media.startIcon') with the image resource file you use.
    let item1: TextMenuItem = {
      content: 'create1',
      icon: $r('app.media.startIcon'),
      id: TextMenuItemId.of('create1'),
    };
    let item2: TextMenuItem = {
      content: 'create2',
      id: TextMenuItemId.of('create2'),
      icon: $r('app.media.startIcon'),
    };
    items.push(item1);
    items.unshift(item2);
    return items;
  }
  onMenuItemClick = (menuItem: TextMenuItem, textRange: TextRange) => {
    if (menuItem.id.equals(TextMenuItemId.of("create2"))) {
      console.info("Intercept id: create2 start:" + textRange.start + "; end:" + textRange.end);
      return true;
    }
    if (menuItem.id.equals(TextMenuItemId.of("prepare1"))) {
      console.info("Intercept id: prepare1 start:" + textRange.start + "; end:" + textRange.end);
      return true;
    }
    if (menuItem.id.equals(TextMenuItemId.COPY)) {
      console.info("Intercept COPY start:" + textRange.start + "; end:" + textRange.end);
      return true;
    }
    if (menuItem.id.equals(TextMenuItemId.SELECT_ALL)) {
      console.info("Do not intercept SELECT_ALL start:" + textRange.start + "; end:" + textRange.end);
      return false;
    }
    return false;
  }
  // Replace $r('app.media.startIcon') with the image resource file you use.
  onPrepareMenu = (menuItems: Array<TextMenuItem>) => {
    let item1: TextMenuItem = {
      content: 'prepare1_' + this.endIndex,
      icon: $r('app.media.startIcon'),
      id: TextMenuItemId.of('prepare1'),
    };
    menuItems.unshift(item1);
    return menuItems;
  }
  @State editMenuOptions: EditMenuOptions = {
    onCreateMenu: this.onCreateMenu,
    onMenuItemClick: this.onMenuItemClick,
    onPrepareMenu: this.onPrepareMenu
  };

  build() {
    Column() {
      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan("RichEditor editMenuOptions")
        })
        .editMenuOptions(this.editMenuOptions)
        .onSelectionChange((range: RichEditorRange) => {
          console.info("onSelectionChange, (" + range.start + "," + range.end + ")");
          this.endIndex = range.end
        })
        .height(50)
        .margin({ top: 100 })
        .borderWidth(1)
        .borderColor(Color.Red)
    }
    .width("90%")
    .margin("5%")
  }
}

RichEditorEditMenuOptions

Example 24: Setting Common Component Attributes

This example shows how to set common attributes for the component. This includes the following:
- Set the scrollbar display mode using barState (available since API version 18).
- Configure whether the soft keyboard is automatically displayed when the component gains focus via non‑click triggers, using enableKeyboardOnFocus.
- Enable or disable haptic feedback for the component using enableHapticFeedback.
- Obtain preview text information using getPreviewText.
- Specify whether to prevent the back button press from being propagated to other components or apps, using stopBackPress.
This example shows how to set the scrollbar color of the RichEditor component using the scrollBarColor attribute, available since API version 21.

// xxx.ets
import { JSON } from '@kit.ArkTS';
import { ColorMetrics } from '@kit.ArkUI';

@Entry
@Component
struct RichEditor_example {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };

  controller1: RichEditorController = new RichEditorController();
  options1: RichEditorOptions = { controller: this.controller1 };

  @State e: boolean = true;
  @State bs_num: number = 0;
  @State bs: (BarState | undefined)[] = [BarState.Auto, BarState.On, BarState.Off, undefined];
  @State bs_string: string[] = ["Auto", "On", "Off", "undefined"];

  build() {
    Column({space: 3}) {
      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan('This text is for demonstration purposes. This text is for demonstration purposes. This text is for demonstration purposes. ', {
            style: {
              fontColor: Color.Black,
              fontSize: 20
            }
          });
        })
        .onDidIMEInput((value: TextRange) => {
          this.controller1.addTextSpan("\n" + "onDidIMEInput callback triggered. Input range: (" + value.start + "," + value.end + ")", {
            style: {
              fontColor: Color.Gray,
              fontSize: 10
            }
          });
        })
        .onSelectionChange((value: RichEditorRange) => {
          this.controller1.addTextSpan("\n" + "onSelectionChange callback triggered. Input range: (" + value.start + "," + value.end + ")", {
            style: {
              fontColor: Color.Gray,
              fontSize: 10
            }
          });
        })
        .width(300)
        .height(100)
        .margin(20)
        .barState(this.bs[this.bs_num])
        .enableKeyboardOnFocus(this.e)
        .enableHapticFeedback(true)
        .stopBackPress(false)
        .scrollBarColor(ColorMetrics.resourceColor("#2787D9"));

      RichEditor(this.options1).width(300)

      Button('Set barState to ' + this.bs_string[this.bs_num])
        .height(30)
        .fontSize(13)
        .onClick(() => {
          this.bs_num++;
          if (this.bs_num > (this.bs.length - 1)) {
            this.bs_num = 0;
          }
        })

      Button('Set enableKeyboardOnFocus to ' + this.e)
        .height(30)
        .fontSize(13)
        .onClick(() => {
          this.e = !this.e;
        })

      Button('Get Preview Text')
        .height(30)
        .fontSize(13)
        .onClick(() => {
          this.controller1.addTextSpan("\nObtained preview text: " + JSON.stringify(this.controller.getPreviewText()))
        })
    }
  }
}

StyledString

Example 25: Obtaining the Caret's Relative Position Rectangle in the Component

This example shows how to obtain the caret's relative position rectangle in the component using the getCaretRect method of RichEditorBaseController, available since API version 18.

// xxx.ets
@Entry
@Component
struct Index {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State caretRect: string = "not found";

  build() {
    Column() {
      Button('get caret rect')
        .onClick(() => {
          let rectCaret = this.controller.getCaretRect();
          if(rectCaret == undefined) {
            this.caretRect = 'undefined';
          } else {
            this.caretRect = 'X: ' + rectCaret.x + '\nY: ' + rectCaret.y
              + '\nWidth: ' + rectCaret.width + '\nHeight: ' + rectCaret.height;
          }
        })
        .fontSize(24)
        .width("60%")
        .height("10%")

      Text(this.caretRect)
        .margin(12)
        .fontSize(24)

      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan('12345678901234567890', {
            style:
            {
              fontColor: Color.Orange,
              fontSize: 50
            }
          })
        })
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
        .height("60%")
    }
  }
}

StyledString

Example 26: Setting the Maximum Number of Lines and Maximum Number of Characters

This example shows how to set the maximum number of characters using maxLength and the maximum number of lines using maxLines, available since API version 18.

@Entry
@Component
struct RichEditorExample {
  @State text: string = "As the sun begins to set, casting a warm golden hue across the sky," +
    "the world seems to slow down and breathe a sigh of relief. The sky is painted with hues of orange, " +
    " pink, and lavender, creating a breathtaking tapestry that stretches as far as the eye can see." +
    "The air is filled with the sweet scent of blooming flowers, mingling with the earthy aroma of freshly turned soil." +
    "it casts a warm," +
    "golden hue that spreads like liquid amber across the vast expanse of the sky." +
    "The once-blue heavens gradually transform, " +
    "now painted in a breathtaking palette of soft oranges, pinks, " +
    "and purples, each color blending seamlessly into the next. Wisps of clouds, tinged with fiery edges, " +
    "float lazily amidst this celestial canvas," +
    "creating a scene so serene and beautiful that it almost seems to pause time itself." +
    "As the sun begins to set, casting a warm golden hue across the sky," +
    "the world seems to slow down and breathe a sigh of relief. The sky is painted with hues of orange, " +
    " pink, and lavender, creating a breathtaking tapestry that stretches as far as the eye can see." +
    "The air is filled with the sweet scent of blooming flowers, mingling with the earthy aroma of freshly turned soil." +
    "it casts a warm," +
    "golden hue that spreads like liquid amber across the vast expanse of the sky." +
    "The once-blue heavens gradually transform, ";
  @State maxLineList: (number | undefined)[] = [2, 6, undefined];
  @State maxLineIndex: number = 0;
  @State maxLineStringList: (string)[] = ["2", "6", "undefined"];
  richEditorStyledString: MutableStyledString = new MutableStyledString("");
  controller1: RichEditorController = new RichEditorController();
  controller2: TextInputController = new TextInputController();
  controller3: RichEditorController = new RichEditorController();
  controller4: RichEditorStyledStringController = new RichEditorStyledStringController();
  controller: RichEditorController = new RichEditorController();
  option: RichEditorOptions = { controller: this.controller };

  build() {
    Column() {
      Text("Current maxLength value: 7")
        .margin(10)
        .fontSize(25)
      Row() {
        Button("Insert 1-Character Image")
          .onClick(() => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller1.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["57px", "57px"]
                }
              })
          })
        Button("Insert 2-Character Image")
          .onClick(() => {
            this.controller1.addSymbolSpan($r("sys.symbol.ohos_trash"),
              {
                style:
                {
                  fontSize: 30
                }
              })
          })
          .margin({ left: 20 })
      }

      RichEditor({ controller: this.controller1 })
        .width('95%')
        .margin(10)
        .height(60)
        .maxLength(7)
        .backgroundColor('rgb(240,250,255)')
      Text("Current maxLine value: " + this.maxLineStringList[this.maxLineIndex]).margin(10)
        .fontSize(25)
      Button("Change maxLines").onClick(() => {
        this.maxLineIndex++;
        if (this.maxLineIndex > this.maxLineList.length - 1) {
          this.maxLineIndex = 0;
        }
      })
      RichEditor({ controller: this.controller3 })
        .onReady(() => {
          this.controller3.addTextSpan(this.text,
            {
              style:
              {
                fontColor: 'rgb(0,74,175)'
              }
            })
        })
        .margin(10)
        .width('95%')
        .maxLines(this.maxLineList[this.maxLineIndex])
        .backgroundColor('rgb(240,250,255)')
    }
  }
}

StyledString

Example 27: Setting the URL Style for Text

This example demonstrates how to implement text hyperlink using UrlStyle, which is supported by the addTextSpan and updateSpanStyle APIs. When users tap the formatted text, the app navigates to the specified URL. This feature is available since API version 19.

// xxx.ets

@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };

  build() {
    Column() {
      Row() {
        Button("Add Example Url").onClick(() => {
          this.controller.addTextSpan("Example URL", {
            urlStyle: { url: "https://www.example.com" }
          })
        })
        Button("Clear Url").onClick(() => {
          this.controller.updateSpanStyle({
            start: 0,
            textStyle: {},
            urlStyle: { url: "" }
          })
        })
      }

      Row() {
        RichEditor(this.options)
          .height('35%')
          .border({ width: 1, color: Color.Blue })
      }
    }
  }
}

UrlStyle

Example 28: Configuring Style Behavior for Undo Operations

This example demonstrates how to retain original content styles upon undo operations for RichEditor components that do not use styled strings. You can enable this behavior by setting undoStyle (available since API version 20) to UndoStyle.KEEP_STYLE.

// xxx.ets

@Entry
@Component
struct StyledUndo {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  private start: number = 0;
  private end: number = 0;
  @State undoStyle: UndoStyle = UndoStyle.KEEP_STYLE;
  build() {
    Column() {
      Column() {
        Row({space:2}) {
          Button("Insert Text").onClick () => {
            this.controller.addTextSpan("Insert text",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 32
                }
              })
          })
          Button("Insert Image").onClick () => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["100px", "100px"]
                }
              });
          })
          Button("Insert Symbol").onClick(() => {
            this.controller.addSymbolSpan($r("sys.symbol.ohos_trash"),
              {
                style:
                {
                  fontSize: 32
                }
              });
          })
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .justifyContent(FlexAlign.Center)
        .width("100%")
        .height("10%")
        Row({space:2}) {
          Button("Update Selection Style").onClick(() => {
            if (this.start < this.end) {
              this.controller.updateSpanStyle({
                start: this.start,
                end: this.end,
                textStyle:
                {
                  fontColor: Color.Red,
                  fontWeight: FontWeight.Bolder
                }
              });
            }
          })
          Button("Delete Selection").onClick(() => {
            if (this.start < this.end) {
              this.controller.deleteSpans({
                start: this.start,
                end: this.end
              })
            }
          })
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .justifyContent(FlexAlign.Center)
        .width("100%")
        .height("10%")
        Row({space:2}) {
          Button("Clear Style on Undo").onClick(() => {
            this.undoStyle = UndoStyle.CLEAR_STYLE;
          })
          Button("Retain Style on Undo").onClick(() => {
            this.undoStyle = UndoStyle.KEEP_STYLE;
          })
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .justifyContent(FlexAlign.Center)
        .width("100%")
        .height("10%")
      }
      Column() {
        RichEditor(this.options)
          .onReady(()=>{
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
            {
              imageStyle:
              {
                size: ["100px", "100px"]
              }
            });
            this.controller.addTextSpan("Initialize the mixed content of text and images.",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 32
                }
              })
            this.controller.addSymbolSpan($r("sys.symbol.ohos_trash"),
              {
                style:
                {
                  fontSize: 32
                }
              });
          })
          .undoStyle(this.undoStyle)
          .onSelect((value: RichEditorSelection) => {
            this.start = value.selection[0];
            this.end = value.selection[1];
          })
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height("50%")
      }
    }
  }
}

UndoStyle

Example 29: Setting the Preset Paragraph Style

This example demonstrates how to set the preset paragraph style using the setTypingParagraphStyle API, available since API version 20.

@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController()
  options: RichEditorOptions = { controller: this.controller }
  ssController: RichEditorStyledStringController = new RichEditorStyledStringController()
  ssOptions: RichEditorStyledStringOptions = { controller: this.ssController }
  contentChangedListener: StyledStringChangedListener = {
    onWillChange: (value: StyledStringChangeValue) => {
      let range = '[ ' + value.range.start + ' , ' + value.range.end + ' ]';
      let replaceString = value.replacementString.getString();
      console.info('styledString, onWillChange, range=' + range);
      console.info('styledString, onWillChange, replaceString=' + replaceString);
      let styles: Array<SpanStyle> = []
      if (replaceString.length != 0) {
        styles = value.replacementString.getStyles(0, replaceString.length, StyledStringKey.PARAGRAPH_STYLE)
      }
      styles.forEach((style) => {
        let value = style.styledValue
        let paraStyle: ParagraphStyle = value as ParagraphStyle
        if (paraStyle != undefined) {
          console.info('styledString, onWillChange, textAlign=' + JSON.stringify(paraStyle.textAlign)
            + ', textIndent=' + JSON.stringify(paraStyle.textIndent)
            + ', maxLines=' + JSON.stringify(paraStyle.maxLines)
            + ', overflow=' + JSON.stringify(paraStyle.overflow)
            + ', wordBreak=' + JSON.stringify(paraStyle.wordBreak)
            + ', leadingMargin=' + JSON.stringify(paraStyle.leadingMargin)
            + ', paragraphSpacing=' + JSON.stringify(paraStyle.paragraphSpacing)
          );
        }
      })
      return true;
    }
  }

  build() {
    Column() {
      Row() {
        Text('ParaStyle')
        // Set the preset paragraph style to center alignment.
        Button('setStyle1').onClick(() => {
          let paragraphStyle: RichEditorParagraphStyle = {
            textAlign: TextAlign.Center
          }
          this.controller.setTypingParagraphStyle(paragraphStyle)
          this.ssController.setTypingParagraphStyle(paragraphStyle)
        })
        // Set the preset paragraph style to left alignment with indentation.
        Button('setStyle2').onClick(() => {
          let paragraphStyle: RichEditorParagraphStyle = {
            textAlign: TextAlign.Start,
            leadingMargin: 80
          }
          this.controller.setTypingParagraphStyle(paragraphStyle)
          this.ssController.setTypingParagraphStyle(paragraphStyle)
        })
        // Clear the preset paragraph style.
        Button('clearParaStyle').onClick(() => {
          this.controller.setTypingParagraphStyle(undefined)
          this.ssController.setTypingParagraphStyle(undefined)
        })
      }

      Row() {
        Column() {
          RichEditor(this.options)
            .height('25%')
            .width('100%')
            .border({ width: 1, color: Color.Blue })
            .onWillChange((value: RichEditorChangeValue) => {
              console.info('controller, onWillChange, rangeBefore=' + JSON.stringify(value.rangeBefore))
              value.replacedSpans.forEach((item: RichEditorTextSpanResult) => {
                console.info('controller, onWillChange, replacedTextSpans=' + JSON.stringify(item))
              })
              return true
            })
          RichEditor(this.ssOptions)
            .height('25%')
            .width('100%')
            .onReady(() => {
              this.ssController.onContentChanged(this.contentChangedListener);
            })
        }
      }
    }
  }
}

Example 30: Setting Text Decoration Thickness and Multiple Decorations

This example demonstrates how to use thicknessScale to set the thickness of text decoration and enableMultiType to set multiple decorations, available since API version 20.

import { LengthMetrics } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  private controller: RichEditorController = new RichEditorController();
  private styledStringController: RichEditorStyledStringController = new RichEditorStyledStringController();

  build() {
    Column({ space: 20 }) {
      RichEditor({ controller: this.controller })
        .onReady(() => {
          // Add preset text.
          this.controller.addTextSpan('Preset text. ', {
            style: {
              fontSize: 25,
              decoration: {
                type: TextDecorationType.LineThrough,
                // Set decoration thickness scale to 2.
                thicknessScale: 2
              }
            }
          })
        })

      // Set RichEditor for multiple text decorations.
      RichEditor({ controller: this.styledStringController })

      Button('Append Text (Decoration Scale 8)')
        .fontSize(20)
        .onClick(() => {
          this.controller.addTextSpan('Appended text.', {
            style: {
              fontSize: 25,
              decoration: {
                type: TextDecorationType.LineThrough,
                // Set decoration thickness scale to 8.
                thicknessScale: 8
              }
            }
          })
        })

      Button('Update Decoration Scale to 4')
        .fontSize(20)
        .onClick(() => {
          this.controller.updateSpanStyle({
            start: 0,
            end: 1000, // When the index exceeds the text length, the entire text is updated.
            textStyle: {
              decoration: {
                type: TextDecorationType.LineThrough,
                // Set decoration thickness scale to 4.
                thicknessScale: 4
              }
            }
          })
        })

      Button('Add Multi-Decoration Text')
        .fontSize(20)
        .onClick(() => {
          let mutString: MutableStyledString = new MutableStyledString('Set multiple text decorations for RichEditor.', [
            {
              start: 0,
              length: 9,
              styledKey: StyledStringKey.FONT,
              styledValue: new TextStyle({ fontSize: LengthMetrics.vp(25) })
            },
            {
              start: 0,
              length: 5,
              styledKey: StyledStringKey.DECORATION,
              styledValue: new DecorationStyle(
                {
                  type: TextDecorationType.Underline,
                },
                {
                  // Enable multiple text decorations.
                  enableMultiType: true
                }
              )
            },
            {
              start: 2,
              length: 4,
              styledKey: StyledStringKey.DECORATION,
              styledValue: new DecorationStyle(
                {
                  type: TextDecorationType.LineThrough,
                },
                {
                  // Enable multiple text decorations.
                  enableMultiType: true
                }
              )
            },
            {
              start: 4,
              length: 5,
              styledKey: StyledStringKey.DECORATION,
              styledValue: new DecorationStyle(
                {
                  type: TextDecorationType.Overline,
                },
                {
                  // Enable multiple text decorations.
                  enableMultiType: true
                }
              )
            },
          ])
          this.styledStringController.setStyledString(mutString);
        })
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

Decoration

Example 31: Enabling Automatic Spacing Between Chinese and Western Text

This example demonstrates how to configure automatic spacing between Chinese and Western characters using the enableAutoSpacing attribute, available since API version 20.

@Entry
@Component
struct AutoSpacing {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State enableAutoSpace: boolean = false;

  build() {
    Column() {
      Column() {
        Row({ space: 2 }) {
          Button ("Insert Chinese & Western Text").onClick (() ==> {
            this.controller.addTextSpan("Add a text span",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 20
                }
              })
          })
          Button("Insert Image").onClick () => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["100px", "100px"]
                }
              });
          })
          Button("Insert Symbol").onClick(() => {
            this.controller.addSymbolSpan($r("sys.symbol.ohos_trash"),
              {
                style:
                {
                  fontSize: 32
                }
              });
          })
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .justifyContent(FlexAlign.Center)
        .width("100%")
        .height("10%")

        Row({ space: 2 }) {
          Button("Enable Auto Spacing").onClick(() => {
            this.enableAutoSpace = true;
          })
          Button("Disable Auto Spacing").onClick(() => {
            this.enableAutoSpace = false;
          })
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .justifyContent(FlexAlign.Center)
        .width("100%")
        .height("10%")
      }

      Column() {
        RichEditor(this.options)
          .onReady(() => {
            // Replace $r('app.media.startIcon') with the image resource file you use.
            this.controller.addImageSpan($r('app.media.startIcon'),
              {
                imageStyle:
                {
                  size: ["100px", "100px"]
                }
              });
            this.controller.addTextSpan("Auto spacing between Chinese and Western text",
              {
                style:
                {
                  fontColor: Color.Orange,
                  fontSize: 20
                }
              })
            this.controller.addSymbolSpan($r("sys.symbol.ohos_trash"),
              {
                style:
                {
                  fontSize: 20
                }
              });
          })
          .enableAutoSpacing(this.enableAutoSpace)
          .borderWidth(1)
          .borderColor(Color.Green)
          .width("100%")
          .height("50%")
      }
    }
  }
}

AutoSpacing

Example 32: Setting an AI Menu for Text Selection

This example demonstrates how to configure the AI menu for text selection using the enableSelectedDataDetector API, available since API version 22.

@Entry
@Component
struct Demo32 {
  controller: RichEditorController = new RichEditorController();
  textSpanOptions: RichEditorTextSpanOptions = { style: { fontSize: 20 } };
  exampleText: string ='Example website: www.example.com';

  build() {
    Column() {
      Row() {
        RichEditor({ controller: this.controller })
          .onReady(() => {
            this.controller.addTextSpan(this.exampleText, this.textSpanOptions)
          })
          .copyOptions(CopyOptions.LocalDevice)
          .enableSelectedDataDetector(true)
          .border({ width: 1, color: Color.Black })
          .height(300)
          .margin(10)
      }
    }
  }
}

Example 33: Listening for the Input Method Binding Event

This example demonstrates how to use the onWillAttachIME event to listen for the input method binding event, available since API version 22.

@Entry
@Component
struct SetOnWillAttachIME {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };
  @State message: string = "RichEditor Not Bound to an Input Method"

  build() {
    Column() {
     Text(this.message)
       .fontSize(24)
       .width("100%")
       .textAlign(TextAlign.Center)
      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan("RichEditor component",
            {
              style:
              {
                fontColor: Color.Orange,
                fontSize: 30
              }
            })
        })
        .onWillAttachIME((value:IMEClient) => {
          // Pass a custom message to the input method.
          const inputConfig: InputMethodExtraConfig = {
            customSettings: {
              component: 'RichEditor',
              id: 8 as number,
              isEnable: true
            }
          };
          value.setExtraConfig(inputConfig);
          this.message = "RichEditor Bound to an Input Method"
        })
        .borderWidth(1)
        .borderColor(Color.Green)
        .width("100%")
        .height("20%")
    }
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

OnWillAttachIME

Example 34: Deleting the Character at the End of the Text Box

This example demonstrates how to call deleteBackward to delete the character before the caret in the editing state with a custom keyboard, available since API version 23.

@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();

  // Set the delete button on the custom keyboard.
  @Builder
  CustomKeyboardBuilder() {
    Column() {
      Button('DELETE')
        .width(200)
        .height(60)
        .backgroundColor(Color.Blue)
        .fontColor(Color.White)
        .fontSize(16)
        .onClick(() => {
          // Call deleteBackward to delete the character.
          this.controller.deleteBackward()
        })
    }
    .padding(10)
    .backgroundColor(Color.Gray)
  }

  build() {
    Column() {
      Blank()
        .height(400)
      RichEditor({ controller: this.controller })
        .customKeyboard(this.CustomKeyboardBuilder())
        .margin(10)
        .border({ width: 1 })
        .height(150)
        .borderWidth(1)
        .borderColor(Color.Blue)
        .width("100%")
        .onReady(() => {
          // Set the initial text for testing.
          this.controller.addTextSpan ('Click DELETE to test the deletion function', {
            style: {
              fontColor: Color.Black,
              fontSize: 16
            }
          })
        })
    }.margin(90)
  }
}

DeleteBackward

Example 35: Optimizing the Display of Minority Languages

This example uses the includeFontPadding attribute to add font padding at the top of the first line and the bottom of the last line of text. It also uses the fallbackLineSpacing attribute to implement adaptive line spacing which adjusts dynamically according to the actual text height.

The includeFontPadding and fallbackLineSpacing attributes are added since API version 23.

@Entry
@Component
struct RichEditorExample {
  controller: RichEditorController = new RichEditorController();
  @State fallbackLineSpacing: boolean = true;
  @State includeFontPadding: boolean = true;

  build() {
    Column() {
      RichEditor({ controller: this.controller })
        .onReady(() => {
          this.controller.addTextSpan('བོད་ཀྱི་སྐད་ཡིག་ནི་བོད་མིའི་རྒྱུན་ལྡན་པའི་སྐད་ཡིག་དང་།\n འཇིག་རྟེན་གྱི་ཆོས་ལུགས་དང་རྒྱུན་ལྡན་པའི་ཆོས་ལུགས་ཀྱི་དོན་ཚན་གྱི་སྐད་ཡིག་རེད།\n',
            {
              style: {
                fontColor: Color.Black,
                fontSize: "30",
                lineHeight: 10
              },
              paragraphStyle: {
                textAlign: TextAlign.Start,
              }
            })
          this.controller.addTextSpan('བོད་ཀྱི་སྐད་ཡིག་ནི་བོད་མིའི་རྒྱུན་ལྡན་པའི་སྐད་ཡིག་དང་།\n འཇིག་རྟེན་གྱི་ཆོས་ལུགས་དང་རྒྱུན་ལྡན་པའི་ཆོས་ལུགས་ཀྱི་དོན་ཚན་གྱི་སྐད་ཡིག་རེད།',
            {
              style: {
                fontColor: Color.Black,
                fontSize: "30",
              },
              paragraphStyle: {
                textAlign: TextAlign.Start,
              }
            })
        })
        .width("100%")
        .height("35%")
        .border({ width: 1, radius: 5 })
        .draggable(false)
        .includeFontPadding(this.includeFontPadding)
        .fallbackLineSpacing(this.fallbackLineSpacing)
      Row() {
        Button ('Enable Adaptive Line Spacing')
          .onClick(() => {
            this.fallbackLineSpacing = true
          })
          .width("45%")
          .height("10%")
          .margin({ right: 10 })
        Button ('Disable Adaptive Line Spacing')
          .onClick(() => {
            this.fallbackLineSpacing = false
          })
          .width("45%")
          .height("10%")
          .margin({ left: 5 })
      }
      .margin({ top: 20 })

      Row() {
        Button ('Enable Font Padding')
          .onClick(() => {
            this.includeFontPadding = true
          })
          .width("45%")
          .height("10%")
          .margin({ right: 10 })
        Button ('Disable Font Padding')
          .onClick(() => {
            this.includeFontPadding = false
          })
          .width("45%")
          .height("10%")
          .margin({ left: 5 })
      }
      .margin({ top: 20 })
    }
  }
}

richEditorIncludeFontPadding

Example 36: Enabling Leading Punctuation Compression

This example demonstrates how to compress punctuation at the beginning of a line using the compressLeadingPunctuation attribute.

The compressLeadingPunctuation attribute is added since API version 23.

@Entry
@Component
struct CompressLeadingPunctuationDemo {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };

  @State compressLeadingPunctuation: boolean = false;
  @State text: string = '「0123456789\n『0123456789\n(0123456789\n《0123456789\n〈0123456789\n【0123456789\n〖0123456789\n〔0123456789\n[0123456789\n{0123456789';

  build() {
    Column() {
      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan(this.text)
        })
        .compressLeadingPunctuation(this.compressLeadingPunctuation)
        .borderWidth(1)
        .borderColor(Color.Green)
        .align(Alignment.Center)
        .height("30%")
        .width("50%")

      Column() {
        Button("Enable Leading Punctuation Compression").onClick(() => {
          this.compressLeadingPunctuation = true
        }).margin({ top: 10 })
        Button ("Disable Leading Punctuation Compression").onClick (() => {
          this.compressLeadingPunctuation = false
        }).margin({ top: 10 })
      }
    }.width("100%").padding(20)
  }
}

CompressLeadingPunctuation

Example 37: Setting the Drag Preview Style

This example demonstrates how to set the drag preview style using the selectedDragPreviewStyle API.

The selectedDragPreviewStyle API is supported since API version 23.

@Entry
@Component
struct RichEditorDemo {
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller };

  build() {
    Column({ space: 2 }) {
      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan('RichEditor selectedDragPreviewStyle')
        })
        .borderWidth(1)
        .borderColor(Color.Green)
        .draggable(true)
        .selectedDragPreviewStyle({ color: Color.Gray })
        .width('100%')
        .height('20%')
    }
  }
}

DeleteBackward

Example 38: Setting Single-Line Mode

This example demonstrates how to set single-line mode using the singleLine API.

The singleLine API is added since API version 23.

@Entry
@Component
struct SingleLineDemo {
  controller: RichEditorController = new RichEditorController();
  textSpanOptions: RichEditorTextSpanOptions = { style: { fontSize: 30 } };
  exampleText: string = 'This is a sample text.\nThis is a sample text.\nThis is a sample text.';
  @State enableSingleLine: boolean = false;

  build() {
    Column() {
      Row() {
        RichEditor({ controller: this.controller })
          .onReady(() => {
            this.controller.addTextSpan(this.exampleText, this.textSpanOptions)
          })
          .singleLine(this.enableSingleLine)
          .border({ width: 1, color: Color.Black })
          .margin(10)
      }
      Row() {
        Button('Apply Single-Line Mode').onClick((event: ClickEvent) => {
          this.enableSingleLine = true
        }).margin(5)
        Button('Apply Multi-Line Mode').onClick((event: ClickEvent) => {
          this.enableSingleLine = false
        }).margin(5)
      }
    }
  }
}

SingleLine

Example 39: Setting the Placeholder Text of the Styled String

This example demonstrates how to set the placeholder text of the styled string using the setStyledPlaceholder API.

The setStyledPlaceholder API is added since API version 24.

import { LengthMetrics } from '@kit.ArkUI';

@Entry
@Component
struct RichEditorExample {
  styledString: MutableStyledString = new MutableStyledString("Placeholder: text",
    [
      {
        start: 0,
        length: 12,
        styledKey: StyledStringKey.FONT,
        styledValue: new TextStyle({
          fontColor: Color.Orange,
          fontSize: LengthMetrics.fp(24)
        })
      },
      {
        start: 12,
        length: 4,
        styledKey: StyledStringKey.FONT,
        styledValue: new TextStyle({
          fontColor: Color.Gray,
          fontSize: LengthMetrics.fp(20),
          fontWeight: FontWeight.Bold
        })
      },
      {
        start: 0,
        length: 1,
        styledKey: StyledStringKey.PARAGRAPH_STYLE,
        styledValue: new ParagraphStyle({
          textVerticalAlign: TextVerticalAlign.CENTER
        })
      }
    ]);
  imageStyledString = new MutableStyledString(new ImageAttachment(
    {
      // Replace $r('app.media.startIcon') with the image resource file you use.
      resourceValue: $r('app.media.startIcon'),
      size: { width: 50, height: 50 },
      verticalAlign: ImageSpanAlignment.BASELINE,
      objectFit: ImageFit.Fill
    } as ResourceImageAttachmentOptions
  ));

  controller: RichEditorController = new RichEditorController();

  aboutToAppear() {
    this.styledString.appendStyledString(this.imageStyledString);
    this.controller.setStyledPlaceholder(this.styledString)
  }

  build() {
    Column() {
      Text("RichEditor Placeholders Support Rich Text Styles")
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
      RichEditor({ controller: this.controller })
        .width('80%')
        .height('20%')
        .margin(10)
        .borderWidth(1)
        .borderColor(Color.Blue)
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('70%')
  }
}

setStyledPlaceholder

Example 40: Enabling/Disabling Orphan Character Optimization

This example demonstrates how to use the orphanCharOptimization API to enable/disable orphan character optimization, ensuring no orphan character appears in the last line of a paragraph.

The orphanCharOptimization API is supported since API version 26.0.0.

// xxx.ets
@Entry
@Component
struct RichEditorDemo {
  controller1: RichEditorController = new RichEditorController();
  controller2: RichEditorController = new RichEditorController();
  @State text: string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa文本';
  textSpanOptions: RichEditorTextSpanOptions = { style: { fontSize: 20 } };

  build() {
    Column({ space: 10 }) {
      Text('orphanCharOptimization: true')
        .fontSize(12).width('90%')
      RichEditor({ controller: this.controller1 })
        .onReady(() => {
          this.controller1.addTextSpan(this.text, this.textSpanOptions)
        })
        .orphanCharOptimization(true)
        .width(430)
        .borderWidth(1)

      Divider()

      Text('orphanCharOptimization: false')
        .fontSize(12).width('90%')

      RichEditor({ controller: this.controller2 })
        .onReady(() => {
          this.controller2.addTextSpan(this.text, this.textSpanOptions)
        })
        .orphanCharOptimization(false)
        .width(430)
        .borderWidth(1)
    }
    .width('100%')
    .height('100%')
  }
}

orphanCharOptimization

Example 41: Setting Horizontal Scrolling

This example demonstrates how to set horizontal scrolling using horizontalScrolling.

The horizontalScrolling API is added since API version 26.0.0.

// xxx.ets
@Entry
@Component
struct HorizontalScrollDemo {
  controller: RichEditorController = new RichEditorController();
  textSpanOptions: RichEditorTextSpanOptions = { style: { fontSize: 30 } };
  exampleText: string = 'This is an ultra-long sample text.\nThis is an ultra-long sample text.';
  @State enableHorizontalScroll: boolean = false;

  build() {
    Column() {
      Row() {
        RichEditor({ controller: this.controller })
          .onReady(() => {
            this.controller.addTextSpan(this.exampleText, this.textSpanOptions)
          })
          .width('220vp')
          .height('160vp')
          .horizontalScrolling(this.enableHorizontalScroll)
          .border({ width: 1, color: Color.Black })
          .margin(10)
      }
      Row() {
        Button('Enable Horizontal Scroll').onClick((event: ClickEvent) => {
          this.enableHorizontalScroll = true
        }).margin(5)
        Button('Disable Horizontal Scroll').onClick((event: ClickEvent) => {
          this.enableHorizontalScroll = false
        }).margin(5)
      }
    }
  }
}

enableHorizontalScroll