diff --git a/zh-cn/application-dev/reference/arkui-ts/figures/richEditorSelectionMenu.png b/zh-cn/application-dev/reference/arkui-ts/figures/richEditorSelectionMenu.png
new file mode 100644
index 0000000000000000000000000000000000000000..65f3eed0f6d6fe979f591084ea8af69e1c82405a
Binary files /dev/null and b/zh-cn/application-dev/reference/arkui-ts/figures/richEditorSelectionMenu.png differ
diff --git a/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-richeditor.md b/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-richeditor.md
index 394eaf61e4f401e2f3e08b1db250434305ed8f70..1a4950ef1a5e42e5c577b688740efa9cd057ccb5 100644
--- a/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-richeditor.md
+++ b/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-richeditor.md
@@ -33,9 +33,9 @@ RichEditor(value: RichEditorOptions)
| 名称 | 参数类型 | 描述 |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| customKeyboard10+ | [CustomBuilder](ts-types.md#custombuilder8) | 设置自定义键盘。
**说明:**
当设置自定义键盘时,输入框激活后不会打开系统输入法,而是加载指定的自定义组件。
自定义键盘的高度可以通过自定义组件根节点的height属性设置,宽度不可设置,使用系统默认值。
自定义键盘采用覆盖原始界面的方式呈现,不会对应用原始界面产生压缩或者上提。
自定义键盘无法获取焦点,但是会拦截手势事件。
默认在输入控件失去焦点时,关闭自定义键盘。 |
-
-
+| customKeyboard | [CustomBuilder](ts-types.md#custombuilder8) | 设置自定义键盘。
**说明:**
当设置自定义键盘时,输入框激活后不会打开系统输入法,而是加载指定的自定义组件。
自定义键盘的高度可以通过自定义组件根节点的height属性设置,宽度不可设置,使用系统默认值。
自定义键盘采用覆盖原始界面的方式呈现,不会对应用原始界面产生压缩或者上提。
自定义键盘无法获取焦点,但是会拦截手势事件。
默认在输入控件失去焦点时,关闭自定义键盘。 |
+| bindSelectionMenu | {
spantype: [RichEditorSpanType](#richeditorspantype),
content: [CustomBuilder](ts-types.md#custombuilder8),
responseType: [ResponseType](ts-appendix-enums.md#responsetype8),
options?: [SelectionMenuOptions](#selectionmenuoptions)
} | 设置自定义选择菜单。
默认值:{
spanType: RichEditorSpanType:TEXT
responseType: ResponseType.LongPress
其他:空
}
**说明:**
当前spanType参数设置不会生效,不区分类型。|
+| copyOption | [CopyOptions](ts-appendix-enums.md#copyoptions9) | 组件支持设置内容是否可复制粘贴。
默认值:CopyOptions.LocalDevice
**说明:**
设置copyOptions为CopyOptions.InApp或者CopyOptions.LocalDevice,长按组件内容,会弹出文本默认选择菜单,可选中内容并进行复制、全选操作。
设置copyOptions为CopyOptions.None,复制、剪切功能不生效。 |
## 事件
除支持[通用事件](ts-universal-events-click.md)外,还支持以下事件:
@@ -100,6 +100,16 @@ Span位置信息。
| spanIndex | number | 是 | Span索引值。 |
| spanRange | [number, number] | 是 | Span内容在RichEditor内的起始和结束位置。 |
+## RichEditorSpanType
+
+Span类型信息。
+
+| 名称 | 类型 | 必填 | 说明 |
+| -------- | -------- | -------- | -------- |
+| TEXT | number | 是 | Span为文字类型。 |
+| IMAGE | number | 是 | Span为图像类型。|
+| MIXED | number | 是 | Span为图文混合类型。|
+
## RichEditorTextStyleResult
@@ -257,6 +267,11 @@ deleteSpans(value?: RichEditorRange): void
| ------ | -------- | ---- | -------------------------------------- |
| value | [RichEditorRange](#richeditorrange) | 否 | 删除范围。省略时,删除所有文本和图片。|
+### closeSelectionMenu
+
+closeSelectionMenu(): void
+
+关闭自定义选择菜单或系统默认选择菜单。
## RichEditorSelection
@@ -341,6 +356,15 @@ deleteSpans(value?: RichEditorRange): void
| start | number | 否 | 起始位置,省略或者设置负值时表示从0开始。 |
| end | number | 否 | 结束位置,省略或者超出文本范围时表示到结尾。 |
+## SelectionMenuOptions
+
+范围信息。
+
+| 名称 | 类型 | 必填 | 描述 |
+| ------ | -------- | ---- | -------------------------------------- |
+| onAppear | ?(() => void) | 否 | 自定义选择菜单弹出时回调。 |
+| onDisappear | ?(() => void) | 否 | 自定义选择菜单关闭时回调。 |
+
## 示例
@@ -538,4 +562,362 @@ struct RichEditorExample {
}
```
-
\ No newline at end of file
+
+
+### 示例3
+
+```ts
+// xxx.ets
+import pasteboard from '@ohos.pasteboard'
+
+@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 }
+ private iconArr: Array =
+ [$r('app.media.icon'), $r("app.media.icon"), $r('app.media.icon'),
+ $r("app.media.icon"), $r('app.media.icon')]
+ private listArr: Array<{
+ imageSrc: Resource,
+ id: string,
+ label: string
+ }> =
+ [{ imageSrc: $r('sys.media.ohos_ic_public_cut'), id: '剪切', label: "Ctrl+X" },
+ { imageSrc: $r('sys.media.ohos_ic_public_copy'), id: '复制', label: "Ctrl+C" },
+ { imageSrc: $r('sys.media.ohos_ic_public_paste'), id: '粘贴', label: "Ctrl+V" },
+ { imageSrc: $r('sys.media.ohos_ic_public_select_all'), id: '全选', label: "Ctrl+A" },
+ { imageSrc: $r('sys.media.ohos_ic_public_share'), id: '分享', label: "" },
+ { imageSrc: $r('sys.media.ohos_ic_public_translate_c2e'), id: '翻译', label: "" },
+ { imageSrc: $r('sys.media.ohos_ic_public_search_filled'), id: '搜索', label: "" }]
+ @State iconBgColor: ResourceColor[] = new Array(this.iconArr.length).fill(this.colorTransparent)
+ @State listBgColor: ResourceColor[] = new Array(this.listArr.length).fill(this.colorTransparent)
+ @State iconIsFocus: boolean[] = new Array(this.iconArr.length).fill(false)
+ @State listIsFocus: boolean[] = new Array(this.iconArr.length).fill(false)
+ @State clickWeightNum: number = 0
+ @State clickNum: number[] = [0, 0, 0]
+
+ build() {
+ Column() {
+ Column() {
+ RichEditor(this.options)
+ .onReady(() => {
+ this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
+ })
+ .onSelect((value: RichEditorSelection) => {
+ if (value.selection == [-1. - 1]) return
+ [this.start, this.end] = value.selection
+ })
+ .bindSelectionMenu(RichEditorSpanType.TEXT, this.panel(), ResponseType.LongPress, { onDisappear: () => {
+ this.sliderShow = false
+ }})
+ .borderWidth(1)
+ .borderColor(Color.Red)
+ .width(200)
+ .height(200)
+ .position({ x: 150, y: 100 })
+ }.width('100').backgroundColor(Color.White)
+ }.height('100')
+ }
+
+ @Builder
+ panel() {
+ Column() {
+ Menu() {
+ MenuItem({ builder: this.iconPanel() })
+ }.shadow(ShadowStyle.OUTER_DEFAULT_MD).margin({ bottom: 8 })
+
+ Menu() {
+ if (!this.sliderShow) {
+ MenuItem({ builder: this.listPanel() })
+ } else {
+ MenuItem({ builder: this.sliderPanel() })
+ }
+ }
+ .backgroundColor(Color.Transparent).focusable(true).shadow(ShadowStyle.OUTER_DEFAULT_MD)
+ }
+ }
+
+ @Builder iconPanel() {
+ Column() {
+ Row({ space: 2 }) {
+ ForEach(this.iconArr, (item, index) => {
+ Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
+ Image(item).fillColor($r('sys.color.ohos_id_color_primary')).width(24).height(24).focusable(true)
+ }
+ .border({ width: this.iconIsFocus[index] ? 2 : 0, color: $r('sys.color.ohos_id_color_focused_outline') })
+ .borderRadius($r('sys.float.ohos_id_corner_radius_default_m'))
+ .width(48)
+ .height(48)
+ .focusable(true)
+ .focusOnTouch(true)
+ .onClick(() => {
+ if (index == 0) {
+ this.clickNum[0]++
+ this.sliderShow = false
+ this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: {
+ fontWeight: this.clickNum[0] % 2 !== 0 ? FontWeight.Bolder : FontWeight.Normal
+ } })
+ } else if (index == 1) {
+ this.clickNum[1]++
+ this.sliderShow = false
+ this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: {
+ fontStyle: this.clickNum[1] % 2 !== 0 ? FontStyle.Italic : FontStyle.Normal
+ } })
+ } else if (index == 2) {
+ this.clickNum[2]++
+ this.sliderShow = false
+ this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: {
+ decoration: {
+ type: this.clickNum[2] % 2 !== 0 ? TextDecorationType.Underline : TextDecorationType.None
+ } } })
+ } else if (index == 3) {
+ this.sliderShow = !this.sliderShow
+ } else if (index == 4) {
+ this.sliderShow = false
+ }
+ })
+ .onTouch((event: TouchEvent) => {
+ if (event.type === TouchType.Down) {
+ this.iconBgColor[index] = $r('sys.color.ohos_id_color_click_effect')
+ }
+ if (event.type === TouchType.Up) {
+ this.iconBgColor[index] = this.colorTransparent
+ }
+ })
+ .onHover((isHover: boolean) => {
+ this.iconBgColor.forEach((icon, index1) => {
+ this.iconBgColor[index1] = this.colorTransparent
+ })
+ isHover ? this.iconBgColor[index] = $r('sys.color.ohos_id_color_hover') :
+ this.listBgColor[index] = this.colorTransparent
+ })
+ .onFocus(() => {
+ this.iconIsFocus[index] = true
+ })
+ .onBlur(() => {
+ this.iconIsFocus[index] = false
+ })
+ .backgroundColor(this.iconBgColor[index])
+ })
+ }
+ }
+ .backgroundColor(this.colorTransparent)
+ .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
+ .width(256)
+ .height(56)
+ .padding(4)
+ }
+
+ @Builder listPanel() {
+ Column() {
+ List({ space: 0, initialIndex: 0 }) {
+ ForEach(this.listArr, (item, index) => {
+ ListItem() {
+ listChild({
+ item,
+ index,
+ listBgColor: $listBgColor,
+ colorTransparent: $colorTransparent
+ })
+ .onClick(() => {
+ let sysBoard = pasteboard.getSystemPasteboard()
+ this.controller.closeSelectionMenu()
+ let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '')
+ this.controller.getSpans({ start: this.start, end: this.end })
+ .forEach((item, i) => {
+ if ("imageStyle" in item) {
+ var style = item.imageStyle
+ let data = pasteboard.createRecord(pasteboard.MIMETYPE_PIXELMAP, item.valuePixelMap)
+ let prop = pasteData.getProperty()
+ prop.additions[i] = { 'width': style.size[0], 'height': style.size[1], 'fit': style.objectFit }
+ pasteData.addRecord(data)
+ pasteData.setProperty(prop)
+ } else {
+ let style = item.textStyle
+ let data = pasteboard.createRecord(pasteboard.MIMETYPE_TEXT_PLAIN, item.value)
+ let prop = pasteData.getProperty()
+ prop.additions[i] = { 'color': style.fontColor, 'size': style.fontSize, 'style': style.fontStyle,
+ 'weight': style.fontWeight }
+ pasteData.addRecord(data)
+ pasteData.setProperty(prop)
+ }
+ })
+ switch (index) {
+ case 0:
+ this.controller.deleteSpans({ start: this.start, end: this.end })
+ case 1:
+ sysBoard.clearData()
+ sysBoard.setData(pasteData).then(() => {
+ console.info('Succeeded in setting PasteData.');
+ }).catch((err) => {
+ console.error('Failed to set PasteData. Cause: ' + err.message);
+ })
+ break
+ case 2:
+ sysBoard.getData((err, data) => {
+ if (err) {
+ return
+ }
+ var count = data.getRecordCount()
+ for (let m = 0; m < count; m++) {
+ const element = data.getRecord(m);
+ let tex: RichEditorTextStyle = {
+ fontSize: 30,
+ fontColor: Color.Orange,
+ fontWeight: FontWeight.Normal
+ }
+ let im: RichEditorImageSpanStyle = { objectFit: ImageFit.Contain, size: [50, 50] }
+ if (data.getProperty().additions[m]) {
+ const entry = Object.entries(data.getProperty().additions[m])
+ for (let [key, value] of entry) {
+ switch (key) {
+ case 'width':
+ im.size[0] = value
+ continue
+ case 'height':
+ im.size[1] = value
+ continue
+ case 'fit':
+ im.objectFit = value
+ continue
+ case 'color':
+ tex.fontColor = value
+ continue
+ case 'size':
+ tex.fontSize = value
+ continue
+ case 'style':
+ tex.fontStyle = value
+ continue
+ case 'weight':
+ tex.fontWeight = value
+ }
+ }
+ }
+
+ if (element.mimeType == pasteboard.MIMETYPE_TEXT_PLAIN) {
+ this.controller.addTextSpan(element.plainText,
+ {
+ style: tex,
+ offset: this.controller.getCaretOffset()
+ })
+ }
+ if (element.mimeType == pasteboard.MIMETYPE_PIXELMAP) {
+ this.controller.addImageSpan(element.pixelMap,
+ {
+ imageStyle: im,
+ offset: this.controller.getCaretOffset()
+ })
+ }
+ }
+ })
+ break
+ case 3: // 全选
+ }
+ })
+ }
+ .height(48)
+ .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
+ .focusable(true)
+ .focusOnTouch(true)
+ .border({ width: this.listIsFocus[index] ? 2 : 0, color: $r('sys.color.ohos_id_color_focused_outline') })
+ .onFocus(() => {
+ this.listIsFocus[index] = true
+ })
+ .onBlur(() => {
+ this.listIsFocus[index] = false
+ })
+ }, item => item)
+ }
+ }
+ .focusable(true)
+ .width(256)
+ .padding(4)
+ .backgroundColor(this.colorTransparent)
+ .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
+ }
+
+ @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) => {
+ this.textSize = value
+ this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: { fontSize: this.textSize }
+ })
+ })
+ Text('A').fontSize(20).fontWeight(FontWeight.Medium)
+ }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
+ }
+ .backgroundColor(this.colorTransparent)
+ .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
+ .padding(15)
+ .width(256)
+ .height(56)
+ .margin({ bottom: 8 })
+ }
+}
+
+@Component
+struct listChild {
+ item
+ index
+ @Link listBgColor: (Resource | Color)[]
+ @Link colorTransparent: Resource
+
+ build() {
+ Column() {
+ Flex({
+ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center
+ }) {
+ Row() {
+ Image(this.item.imageSrc)
+ .width(20)
+ .height(20)
+ .margin({ right: 8 })
+ .fillColor($r('sys.color.ohos_id_color_primary'))
+ .focusable(true)
+ Text('' + this.item.id)
+ .textAlign(TextAlign.Center)
+ .borderRadius(10)
+ .focusable(true)
+ .fontColor($r('sys.color.ohos_id_color_primary'))
+ .fontSize($r('sys.float.ohos_id_text_size_body1'))
+ }
+
+ Row() {
+ Text('' + this.item.label)
+ .fontColor($r('sys.color.ohos_id_color_text_secondary')).fontSize($r('sys.float.ohos_id_text_size_body1'))
+ }
+ }
+ .onTouch((event: TouchEvent) => {
+ if (event.type === TouchType.Down) {
+ this.listBgColor[this.index] = $r('sys.color.ohos_id_color_click_effect')
+ }
+ if (event.type === TouchType.Up) {
+ this.listBgColor[this.index] = this.colorTransparent
+ }
+ })
+ .onHover((isHover: boolean) => {
+ this.listBgColor[this.index] = isHover ? $r('sys.color.ohos_id_color_hover') : this.colorTransparent
+ })
+ .backgroundColor(this.listBgColor[this.index])
+ .padding({ right: 12, left: 12 })
+ .height('48')
+ .focusable(true)
+ .borderRadius($r('sys.float.ohos_id_corner_radius_default_m'))
+ }
+ }
+}
+```
+
+
\ No newline at end of file