diff --git a/client/packages/lowcoder-design/src/icons/icon-autocomplete-comp.svg b/client/packages/lowcoder-design/src/icons/icon-autocomplete-comp.svg new file mode 100644 index 000000000..dd882963a --- /dev/null +++ b/client/packages/lowcoder-design/src/icons/icon-autocomplete-comp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/packages/lowcoder-design/src/icons/index.ts b/client/packages/lowcoder-design/src/icons/index.ts index dd55eb897..05fd271e7 100644 --- a/client/packages/lowcoder-design/src/icons/index.ts +++ b/client/packages/lowcoder-design/src/icons/index.ts @@ -288,4 +288,5 @@ export { ReactComponent as ExpandIcon } from "icons/icon-expand.svg"; export { ReactComponent as CompressIcon } from "icons/icon-compress.svg"; export { ReactComponent as TableCellsIcon } from "icons/icon-table-cells.svg"; // Added By Aqib Mirza export { ReactComponent as TimeLineIcon } from "icons/icon-timeline-comp.svg" -export { ReactComponent as LottieIcon } from "icons/icon-lottie.svg"; \ No newline at end of file +export { ReactComponent as LottieIcon } from "icons/icon-lottie.svg"; +export { ReactComponent as AutoCompleteCompIcon } from "icons/icon-autocomplete-comp.svg"; \ No newline at end of file diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index 4220f810e..278628768 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -42,6 +42,7 @@ "axios": "^0.21.1", "buffer": "^6.0.3", "clsx": "^1.2.1", + "cnchar": "^3.2.4", "copy-to-clipboard": "^3.3.3", "core-js": "^3.25.2", "echarts": "^5.4.2", diff --git a/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx new file mode 100644 index 000000000..81a8244bb --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx @@ -0,0 +1,410 @@ +import React, { useEffect, useState } from "react"; +import { Input, Section, sectionNames } from "lowcoder-design"; +import { BoolControl } from "comps/controls/boolControl"; +import { styleControl } from "comps/controls/styleControl"; +import { + InputLikeStyle, + InputLikeStyleType, +} from "comps/controls/styleControlConstants"; +import { + NameConfig, + NameConfigPlaceHolder, + NameConfigRequired, + withExposingConfigs, +} from "comps/generators/withExposing"; +import styled, { css } from "styled-components"; +import { UICompBuilder } from "../../generators"; +import { FormDataPropertyView } from "../formComp/formDataConstants"; +import { jsonControl } from "comps/controls/codeControl"; +import { dropdownControl } from "comps/controls/dropdownControl"; +import { + getStyle, + TextInputBasicSection, + textInputChildren, + TextInputConfigs, + TextInputInteractionSection, + textInputValidate, + TextInputValidationSection, +} from "../textInputComp/textInputConstants"; +import { + allowClearPropertyView, + hiddenPropertyView, +} from "comps/utils/propertyUtils"; +import { trans } from "i18n"; +import { IconControl } from "comps/controls/iconControl"; +import { hasIcon } from "comps/utils"; +import { + ConfigProvider, + InputRef, + AutoComplete, + Input as AntInput, +} from "antd"; +import { RefControl } from "comps/controls/refControl"; +import { + booleanExposingStateControl, +} from "comps/controls/codeStateControl"; + +import { getMomentLocale } from "i18n/momentLocale"; +import { + autoCompleteDate, + itemsDataTooltip, + convertAutoCompleteData, + valueOrLabelOption, + autoCompleteRefMethods, + autoCompleteType, + autocompleteIconColor, + componentSize, +} from "./autoCompleteConstants"; + +// const InputStyle = styled(Input)<{ $style: InputLikeStyleType }>` +// ${(props) => props.$style && getStyle(props.$style) } +// `; + +const InputStyle = styled(Input)<{ $style: InputLikeStyleType }>` + ${(props) => css` + ${getStyle(props.$style)} + .ant-select-selection-search-input { + height: 100%; + } + input { + padding: ${props.style?.padding} + } + `} +`; + +const CustomStyledSearch = styled(AntInput.Search)<{ $style: InputLikeStyleType }>` + ${(props) => css` + padding: 0; + input.ant-input { + padding: ${props.$style?.padding}; + } + .ant-btn.ant-input-search-button { + height: 100%; + padding: ${props.$style?.padding} !important; + padding-left: 15px !important; + padding-right: 15px !important; + .ant-btn-icon { + line-height: 28px; + } + } + `} +`; + +const childrenMap = { + ...textInputChildren, + viewRef: RefControl, + allowClear: BoolControl.DEFAULT_TRUE, + style: styleControl(InputLikeStyle), + prefixIcon: IconControl, + suffixIcon: IconControl, + items: jsonControl(convertAutoCompleteData, autoCompleteDate), + ignoreCase: BoolControl.DEFAULT_TRUE, + searchFirstPY: BoolControl.DEFAULT_TRUE, + searchCompletePY: BoolControl, + searchLabelOnly: BoolControl.DEFAULT_TRUE, + valueOrLabel: dropdownControl(valueOrLabelOption, "label"), + autoCompleteType: dropdownControl(autoCompleteType, "AntDesign"), + autocompleteIconColor: dropdownControl(autocompleteIconColor, "blue"), + componentSize: dropdownControl(componentSize, "small"), + valueInItems: booleanExposingStateControl("valueInItems"), +}; + +const getValidate = (value: any): "" | "warning" | "error" | undefined => { + if ( + value.hasOwnProperty("validateStatus") && + value["validateStatus"] === "error" + ) + return "error"; + return ""; +}; + +let AutoCompleteCompBase = (function () { + return new UICompBuilder(childrenMap, (props) => { + const { + items, + onEvent, + placeholder, + searchFirstPY, + searchCompletePY, + searchLabelOnly, + ignoreCase, + valueOrLabel, + autoCompleteType, + autocompleteIconColor, + componentSize, + } = props; + + const getTextInputValidate = () => { + return { + value: { value: props.value.value }, + required: props.required, + minLength: props?.minLength ?? 0, + maxLength: props?.maxLength ?? 0, + validationType: props.validationType, + regex: props.regex, + customRule: props.customRule, + }; + }; + + const [activationFlag, setActivationFlag] = useState(false); + const [searchtext, setsearchtext] = useState(props.value.value); + const [validateState, setvalidateState] = useState({}); + + // 是否中文环境 + const [chineseEnv, setChineseEnv] = useState(getMomentLocale() === "zh-cn"); + + useEffect(() => { + setsearchtext(props.value.value); + activationFlag && + setvalidateState(textInputValidate(getTextInputValidate())); + }, [ + props.value.value, + props.required, + props?.minLength, + props?.maxLength, + props.validationType, + props.regex, + props.customRule, + ]); + + return props.label({ + required: props.required, + children: ( + <> + + { + props.valueInItems.onChange(false); + setvalidateState(textInputValidate(getTextInputValidate())); + setsearchtext(value); + props.value.onChange(value); + props.onEvent("change") + }} + onFocus={() => { + setActivationFlag(true) + props.onEvent("focus") + }} + onBlur={() => props.onEvent("blur")} + onSelect={(data: string, option) => { + setsearchtext(option[valueOrLabel]); + props.valueInItems.onChange(true); + props.value.onChange(option[valueOrLabel]); + props.onEvent("submit"); + }} + filterOption={(inputValue: string, option) => { + if (ignoreCase) { + if ( + option?.label && + option?.label + .toUpperCase() + .indexOf(inputValue.toUpperCase()) !== -1 + ) + return true; + } else { + if (option?.label && option?.label.indexOf(inputValue) !== -1) + return true; + } + if ( + chineseEnv && + searchFirstPY && + option?.label && + option.label + .spell("first") + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + if ( + chineseEnv && + searchCompletePY && + option?.label && + option.label + .spell() + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + if (!searchLabelOnly) { + if (ignoreCase) { + if ( + option?.value && + option?.value + .toUpperCase() + .indexOf(inputValue.toUpperCase()) !== -1 + ) + return true; + } else { + if ( + option?.value && + option?.value.indexOf(inputValue) !== -1 + ) + return true; + } + if ( + chineseEnv && + searchFirstPY && + option?.value && + option.value + .spell("first") + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + if ( + chineseEnv && + searchCompletePY && + option?.value && + option.value + .spell() + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + } + return false; + }} + > + {autoCompleteType === "AntDesign" ? ( + props.onEvent("submit")} + $style={props.style} + /> + ) : ( + + )} + + + + ), + style: props.style, + ...validateState, + }); + }) + .setPropertyViewFn((children) => { + return ( + <> +
+ {children.autoCompleteType.propertyView({ + label: trans("autoComplete.type"), + radioButton: true, + })} + {children.autoCompleteType.getView() === "AntDesign" && + children.autocompleteIconColor.propertyView({ + label: trans("button.prefixIcon"), + radioButton: true, + })} + + {children.autoCompleteType.getView() === "normal" && + children.prefixIcon.propertyView({ + label: trans("button.prefixIcon"), + })} + {children.autoCompleteType.getView() === "normal" && + children.suffixIcon.propertyView({ + label: trans("button.suffixIcon"), + })} + {allowClearPropertyView(children)} +
+
+ {children.items.propertyView({ + label: trans("autoComplete.value"), + tooltip: itemsDataTooltip, + placeholder: "[]", + })} + {getMomentLocale() === "zh-cn" && + children.searchFirstPY.propertyView({ + label: trans("autoComplete.searchFirstPY"), + })} + {getMomentLocale() === "zh-cn" && + children.searchCompletePY.propertyView({ + label: trans("autoComplete.searchCompletePY"), + })} + {children.searchLabelOnly.propertyView({ + label: trans("autoComplete.searchLabelOnly"), + })} + {children.ignoreCase.propertyView({ + label: trans("autoComplete.ignoreCase"), + })} + {children.valueOrLabel.propertyView({ + label: trans("autoComplete.checkedValueFrom"), + radioButton: true, + })} +
+ + + + {children.label.getPropertyView()} + + + + {} + +
+ {hiddenPropertyView(children)} +
+ +
+ {children.style.getPropertyView()} +
+ + ); + }) + .setExposeMethodConfigs(autoCompleteRefMethods) + .setExposeStateConfigs([ + new NameConfig("value", trans("export.inputValueDesc")), + new NameConfig("valueInItems", trans("autoComplete.valueInItems")), + NameConfigPlaceHolder, + NameConfigRequired, + ...TextInputConfigs, + ]) + .build(); +})(); + +AutoCompleteCompBase = class extends AutoCompleteCompBase { + override autoHeight(): boolean { + return true; + } +}; + +export const AutoCompleteComp = withExposingConfigs(AutoCompleteCompBase, [ + new NameConfig("value", trans("export.inputValueDesc")), + new NameConfig("valueInItems", trans("autoComplete.valueInItems")), + NameConfigPlaceHolder, + NameConfigRequired, + ...TextInputConfigs, +]); diff --git a/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteConstants.tsx b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteConstants.tsx new file mode 100644 index 000000000..eb375a170 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteConstants.tsx @@ -0,0 +1,114 @@ +import { trans } from "i18n"; +import { check } from "util/convertUtils"; +import { refMethods } from "comps/generators/withMethodExposing"; +import { InputRef } from "antd"; + + +import { + blurMethod, + focusWithOptions, + selectMethod, + setSelectionRangeMethod, +} from "comps/utils/methodUtils"; + +export const autoCompleteRefMethods = [ + ...refMethods([focusWithOptions, blurMethod, selectMethod, setSelectionRangeMethod]), +]; + + +export type autoCompleteDataTYPE = { + value: string; + label: string; +}; + +export const autocompleteIconColor = [ + { + label: trans("autoComplete.colorIcon"), + value: 'blue', + }, + { + label: trans("autoComplete.grewIcon"), + value: 'grew', + }, +] as const; + +export const valueOrLabelOption = [ + { + label: trans("autoComplete.selectLable"), + value: "label", + }, + { + label: trans("autoComplete.selectKey"), + value: "value", + }, +] as const; + +export const componentSize = [ + { + label: trans("autoComplete.small"), + value: "small", + }, + { + label: trans("autoComplete.large"), + value: "large", + }, +] as const; + +export const autoCompleteType = [ + { + label: trans("autoComplete.antDesign"), + value: "AntDesign", + }, + { + label: trans("autoComplete.normal"), + value: "normal", + }, +] as const; + +export const itemsDataTooltip = ( +
  • + {trans("autoComplete.Introduction")}: +
    + 1. label - {trans("autoComplete.helpLabel")} +
    + 2. value - {trans("autoComplete.helpValue")} +
  • +); + +export const autoCompleteDate = [ + {value: '1-BeiJing',label: '北京'}, + {value: '2-ShangHai',label: '上海'}, + {value: '3-GuangDong',label: '广东'}, + {value: '4-ShenZhen',label: '深圳'}, +]; + +export function convertAutoCompleteData(data: any) { + return data === "" ? [] : checkDataNodes(data) ?? []; +} + +function checkDataNodes( + value: any, + key?: string +): autoCompleteDataTYPE[] | undefined { + return check(value, ["array", "undefined"], key, (node, k) => { + check(node["value"], ["string"], "value"); + check(node["label"], ["string"], "label"); + return node; + }); +} +export function checkUserInfoData(data: any) { + check(data?.name, ["string"], "name") + check(data?.avatar, ["string","undefined"], "avatar") + return data +} + +export function checkMentionListData(data: any) { + if(data === "") return {} + for(const key in data) { + check(data[key], ["array"], key,(node)=>{ + check(node, ["string"], ); + return node + }) + } + return data +} \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx b/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx index f6bdabe60..0527c5729 100644 --- a/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx +++ b/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx @@ -151,7 +151,7 @@ export const textInputChildren = { ...formDataChildren, }; -const textInputProps = (props: RecordConstructorToView) => ({ +export const textInputProps = (props: RecordConstructorToView) => ({ disabled: props.disabled, readOnly: props.readOnly, placeholder: props.placeholder, diff --git a/client/packages/lowcoder/src/comps/index.tsx b/client/packages/lowcoder/src/comps/index.tsx index e2bdd418c..cbecb0fde 100644 --- a/client/packages/lowcoder/src/comps/index.tsx +++ b/client/packages/lowcoder/src/comps/index.tsx @@ -1,5 +1,6 @@ import "comps/comps/layout/navLayout"; import "comps/comps/layout/mobileTabLayout"; +import cnchar from "cnchar"; import { ModalComp } from "comps/hooks/modalComp"; import { ButtonComp } from "./comps/buttonComp/buttonComp"; import { DropdownComp } from "./comps/buttonComp/dropdownComp"; @@ -93,6 +94,7 @@ import { VideoCompIcon, TimeLineIcon, LottieIcon, + AutoCompleteCompIcon, } from "lowcoder-design"; import { defaultFormData, FormComp } from "./comps/formComp/formComp"; @@ -119,7 +121,7 @@ import { RemoteCompInfo } from "types/remoteComp"; import { ScannerComp } from "./comps/buttonComp/scannerComp"; import { SignatureComp } from "./comps/signatureComp"; import { TimeLineComp } from "./comps/timelineComp/timelineComp"; - +import { AutoCompleteComp } from "./comps/autoCompleteComp/autoCompleteComp"; //Added by Aqib Mirza import { JsonLottieComp } from "./comps/jsonComp/jsonLottieComp"; @@ -813,7 +815,7 @@ const uiCompMap: Registry = { layoutInfo: { w: 24, h: 60, - } + }, }, signature: { name: trans("uiComp.signatureCompName"), @@ -855,6 +857,19 @@ const uiCompMap: Registry = { h: 55, }, }, + autocomplete: { + name: trans("uiComp.autoCompleteCompName"), + enName: "autoComplete", + description: trans("uiComp.autoCompleteCompDesc"), + categories: ["dataInputText"], + icon: AutoCompleteCompIcon, + keywords: cnchar.spell(trans("uiComp.autoCompleteCompName"), "first", "low").toString(), + comp: AutoCompleteComp, + layoutInfo: { + w: 7, + h: 5, + }, + }, }; export function loadComps() { diff --git a/client/packages/lowcoder/src/comps/uiCompRegistry.ts b/client/packages/lowcoder/src/comps/uiCompRegistry.ts index bba81c125..a2fac91c3 100644 --- a/client/packages/lowcoder/src/comps/uiCompRegistry.ts +++ b/client/packages/lowcoder/src/comps/uiCompRegistry.ts @@ -112,6 +112,7 @@ export type UICompType = | "signature" | "jsonLottie" //Added By Aqib Mirza | "timeline" + | "autocomplete" export const uiCompRegistry = {} as Record; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 160955098..c4eb204a7 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -845,6 +845,9 @@ export const en = { timelineCompName: "Time Line", timelineCompDesc: "Time Line", timelineCompKeywords: "", + autoCompleteCompName: "autoComplete", + autoCompleteCompDesc: "autoComplete", + autoCompleteCompKeywords: "", }, comp: { menuViewDocs: "View documentation", @@ -2467,5 +2470,31 @@ export const en = { valueDesc: "data of timeline", clickedObjectDesc: "clicked item data", clickedIndexDesc: "clicked item index", - } + }, + autoComplete: { + value: "auto complete value", + checkedValueFrom: "checked value from", + ignoreCase: "search ignore case", + searchLabelOnly: "search label only", + searchFirstPY: "search first pinying", + searchCompletePY: "search complete pinying", + searchText: "search text", + SectionDataName: "autoComplete Data", + valueInItems: "value in items", + type: "type", + antDesign: "AntDesign", + normal: "Normal", + selectKey: 'key', + selectLable: 'label', + ComponentType: 'Component Type', + colorIcon: 'blue', + grewIcon: 'grew', + noneIcon: 'none', + small: 'small', + large: "large", + componentSize: "component size", + Introduction: "Introduction keys", + helpLabel: "label", + helpValue: "value", + }, }; diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts index f92dff893..303262a91 100644 --- a/client/packages/lowcoder/src/i18n/locales/zh.ts +++ b/client/packages/lowcoder/src/i18n/locales/zh.ts @@ -828,6 +828,9 @@ uiComp: { timelineCompName: "时间线", timelineCompDesc: "时间线组件", timelineCompKeywords: "sjx", + autoCompleteCompName: "自动完成", + autoCompleteCompDesc: "自动完成", + autoCompleteCompKeywords: "zdwc", }, comp: { menuViewDocs: "查看文档", @@ -2458,5 +2461,31 @@ timeLine: { endlessLoop: "循环播放", keepLastFrame: "冻结最后一帧", }, + autoComplete: { + value: "数据", + checkedValueFrom: "选择提示时获取", + ignoreCase: "搜索忽略大小写", + searchLabelOnly: "仅搜索标签", + searchFirstPY: "搜索首拼", + searchCompletePY: "搜索全拼", + searchText: "搜索文字", + valueInItems: "项目中的值", + SectionDataName: "组件数据", + type: "类型", + antDesign: "AntDesign", + normal: "常规", + selectKey: '值', + selectLable: '标签', + ComponentType: '组件类型', + colorIcon: '彩色', + grewIcon: '黑白', + noneIcon: '无', + small: '小', + large: "大", + componentSize: "组件尺寸", + Introduction: '键值介绍', + helpLabel: "标签", + helpValue: "值", + }, }; diff --git a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx index 7e23f6d99..339a15484 100644 --- a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx @@ -37,6 +37,7 @@ import { LeftVideo, LeftSignature, TimeLineIcon, + AutoCompleteCompIcon, } from "lowcoder-design"; export const CompStateIcon: { @@ -103,4 +104,5 @@ export const CompStateIcon: { signature: , jsonLottie: , //Added By Aqib Mirza timeline: , + autocomplete: , }; diff --git a/client/yarn.lock b/client/yarn.lock index b30035da5..c000939f6 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -6065,6 +6065,22 @@ __metadata: languageName: node linkType: hard +"cnchar-types@npm:^3.2.4": + version: 3.2.4 + resolution: "cnchar-types@npm:3.2.4" + checksum: 5a79f30632cbc34e94c1fa5a81efa535956a1d9ad2c6f0dcf4f5fdaa20cbe3983056769806b04a37ed6e9aa2e80f529eaa90436ebb6faac7c8341e4a9440bb4d + languageName: node + linkType: hard + +"cnchar@npm:^3.2.4": + version: 3.2.4 + resolution: "cnchar@npm:3.2.4" + dependencies: + cnchar-types: ^3.2.4 + checksum: 536502ce4e5e3087ce33658fe250350bf247f8a546b5bb7e67fe760ee122618a7cdc5d3019171d326b7123afc05ddc573f70adf949587375c70685e23a95c172 + languageName: node + linkType: hard + "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -11706,6 +11722,7 @@ __metadata: axios: ^0.21.1 buffer: ^6.0.3 clsx: ^1.2.1 + cnchar: ^3.2.4 copy-to-clipboard: ^3.3.3 core-js: ^3.25.2 dotenv: ^16.0.3