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 { } ``` -![customKeyboard](figures/richEditorCustomKeyboard.png) \ No newline at end of file +![customKeyboard](figures/richEditorCustomKeyboard.png) + +### 示例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')) + } + } +} +``` + +![selectionMenu](figures/richEditorSelectionMenu.png) \ No newline at end of file