From fbb5b2433e6654e5e0a51fbf00c72e101fd3d1d9 Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Fri, 16 Aug 2024 14:56:41 +0800 Subject: [PATCH 01/15] =?UTF-8?q?fix(icon):=20=E4=BF=AE=E5=A4=8Dicon?= =?UTF-8?q?=E4=B8=8D=E5=B1=85=E4=B8=AD=E6=98=BE=E7=A4=BA=E5=92=8Cclass?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E8=AE=BE=E7=BD=AE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 14 +++++++------- package.json | 4 ++-- src/_common | 2 +- src/_util/icon.ts | 11 +++++++++++ src/button/_example/icon.tsx | 10 +++++----- src/button/_example/shape.tsx | 17 +++++++++-------- src/button/button.tsx | 5 +++-- src/icon/README.md | 6 ++++-- src/tag/tag.tsx | 7 +++++-- 9 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 src/_util/icon.ts diff --git a/package-lock.json b/package-lock.json index ae0de59..5530266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "omi": "^7.6.17", "omi-transition": "^0.1.8", "tailwind-merge": "^2.2.1", - "tdesign-icons-web-components": "^0.1.1" + "tdesign-icons-web-components": "^0.1.2" }, "devDependencies": { "@babel/core": "^7.24.7", @@ -12625,9 +12625,9 @@ } }, "node_modules/tdesign-icons-web-components": { - "version": "0.1.1", - "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.1.tgz", - "integrity": "sha512-TgszImVZxmAU9zoRd1BrK42mr4Y+h61uyBlihrxHhlEjvvGFnKMIdaGh9fuYi7znCKBrlzw4HVHvfVmidzh0qw==", + "version": "0.1.2", + "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.2.tgz", + "integrity": "sha512-e+or0ozppYUl41iN1tMjnlM2JBBp/sjLiFi70T5203S8y/ZcdaDBAO50iF/t4jrAHAtoWfw/NSQGIFJHOF/olw==", "dependencies": { "@babel/runtime": "^7.16.5", "clsx": "^2.1.1", @@ -23050,9 +23050,9 @@ } }, "tdesign-icons-web-components": { - "version": "0.1.1", - "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.1.tgz", - "integrity": "sha512-TgszImVZxmAU9zoRd1BrK42mr4Y+h61uyBlihrxHhlEjvvGFnKMIdaGh9fuYi7znCKBrlzw4HVHvfVmidzh0qw==", + "version": "0.1.2", + "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.2.tgz", + "integrity": "sha512-e+or0ozppYUl41iN1tMjnlM2JBBp/sjLiFi70T5203S8y/ZcdaDBAO50iF/t4jrAHAtoWfw/NSQGIFJHOF/olw==", "requires": { "@babel/runtime": "^7.16.5", "clsx": "^2.1.1", diff --git a/package.json b/package.json index c7c1705..f45258f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "license": "MIT", "scripts": { "start": "npm run dev", - "dev": "npm run generate:entry && cd site && vite", + "dev": "npm run generate:entry && cd site && vite --force", "site": "cd site && vite build", "site:intranet": "cd site && vite build --mode intranet", "site:preview": "cd site && vite build --mode preview && cd ../_site && cp index.html 404.html", @@ -67,7 +67,7 @@ "omi": "^7.6.17", "omi-transition": "^0.1.8", "tailwind-merge": "^2.2.1", - "tdesign-icons-web-components": "^0.1.1" + "tdesign-icons-web-components": "^0.1.2" }, "devDependencies": { "@babel/core": "^7.24.7", diff --git a/src/_common b/src/_common index fb55c35..11af9d6 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit fb55c3516c7357878f234dc09aa92efdf4cf2b69 +Subproject commit 11af9d6eed1f28a10dff5c3d1fd7cdb9a5031c9a diff --git a/src/_util/icon.ts b/src/_util/icon.ts new file mode 100644 index 0000000..ff1d667 --- /dev/null +++ b/src/_util/icon.ts @@ -0,0 +1,11 @@ +import { cloneElement, VNode } from 'omi'; + +import { TNode } from '../common'; + +// 在light-dom的icon上添加inline-flex样式,解决icon在flex布局下不居中的问题 +export const flexIcon = (icon: TNode) => { + if (!icon) { + return icon; + } + return cloneElement(icon as VNode, { className: `inline-flex ${(icon as VNode).attributes.className || ''}` }); +}; diff --git a/src/button/_example/icon.tsx b/src/button/_example/icon.tsx index a66d732..2ea725d 100644 --- a/src/button/_example/icon.tsx +++ b/src/button/_example/icon.tsx @@ -5,13 +5,13 @@ import 'tdesign-web-components/space'; export default function Button() { return ( - }>新建 - }> + }>新建 + }> 上传文件 - } /> - } /> - }> + } /> + } /> + }> Function Icon diff --git a/src/button/_example/shape.tsx b/src/button/_example/shape.tsx index e01277b..cf441b5 100644 --- a/src/button/_example/shape.tsx +++ b/src/button/_example/shape.tsx @@ -1,5 +1,6 @@ import 'tdesign-web-components/button'; import 'tdesign-web-components/space'; +import 'tdesign-icons-web-components/esm/components/calendar'; export default function Button() { return ( @@ -9,13 +10,13 @@ export default function Button() { 填充按钮 - + 填充按钮 - + @@ -23,13 +24,13 @@ export default function Button() { 描边按钮 - + 描边按钮 - + @@ -37,13 +38,13 @@ export default function Button() { 虚框按钮 - + 虚框按钮 - + @@ -51,13 +52,13 @@ export default function Button() { 文字按钮 - + 文字按钮 - + diff --git a/src/button/button.tsx b/src/button/button.tsx index 72ac81b..2f231dd 100644 --- a/src/button/button.tsx +++ b/src/button/button.tsx @@ -4,6 +4,7 @@ import { Component, tag } from 'omi'; import classname, { getClassPrefix } from '../_util/classname'; import eventDispose from '../_util/eventDispose'; +import { flexIcon } from '../_util/icon'; import { convertToLightDomNode } from '../_util/lightDom'; import parseTNode from '../_util/parseTNode'; import { StyledProps } from '../common'; @@ -96,8 +97,8 @@ export default class Button extends Component { }); } - let iconNode = convertToLightDomNode(icon); - if (loading) iconNode = convertToLightDomNode(); + let iconNode = convertToLightDomNode(flexIcon(icon)); + if (loading) iconNode = convertToLightDomNode(flexIcon()); const Tag = this.tag as string; return ( diff --git a/src/icon/README.md b/src/icon/README.md index 7eacadd..17ffb96 100644 --- a/src/icon/README.md +++ b/src/icon/README.md @@ -60,7 +60,8 @@ import 'tdesign-icons-web-components/esm/iconfont'; 名称 | 类型 | 默认值 | 说明 | 必传 -- | -- | -- | -- | -- -className | String | - | 类名 | N +className | String | - | 最外层标签类名 | N +cls | String | - | 内部子标签(svg或者i标签)类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N loadDefaultIcons | Boolean | true | 是否加载组件库内置图标 | N name | String | - | 必需。图标名称 | Y @@ -72,7 +73,8 @@ onClick | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
名称 | 类型 | 默认值 | 说明 | 必传 -- | -- | -- | -- | -- -className | String | - | 类名 | N +className | String | - | 最外层标签类名 | N +cls | String | - | 内部子标签(svg或者i标签)类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N loadDefaultIcons | Boolean | true | 是否加载组件库内置图标 | N name | String | - | 必需。图标名称 | Y diff --git a/src/tag/tag.tsx b/src/tag/tag.tsx index 1fb798f..f54b752 100644 --- a/src/tag/tag.tsx +++ b/src/tag/tag.tsx @@ -3,6 +3,7 @@ import 'tdesign-icons-web-components/esm/components/close'; import { classNames, Component, createRef, tag } from 'omi'; import { classPrefix } from '../_util/classname'; +import { flexIcon } from '../_util/icon'; import { convertToLightDomNode } from '../_util/lightDom'; import { TagProps } from './type'; @@ -65,7 +66,7 @@ export default class Tag extends Component { e.stopImmediatePropagation(); this.props.onClose(e); }} - class={classNames(TagClassNamePrefix(`__icon-close`))} + cls={classNames(TagClassNamePrefix(`__icon-close`))} /> ); @@ -86,6 +87,8 @@ export default class Tag extends Component { icon.attributes.style.marginRight = 4; } + console.log('===deleteIcon', this.deleteIcon); + return ( { {children ?? content} - {closable && !disabled && convertToLightDomNode(this.deleteIcon)} + {closable && !disabled && convertToLightDomNode(flexIcon(this.deleteIcon))} ); From 7b524fb592d53140c8191ab076d204fb72fe5830 Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Fri, 16 Aug 2024 14:58:08 +0800 Subject: [PATCH 02/15] =?UTF-8?q?fix(icon):=20=E4=BF=AE=E5=A4=8Dicon?= =?UTF-8?q?=E4=B8=8D=E5=B1=85=E4=B8=AD=E6=98=BE=E7=A4=BA=E5=92=8Cclass?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E8=AE=BE=E7=BD=AE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f45258f..0fb26b7 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "license": "MIT", "scripts": { "start": "npm run dev", - "dev": "npm run generate:entry && cd site && vite --force", + "dev": "npm run generate:entry && cd site && vite", "site": "cd site && vite build", "site:intranet": "cd site && vite build --mode intranet", "site:preview": "cd site && vite build --mode preview && cd ../_site && cp index.html 404.html", From 923b9a337eff1e0ab37228d04cc628fc4d494a8b Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Fri, 16 Aug 2024 14:59:20 +0800 Subject: [PATCH 03/15] =?UTF-8?q?fix(icon):=20=E4=BF=AE=E5=A4=8Dicon?= =?UTF-8?q?=E4=B8=8D=E5=B1=85=E4=B8=AD=E6=98=BE=E7=A4=BA=E5=92=8Cclass?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E8=AE=BE=E7=BD=AE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tag/tag.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tag/tag.tsx b/src/tag/tag.tsx index f54b752..d91d7df 100644 --- a/src/tag/tag.tsx +++ b/src/tag/tag.tsx @@ -87,8 +87,6 @@ export default class Tag extends Component { icon.attributes.style.marginRight = 4; } - console.log('===deleteIcon', this.deleteIcon); - return ( Date: Thu, 29 Aug 2024 16:22:10 +0800 Subject: [PATCH 04/15] =?UTF-8?q?fix(watermark):=20=E4=BF=AE=E5=A4=8Dwater?= =?UTF-8?q?mark=E6=97=A0=E6=B3=95=E7=94=9F=E6=88=90=E6=B0=B4=E5=8D=B0?= =?UTF-8?q?=E5=8F=8A=E5=85=B6=E5=AE=83=E5=B1=95=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/_common | 2 +- src/_util/lightDom.ts | 2 +- src/watermark/_example/image.tsx | 2 +- src/watermark/index.ts | 4 +++ src/watermark/watermark.tsx | 58 +++++++++++--------------------- 5 files changed, 26 insertions(+), 42 deletions(-) diff --git a/src/_common b/src/_common index 11af9d6..a827f05 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 11af9d6eed1f28a10dff5c3d1fd7cdb9a5031c9a +Subproject commit a827f05ced50e7191612283b12930053c126b0a1 diff --git a/src/_util/lightDom.ts b/src/_util/lightDom.ts index 020a891..b3d5312 100644 --- a/src/_util/lightDom.ts +++ b/src/_util/lightDom.ts @@ -3,7 +3,7 @@ import { Component, define } from 'omi'; import { TNode } from '../common'; import parseTNode from './parseTNode'; -const createStyleSheet = (style: string) => { +export const createStyleSheet = (style: string) => { const styleSheet = new CSSStyleSheet(); styleSheet.replaceSync(style); diff --git a/src/watermark/_example/image.tsx b/src/watermark/_example/image.tsx index ac093d5..6c0a910 100644 --- a/src/watermark/_example/image.tsx +++ b/src/watermark/_example/image.tsx @@ -6,7 +6,7 @@ export default class ImageWatermark extends Component { render() { return ( { - static css = styleSheet; - static propsType = { alpha: Number, content: [String, Number, Object, Function], @@ -41,6 +38,8 @@ export default class Watermark extends Component { moveInterval: 3000, removable: false, rotate: -22, + width: 120, + height: 60, offset: [], }; @@ -50,12 +49,12 @@ export default class Watermark extends Component { styleStr = ''; - base64Url = signal(''); - stopObservation = signal(false); - ready(): void { - const { x, y, rotate: tempRotate, removable, movable, offset } = this.props; + selfDisconnect; + + async ready() { + const { x, y, rotate: tempRotate, movable, offset } = this.props; let gapX = x; let gapY = y; @@ -71,10 +70,8 @@ export default class Watermark extends Component { this.generateBase64Url(this.props, { gapX, gapY, rotate, offsetLeft, offsetTop }); - this.generateStyleStr(this.props, { gapX }); - // 水印节点 - 变化时重新渲染 - createMutationObservable(this.watermarkRef.current, (mutations) => { + this.selfDisconnect = createMutationObservable(this.watermarkRef.current, (mutations) => { if (this.stopObservation.value) return; if (movable) return; mutations.forEach((mutation) => { @@ -96,31 +93,12 @@ export default class Watermark extends Component { }); // 组件父节点 - 增加 keyframes - const parent = createRef(); - parent.current = this.watermarkRef.current.parentElement; const keyframesStyle = randomMovingStyle(); - injectStyle(keyframesStyle); - - // 水印节点的父节点 - 防删除 - createMutationObservable(typeof document !== 'undefined' ? document.body : null, (mutations) => { - if (removable) return; - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - const removeNodes = mutation.removedNodes; - removeNodes.forEach((node) => { - const element = node as HTMLElement; - if (element === this.watermarkRef.current) { - parent.current?.appendChild(element); - } - }); - } - }); - }); - this.renderWatermark(); + this.shadowRoot.adoptedStyleSheets.push(createStyleSheet(keyframesStyle)); } - receiveProps(newProps) { + async receiveProps(newProps) { const { x, y, rotate: tempRotate, movable, offset } = newProps; let gapX = x; @@ -134,15 +112,12 @@ export default class Watermark extends Component { const offsetLeft = offset[0] || gapX / 2; const offsetTop = offset[1] || gapY / 2; - this.generateBase64Url(newProps, { gapX, gapY, rotate, offsetLeft, offsetTop }); - this.generateStyleStr(newProps, { gapX }); - this.renderWatermark(); + this.generateBase64Url(newProps, { gapX, gapY, rotate, offsetLeft, offsetTop }); } generateBase64Url(props, { rotate, gapX, gapY, offsetLeft, offsetTop }) { const { width, height, lineSpace, alpha, watermarkContent } = props; - generateBase64Url( { width, @@ -157,12 +132,13 @@ export default class Watermark extends Component { offsetTop, }, (url) => { - this.base64Url.value = url; + this.generateStyleStr(url, props, { gapX }); + this.renderWatermark(); }, ); } - generateStyleStr(props, { gapX }) { + async generateStyleStr(url, props, { gapX }) { const { zIndex, isRepeat, moveInterval, movable, width, style } = props; let backgroundRepeat = ''; @@ -183,7 +159,7 @@ export default class Watermark extends Component { backgroundSize: `${gapX + width}px`, pointerEvents: 'none', backgroundRepeat, - backgroundImage: `url('${this.base64Url.value}')`, + backgroundImage: `url('${url}')`, animation: movable ? `watermark infinite ${(moveInterval * 4) / 60}s` : 'none', ...style, }); @@ -205,6 +181,10 @@ export default class Watermark extends Component { }); } + uninstall(): void { + this.selfDisconnect(); + } + render(props: WatermarkProps) { const { content, children, className } = props; From 93cf3cc7a1eb25972c3389eb77e273a1bba69ea6 Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Thu, 29 Aug 2024 17:11:04 +0800 Subject: [PATCH 05/15] =?UTF-8?q?fix(message):=20=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E4=BD=BF=E7=94=A8console=E4=BB=A3=E6=9B=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/card/_example/bordered-none.tsx | 2 +- src/card/_example/footer-actions.tsx | 2 +- src/card/_example/footer-content-actions.tsx | 2 +- src/card/_example/header-bordered.tsx | 2 +- src/card/_example/header-footer-actions.tsx | 2 +- src/card/_example/header-subtitle-footer-actions.tsx | 2 +- src/card/_example/header.tsx | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/card/_example/bordered-none.tsx b/src/card/_example/bordered-none.tsx index 9710708..ddf4e49 100644 --- a/src/card/_example/bordered-none.tsx +++ b/src/card/_example/bordered-none.tsx @@ -5,7 +5,7 @@ import { bind, Component } from 'omi'; export default class Button extends Component { @bind clickHandler() { - window.alert('操作'); + console.log('操作'); } render() { diff --git a/src/card/_example/footer-actions.tsx b/src/card/_example/footer-actions.tsx index 6fcdbb7..e77ab22 100644 --- a/src/card/_example/footer-actions.tsx +++ b/src/card/_example/footer-actions.tsx @@ -33,7 +33,7 @@ export default class Button extends Component { ]; clickHandler: ClickHandler = (data) => { - alert(`选中【${data.value}】`); + console.log(`选中【${data.value}】`); }; render() { diff --git a/src/card/_example/footer-content-actions.tsx b/src/card/_example/footer-content-actions.tsx index 6220bc4..10e5db6 100644 --- a/src/card/_example/footer-content-actions.tsx +++ b/src/card/_example/footer-content-actions.tsx @@ -24,7 +24,7 @@ export default class Button extends Component { ]; clickHandler: ClickHandler = (data) => { - alert(`选中【${data.value}】`); + console.log(`选中【${data.value}】`); }; render() { diff --git a/src/card/_example/header-bordered.tsx b/src/card/_example/header-bordered.tsx index 17520fa..a70ca7a 100644 --- a/src/card/_example/header-bordered.tsx +++ b/src/card/_example/header-bordered.tsx @@ -6,7 +6,7 @@ export default class Button extends Component { @bind clickHandler() { // 缺少Message全局提示组件,暂用alert代替 - window.alert('操作'); + console.log('操作'); } render() { diff --git a/src/card/_example/header-footer-actions.tsx b/src/card/_example/header-footer-actions.tsx index d7b3fd2..bce9b98 100644 --- a/src/card/_example/header-footer-actions.tsx +++ b/src/card/_example/header-footer-actions.tsx @@ -29,7 +29,7 @@ export default class Button extends Component { ]; clickHandler: ClickHandler = (data) => { - alert(`选中【${data.value}】`); + console.log(`选中【${data.value}】`); }; render() { diff --git a/src/card/_example/header-subtitle-footer-actions.tsx b/src/card/_example/header-subtitle-footer-actions.tsx index c1439ba..a187408 100644 --- a/src/card/_example/header-subtitle-footer-actions.tsx +++ b/src/card/_example/header-subtitle-footer-actions.tsx @@ -28,7 +28,7 @@ export default class Button extends Component { ]; clickHandler: ClickHandler = (data) => { - alert(`选中【${data.value}】`); + console.log(`选中【${data.value}】`); }; render() { diff --git a/src/card/_example/header.tsx b/src/card/_example/header.tsx index dfba700..057c728 100644 --- a/src/card/_example/header.tsx +++ b/src/card/_example/header.tsx @@ -5,7 +5,7 @@ import { bind, Component } from 'omi'; export default class Button extends Component { @bind clickHandler() { - alert('操作'); + console.log('操作'); } render() { From 1afda539b7c8fdc07399e8d3b994e69c0bcf658c Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Fri, 13 Sep 2024 16:09:16 +0800 Subject: [PATCH 06/15] =?UTF-8?q?feat(select-input):=20=E5=A2=9E=E5=8A=A0s?= =?UTF-8?q?elect-input=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/sidebar.config.ts | 7 + src/_common | 2 +- src/_util/useControlled.ts | 53 ++++++ src/index.ts | 1 + src/input/_example/clearable.tsx | 27 +++ src/input/input.tsx | 6 +- src/popconfirm/popcontent.tsx | 4 +- src/select-input/README.md | 143 ++++++++++++++ src/select-input/SelectInput.tsx | 149 +++++++++++++++ src/select-input/SelectInputMultiple.tsx | 100 ++++++++++ src/select-input/SelectInputSingle.tsx | 101 ++++++++++ src/select-input/_example/single.tsx | 91 +++++++++ src/select-input/defaultProps.ts | 19 ++ src/select-input/index.tsx | 9 + src/select-input/interface.ts | 18 ++ src/select-input/style/index.js | 1 + src/select-input/type.ts | 231 +++++++++++++++++++++++ src/select-input/useOverlayInnerStyle.ts | 96 ++++++++++ 18 files changed, 1053 insertions(+), 5 deletions(-) create mode 100644 src/_util/useControlled.ts create mode 100644 src/input/_example/clearable.tsx create mode 100644 src/select-input/README.md create mode 100644 src/select-input/SelectInput.tsx create mode 100644 src/select-input/SelectInputMultiple.tsx create mode 100644 src/select-input/SelectInputSingle.tsx create mode 100644 src/select-input/_example/single.tsx create mode 100644 src/select-input/defaultProps.ts create mode 100644 src/select-input/index.tsx create mode 100644 src/select-input/interface.ts create mode 100644 src/select-input/style/index.js create mode 100644 src/select-input/type.ts create mode 100644 src/select-input/useOverlayInnerStyle.ts diff --git a/site/sidebar.config.ts b/site/sidebar.config.ts index 53edb8c..6cfbccd 100644 --- a/site/sidebar.config.ts +++ b/site/sidebar.config.ts @@ -148,6 +148,13 @@ export default [ path: '/components/range-input', component: () => import('tdesign-web-components/range-input/README.md'), }, + { + title: 'SelectInput 筛选器输入框', + name: 'select-input', + docType: 'form', + path: '/components/select-input', + component: () => import('tdesign-web-components/select-input/README.md'), + }, { title: 'TagInput 标签输入框', name: ' tag-input', diff --git a/src/_common b/src/_common index 3c5e05b..a827f05 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 3c5e05bc628c06ebdac96e99d67119017920f08f +Subproject commit a827f05ced50e7191612283b12930053c126b0a1 diff --git a/src/_util/useControlled.ts b/src/_util/useControlled.ts new file mode 100644 index 0000000..86f2f14 --- /dev/null +++ b/src/_util/useControlled.ts @@ -0,0 +1,53 @@ +import upperFirst from 'lodash/upperFirst'; +import { signal, SignalValue } from 'omi'; + +export interface ChangeHandler { + (value: T, ...args: P); +} + +type Defaultoptions = `default${Capitalize}`; + +type ToString = T extends string ? T : `${Extract}`; + +const useControlled:

( + props: R, + valueKey: K, + onChange: ChangeHandler, + defaultOptions?: + | { + [key in Defaultoptions>]: R[K]; + } + | object, +) => [SignalValue | R[K], ChangeHandler] = ( + // eslint-disable-next-line default-param-last + props = {} as any, + valueKey, + onChange, + defaultOptions = {}, +) => { + // 外部设置 props,说明希望受控 + const controlled = Reflect.has(props, valueKey); + // 受控属性 + const value = props[valueKey]; + // 约定受控属性的非受控 key 为 defaultXxx,某些条件下要在运行时确定 defaultXxx 则通过 defaultOptions 来覆盖 + const defaultValue = + defaultOptions[`default${upperFirst(valueKey as string)}`] || props[`default${upperFirst(valueKey as string)}`]; + + // 无论是否受控,都要维护一个内部变量,默认值由 defaultValue 控制 + const internalValue = signal(defaultValue); + // 受控模式 + console.log('===controlled', controlled, valueKey); + if (controlled) return [value, onChange || (() => {})]; + + // 非受控模式 + return [ + internalValue.value, + (newValue, ...args) => { + console.log('===newValue', newValue); + internalValue.value = newValue; + onChange?.(newValue, ...args); + }, + ]; +}; + +export default useControlled; diff --git a/src/index.ts b/src/index.ts index c951a7e..89ee5e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ export * from './popconfirm'; export * from './popup'; export * from './radio'; export * from './range-input'; +export * from './select-input'; export * from './slider'; export * from './space'; export * from './swiper'; diff --git a/src/input/_example/clearable.tsx b/src/input/_example/clearable.tsx new file mode 100644 index 0000000..edc1392 --- /dev/null +++ b/src/input/_example/clearable.tsx @@ -0,0 +1,27 @@ +import 'tdesign-web-components/input'; + +import { Component } from 'omi'; + +export default class InputExample extends Component { + value = 'TDesign'; + + onChange = (v) => { + this.value = v; + }; + + render() { + return ( + { + this.onChange(value); + }} + onClear={() => { + console.log('onClear'); + }} + /> + ); + } +} diff --git a/src/input/input.tsx b/src/input/input.tsx index 516de72..f85a290 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -138,13 +138,14 @@ export default class Input extends Component { }; private handleFocus = (e: FocusEvent) => { + console.log('===e', e.composedPath()); e.stopImmediatePropagation(); const { readonly, onFocus } = this.props; if (readonly) return; const { currentTarget }: { currentTarget: any } = e; onFocus?.(currentTarget.value, { e }); this.isFocused = true; - this.update(); + // (this as any).queuedUpdate(); }; private handleBlur = (e: FocusEvent) => { @@ -172,6 +173,7 @@ export default class Input extends Component { }; private handleClear = (e: MouseEvent) => { + console.log('---clear'); const { onChange, onClear } = this.props; this.composingValue = ''; this.value = ''; @@ -309,7 +311,7 @@ export default class Input extends Component { onValidate, }); - const isShowClearIcon = ((clearable && this.value && !disabled) || showClearIconOnEmpty) && this.isHover; + const isShowClearIcon = (clearable && this.value && !disabled) || showClearIconOnEmpty; const prefixIconContent = renderIcon('t', 'prefix', parseTNode(prefixIcon)); let suffixIconNew = suffixIcon; diff --git a/src/popconfirm/popcontent.tsx b/src/popconfirm/popcontent.tsx index 714969c..d31f0d3 100644 --- a/src/popconfirm/popcontent.tsx +++ b/src/popconfirm/popcontent.tsx @@ -1,4 +1,4 @@ -import 'tdesign-icons-web-components'; +import 'tdesign-icons-web-components/esm/components/info-circle-filled'; import isString from 'lodash/isString'; import { cloneElement, Component, OmiProps, tag, VNode } from 'omi'; @@ -47,7 +47,7 @@ export default class Popconfirm extends Component; + const defaultIcon = ; switch (this.props.theme) { case 'warning': // 黄色 color = '#FFAA00'; diff --git a/src/select-input/README.md b/src/select-input/README.md new file mode 100644 index 0000000..b7fabca --- /dev/null +++ b/src/select-input/README.md @@ -0,0 +1,143 @@ +--- +title: SelectInput 筛选器输入框 +description: 定义:筛选器通用输入框, +isComponent: true +usage: { title: '', description: '' } +spline: data +--- + +### 筛选器输入框 + +统一筛选器逻辑包含:输入框、下拉框、无边框模式等,可以使用筛选器输入框定制复杂的筛选器。 + +基于 `TagInput` `Input` `Popup` 等组件开发,支持这些组件的全部特性。将主要应用于 `Select` `Cascader` `TreeSelect` `DatePicker` `TimePicker` 等筛选器组件。 + +### 单选筛选器输入框 + +可使用 `SelectInput` 自由定制任何风格的单选选择器。 + +{{ single }} + +### 多选筛选器输入框 + +可使用 `SelectInput` 自由定制任何风格的多选选择器。 + +{{ multiple }} + +### 自动填充筛选器 + +可使用 `SelectInput` 自由定制任何风格的自动填充筛选器。 + +{{ autocomplete }} + +### 有前置或后置内容的输入框 + +- 前置内容使用 `label` 自定义。 +- 后置内容使用 `suffix` 自定义。 +- 前置图标使用 `prefixIcon` 自定义。 +- 后置图标使用 `suffixIcon` 自定义。 + +{{ label-suffix }} + +### 不同状态的筛选器输入框 + +使用 `status` 和 `tips` 控制状态和提示文案。 + +{{ status }} + +### 可调整下拉框宽度的筛选器输入框 + +下拉框宽度规则:下拉框宽度默认和触发元素宽度保持同宽,如果下拉框宽度超出输入框组件会自动撑开下拉框宽度,但最大宽度不超过 `1000px`。也可以通过 `popupProps.overlayInnerStyle.width` 自由设置下拉框宽度。`popupProps.overlayInnerStyle` 类型为函数时,可以更灵活地动态控制下拉框宽度。 + +{{ width }} + +### 选中项数量超出的输入框 + +使用 `excessTagsDisplayType` 控制标签超出时的呈现方式:横向滚动显示和换行显示,默认为换行显示。 + +{{ excess-tags-display-type }} + + +### 可折叠选中项的筛选器输入框 + +选中项数量超过 `minCollapsedNum` 时会被折叠,可使用 `collapsedItems` 自定义折叠选项中的呈现方式。 + +{{ collapsed-items }} + +### 可自定义选中项的筛选器输入框 + +使用 `valueDisplay` 或者 `tag` 自定义选中项。 + +{{ custom-tag }} + +### 无边框模式的单选筛选器 + +`borderless` 用于控制是否呈现为无边框模式。 + +{{ borderless }} + +### 无边框模式的多选筛选器 + +{{ borderless-multiple }} + +### 自动宽度的单选筛选器 + +{{ autowidth }} + + +### 自动宽度的多选筛选器 + +{{ autowidth-multiple }} + + +## API + +### SelectInput Props + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +allowInput | Boolean | false | 是否允许输入 | N +autoWidth | Boolean | false | 宽度随内容自适应 | N +autofocus | Boolean | false | 自动聚焦 | N +borderless | Boolean | false | 无边框模式 | N +clearable | Boolean | false | 是否可清空 | N +collapsedItems | TElement | - | 标签过多的情况下,折叠项内容,默认为 `+N`。如果需要悬浮就显示其他内容,可以使用 `collapsedItems` 自定义。`value` 表示所有标签值,`collapsedSelectedItems` 表示折叠标签值,`count` 表示折叠的数量,`onClose` 表示移除标签的事件回调。TS 类型:`TNode<{ value: SelectInputValue; collapsedSelectedItems: SelectInputValue; count: number; onClose: (context: { index: number, e?: MouseEvent }) => void }>`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +disabled | Boolean | - | 是否禁用 | N +inputProps | Object | - | 透传 Input 输入框组件全部属性。TS 类型:`InputProps`,[Input API Documents](./input?tab=api)。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts) | N +inputValue | String / Number | - | 输入框的值。TS 类型:`string` | N +defaultInputValue | String / Number | - | 输入框的值。非受控属性。TS 类型:`string` | N +keys | Object | - | 定义字段别名,示例:`{ label: 'text', value: 'id', children: 'list' }`。TS 类型:`SelectInputKeys` `interface SelectInputKeys { label?: string; value?: string; children?: string }`。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts) | N +label | TNode | - | 左侧文本。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +loading | Boolean | false | 是否处于加载状态 | N +minCollapsedNum | Number | 0 | 最小折叠数量,用于标签数量过多的情况下折叠选中项,超出该数值的选中项折叠。值为 0 则表示不折叠 | N +multiple | Boolean | false | 是否为多选模式,默认为单选 | N +panel | TNode | - | 下拉框内容,可完全自定义。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +placeholder | String | - | 占位符 | N +popupProps | Object | - | 透传 Popup 浮层组件全部属性。TS 类型:`PopupProps`,[Popup API Documents](./popup?tab=api)。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts) | N +popupVisible | Boolean | - | 是否显示下拉框 | N +defaultPopupVisible | Boolean | - | 是否显示下拉框。非受控属性 | N +prefixIcon | TElement | - | 组件前置图标。TS 类型:`TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +readonly | Boolean | false | 只读状态,值为真会隐藏输入框,且无法打开下拉框 | N +reserveKeyword | Boolean | false | 多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词 | N +size | String | medium | 组件尺寸。可选项:small/medium/large。TS 类型:`SizeEnum`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +status | String | default | 输入框状态。可选项:default/success/warning/error | N +suffix | TNode | - | 后置图标前的后置内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +suffixIcon | TElement | - | 组件后置图标。TS 类型:`TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +tag | TNode | - | 多选场景下,自定义选中标签的内部内容。注意和 `valueDisplay` 区分,`valueDisplay` 是用来定义全部标签内容,而非某一个标签。TS 类型:`string \| TNode<{ value: string \| number }>`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +tagInputProps | Object | - | 透传 TagInput 组件全部属性。TS 类型:`TagInputProps`,[TagInput API Documents](./tag-input?tab=api)。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts) | N +tagProps | Object | - | 透传 Tag 标签组件全部属性。TS 类型:`TagProps`,[Tag API Documents](./tag?tab=api)。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts) | N +tips | TNode | - | 输入框下方提示文本,会根据不同的 `status` 呈现不同的样式。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +value | String / Number / Boolean / Object / Array / Date | undefined | 全部标签值。值为数组表示多个标签,值为非数组表示单个数值。TS 类型:`SelectInputValue` `type SelectInputValue = string \| number \| boolean \| Date \| Object \| Array \| Array`。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts) | N +valueDisplay | TNode | - | 自定义值呈现的全部内容,参数为所有标签的值。TS 类型:`string \| TNode<{ value: TagInputValue; onClose: (index: number, item?: any) => void }>`。[通用类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/common.ts) | N +onBlur | Function | | TS 类型:`(value: SelectInputValue, context: SelectInputBlurContext) => void`
失去焦点时触发,`context.inputValue` 表示输入框的值;`context.tagInputValue` 表示标签输入框的值。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts)。
`type SelectInputBlurContext = PopupVisibleChangeContext & { inputValue: string; tagInputValue?: TagInputValue; }`
| N +onClear | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
清空按钮点击时触发 | N +onEnter | Function | | TS 类型:`(value: SelectInputValue, context: { e: KeyboardEvent; inputValue: string; tagInputValue?: TagInputValue }) => void`
按键按下 Enter 时触发 | N +onFocus | Function | | TS 类型:`(value: SelectInputValue, context: SelectInputFocusContext) => void`
聚焦时触发。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts)。
`interface SelectInputFocusContext { inputValue: string; tagInputValue?: TagInputValue; e: FocusEvent }`
| N +onInputChange | Function | | TS 类型:`(value: string, context?: SelectInputValueChangeContext) => void`
输入框值发生变化时触发,`context.trigger` 表示触发输入框值变化的来源:文本输入触发、清除按钮触发等。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts)。
`interface SelectInputValueChangeContext { e?: Event \| InputEvent \| MouseEvent \| FocusEvent \| KeyboardEvent \| CompositionEvent; trigger: 'input' \| 'clear' \| 'blur' \| 'focus' \| 'initial' \| 'change' }`
| N +onMouseenter | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
进入输入框时触发 | N +onMouseleave | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
离开输入框时触发 | N +onPaste | Function | | TS 类型:`(context: { e: ClipboardEvent; pasteValue: string }) => void`
粘贴事件,`pasteValue` 表示粘贴板的内容 | N +onPopupVisibleChange | Function | | TS 类型:`(visible: boolean, context: PopupVisibleChangeContext) => void`
下拉框显示或隐藏时触发。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts)。
`import { PopupVisibleChangeContext } from '@Popup'`
| N +onTagChange | Function | | TS 类型:`(value: TagInputValue, context: SelectInputChangeContext) => void`
值变化时触发,参数 `context.trigger` 表示数据变化的触发来源;`context.index` 指当前变化项的下标;`context.item` 指当前变化项;`context.e` 表示事件参数。[详细类型定义](https://github.com/TDesignOteam/tdesign-web-components/tree/main/src/select-input/type.ts)。
`type SelectInputChangeContext = TagInputChangeContext`
| N diff --git a/src/select-input/SelectInput.tsx b/src/select-input/SelectInput.tsx new file mode 100644 index 0000000..a1b4aae --- /dev/null +++ b/src/select-input/SelectInput.tsx @@ -0,0 +1,149 @@ +import '../loading'; +import './SelectInputMultiple'; +import './SelectInputSingle'; +import '../popup'; + +import classNames from 'classnames'; +import { pick } from 'lodash'; +import { Component, createRef, OmiProps, tag } from 'omi'; + +import { getClassPrefix } from '../_util/classname'; +import { StyledProps } from '../common'; +import { selectInputDefaultProps } from './defaultProps'; +import { SelectInputCommonProperties } from './interface'; +import { TdSelectInputProps } from './type'; +import useOverlayInnerStyle from './useOverlayInnerStyle'; + +export interface SelectInputProps extends TdSelectInputProps, StyledProps { + updateScrollTop?: (content: HTMLDivElement) => void; +} + +// single 和 multiple 共有特性 +const COMMON_PROPERTIES = [ + 'status', + 'clearable', + 'disabled', + 'label', + 'placeholder', + 'readonly', + 'suffix', + 'suffixIcon', + 'onPaste', + 'onEnter', + 'onMouseenter', + 'onMouseleave', + 'size', + 'prefixIcon', +]; + +@tag('t-select-input') +class SelectInput extends Component { + static defaultProps = selectInputDefaultProps; + + selectInputRef = createRef(); + + selectInputWrapRef = createRef(); + + classPrefix = getClassPrefix(); + + commonInputProps: SelectInputCommonProperties; + + tOverlayInnerStyle; + + innerPopupVisible; + + onInnerPopupVisibleChange; + + install(): void { + const { loading, suffixIcon } = this.props; + this.commonInputProps = { + ...pick(this.props, COMMON_PROPERTIES), + suffixIcon: loading ? : suffixIcon, + }; + + const { innerPopupVisible, tOverlayInnerStyle, onInnerPopupVisibleChange } = useOverlayInnerStyle(this.props, { + // afterHidePopup: this.onInnerBlur, + }); + this.tOverlayInnerStyle = tOverlayInnerStyle; + this.innerPopupVisible = innerPopupVisible; + this.onInnerPopupVisibleChange = onInnerPopupVisibleChange; + } + + // onInnerBlur(ctx: PopupVisibleChangeContext) { + // const inputValue = this.props.multiple ? multipleInputValue : singleInputValue; + // const params: Parameters[1] = { e: ctx.e, inputValue }; + // this.props.onBlur?.(this.props.value, params); + // } + + render(props: SelectInputProps | OmiProps) { + const { multiple, value, popupVisible, popupProps, borderless, disabled } = props; + + // 浮层显示的受控与非受控 + const visibleProps = { visible: popupVisible ?? this.innerPopupVisible }; + + const popupClasses = classNames([ + props.className, + `${this.classPrefix}-select-input`, + { + [`${this.classPrefix}-select-input--borderless`]: borderless, + [`${this.classPrefix}-select-input--multiple`]: multiple, + [`${this.classPrefix}-select-input--popup-visible`]: popupVisible ?? this.innerPopupVisible, + [`${this.classPrefix}-select-input--empty`]: value instanceof Array ? !value.length : !value, + }, + ]); + + const mainContent = ( +

console.log('333', e)}> + + {multiple ? ( + + ) : ( + + )} + +
+ ); + + if (!props.tips) { + return mainContent; + } + + return ( +
console.log('222', e)} + > + {mainContent} + {props.tips && ( +
+ {props.tips} +
+ )} +
+ ); + } +} + +export default SelectInput; diff --git a/src/select-input/SelectInputMultiple.tsx b/src/select-input/SelectInputMultiple.tsx new file mode 100644 index 0000000..afd243f --- /dev/null +++ b/src/select-input/SelectInputMultiple.tsx @@ -0,0 +1,100 @@ +import '../tag-input'; + +import classNames from 'classnames'; +import isObject from 'lodash/isObject'; +import { Component, createRef, tag } from 'omi'; + +import { getClassPrefix } from '../_util/classname'; +import useControlled from '../_util/useControlled'; +import { TagInputValue } from '../tag-input'; +import { SelectInputCommonProperties } from './interface'; +import { SelectInputChangeContext, SelectInputKeys, SelectInputValue, TdSelectInputProps } from './type'; + +export interface RenderSelectMultipleParams { + commonInputProps: SelectInputCommonProperties; + onInnerClear: (context: { e: MouseEvent }) => void; + popupVisible: boolean; + allowInput: boolean; +} + +const DEFAULT_KEYS = { + label: 'label', + key: 'key', + children: 'children', +}; + +@tag('t-select-input-multiple') +export default class SelectInputMultiple extends Component { + classPrefix = getClassPrefix(); + + tagInputRef = createRef(); + + render(props) { + const { value, popupVisible, commonInputProps, allowInput } = props; + const [tInputValue, setTInputValue] = useControlled(props, 'inputValue', props.onInputChange); + const iKeys: SelectInputKeys = { ...DEFAULT_KEYS, ...props.keys }; + + const getTags = () => { + if (!(value instanceof Array)) { + return isObject(value) ? [value[iKeys.label]] : [value]; + } + return value.map((item: SelectInputValue) => (isObject(item) ? item[iKeys.label] : item)); + }; + const tags = getTags(); + + const tPlaceholder = !tags || !tags.length ? props.placeholder : ''; + + const onTagInputChange = (val: TagInputValue, context: SelectInputChangeContext) => { + // 避免触发浮层的显示或隐藏 + if (context.trigger === 'tag-remove') { + context.e?.stopPropagation(); + } + props.onTagChange?.(val, context); + }; + + const onInnerClear = (context: { e: MouseEvent }) => { + console.log('====cle'); + context?.e?.stopPropagation(); + props.onClear?.(context); + setTInputValue('', { trigger: 'clear' }); + }; + + return ( + { + // 筛选器统一特性:筛选器按下回车时不清空输入框 + if (context?.trigger === 'enter' || context?.trigger === 'blur') return; + setTInputValue(val, { trigger: context.trigger, e: context.e }); + }} + tagProps={props.tagProps} + onClear={onInnerClear} + // [Important Info]: SelectInput.blur is not equal to TagInput, example: click popup panel + onFocus={(val, context) => { + props.onFocus?.(props.value, { ...context, tagInputValue: val }); + }} + onBlur={!props.panel ? props.onBlur : null} + {...props.tagInputProps} + inputProps={{ + ...props.inputProps, + readonly: !props.allowInput || props.readonly, + inputClass: classNames(props.tagInputProps?.className, { + [`${this.classPrefix}-input--focused`]: popupVisible, + [`${this.classPrefix}-is-focused`]: popupVisible, + }), + }} + /> + ); + } +} diff --git a/src/select-input/SelectInputSingle.tsx b/src/select-input/SelectInputSingle.tsx new file mode 100644 index 0000000..fbcd706 --- /dev/null +++ b/src/select-input/SelectInputSingle.tsx @@ -0,0 +1,101 @@ +import '../input'; + +import classNames from 'classnames'; +import isObject from 'lodash/isObject'; +import { Component, createRef, tag } from 'omi'; + +import { getClassPrefix } from '../_util/classname'; +import useControlled from '../_util/useControlled'; +import { TdInputProps } from '../input'; +import { TdSelectInputProps } from './type'; + +export interface RenderSelectSingleInputParams { + tPlaceholder: string; +} + +const DEFAULT_KEYS: TdSelectInputProps['keys'] = { + label: 'label', + value: 'value', +}; + +function getInputValue(value: TdSelectInputProps['value'], keys: TdSelectInputProps['keys']) { + const iKeys = keys || DEFAULT_KEYS; + return isObject(value) ? value[iKeys.label] : value; +} + +@tag('t-select-input-single') +export default class SingleSelectInput extends Component { + classPrefix = getClassPrefix(); + + inputRef = createRef(); + + inputValue; + + setInputValue; + + install(): void { + const [inputValue, setInputValue] = useControlled(this.props, 'inputValue', this.props.onInputChange); + this.inputValue = inputValue; + this.setInputValue = setInputValue; + } + + render(props) { + const { value, keys, commonInputProps, popupVisible } = props; + + const onInnerClear = (context: { e: MouseEvent }) => { + console.log('---fff'); + context?.e?.stopPropagation(); + props.onClear?.(context); + this.setInputValue('', { trigger: 'clear' }); + }; + + const onInnerInputChange: TdInputProps['onChange'] = (value, context) => { + if (props.allowInput) { + this.setInputValue(value, { ...context, trigger: 'input' }); + } + }; + + const handleEmptyPanelBlur = (value: string, { e }: { e: FocusEvent }) => { + props.onBlur?.(value, { e, inputValue: value }); + }; + + // 单选,值的呈现方式 + const singleValueDisplay = !props.multiple ? props.valueDisplay : null; + const displayedValue = popupVisible && props.allowInput ? this.inputValue : getInputValue(value, keys); + return ( + + {props.label} + {singleValueDisplay} + + } + onChange={onInnerInputChange} + readonly={!props.allowInput} + onClear={onInnerClear} + // [Important Info]: SelectInput.blur is not equal to Input, example: click popup panel + onFocus={(val, context) => { + console.log('focus'); + props.onFocus?.(value, { ...context, inputValue: val }); + // focus might not need to change input value. it will caught some curious errors in tree-select + // !popupVisible && setInputValue(getInputValue(value, keys), { ...context, trigger: 'input' }); + }} + onEnter={(val, context) => { + props.onEnter?.(value, { ...context, inputValue: val }); + }} + // onBlur need to triggered by input when popup panel is null + onBlur={!props.panel ? handleEmptyPanelBlur : null} + {...props.inputProps} + inputClass={classNames(props.inputProps?.className, { + [`${this.classPrefix}-input--focused`]: popupVisible, + [`${this.classPrefix}-is-focused`]: popupVisible, + })} + /> + ); + } +} diff --git a/src/select-input/_example/single.tsx b/src/select-input/_example/single.tsx new file mode 100644 index 0000000..9cdd9cb --- /dev/null +++ b/src/select-input/_example/single.tsx @@ -0,0 +1,91 @@ +import 'tdesign-web-components/select-input'; +import 'tdesign-icons-web-components/esm/components/chevron-down'; + +import { Component, signal } from 'omi'; + +const classStyles = ` +.tdesign-demo__select-input-ul-single { + display: flex; + flex-direction: column; + padding: 0; + gap: 2px; +} +.tdesign-demo__select-input-ul-single > li { + display: block; + border-radius: 3px; + line-height: 22px; + cursor: pointer; + padding: 3px 8px; + color: var(--td-text-color-primary); + transition: background-color 0.2s linear; + white-space: nowrap; + word-wrap: normal; + overflow: hidden; + text-overflow: ellipsis; +} + +.tdesign-demo__select-input-ul-single > li:hover { + background-color: var(--td-bg-color-container-hover); +} +`; + +const OPTIONS = [ + { label: 'tdesign-vue', value: 1 }, + { label: 'tdesign-react', value: 2 }, + { label: 'tdesign-miniprogram', value: 3 }, + { label: 'tdesign-angular', value: 4 }, + { label: 'tdesign-mobile-vue', value: 5 }, + { label: 'tdesign-mobile-react', value: 6 }, +]; + +export default class SelectInputSingle extends Component { + static css = classStyles; + + selectValue = signal({ label: 'tdesign-vue', value: 1 }); + + popupVisible = signal(false); + + onOptionClick = (e: Event, item: { label: string; value: number }) => { + e.stopPropagation(); + console.log('===item', item); + this.selectValue.value = item; + // 选中后立即关闭浮层 + this.popupVisible.value = false; + }; + + onClear = () => { + this.selectValue.value = undefined; + }; + + onPopupVisibleChange = (val) => { + this.popupVisible.value = val; + }; + + render() { + return ( +
+ + {OPTIONS.map((item) => ( +
  • this.onOptionClick(e, item)}> + {item.label} +
  • + ))} + + } + suffixIcon={} + /> +
    + ); + } +} diff --git a/src/select-input/defaultProps.ts b/src/select-input/defaultProps.ts new file mode 100644 index 0000000..c735a60 --- /dev/null +++ b/src/select-input/defaultProps.ts @@ -0,0 +1,19 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdSelectInputProps } from './type'; + +export const selectInputDefaultProps: TdSelectInputProps = { + allowInput: false, + autoWidth: false, + autofocus: false, + borderless: false, + clearable: false, + loading: false, + minCollapsedNum: 0, + multiple: false, + readonly: false, + reserveKeyword: false, + status: 'default', +}; diff --git a/src/select-input/index.tsx b/src/select-input/index.tsx new file mode 100644 index 0000000..028b9f6 --- /dev/null +++ b/src/select-input/index.tsx @@ -0,0 +1,9 @@ +import './style/index.js'; + +import _SelectInput from './SelectInput'; + +export type { SelectInputProps } from './SelectInput'; +export * from './type'; + +export const SelectInput = _SelectInput; +export default SelectInput; diff --git a/src/select-input/interface.ts b/src/select-input/interface.ts new file mode 100644 index 0000000..48b68b0 --- /dev/null +++ b/src/select-input/interface.ts @@ -0,0 +1,18 @@ +import { TdSelectInputProps } from './type'; + +export interface SelectInputCommonProperties { + status?: TdSelectInputProps['status']; + tips?: TdSelectInputProps['tips']; + clearable?: TdSelectInputProps['clearable']; + disabled?: TdSelectInputProps['disabled']; + label?: TdSelectInputProps['label']; + placeholder?: TdSelectInputProps['placeholder']; + readonly?: TdSelectInputProps['readonly']; + suffix?: TdSelectInputProps['suffix']; + suffixIcon?: TdSelectInputProps['suffixIcon']; + size?: TdSelectInputProps['size']; + onPaste?: TdSelectInputProps['onPaste']; + onEnter?: TdSelectInputProps['onEnter']; + onMouseenter?: TdSelectInputProps['onMouseenter']; + onMouseleave?: TdSelectInputProps['onMouseleave']; +} diff --git a/src/select-input/style/index.js b/src/select-input/style/index.js new file mode 100644 index 0000000..72b5677 --- /dev/null +++ b/src/select-input/style/index.js @@ -0,0 +1 @@ +import '../../_common/style/web/components/select-input/_index.less'; diff --git a/src/select-input/type.ts b/src/select-input/type.ts new file mode 100644 index 0000000..b2966f9 --- /dev/null +++ b/src/select-input/type.ts @@ -0,0 +1,231 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { InputProps } from '../input'; +import { PopupProps } from '../popup'; +import { TagInputProps, TagInputValue, TagInputChangeContext } from '../tag-input'; +import { TagProps } from '../tag'; +import { PopupVisibleChangeContext } from '../popup'; +import { TNode, TElement, SizeEnum } from '../common'; + +export interface TdSelectInputProps { + /** + * 是否允许输入 + * @default false + */ + allowInput?: boolean; + /** + * 宽度随内容自适应 + * @default false + */ + autoWidth?: boolean; + /** + * 自动聚焦 + * @default false + */ + autofocus?: boolean; + /** + * 无边框模式 + * @default false + */ + borderless?: boolean; + /** + * 是否可清空 + * @default false + */ + clearable?: boolean; + /** + * 标签过多的情况下,折叠项内容,默认为 `+N`。如果需要悬浮就显示其他内容,可以使用 `collapsedItems` 自定义。`value` 表示所有标签值,`collapsedSelectedItems` 表示折叠标签值,`count` 表示折叠的数量,`onClose` 表示移除标签的事件回调 + */ + collapsedItems?: TNode<{ + value: SelectInputValue; + collapsedSelectedItems: SelectInputValue; + count: number; + onClose: (context: { index: number; e?: MouseEvent }) => void; + }>; + /** + * 是否禁用 + */ + disabled?: boolean; + /** + * 透传 Input 输入框组件全部属性 + */ + inputProps?: InputProps; + /** + * 输入框的值 + */ + inputValue?: string; + /** + * 输入框的值,非受控属性 + */ + defaultInputValue?: string; + /** + * 定义字段别名,示例:`{ label: 'text', value: 'id', children: 'list' }` + */ + keys?: SelectInputKeys; + /** + * 左侧文本 + */ + label?: TNode; + /** + * 是否处于加载状态 + * @default false + */ + loading?: boolean; + /** + * 最小折叠数量,用于标签数量过多的情况下折叠选中项,超出该数值的选中项折叠。值为 0 则表示不折叠 + * @default 0 + */ + minCollapsedNum?: number; + /** + * 是否为多选模式,默认为单选 + * @default false + */ + multiple?: boolean; + /** + * 下拉框内容,可完全自定义 + */ + panel?: TNode; + /** + * 占位符 + * @default '' + */ + placeholder?: string; + /** + * 透传 Popup 浮层组件全部属性 + */ + popupProps?: PopupProps; + /** + * 是否显示下拉框 + */ + popupVisible?: boolean; + /** + * 是否显示下拉框,非受控属性 + */ + defaultPopupVisible?: boolean; + /** + * 组件前置图标 + */ + prefixIcon?: TElement; + /** + * 只读状态,值为真会隐藏输入框,且无法打开下拉框 + * @default false + */ + readonly?: boolean; + /** + * 多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词 + * @default false + */ + reserveKeyword?: boolean; + /** + * 组件尺寸 + * @default medium + */ + size?: SizeEnum; + /** + * 输入框状态 + * @default default + */ + status?: 'default' | 'success' | 'warning' | 'error'; + /** + * 后置图标前的后置内容 + */ + suffix?: TNode; + /** + * 组件后置图标 + */ + suffixIcon?: TNode; + /** + * 多选场景下,自定义选中标签的内部内容。注意和 `valueDisplay` 区分,`valueDisplay` 是用来定义全部标签内容,而非某一个标签 + */ + tag?: string | TNode<{ value: string | number }>; + /** + * 透传 TagInput 组件全部属性 + */ + tagInputProps?: TagInputProps; + /** + * 透传 Tag 标签组件全部属性 + */ + tagProps?: TagProps; + /** + * 输入框下方提示文本,会根据不同的 `status` 呈现不同的样式 + */ + tips?: TNode; + /** + * 全部标签值。值为数组表示多个标签,值为非数组表示单个数值 + */ + value?: SelectInputValue; + /** + * 自定义值呈现的全部内容,参数为所有标签的值 + */ + valueDisplay?: string | TNode<{ value: TagInputValue; onClose: (index: number, item?: any) => void }>; + /** + * 失去焦点时触发,`context.inputValue` 表示输入框的值;`context.tagInputValue` 表示标签输入框的值 + */ + onBlur?: (value: SelectInputValue, context: SelectInputBlurContext) => void; + /** + * 清空按钮点击时触发 + */ + onClear?: (context: { e: MouseEvent }) => void; + /** + * 按键按下 Enter 时触发 + */ + onEnter?: ( + value: SelectInputValue, + context: { e: KeyboardEvent; inputValue: string; tagInputValue?: TagInputValue }, + ) => void; + /** + * 聚焦时触发 + */ + onFocus?: (value: SelectInputValue, context: SelectInputFocusContext) => void; + /** + * 输入框值发生变化时触发,`context.trigger` 表示触发输入框值变化的来源:文本输入触发、清除按钮触发等 + */ + onInputChange?: (value: string, context?: SelectInputValueChangeContext) => void; + /** + * 进入输入框时触发 + */ + onMouseenter?: (context: { e: MouseEvent }) => void; + /** + * 离开输入框时触发 + */ + onMouseleave?: (context: { e: MouseEvent }) => void; + /** + * 粘贴事件,`pasteValue` 表示粘贴板的内容 + */ + onPaste?: (context: { e: ClipboardEvent; pasteValue: string }) => void; + /** + * 下拉框显示或隐藏时触发 + */ + onPopupVisibleChange?: (visible: boolean, context: PopupVisibleChangeContext) => void; + /** + * 值变化时触发,参数 `context.trigger` 表示数据变化的触发来源;`context.index` 指当前变化项的下标;`context.item` 指当前变化项;`context.e` 表示事件参数 + */ + onTagChange?: (value: TagInputValue, context: SelectInputChangeContext) => void; +} + +export interface SelectInputKeys { + label?: string; + value?: string; + children?: string; +} + +export type SelectInputValue = string | number | boolean | Date | Object | Array | Array; + +export type SelectInputBlurContext = PopupVisibleChangeContext & { inputValue: string; tagInputValue?: TagInputValue }; + +export interface SelectInputFocusContext { + inputValue: string; + tagInputValue?: TagInputValue; + e: FocusEvent; +} + +export interface SelectInputValueChangeContext { + e?: Event | MouseEvent | FocusEvent | KeyboardEvent | CompositionEvent; + trigger: 'input' | 'clear' | 'blur' | 'focus' | 'initial' | 'change'; +} + +export type SelectInputChangeContext = TagInputChangeContext; diff --git a/src/select-input/useOverlayInnerStyle.ts b/src/select-input/useOverlayInnerStyle.ts new file mode 100644 index 0000000..40c0012 --- /dev/null +++ b/src/select-input/useOverlayInnerStyle.ts @@ -0,0 +1,96 @@ +import isFunction from 'lodash/isFunction'; +import isObject from 'lodash/isObject'; + +import useControlled from '../_util/useControlled'; +import { PopupVisibleChangeContext, TdPopupProps } from '../popup'; +import { TdSelectInputProps } from './type'; + +export type overlayStyleProps = Pick< + TdSelectInputProps, + | 'popupProps' + | 'autoWidth' + | 'readonly' + | 'onPopupVisibleChange' + | 'disabled' + | 'allowInput' + | 'popupVisible' + | 'defaultPopupVisible' +>; + +// 单位:px +const MAX_POPUP_WIDTH = 1000; + +export default function useOverlayInnerStyle( + props: overlayStyleProps, + extra?: { + afterHidePopup?: (ctx: PopupVisibleChangeContext) => void; + }, +) { + const { popupProps, autoWidth, readonly, disabled, onPopupVisibleChange, allowInput } = props; + const [innerPopupVisible, setInnerPopupVisible] = useControlled(props, 'popupVisible', onPopupVisibleChange); + + const matchWidthFunc = (triggerElement: HTMLElement, popupElement: HTMLElement) => { + if (!triggerElement || !popupElement) return; + + // 设置display来可以获取popupElement的宽度 + // eslint-disable-next-line no-param-reassign + popupElement.style.display = ''; + // popupElement的scrollBar宽度 + const overlayScrollWidth = popupElement.offsetWidth - popupElement.scrollWidth; + + /** + * issue:https://github.com/Tencent/tdesign-react/issues/2642 + * + * popupElement的内容宽度不超过triggerElement的宽度,就使用triggerElement的宽度减去popup的滚动条宽度, + * 让popupElement的宽度加上scrollBar的宽度等于triggerElement的宽度; + * + * popupElement的内容宽度超过triggerElement的宽度,就使用popupElement的scrollWidth, + * 不用offsetWidth是会包含scrollBar的宽度 + */ + const width = + popupElement.offsetWidth - overlayScrollWidth > triggerElement.offsetWidth + ? popupElement.scrollWidth + : triggerElement.offsetWidth - overlayScrollWidth; + + let otherOverlayInnerStyle = {}; + if (popupProps && typeof popupProps.overlayInnerStyle === 'object' && !popupProps.overlayInnerStyle.width) { + otherOverlayInnerStyle = popupProps.overlayInnerStyle; + } + return { + width: `${Math.min(width, MAX_POPUP_WIDTH)}px`, + ...otherOverlayInnerStyle, + }; + }; + + const onInnerPopupVisibleChange = (visible: boolean, context: PopupVisibleChangeContext) => { + if (disabled || readonly) { + return; + } + // 如果点击触发元素(输入框)且为可输入状态,则继续显示下拉框 + const newVisible = context.trigger === 'trigger-element-click' && allowInput ? true : visible; + + if (props.popupVisible !== newVisible) { + setInnerPopupVisible(newVisible, context); + if (!newVisible) { + extra?.afterHidePopup?.(context); + } + } + }; + + const tOverlayInnerStyle = () => { + let result: TdPopupProps['overlayInnerStyle'] = {}; + const overlayInnerStyle = popupProps?.overlayInnerStyle || {}; + if (isFunction(overlayInnerStyle) || (isObject(overlayInnerStyle) && overlayInnerStyle.width)) { + result = overlayInnerStyle; + } else if (!autoWidth) { + result = matchWidthFunc; + } + return result; + }; + + return { + tOverlayInnerStyle, + innerPopupVisible, + onInnerPopupVisibleChange, + }; +} From 8551d7ac02bcd50e0e436c6c67d5eeed4285777e Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Fri, 13 Sep 2024 16:48:03 +0800 Subject: [PATCH 07/15] refactor(input): restore input --- src/input/input.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/input/input.tsx b/src/input/input.tsx index a6cc29c..a44e828 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -149,14 +149,13 @@ export default class Input extends Component { }; private handleFocus = (e: FocusEvent) => { - console.log('===e', e.composedPath()); e.stopImmediatePropagation(); const { readonly, onFocus } = this.props; if (readonly) return; const { currentTarget }: { currentTarget: any } = e; onFocus?.(currentTarget.value, { e }); this.isFocused = true; - // (this as any).queuedUpdate(); + this.update(); }; private handleBlur = (e: FocusEvent) => { @@ -186,7 +185,6 @@ export default class Input extends Component { }; private handleClear = (e: MouseEvent) => { - console.log('---clear'); const { onChange, onClear } = this.props; this.composingValue = ''; this.value = ''; @@ -331,7 +329,7 @@ export default class Input extends Component { 't', 'prefix', cloneElement(parseTNode(convertToLightDomNode(prefixIcon)) as VNode, { - className: `${classPrefix}-input__prefix`, + cls: `${classPrefix}-input__prefix`, style: { marginRight: '0px' }, }), ) @@ -342,7 +340,7 @@ export default class Input extends Component { suffixIconNew = ( { suffixIconNew = ( { suffixIconNew = ( Date: Sat, 14 Sep 2024 18:45:13 +0800 Subject: [PATCH 08/15] =?UTF-8?q?perf(popup):=20=E4=BC=98=E5=8C=96popup=20?= =?UTF-8?q?dom=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/checkbox/checkbox-group.tsx | 34 +++-- src/checkbox/index.ts | 2 + src/checkbox/type.ts | 4 + src/popup/popup.tsx | 107 ++++++++++---- src/select-input/_example/multiple.tsx | 197 +++++++++++++++++++++++++ src/select-input/style/index.js | 10 +- src/tag-input/style/index.js | 9 ++ src/tag-input/style/index.ts | 15 -- src/tag-input/tag-input.tsx | 2 +- 9 files changed, 321 insertions(+), 59 deletions(-) create mode 100644 src/select-input/_example/multiple.tsx create mode 100644 src/tag-input/style/index.js delete mode 100644 src/tag-input/style/index.ts diff --git a/src/checkbox/checkbox-group.tsx b/src/checkbox/checkbox-group.tsx index 42182da..6bd93ed 100644 --- a/src/checkbox/checkbox-group.tsx +++ b/src/checkbox/checkbox-group.tsx @@ -1,7 +1,8 @@ import { intersection, isObject, isString, isUndefined, toArray } from 'lodash'; import { bind, Component, signal, tag, VNode } from 'omi'; -import { getClassPrefix } from '../_util/classname.ts'; +import classname, { getClassPrefix } from '../_util/classname.ts'; +import { convertToLightDomNode } from '../_util/lightDom.ts'; import { StyledProps, TNode } from '../common'; import { CheckboxContextKey } from './checkbox'; import { @@ -182,22 +183,33 @@ export default class CheckboxGroup extends Component { const classPrefix = getClassPrefix(); let children = null; if (this.props.options?.length) { - children = this.optionList?.map((option, index) => ( - - )); + children = this.optionList?.map((option, index) => { + const { isLightDom, ...rest } = option; + const checkbox = ( + + ); + if (isLightDom) { + return convertToLightDomNode(checkbox); + } + return checkbox; + }); } else { this.innerOptionList.value = this.getOptionListBySlots(); children = this.props.children; } return ( -
    +
    {children}
    ); diff --git a/src/checkbox/index.ts b/src/checkbox/index.ts index e9630b3..180d342 100644 --- a/src/checkbox/index.ts +++ b/src/checkbox/index.ts @@ -10,4 +10,6 @@ export type CheckboxGroupProps = TdCheckboxGroupProps; export const Checkbox = _Checkbox; export const CheckboxGroup = _Group; +export * from './type'; + export default Checkbox; diff --git a/src/checkbox/type.ts b/src/checkbox/type.ts index 2561e48..9171bb0 100644 --- a/src/checkbox/type.ts +++ b/src/checkbox/type.ts @@ -42,6 +42,10 @@ export interface TdCheckboxProps { * 多选框的值 */ value?: string | number | boolean; + /** + * 是否去除 t-checkbox 的shadowdom,只在group中生效 + */ + isLightDom?: boolean; /** * 值变化时触发 */ diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index a8b84a1..ffc3719 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -3,12 +3,13 @@ import './popupTrigger'; import { createPopper } from '@popperjs/core'; import debounce from 'lodash/debounce'; -import { Component, createRef, OmiProps, tag } from 'omi'; +import { cloneElement, Component, createRef, OmiProps, tag, VNode } from 'omi'; import { getIEVersion } from '../_common/js/utils/helper'; import classname from '../_util/classname'; +import { getChildrenArray } from '../_util/component'; import { domContains } from '../_util/dom'; -import { StyledProps } from '../common'; +import { StyledProps, TNode } from '../common'; import { PopupVisibleChangeContext, TdPopupProps } from './type'; import { attachListeners, getPopperPlacement, triggers } from './utils'; @@ -40,7 +41,6 @@ export const PopupTypes = { onScroll: Function, onScrollToBottom: Function, onVisibleChange: Function, - strategy: String, expandAnimation: Boolean, updateScrollTop: Function, }; @@ -65,7 +65,6 @@ export default class Popup extends Component { placement: 'top', showArrow: true, trigger: 'hover', - strategy: 'fixed', }; triggerRef = createRef(); @@ -81,9 +80,11 @@ export default class Popup extends Component { hasDocumentEvent = false; visible = false; - // watch visible TODO: - hasTrigger = () => + // 防止多次触发显隐 + leaveFlag = false; + + triggerType = () => triggers.reduce( (map, trigger) => ({ ...map, @@ -122,7 +123,7 @@ export default class Popup extends Component { () => { this.handlePopVisible(true, context); }, - this.hasTrigger().click ? 0 : this.normalizedDelay().open, + this.triggerType().click ? 0 : this.normalizedDelay().open, ); }; @@ -132,7 +133,7 @@ export default class Popup extends Component { () => { this.handlePopVisible(false, context); }, - this.hasTrigger().click ? 0 : this.normalizedDelay().close, + this.triggerType().click ? 0 : this.normalizedDelay().close, ); }; @@ -183,25 +184,31 @@ export default class Popup extends Component { } }; - updateTrigger = () => { - const trigger = attachListeners(this.rootElement); + addTriggerEvent = () => { + const triggerRef = this.triggerRef.current as HTMLElement; + + if (!triggerRef) return; + + const trigger = attachListeners(triggerRef); trigger.clean(); - const hasTrigger = this.hasTrigger(); - if (hasTrigger.hover) { + const triggerType = this.triggerType(); + if (triggerType.hover) { trigger.add('mouseenter', () => { + this.leaveFlag = false; this.handleOpen({ trigger: 'trigger-element-hover' }); }); trigger.add('mouseleave', () => { + this.leaveFlag = false; this.handleClose({ trigger: 'trigger-element-hover' }); }); - } else if (hasTrigger.focus) { + } else if (triggerType.focus) { trigger.add('focusin', () => this.handleOpen({ trigger: 'trigger-element-focus' })); trigger.add('focusout', () => this.handleClose({ trigger: 'trigger-element-blur' })); - } else if (hasTrigger.click) { + } else if (triggerType.click) { trigger.add('click', (e: MouseEvent) => { this.clickHandle(e); }); - } else if (hasTrigger['context-menu']) { + } else if (triggerType['context-menu']) { trigger.add('contextmenu', (e: MouseEvent) => { e.preventDefault(); e.button === 2 && this.handleToggle({ trigger: 'context-menu' }); @@ -209,9 +216,35 @@ export default class Popup extends Component { } }; + addPopContentEvent() { + const popperEl = this.popperRef.current as HTMLElement; + if (!popperEl) return; + const popper = attachListeners(popperEl); + popper.clean(); + + const triggerType = this.triggerType(); + if (triggerType.hover) { + popper.add('mouseenter', () => { + console.log('====mouseenter'); + if (!this.leaveFlag) { + clearTimeout(this.timeout); + this.handleOpen({ trigger: 'trigger-element-hover' }); + } + }); + + popper.add('mouseleave', () => { + this.leaveFlag = true; + clearTimeout(this.timeout); + this.handleClose({ trigger: 'trigger-element-hover' }); + }); + } + } + installed() { this.updatePopper(); - this.updateTrigger(); + this.addTriggerEvent(); + this.addPopContentEvent(); + this.visible = this.props.visible; } @@ -240,7 +273,7 @@ export default class Popup extends Component { updatePopper = () => { this.popper = createPopper(this.triggerRef.current as HTMLElement, this.popperRef.current as HTMLElement, { placement: getPopperPlacement(this.props.placement as PopupProps['placement']), - strategy: this.props.strategy, + ...this.props?.popperOptions, }); }; @@ -287,21 +320,33 @@ export default class Popup extends Component { props.overlayInnerClassName, ); - const trigger = props.triggerElement ? props.triggerElement : this.props.children; + const trigger = getChildrenArray(props.triggerElement ? props.triggerElement : this.props.children); + + const children = trigger.map((child: TNode) => { + // 对 t-button 做特殊处理 + if (typeof child === 'object' && (child as any).nodeName === 't-button') { + const oldClick = (child as VNode).attributes?.onClick; + return cloneElement(child as VNode, { + onClick: (e) => { + if (oldClick) oldClick(e); + this.clickHandle(e.detail.e); + }, + }); + } + return child; + }); + + console.log('===children', children); return ( - - { - if (e?.detail?.context?.nodeName === 'T-BUTTON') { - this.clickHandle(e.detail.e); - } - }} - > - {trigger} - + <> + {children.length > 1 ? ( + + {children} + + ) : ( + cloneElement(children[0] as VNode, { ref: this.triggerRef }) + )} {this.getVisible() || !props.destroyOnClose ? (
    { )}
    ) : null} -
    + ); } } diff --git a/src/select-input/_example/multiple.tsx b/src/select-input/_example/multiple.tsx new file mode 100644 index 0000000..b480ddf --- /dev/null +++ b/src/select-input/_example/multiple.tsx @@ -0,0 +1,197 @@ +import 'tdesign-web-components/select-input'; +import 'tdesign-web-components/checkbox'; +import 'tdesign-icons-web-components/esm/components/chevron-down'; + +import { Component, signal } from 'omi'; +import type { CheckboxGroupProps, CheckboxOptionObj, SelectInputProps } from 'tdesign-web-components'; + +const classStyles = ` +.tdesign-demo__panel-options-multiple { + width: 100%; + padding: 0 !important; + display: flex !important; + flex-direction: column; + gap: 2px !important; +} +.tdesign-demo__panel-options-multiple .t-checkbox { + display: flex; + border-radius: 3px; + line-height: 22px; + cursor: pointer; + padding: 3px 8px; + color: var(--td-text-color-primary); + transition: background-color 0.2s linear; + white-space: nowrap; + word-wrap: normal; + overflow: hidden; + text-overflow: ellipsis; + margin: 0; +} +.tdesign-demo__panel-options-multiple .t-checkbox:hover { + background-color: var(--td-bg-color-container-hover); +} +`; + +const OPTIONS: CheckboxOptionObj[] = [ + // 全选 + { label: 'Check All', checkAll: true, isLightDom: true }, + { label: 'tdesign-vue', value: 1, isLightDom: true }, + { label: 'tdesign-react', value: 2, isLightDom: true }, + { label: 'tdesign-miniprogram', value: 3, isLightDom: true }, + { label: 'tdesign-angular', value: 4, isLightDom: true }, + { label: 'tdesign-mobile-vue', value: 5, isLightDom: true }, + { label: 'tdesign-mobile-react', value: 6, isLightDom: true }, +]; + +type ExcessTagsDisplayType = SelectInputProps['tagInputProps']['excessTagsDisplayType']; + +export default class SelectInputMultiple extends Component { + excessTagsDisplayType = signal('break-line'); + + allowInput = signal(true); + + creatable = signal(true); + + inputValue = signal(''); + + // 全量数据 + options = signal([...OPTIONS]); + + // 仅用作展示的数据(过滤功能需要使用) + displayOptions = signal([...OPTIONS]); + + value = signal>([ + { label: 'Vue', value: 1 }, + { label: 'React', value: 2 }, + { label: 'Miniprogram', value: 3 }, + ]); + + getCheckboxValue = () => { + const arr = []; + const list = this.value.value; + // 此处不使用 forEach,减少函数迭代 + for (let i = 0, len = list.length; i < len; i++) { + list[i].value && arr.push(list[i].value); + } + return arr; + }; + + // 直接 checkboxgroup 组件渲染输出下拉选项,自定义处理可以避免顺序和 tagChange 冲突 + onCheckedChange: CheckboxGroupProps['onChange'] = (val, { current, type }) => { + // current 不存在,则表示操作全选 + if (!current) { + const newValue = type === 'check' ? this.options.value.slice(1) : []; + this.value.value = newValue; + return; + } + // 普通操作 + if (type === 'check') { + const option = this.options.value.find((t) => t.value === current); + this.value.value = this.value.value.concat(option); + } else { + const newValue = this.value.value.filter((v) => v.value !== current); + this.value.value = newValue; + } + }; + + // 可以根据触发来源,自由定制标签变化时的筛选器行为 + onTagChange: SelectInputProps['onTagChange'] = (currentTags, context) => { + const { trigger, index } = context; + if (trigger === 'clear') { + this.value.value = []; + } + if (['tag-remove', 'backspace'].includes(trigger)) { + const newValue = [...this.value.value]; + newValue.splice(index, 1); + this.value.value = newValue; + } + }; + + onInputChange: SelectInputProps['onInputChange'] = (val, context) => { + this.inputValue.value = val; + // 过滤功能 + console.log(val, context); + }; + + onInputEnter: SelectInputProps['onEnter'] = (_, { inputValue }) => { + // 如果允许创建新条目 + if (this.creatable.value) { + const current = { label: inputValue, value: inputValue }; + const newValue = [...this.value.value]; + this.value.value = newValue.concat(current); + const newOptions = this.options.value.concat(current); + this.options.value = newOptions; + this.displayOptions.value = newOptions; + this.inputValue.value = ''; + } + }; + + render() { + const checkboxValue = this.getCheckboxValue(); + return ( +
    +
    + { + this.allowInput.value = v; + }} + > + 是否允许输入 + + { + this.creatable.value = v; + }} + > + 允许创建新选项(Enter 创建) + +
    +
    +
    + (this.excessTagsDisplayType.value = val)} + options={[ + { label: '选中项过多横向滚动', value: 'scroll' }, + { label: '选中项过多换行显示', value: 'break-line' }, + ]} + /> +
    +
    +
    + + {/* */} + 多选:} + panel={ + this.displayOptions.value.length ? ( + + ) : ( +
    暂无数据
    + ) + } + suffixIcon={} + clearable + multiple + onTagChange={this.onTagChange} + onInputChange={this.onInputChange} + onEnter={this.onInputEnter} + /> +
    + ); + } +} diff --git a/src/select-input/style/index.js b/src/select-input/style/index.js index 72b5677..e927dae 100644 --- a/src/select-input/style/index.js +++ b/src/select-input/style/index.js @@ -1 +1,9 @@ -import '../../_common/style/web/components/select-input/_index.less'; +import { css, globalCSS } from 'omi'; + +import styles from '../../_common/style/web/components/select-input/_index.less'; + +export const styleSheet = css` + ${styles} +`; + +globalCSS(styleSheet); diff --git a/src/tag-input/style/index.js b/src/tag-input/style/index.js new file mode 100644 index 0000000..9af4143 --- /dev/null +++ b/src/tag-input/style/index.js @@ -0,0 +1,9 @@ +import { css, globalCSS } from 'omi'; + +import styles from '../../_common/style/web/components/tag-input/_index.less'; + +export const styleSheet = css` + ${styles} +`; + +globalCSS(styleSheet); diff --git a/src/tag-input/style/index.ts b/src/tag-input/style/index.ts deleted file mode 100644 index 0ba4125..0000000 --- a/src/tag-input/style/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { css, globalCSS } from 'omi'; - -import inputStyles from '../../_common/style/web/components/input/_index.less'; -import tagStyles from '../../_common/style/web/components/tag/_index.less'; -import styles from '../../_common/style/web/components/tag-input/_index.less'; -import theme from '../../_common/style/web/theme/_index.less'; - -export const styleSheet = css` - ${styles} - ${inputStyles} - ${tagStyles} - ${theme} -`; - -globalCSS(styleSheet); diff --git a/src/tag-input/tag-input.tsx b/src/tag-input/tag-input.tsx index 16f4a94..2579c2d 100644 --- a/src/tag-input/tag-input.tsx +++ b/src/tag-input/tag-input.tsx @@ -507,7 +507,7 @@ export default class TagInput extends Component { const suffixIconNode = showClearIcon ? ( Date: Wed, 18 Sep 2024 17:10:11 +0800 Subject: [PATCH 09/15] =?UTF-8?q?feat(popup):=20=E4=BC=98=E5=8C=96popup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/button/button.tsx | 9 ++- src/common.ts | 4 ++ src/common/portal.tsx | 4 ++ src/input/input.tsx | 12 +++- src/popup/_example/placement.tsx | 70 ++++++------------- src/popup/popup.tsx | 97 +++++++++++++++----------- src/select-input/SelectInput.tsx | 13 +++- src/select-input/SelectInputSingle.tsx | 10 +++ src/select-input/_example/single.tsx | 2 +- 9 files changed, 123 insertions(+), 98 deletions(-) diff --git a/src/button/button.tsx b/src/button/button.tsx index 2f231dd..30cbb8f 100644 --- a/src/button/button.tsx +++ b/src/button/button.tsx @@ -32,6 +32,7 @@ export default class Button extends Component { content: [String, Object], onClick: Function, ignoreAttributes: Array, + innerStyle: String, }; static defaultProps = { @@ -73,7 +74,6 @@ export default class Button extends Component { render(props: ButtonProps) { const { icon, - className, variant, size, block, @@ -84,10 +84,14 @@ export default class Button extends Component { ignoreAttributes, children, suffix, + innerClass, + innerStyle, ...rest } = props; delete rest.onClick; + delete rest.className; + delete rest.style; const classPrefix = getClassPrefix(); @@ -104,7 +108,7 @@ export default class Button extends Component { return ( { }, )} onClick={this.clickHandle} + style={innerStyle} {...rest} > {iconNode ? iconNode : null} diff --git a/src/common.ts b/src/common.ts index 79cfbc4..090e1c4 100644 --- a/src/common.ts +++ b/src/common.ts @@ -18,6 +18,10 @@ export type Styles = Record; export interface StyledProps { className?: string; style?: Styles; + // shadowDom内部根节点的class + innerClass?: string; + // shadowDom内部根节点的style + innerStyle?: Styles; } /** diff --git a/src/common/portal.tsx b/src/common/portal.tsx index 8163b3e..c4a87a8 100644 --- a/src/common/portal.tsx +++ b/src/common/portal.tsx @@ -68,6 +68,10 @@ export default class Portal extends Component { this.parentElement?.appendChild?.(this.container); } + uninstall(): void { + this.parentElement?.removeChild?.(this.container); + } + render() { const { children } = this.props; return render(children, this.container); diff --git a/src/input/input.tsx b/src/input/input.tsx index a44e828..bf9d02f 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -284,12 +284,13 @@ export default class Input extends Component { render(props: OmiProps) { const { + innerClass, + innerStyle, autoWidth, placeholder, disabled, status, size, - className, prefixIcon, suffixIcon, clearable, @@ -308,11 +309,15 @@ export default class Input extends Component { keepWrapperWidth, showLimitNumber, allowInputOverMax, + inputClass, format, onValidate, ...restProps } = props; + delete restProps.className; + delete restProps.style; + const { limitNumber, tStatus } = useLengthLimit({ value: this.value === undefined ? undefined : String(this.value), status, @@ -419,7 +424,7 @@ export default class Input extends Component { ); const renderInputNode = (
    { { [`${classPrefix}-input--auto-width`]: autoWidth && !keepWrapperWidth, }, - className, + innerClass, )} ref={this.wrapperRef} part="wrap" {...restProps} + style={innerStyle} > {renderInputNode}
    diff --git a/src/popup/_example/placement.tsx b/src/popup/_example/placement.tsx index a441efe..4303a1d 100644 --- a/src/popup/_example/placement.tsx +++ b/src/popup/_example/placement.tsx @@ -3,7 +3,7 @@ import 'tdesign-web-components/popup'; const styles = { container: { - margin: '0 auto', + margin: '20px auto', width: '500px', height: '260px', position: 'relative', @@ -74,50 +74,26 @@ const styles = { export default function Placement() { return (
    - - top + + top - - top-left + + top-left - - top-right + + top-right - - bottom + + bottom - - bottom-left + + bottom-left - - bottom-right + + bottom-right - - left + + left - left-top + left-top - left-bottom + left-bottom - - right + + right - right-top + right-top - right-bottom + right-bottom
    ); diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index ffc3719..d94f16f 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -1,5 +1,6 @@ import 'omi-transition'; import './popupTrigger'; +import '../common/portal'; import { createPopper } from '@popperjs/core'; import debounce from 'lodash/debounce'; @@ -84,6 +85,8 @@ export default class Popup extends Component { // 防止多次触发显隐 leaveFlag = false; + isPopoverInDomTree = false; + triggerType = () => triggers.reduce( (map, trigger) => ({ @@ -114,7 +117,15 @@ export default class Popup extends Component { if (typeof this.props.onVisibleChange === 'function') { this.props.onVisibleChange(visible, context); } + if (this.visible) { + this.isPopoverInDomTree = true; + } else if (this.props.destroyOnClose) { + this.isPopoverInDomTree = false; + } this.update(); + if (this.visible) { + this.addPopContentEvent(); + } }; handleOpen = (context: Pick) => { @@ -225,7 +236,6 @@ export default class Popup extends Component { const triggerType = this.triggerType(); if (triggerType.hover) { popper.add('mouseenter', () => { - console.log('====mouseenter'); if (!this.leaveFlag) { clearTimeout(this.timeout); this.handleOpen({ trigger: 'trigger-element-hover' }); @@ -240,14 +250,6 @@ export default class Popup extends Component { } } - installed() { - this.updatePopper(); - this.addTriggerEvent(); - this.addPopContentEvent(); - - this.visible = this.props.visible; - } - handleToggle = (context: PopupVisibleChangeContext) => { const visible = !this.visible; if (!visible) return; @@ -273,7 +275,7 @@ export default class Popup extends Component { updatePopper = () => { this.popper = createPopper(this.triggerRef.current as HTMLElement, this.popperRef.current as HTMLElement, { placement: getPopperPlacement(this.props.placement as PopupProps['placement']), - ...this.props?.popperOptions, + ...(this.props?.popperOptions || {}), }); }; @@ -282,6 +284,10 @@ export default class Popup extends Component { handlePopVisible(visible, { trigger: 'document' }); }; + handleBeforeEnter = () => { + this.updatePopper(); + }; + beforeUpdate() { // deal visible if (this.getVisible()) { @@ -296,14 +302,23 @@ export default class Popup extends Component { } } - handleBeforeEnter = () => { - this.updatePopper(); - }; - install(): void { window.addEventListener('resize', this.updatePopper); } + installed() { + this.updatePopper(); + this.addTriggerEvent(); + + this.visible = this.props.visible; + // 初始化就显示时 + if (this.visible) { + this.isPopoverInDomTree = true; + this.update(); + this.addPopContentEvent(); + } + } + uninstall(): void { window.removeEventListener('resize', this.updatePopper); } @@ -336,8 +351,6 @@ export default class Popup extends Component { return child; }); - console.log('===children', children); - return ( <> {children.length > 1 ? ( @@ -347,31 +360,33 @@ export default class Popup extends Component { ) : ( cloneElement(children[0] as VNode, { ref: this.triggerRef }) )} - {this.getVisible() || !props.destroyOnClose ? ( -
    (this.contentClicked = true)} - > - {(this.getVisible() || this.popperRef.current) && ( -
    - {props.content} - {props.showArrow ? ( -
    - ) : null} -
    - )} -
    + {this.isPopoverInDomTree ? ( + +
    (this.contentClicked = true)} + > + {(this.getVisible() || this.popperRef.current) && ( +
    + {props.content} + {props.showArrow ? ( +
    + ) : null} +
    + )} +
    + ) : null} ); diff --git a/src/select-input/SelectInput.tsx b/src/select-input/SelectInput.tsx index a1b4aae..aee6c96 100644 --- a/src/select-input/SelectInput.tsx +++ b/src/select-input/SelectInput.tsx @@ -36,15 +36,24 @@ const COMMON_PROPERTIES = [ 'prefixIcon', ]; +const classPrefix = getClassPrefix(); + @tag('t-select-input') class SelectInput extends Component { + static css = [ + `.${classPrefix}-select-input > t-popup { + display: inline-flex; + width: 100%; + };`, + ]; + static defaultProps = selectInputDefaultProps; selectInputRef = createRef(); selectInputWrapRef = createRef(); - classPrefix = getClassPrefix(); + classPrefix = classPrefix; commonInputProps: SelectInputCommonProperties; @@ -93,7 +102,7 @@ class SelectInput extends Component { ]); const mainContent = ( -
    console.log('333', e)}> +
    console.log('333', e)}> { + static css = [ + `:host { + width: 100%; + }; + t-input { + width: 100%; + } + `, + ]; + classPrefix = getClassPrefix(); inputRef = createRef(); diff --git a/src/select-input/_example/single.tsx b/src/select-input/_example/single.tsx index 9cdd9cb..ab2e1ca 100644 --- a/src/select-input/_example/single.tsx +++ b/src/select-input/_example/single.tsx @@ -67,7 +67,7 @@ export default class SelectInputSingle extends Component { Date: Thu, 19 Sep 2024 11:08:34 +0800 Subject: [PATCH 10/15] feat(input-select): add input-select --- package-lock.json | 20 ++++++++++---------- package.json | 4 ++-- src/_util/useControlled.ts | 1 - src/popup/popup.tsx | 3 +++ src/select-input/README.md | 2 +- src/select-input/SelectInput.tsx | 2 +- src/select-input/SelectInputSingle.tsx | 4 +--- src/select-input/_example/single.tsx | 10 +++++++--- src/select-input/useOverlayInnerStyle.ts | 5 +++++ 9 files changed, 30 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d3f263..3c9bb48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "omi": "^7.6.17", "omi-transition": "^0.1.8", "tailwind-merge": "^2.2.1", - "tdesign-icons-web-components": "^0.1.2" + "tdesign-icons-web-components": "^0.1.4-alpha.1" }, "devDependencies": { "@babel/core": "^7.24.7", @@ -12619,17 +12619,17 @@ } }, "node_modules/tdesign-icons-web-components": { - "version": "0.1.2", - "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.2.tgz", - "integrity": "sha512-e+or0ozppYUl41iN1tMjnlM2JBBp/sjLiFi70T5203S8y/ZcdaDBAO50iF/t4jrAHAtoWfw/NSQGIFJHOF/olw==", + "version": "0.1.4-alpha.1", + "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.4-alpha.1.tgz", + "integrity": "sha512-T64U3aIVyMbt/5kw6mjf8b1lZQHQQwY6VB9HXAu4dXZ4cmMe0pH3ttSWyWW9V8sbhdKReYWWJzuaJcNPz5xO6A==", "dependencies": { "@babel/runtime": "^7.16.5", "clsx": "^2.1.1", - "omi": "^7.6.7", + "omi": "^7.6.17", "tailwind-merge": "^2.3.0" }, "peerDependencies": { - "omi": ">=7.6.7" + "omi": ">=7.6.17" } }, "node_modules/tdesign-site-components": { @@ -23033,13 +23033,13 @@ } }, "tdesign-icons-web-components": { - "version": "0.1.2", - "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.2.tgz", - "integrity": "sha512-e+or0ozppYUl41iN1tMjnlM2JBBp/sjLiFi70T5203S8y/ZcdaDBAO50iF/t4jrAHAtoWfw/NSQGIFJHOF/olw==", + "version": "0.1.4-alpha.1", + "resolved": "https://mirrors.tencent.com/npm/tdesign-icons-web-components/-/tdesign-icons-web-components-0.1.4-alpha.1.tgz", + "integrity": "sha512-T64U3aIVyMbt/5kw6mjf8b1lZQHQQwY6VB9HXAu4dXZ4cmMe0pH3ttSWyWW9V8sbhdKReYWWJzuaJcNPz5xO6A==", "requires": { "@babel/runtime": "^7.16.5", "clsx": "^2.1.1", - "omi": "^7.6.7", + "omi": "^7.6.17", "tailwind-merge": "^2.3.0" } }, diff --git a/package.json b/package.json index df53f52..c64e9b0 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "license": "MIT", "scripts": { "start": "npm run dev", - "dev": "npm run generate:entry && cd site && vite", + "dev": "npm run generate:entry && cd site && vite --force", "site": "cd site && vite build", "site:intranet": "cd site && vite build --mode intranet", "site:preview": "cd site && vite build --mode preview && cd ../_site && cp index.html 404.html", @@ -67,7 +67,7 @@ "omi": "^7.6.17", "omi-transition": "^0.1.8", "tailwind-merge": "^2.2.1", - "tdesign-icons-web-components": "^0.1.2" + "tdesign-icons-web-components": "^0.1.4-alpha.1" }, "devDependencies": { "@babel/core": "^7.24.7", diff --git a/src/_util/useControlled.ts b/src/_util/useControlled.ts index 86f2f14..77101a3 100644 --- a/src/_util/useControlled.ts +++ b/src/_util/useControlled.ts @@ -36,7 +36,6 @@ const useControlled:

    ( // 无论是否受控,都要维护一个内部变量,默认值由 defaultValue 控制 const internalValue = signal(defaultValue); // 受控模式 - console.log('===controlled', controlled, valueKey); if (controlled) return [value, onChange || (() => {})]; // 非受控模式 diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index d94f16f..cd3dbb0 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -266,6 +266,7 @@ export default class Popup extends Component { } getOverlayStyle(overlayStyle: PopupProps['overlayStyle']) { + console.log('==ss', overlayStyle, this.triggerRef.current, this.popperRef.current); if (this.triggerRef.current && this.popperRef.current && typeof overlayStyle === 'function') { return { ...overlayStyle(this.triggerRef.current as HTMLElement, this.popperRef.current as HTMLElement) }; } @@ -351,6 +352,8 @@ export default class Popup extends Component { return child; }); + console.log('props.overlayInnerStyle', props.overlayInnerStyle); + return ( <> {children.length > 1 ? ( diff --git a/src/select-input/README.md b/src/select-input/README.md index b7fabca..886fdf1 100644 --- a/src/select-input/README.md +++ b/src/select-input/README.md @@ -22,7 +22,7 @@ spline: data 可使用 `SelectInput` 自由定制任何风格的多选选择器。 -{{ multiple }} + ### 自动填充筛选器 diff --git a/src/select-input/SelectInput.tsx b/src/select-input/SelectInput.tsx index aee6c96..65e436d 100644 --- a/src/select-input/SelectInput.tsx +++ b/src/select-input/SelectInput.tsx @@ -102,7 +102,7 @@ class SelectInput extends Component { ]); const mainContent = ( -

    console.log('333', e)}> +
    { `:host { width: 100%; }; - t-input { - width: 100%; - } `, ]; @@ -100,6 +97,7 @@ export default class SingleSelectInput extends Component { }} // onBlur need to triggered by input when popup panel is null onBlur={!props.panel ? handleEmptyPanelBlur : null} + style={{ width: '100%' }} {...props.inputProps} inputClass={classNames(props.inputProps?.className, { [`${this.classPrefix}-input--focused`]: popupVisible, diff --git a/src/select-input/_example/single.tsx b/src/select-input/_example/single.tsx index ab2e1ca..c64a69a 100644 --- a/src/select-input/_example/single.tsx +++ b/src/select-input/_example/single.tsx @@ -4,6 +4,7 @@ import 'tdesign-icons-web-components/esm/components/chevron-down'; import { Component, signal } from 'omi'; const classStyles = ` + `; const OPTIONS = [ @@ -39,8 +41,6 @@ const OPTIONS = [ ]; export default class SelectInputSingle extends Component { - static css = classStyles; - selectValue = signal({ label: 'tdesign-vue', value: 1 }); popupVisible = signal(false); @@ -61,6 +61,10 @@ export default class SelectInputSingle extends Component { this.popupVisible.value = val; }; + installed(): void { + document.head.insertAdjacentHTML('beforeend', classStyles); + } + render() { return (
    @@ -71,7 +75,7 @@ export default class SelectInputSingle extends Component { placeholder="Please Select" clearable allowInput - popupProps={{ overlayInnerStyle: { padding: 6 }, css: classStyles }} + popupProps={{ overlayInnerStyle: { padding: 6 } }} onPopupVisibleChange={this.onPopupVisibleChange} onClear={this.onClear} panel={ diff --git a/src/select-input/useOverlayInnerStyle.ts b/src/select-input/useOverlayInnerStyle.ts index 40c0012..e691530 100644 --- a/src/select-input/useOverlayInnerStyle.ts +++ b/src/select-input/useOverlayInnerStyle.ts @@ -30,6 +30,7 @@ export default function useOverlayInnerStyle( const [innerPopupVisible, setInnerPopupVisible] = useControlled(props, 'popupVisible', onPopupVisibleChange); const matchWidthFunc = (triggerElement: HTMLElement, popupElement: HTMLElement) => { + console.log('ffff'); if (!triggerElement || !popupElement) return; // 设置display来可以获取popupElement的宽度 @@ -52,6 +53,7 @@ export default function useOverlayInnerStyle( ? popupElement.scrollWidth : triggerElement.offsetWidth - overlayScrollWidth; + console.log('==width', width); let otherOverlayInnerStyle = {}; if (popupProps && typeof popupProps.overlayInnerStyle === 'object' && !popupProps.overlayInnerStyle.width) { otherOverlayInnerStyle = popupProps.overlayInnerStyle; @@ -81,10 +83,13 @@ export default function useOverlayInnerStyle( let result: TdPopupProps['overlayInnerStyle'] = {}; const overlayInnerStyle = popupProps?.overlayInnerStyle || {}; if (isFunction(overlayInnerStyle) || (isObject(overlayInnerStyle) && overlayInnerStyle.width)) { + console.log(1); result = overlayInnerStyle; } else if (!autoWidth) { + console.log(2); result = matchWidthFunc; } + console.log(3); return result; }; From 7f26114a0c3101d56f4a7a897a75addf705a1a8a Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Fri, 20 Sep 2024 10:43:47 +0800 Subject: [PATCH 11/15] =?UTF-8?q?fix(omi-transition):=20=E5=8A=A8=E7=94=BB?= =?UTF-8?q?enter=E3=80=81leave=E5=A4=9A=E6=AC=A1=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 14 +++++------ package.json | 2 +- src/_common | 2 +- src/_util/useControlled.ts | 1 - src/popup/popup.tsx | 30 ++++++++++-------------- src/select-input/SelectInput.tsx | 10 ++++---- src/select-input/SelectInputSingle.tsx | 2 -- src/select-input/useOverlayInnerStyle.ts | 7 +----- 8 files changed, 26 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c9bb48..04f7e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "copy-to-clipboard": "^3.3.3", "lodash": "~4.17.15", "omi": "^7.6.17", - "omi-transition": "^0.1.8", + "omi-transition": "^0.1.9", "tailwind-merge": "^2.2.1", "tdesign-icons-web-components": "^0.1.4-alpha.1" }, @@ -9723,9 +9723,9 @@ } }, "node_modules/omi-transition": { - "version": "0.1.8", - "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.8.tgz", - "integrity": "sha512-5OncdwZSDoczL6WtLxirl2L4BP3UPL6UvvtlTNHOGJZ4HmJ6MOMxvTCiafEzewVL0Yq1MGSBreLZwJRLG6JDJQ==", + "version": "0.1.9", + "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.9.tgz", + "integrity": "sha512-OLXqcn/puTDgIHsDDQnKCkxjGbTB/fuHcvkj2u8jqyaoLhXBxtiq6vAL9eVGSr/Xg0A1z4bsZ9gLdUWPLrPu8w==", "dependencies": { "omi": "latest" } @@ -21009,9 +21009,9 @@ } }, "omi-transition": { - "version": "0.1.8", - "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.8.tgz", - "integrity": "sha512-5OncdwZSDoczL6WtLxirl2L4BP3UPL6UvvtlTNHOGJZ4HmJ6MOMxvTCiafEzewVL0Yq1MGSBreLZwJRLG6JDJQ==", + "version": "0.1.9", + "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.9.tgz", + "integrity": "sha512-OLXqcn/puTDgIHsDDQnKCkxjGbTB/fuHcvkj2u8jqyaoLhXBxtiq6vAL9eVGSr/Xg0A1z4bsZ9gLdUWPLrPu8w==", "requires": { "omi": "latest" } diff --git a/package.json b/package.json index c64e9b0..889f6a1 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "copy-to-clipboard": "^3.3.3", "lodash": "~4.17.15", "omi": "^7.6.17", - "omi-transition": "^0.1.8", + "omi-transition": "^0.1.9", "tailwind-merge": "^2.2.1", "tdesign-icons-web-components": "^0.1.4-alpha.1" }, diff --git a/src/_common b/src/_common index f3725c1..f9683c1 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit f3725c1e1f67a2ad3dcc54b2bc213a2500cacd88 +Subproject commit f9683c1edee5c09f455ba8cb19cb8ce07d4490be diff --git a/src/_util/useControlled.ts b/src/_util/useControlled.ts index 77101a3..cc6dff9 100644 --- a/src/_util/useControlled.ts +++ b/src/_util/useControlled.ts @@ -42,7 +42,6 @@ const useControlled:

    ( return [ internalValue.value, (newValue, ...args) => { - console.log('===newValue', newValue); internalValue.value = newValue; onChange?.(newValue, ...args); }, diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index cd3dbb0..a521ac3 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -266,7 +266,6 @@ export default class Popup extends Component { } getOverlayStyle(overlayStyle: PopupProps['overlayStyle']) { - console.log('==ss', overlayStyle, this.triggerRef.current, this.popperRef.current); if (this.triggerRef.current && this.popperRef.current && typeof overlayStyle === 'function') { return { ...overlayStyle(this.triggerRef.current as HTMLElement, this.popperRef.current as HTMLElement) }; } @@ -274,7 +273,7 @@ export default class Popup extends Component { } updatePopper = () => { - this.popper = createPopper(this.triggerRef.current as HTMLElement, this.popperRef.current as HTMLElement, { + createPopper(this.triggerRef.current as HTMLElement, this.popperRef.current as HTMLElement, { placement: getPopperPlacement(this.props.placement as PopupProps['placement']), ...(this.props?.popperOptions || {}), }); @@ -287,16 +286,15 @@ export default class Popup extends Component { handleBeforeEnter = () => { this.updatePopper(); + this.updatePopper(); }; beforeUpdate() { - // deal visible if (this.getVisible()) { if (this.popperRef.current) { const el = this.popperRef.current as HTMLElement; el.style.display = 'block'; } - this.updatePopper(); } else if (this.popperRef.current) { const el = this.popperRef.current as HTMLElement; el.style.display = 'none'; @@ -352,8 +350,6 @@ export default class Popup extends Component { return child; }); - console.log('props.overlayInnerStyle', props.overlayInnerStyle); - return ( <> {children.length > 1 ? ( @@ -376,18 +372,16 @@ export default class Popup extends Component { ref={this.popperRef} onMouseDown={() => (this.contentClicked = true)} > - {(this.getVisible() || this.popperRef.current) && ( -

    - {props.content} - {props.showArrow ? ( -
    - ) : null} -
    - )} +
    + {props.content} + {props.showArrow ? ( +
    + ) : null} +
    ) : null} diff --git a/src/select-input/SelectInput.tsx b/src/select-input/SelectInput.tsx index 65e436d..c6691c6 100644 --- a/src/select-input/SelectInput.tsx +++ b/src/select-input/SelectInput.tsx @@ -90,6 +90,8 @@ class SelectInput extends Component { // 浮层显示的受控与非受控 const visibleProps = { visible: popupVisible ?? this.innerPopupVisible }; + console.log('===visibleProps', visibleProps); + const popupClasses = classNames([ props.className, `${this.classPrefix}-select-input`, @@ -113,7 +115,7 @@ class SelectInput extends Component { {...visibleProps} {...popupProps} disabled={disabled} - overlayInnerStyle={this.tOverlayInnerStyle} + overlayInnerStyle={this.tOverlayInnerStyle()} > {multiple ? ( { } return ( -
    console.log('222', e)} - > +
    {mainContent} {props.tips && (
    { const { value, keys, commonInputProps, popupVisible } = props; const onInnerClear = (context: { e: MouseEvent }) => { - console.log('---fff'); context?.e?.stopPropagation(); props.onClear?.(context); this.setInputValue('', { trigger: 'clear' }); @@ -87,7 +86,6 @@ export default class SingleSelectInput extends Component { onClear={onInnerClear} // [Important Info]: SelectInput.blur is not equal to Input, example: click popup panel onFocus={(val, context) => { - console.log('focus'); props.onFocus?.(value, { ...context, inputValue: val }); // focus might not need to change input value. it will caught some curious errors in tree-select // !popupVisible && setInputValue(getInputValue(value, keys), { ...context, trigger: 'input' }); diff --git a/src/select-input/useOverlayInnerStyle.ts b/src/select-input/useOverlayInnerStyle.ts index e691530..aef6995 100644 --- a/src/select-input/useOverlayInnerStyle.ts +++ b/src/select-input/useOverlayInnerStyle.ts @@ -30,7 +30,6 @@ export default function useOverlayInnerStyle( const [innerPopupVisible, setInnerPopupVisible] = useControlled(props, 'popupVisible', onPopupVisibleChange); const matchWidthFunc = (triggerElement: HTMLElement, popupElement: HTMLElement) => { - console.log('ffff'); if (!triggerElement || !popupElement) return; // 设置display来可以获取popupElement的宽度 @@ -53,7 +52,6 @@ export default function useOverlayInnerStyle( ? popupElement.scrollWidth : triggerElement.offsetWidth - overlayScrollWidth; - console.log('==width', width); let otherOverlayInnerStyle = {}; if (popupProps && typeof popupProps.overlayInnerStyle === 'object' && !popupProps.overlayInnerStyle.width) { otherOverlayInnerStyle = popupProps.overlayInnerStyle; @@ -70,7 +68,7 @@ export default function useOverlayInnerStyle( } // 如果点击触发元素(输入框)且为可输入状态,则继续显示下拉框 const newVisible = context.trigger === 'trigger-element-click' && allowInput ? true : visible; - + console.log('==dede', newVisible, props.popupVisible); if (props.popupVisible !== newVisible) { setInnerPopupVisible(newVisible, context); if (!newVisible) { @@ -83,13 +81,10 @@ export default function useOverlayInnerStyle( let result: TdPopupProps['overlayInnerStyle'] = {}; const overlayInnerStyle = popupProps?.overlayInnerStyle || {}; if (isFunction(overlayInnerStyle) || (isObject(overlayInnerStyle) && overlayInnerStyle.width)) { - console.log(1); result = overlayInnerStyle; } else if (!autoWidth) { - console.log(2); result = matchWidthFunc; } - console.log(3); return result; }; From d82c874b8da06f9a3352ef7fd14dc1791032b146 Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Fri, 20 Sep 2024 18:57:15 +0800 Subject: [PATCH 12/15] feat(select-input): add select-input --- src/index.ts | 1 + src/select-input/README.md | 2 +- src/select-input/SelectInputSingle.tsx | 1 + src/select-input/_example/multiple.tsx | 4 ++++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 69a1e29..aefb005 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export * from './common'; export * from './divider'; export * from './dropdown'; export * from './grid'; +export * from './hooks'; export * from './image'; export * from './input'; export * from './input-number'; diff --git a/src/select-input/README.md b/src/select-input/README.md index 886fdf1..b7fabca 100644 --- a/src/select-input/README.md +++ b/src/select-input/README.md @@ -22,7 +22,7 @@ spline: data 可使用 `SelectInput` 自由定制任何风格的多选选择器。 - +{{ multiple }} ### 自动填充筛选器 diff --git a/src/select-input/SelectInputSingle.tsx b/src/select-input/SelectInputSingle.tsx index cbb8292..67c2ca6 100644 --- a/src/select-input/SelectInputSingle.tsx +++ b/src/select-input/SelectInputSingle.tsx @@ -50,6 +50,7 @@ export default class SingleSelectInput extends Component { const { value, keys, commonInputProps, popupVisible } = props; const onInnerClear = (context: { e: MouseEvent }) => { + console.log('===ccc'); context?.e?.stopPropagation(); props.onClear?.(context); this.setInputValue('', { trigger: 'clear' }); diff --git a/src/select-input/_example/multiple.tsx b/src/select-input/_example/multiple.tsx index b480ddf..09bd848 100644 --- a/src/select-input/_example/multiple.tsx +++ b/src/select-input/_example/multiple.tsx @@ -126,6 +126,10 @@ export default class SelectInputMultiple extends Component { } }; + // install(): void { + // document.head.insertAdjacentHTML('beforeend', classStyles); + // } + render() { const checkboxValue = this.getCheckboxValue(); return ( From ddd0f34532429791ee4cd1966c01df0cb97c7dad Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Mon, 23 Sep 2024 19:31:57 +0800 Subject: [PATCH 13/15] feat(select-input): add select-input --- package-lock.json | 14 ++-- package.json | 2 +- src/_util/useControlled.ts | 18 +++-- src/index.ts | 1 - src/input/input.tsx | 8 +- src/popup/popup.tsx | 1 + src/select-input/SelectInput.tsx | 14 ++-- src/select-input/SelectInputMultiple.tsx | 8 ++ src/select-input/SelectInputSingle.tsx | 3 +- src/select-input/_example/autocomplete.tsx | 92 ++++++++++++++++++++++ src/select-input/_example/multiple.tsx | 5 +- src/select-input/useOverlayInnerStyle.ts | 6 +- src/tag-input/tag-input.tsx | 14 ++-- 13 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 src/select-input/_example/autocomplete.tsx diff --git a/package-lock.json b/package-lock.json index 1beb2e3..20a9aaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "copy-to-clipboard": "^3.3.3", "lodash": "~4.17.15", "omi": "^7.7.1", - "omi-transition": "^0.1.9", + "omi-transition": "^0.1.10", "tailwind-merge": "^2.2.1", "tdesign-icons-web-components": "^0.1.4" }, @@ -9723,9 +9723,9 @@ } }, "node_modules/omi-transition": { - "version": "0.1.9", - "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.9.tgz", - "integrity": "sha512-OLXqcn/puTDgIHsDDQnKCkxjGbTB/fuHcvkj2u8jqyaoLhXBxtiq6vAL9eVGSr/Xg0A1z4bsZ9gLdUWPLrPu8w==", + "version": "0.1.10", + "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.10.tgz", + "integrity": "sha512-xWICoQ6uaNdrdN4hpTL4BXyqxKB4AcY6ehjmTWOFNDzywerC6hXcmW5mVrVLMlqJdf1MXvVbU7bHeUEaK/hQLw==", "dependencies": { "omi": "latest" } @@ -21009,9 +21009,9 @@ } }, "omi-transition": { - "version": "0.1.9", - "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.9.tgz", - "integrity": "sha512-OLXqcn/puTDgIHsDDQnKCkxjGbTB/fuHcvkj2u8jqyaoLhXBxtiq6vAL9eVGSr/Xg0A1z4bsZ9gLdUWPLrPu8w==", + "version": "0.1.10", + "resolved": "https://mirrors.tencent.com/npm/omi-transition/-/omi-transition-0.1.10.tgz", + "integrity": "sha512-xWICoQ6uaNdrdN4hpTL4BXyqxKB4AcY6ehjmTWOFNDzywerC6hXcmW5mVrVLMlqJdf1MXvVbU7bHeUEaK/hQLw==", "requires": { "omi": "latest" } diff --git a/package.json b/package.json index 8bcecd1..0dee408 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "copy-to-clipboard": "^3.3.3", "lodash": "~4.17.15", "omi": "^7.7.1", - "omi-transition": "^0.1.9", + "omi-transition": "^0.1.10", "tailwind-merge": "^2.2.1", "tdesign-icons-web-components": "^0.1.4" }, diff --git a/src/_util/useControlled.ts b/src/_util/useControlled.ts index cc6dff9..e866a9b 100644 --- a/src/_util/useControlled.ts +++ b/src/_util/useControlled.ts @@ -1,5 +1,5 @@ import upperFirst from 'lodash/upperFirst'; -import { signal, SignalValue } from 'omi'; +import { Component, setActiveComponent, signal, SignalValue } from 'omi'; export interface ChangeHandler { (value: T, ...args: P); @@ -13,11 +13,9 @@ const useControlled:

    ( props: R, valueKey: K, onChange: ChangeHandler, - defaultOptions?: - | { - [key in Defaultoptions>]: R[K]; - } - | object, + defaultOptions?: { + [key in Defaultoptions>]?: R[K]; + } & { [key: string]: any; activeComponent?: Component }, ) => [SignalValue | R[K], ChangeHandler] = ( // eslint-disable-next-line default-param-last props = {} as any, @@ -33,11 +31,15 @@ const useControlled:

    ( const defaultValue = defaultOptions[`default${upperFirst(valueKey as string)}`] || props[`default${upperFirst(valueKey as string)}`]; - // 无论是否受控,都要维护一个内部变量,默认值由 defaultValue 控制 - const internalValue = signal(defaultValue); // 受控模式 if (controlled) return [value, onChange || (() => {})]; + // 无论是否受控,都要维护一个内部变量,默认值由 defaultValue 控制 + const internalValue = signal(defaultValue); + if (defaultOptions.activeComponent) { + setActiveComponent(defaultOptions.activeComponent); + } + // 非受控模式 return [ internalValue.value, diff --git a/src/index.ts b/src/index.ts index aefb005..69a1e29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,6 @@ export * from './common'; export * from './divider'; export * from './dropdown'; export * from './grid'; -export * from './hooks'; export * from './image'; export * from './input'; export * from './input-number'; diff --git a/src/input/input.tsx b/src/input/input.tsx index bf9d02f..a79775b 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -43,6 +43,13 @@ const isFunction = (arg: unknown) => typeof arg === 'function'; @tag('t-input') export default class Input extends Component { + static css = [ + `:host { + width: 100%; + }; + `, + ]; + static defaultProps = { align: 'left', allowInputOverMax: false, @@ -123,7 +130,6 @@ export default class Input extends Component { allowInputOverMax, onValidate, }); - let { value: newStr } = e.currentTarget; if (this.composingRef.current) { this.composingValue = newStr; diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index a521ac3..5f4b40e 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -350,6 +350,7 @@ export default class Popup extends Component { return child; }); + console.log('===this.getVisible()', this.getVisible()); return ( <> {children.length > 1 ? ( diff --git a/src/select-input/SelectInput.tsx b/src/select-input/SelectInput.tsx index c6691c6..17246aa 100644 --- a/src/select-input/SelectInput.tsx +++ b/src/select-input/SelectInput.tsx @@ -70,9 +70,13 @@ class SelectInput extends Component { suffixIcon: loading ? : suffixIcon, }; - const { innerPopupVisible, tOverlayInnerStyle, onInnerPopupVisibleChange } = useOverlayInnerStyle(this.props, { - // afterHidePopup: this.onInnerBlur, - }); + const { innerPopupVisible, tOverlayInnerStyle, onInnerPopupVisibleChange } = useOverlayInnerStyle( + this.props, + { + // afterHidePopup: this.onInnerBlur, + }, + this, + ); this.tOverlayInnerStyle = tOverlayInnerStyle; this.innerPopupVisible = innerPopupVisible; this.onInnerPopupVisibleChange = onInnerPopupVisibleChange; @@ -90,10 +94,8 @@ class SelectInput extends Component { // 浮层显示的受控与非受控 const visibleProps = { visible: popupVisible ?? this.innerPopupVisible }; - console.log('===visibleProps', visibleProps); - const popupClasses = classNames([ - props.className, + props.innerClass, `${this.classPrefix}-select-input`, { [`${this.classPrefix}-select-input--borderless`]: borderless, diff --git a/src/select-input/SelectInputMultiple.tsx b/src/select-input/SelectInputMultiple.tsx index afd243f..f5d2fd5 100644 --- a/src/select-input/SelectInputMultiple.tsx +++ b/src/select-input/SelectInputMultiple.tsx @@ -25,6 +25,13 @@ const DEFAULT_KEYS = { @tag('t-select-input-multiple') export default class SelectInputMultiple extends Component { + static css = [ + `:host { + width: 100%; + }; + `, + ]; + classPrefix = getClassPrefix(); tagInputRef = createRef(); @@ -85,6 +92,7 @@ export default class SelectInputMultiple extends Component { props.onFocus?.(props.value, { ...context, tagInputValue: val }); }} onBlur={!props.panel ? props.onBlur : null} + style={{ width: '100%', display: 'inline-flex' }} {...props.tagInputProps} inputProps={{ ...props.inputProps, diff --git a/src/select-input/SelectInputSingle.tsx b/src/select-input/SelectInputSingle.tsx index 67c2ca6..2406214 100644 --- a/src/select-input/SelectInputSingle.tsx +++ b/src/select-input/SelectInputSingle.tsx @@ -50,13 +50,13 @@ export default class SingleSelectInput extends Component { const { value, keys, commonInputProps, popupVisible } = props; const onInnerClear = (context: { e: MouseEvent }) => { - console.log('===ccc'); context?.e?.stopPropagation(); props.onClear?.(context); this.setInputValue('', { trigger: 'clear' }); }; const onInnerInputChange: TdInputProps['onChange'] = (value, context) => { + console.log('==change', value, context); if (props.allowInput) { this.setInputValue(value, { ...context, trigger: 'input' }); } @@ -96,7 +96,6 @@ export default class SingleSelectInput extends Component { }} // onBlur need to triggered by input when popup panel is null onBlur={!props.panel ? handleEmptyPanelBlur : null} - style={{ width: '100%' }} {...props.inputProps} inputClass={classNames(props.inputProps?.className, { [`${this.classPrefix}-input--focused`]: popupVisible, diff --git a/src/select-input/_example/autocomplete.tsx b/src/select-input/_example/autocomplete.tsx new file mode 100644 index 0000000..a761685 --- /dev/null +++ b/src/select-input/_example/autocomplete.tsx @@ -0,0 +1,92 @@ +import 'tdesign-web-components/select-input'; +import 'tdesign-icons-web-components/esm/components/search'; + +import { Component, signal } from 'omi'; + +const classStyles = ` + +`; + +const OPTIONS = ['Student A', 'Student B', 'Student C', 'Student D', 'Student E', 'Student F']; + +export default class SelectInputAutocomplete extends Component { + popupVisible = signal(false); + + selectValue = ''; + + options = OPTIONS; + + onOptionClick = (item: string) => { + this.selectValue = item; + this.popupVisible.value = false; + }; + + onInputChange = (keyword: string) => { + console.log('===keyword', keyword); + this.selectValue = keyword; + // const options = new Array(5).fill(null).map((t, index) => `${keyword} Student ${index}`); + // this.options = options; + setTimeout(() => this.update(), 1000); + }; + + onPopupVisibleChange = (val: boolean) => { + this.popupVisible.value = val; + }; + + installed(): void { + // 添加示例代码所需样式 + document.head.insertAdjacentHTML('beforeend', classStyles); + } + + // 如果需要输入框宽度自适应,可以使用 autoWidth + render() { + return ( +

    + + {this.options.map((item) => ( +
  • this.onOptionClick(item)}> + {item} +
  • + ))} + + } + suffixIcon={} + /> +
    + ); + } +} diff --git a/src/select-input/_example/multiple.tsx b/src/select-input/_example/multiple.tsx index 09bd848..4e57a9c 100644 --- a/src/select-input/_example/multiple.tsx +++ b/src/select-input/_example/multiple.tsx @@ -1,5 +1,6 @@ import 'tdesign-web-components/select-input'; import 'tdesign-web-components/checkbox'; +import 'tdesign-web-components/radio'; import 'tdesign-icons-web-components/esm/components/chevron-down'; import { Component, signal } from 'omi'; @@ -126,10 +127,6 @@ export default class SelectInputMultiple extends Component { } }; - // install(): void { - // document.head.insertAdjacentHTML('beforeend', classStyles); - // } - render() { const checkboxValue = this.getCheckboxValue(); return ( diff --git a/src/select-input/useOverlayInnerStyle.ts b/src/select-input/useOverlayInnerStyle.ts index aef6995..3c91694 100644 --- a/src/select-input/useOverlayInnerStyle.ts +++ b/src/select-input/useOverlayInnerStyle.ts @@ -1,5 +1,6 @@ import isFunction from 'lodash/isFunction'; import isObject from 'lodash/isObject'; +import { Component } from 'omi'; import useControlled from '../_util/useControlled'; import { PopupVisibleChangeContext, TdPopupProps } from '../popup'; @@ -25,9 +26,12 @@ export default function useOverlayInnerStyle( extra?: { afterHidePopup?: (ctx: PopupVisibleChangeContext) => void; }, + activeComponent?: Component, ) { const { popupProps, autoWidth, readonly, disabled, onPopupVisibleChange, allowInput } = props; - const [innerPopupVisible, setInnerPopupVisible] = useControlled(props, 'popupVisible', onPopupVisibleChange); + const [innerPopupVisible, setInnerPopupVisible] = useControlled(props, 'popupVisible', onPopupVisibleChange, { + activeComponent, + }); const matchWidthFunc = (triggerElement: HTMLElement, popupElement: HTMLElement) => { if (!triggerElement || !popupElement) return; diff --git a/src/tag-input/tag-input.tsx b/src/tag-input/tag-input.tsx index 2579c2d..16ee911 100644 --- a/src/tag-input/tag-input.tsx +++ b/src/tag-input/tag-input.tsx @@ -422,7 +422,7 @@ export default class TagInput extends Component { status, suffixIcon, suffix, - style, + innerStyle, onPaste, onFocus, onBlur, @@ -441,7 +441,7 @@ export default class TagInput extends Component { // 自定义 Tag 节点 const displayNode = isFunction(valueDisplay) - ? valueDisplay({ + ? (valueDisplay as any)({ value: tagValue, onClose: (index, item) => this.onClose({ index, item }), }) @@ -457,7 +457,7 @@ export default class TagInput extends Component { const list = displayNode ? displayNode : newList?.map((item, index) => { - const tagContent = isFunction(props.tag) ? props.tag({ value: item }) : props.tag; + const tagContent = isFunction(props.tag) ? (props.tag as any)({ value: item }) : props.tag; return ( { collapsedSelectedItems: tagValue.slice(props.minCollapsedNum, tagValue.length), onClose: this.onClose, }; - const more = isFunction(props.collapsedItems) ? props.collapsedItems(params) : props.collapsedItems; + const more = isFunction(props.collapsedItems) ? (props.collapsedItems as any)(params) : props.collapsedItems; if (more) { list.push(more); } else { @@ -530,7 +530,7 @@ export default class TagInput extends Component { [`${classPrefix}-input--auto-width`]: !!autoWidth, [`${classPrefix}-input__warp`]: !autoWidth, }, - props.className, + props.innerClass, ]; return ( @@ -547,8 +547,8 @@ export default class TagInput extends Component { readonly={readonly} disabled={disabled} label={renderLabel({ displayNode, label })} - class={classNames(classes)} - style={style} + innerClass={classNames(classes)} + style={innerStyle} tips={tips} status={status} placeholder={tagInputPlaceholder} From f1f63b58640226201e8290b9ffc0a508966e129f Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Tue, 24 Sep 2024 20:45:01 +0800 Subject: [PATCH 14/15] =?UTF-8?q?feat(select-input):=20=E5=8E=BB=E9=99=A4-?= =?UTF-8?q?-force?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0dee408..888260a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "license": "MIT", "scripts": { "start": "npm run dev", - "dev": "npm run generate:entry && cd site && vite --force", + "dev": "npm run generate:entry && cd site && vite", "site": "cd site && vite build", "site:intranet": "cd site && vite build --mode intranet", "site:preview": "cd site && vite build --mode preview && cd ../_site && cp index.html 404.html", From 6c65319b84da76dfba8c2758d6ee14bfee9e2a4f Mon Sep 17 00:00:00 2001 From: duenyang <377153400@qq.com> Date: Wed, 25 Sep 2024 11:27:21 +0800 Subject: [PATCH 15/15] =?UTF-8?q?feat(select-input):=20=E4=BC=98=E5=8C=96c?= =?UTF-8?q?ss=E5=AD=97=E7=AC=A6=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/select-input/SelectInput.tsx | 2 +- src/select-input/SelectInputMultiple.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/select-input/SelectInput.tsx b/src/select-input/SelectInput.tsx index d0d2904..144e9e1 100644 --- a/src/select-input/SelectInput.tsx +++ b/src/select-input/SelectInput.tsx @@ -42,7 +42,7 @@ const classPrefix = getClassPrefix(); @tag('t-select-input') class SelectInput extends Component { static css = [ - `.${classPrefix}-select-input > t-popup { + `.${classPrefix}-select-input > ${classPrefix}-popup { display: inline-flex; width: 100%; };`, diff --git a/src/select-input/SelectInputMultiple.tsx b/src/select-input/SelectInputMultiple.tsx index 65d7bfb..06935d6 100644 --- a/src/select-input/SelectInputMultiple.tsx +++ b/src/select-input/SelectInputMultiple.tsx @@ -27,7 +27,7 @@ const classPrefix = getClassPrefix(); const autoWidthCss = ` .${classPrefix}-input--auto-width.${classPrefix}-tag-input__with-suffix-icon.${classPrefix}-tag-input--with-tag .${classPrefix}-input { padding-right: var(--td-comp-paddingLR-xl); -} +}; `; @tag('t-select-input-multiple') export default class SelectInputMultiple extends Component<