From cd26aafaf221d689b1bcc46ec5da10884ffb8387 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:54:30 +0000 Subject: [PATCH 1/7] Initial plan From 037c9254eb16e6bd02912dad99f72bc061130148 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:00:12 +0000 Subject: [PATCH 2/7] Fix popover positioning when trigger element has fixed position Co-authored-by: Wesley-0808 <69622989+Wesley-0808@users.noreply.github.com> --- packages/components/popover/popover.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/components/popover/popover.ts b/packages/components/popover/popover.ts index 3d5f38f0b..db962f502 100644 --- a/packages/components/popover/popover.ts +++ b/packages/components/popover/popover.ts @@ -215,10 +215,11 @@ export default class Popover extends SuperComponent { const query = this.createSelectorQuery(); query.select(`#${name}-wrapper`).boundingClientRect(); query.select(`#${name}-content`).boundingClientRect(); + query.select(`#${name}-wrapper`).fields({ computedStyle: ['position'] }); query.selectViewport().scrollOffset(); query.exec((res) => { - const [triggerRect, contentRect, viewportOffset] = res; + const [triggerRect, contentRect, triggerStyle, viewportOffset] = res; if (!triggerRect || !contentRect) return; // 最终放置位置 @@ -227,8 +228,10 @@ export default class Popover extends SuperComponent { this.setData({ _placement: finalPlacement }); const { scrollTop = 0, scrollLeft = 0 } = viewportOffset; - const top = basePos.top + scrollTop; - const left = basePos.left + scrollLeft; + // 如果触发元素是 fixed 定位,不需要加上滚动偏移量 + const isFixed = triggerStyle?.position === 'fixed'; + const top = isFixed ? basePos.top : basePos.top + scrollTop; + const left = isFixed ? basePos.left : basePos.left + scrollLeft; const style = `top:${Math.max(top, 0)}px;left:${Math.max(left, 0)}px;`; const arrowStyle = this.calcArrowStyle(_placement, triggerRect, contentRect); From 50ae271f05d89f1919d027b49eac4f4649afd576 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:01:36 +0000 Subject: [PATCH 3/7] Add comment to document query result order Co-authored-by: Wesley-0808 <69622989+Wesley-0808@users.noreply.github.com> --- packages/components/popover/popover.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/popover/popover.ts b/packages/components/popover/popover.ts index db962f502..1ec0d3dc7 100644 --- a/packages/components/popover/popover.ts +++ b/packages/components/popover/popover.ts @@ -219,6 +219,7 @@ export default class Popover extends SuperComponent { query.selectViewport().scrollOffset(); query.exec((res) => { + // res 数组顺序: [triggerRect, contentRect, triggerStyle, viewportOffset] const [triggerRect, contentRect, triggerStyle, viewportOffset] = res; if (!triggerRect || !contentRect) return; From 16ab7b34b168c0418b34837f7fb31502290d423d Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Thu, 11 Dec 2025 15:26:16 +0800 Subject: [PATCH 4/7] feat: add fixed --- packages/components/popover/README.en-US.md | 4 ++-- packages/components/popover/README.md | 26 +++++++++++++++++---- packages/components/popover/popover.less | 4 ++++ packages/components/popover/popover.wxml | 2 +- packages/components/popover/props.ts | 5 ++++ packages/components/popover/type.ts | 8 +++++++ 6 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/components/popover/README.en-US.md b/packages/components/popover/README.en-US.md index 6fbbc2ca3..6dd7c6592 100644 --- a/packages/components/popover/README.en-US.md +++ b/packages/components/popover/README.en-US.md @@ -2,7 +2,6 @@ ## API - ### Popover Props name | type | default | description | required @@ -11,6 +10,7 @@ style | Object | - | CSS(Cascading Style Sheets) | N custom-style | Object | - | CSS(Cascading Style Sheets),used to set style on virtual component | N close-on-click-outside | Boolean | true | \- | N content | String | - | \- | N +fixed | Boolean | false | \- | N placement | String | top | options: top/left/right/bottom/top-left/top-right/bottom-left/bottom-right/left-top/left-bottom/right-top/right-bottom | N show-arrow | Boolean | true | \- | N theme | String | dark | options: dark/light/brand/success/warning/error | N @@ -41,4 +41,4 @@ t-class-content | \- The component provides the following CSS variables, which can be used to customize styles. Name | Default Value | Description -- | -- | -- ---td-popover-padding | 24rpx | - +--td-popover-padding | 24rpx | - diff --git a/packages/components/popover/README.md b/packages/components/popover/README.md index 37aaae18e..6722de89f 100644 --- a/packages/components/popover/README.md +++ b/packages/components/popover/README.md @@ -25,12 +25,27 @@ isComponent: true ### 组件类型 -带箭头的弹出气泡 +#### 带箭头的弹出气泡 {{ base }} -## API +### 组件样式 + +{{ theme }} + +{{ placement }} + +## FAQ +如果使用场景为 `fixed`,除了需要显示指定 `fixed` 属性为 `true`,还需在触发元素的顶层添加`t-popover-wrapper--fixed` 类,用于定位触发元素。 + +```html + + + +``` + +## API ### Popover Props @@ -40,6 +55,7 @@ style | Object | - | 样式 | N custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N close-on-click-outside | Boolean | true | 是否在点击外部元素后关闭菜单 | N content | String | - | 确认框内容 | N +fixed | Boolean | false | 如果 popover 是在一个 `position:fixed` 的区域,需要显式指定属性 fixed 为 true | N placement | String | top | 浮层出现位置。可选项:top/left/right/bottom/top-left/top-right/bottom-left/bottom-right/left-top/left-bottom/right-top/right-bottom | N show-arrow | Boolean | true | 是否显示浮层箭头 | N theme | String | dark | 弹出气泡主题。可选项:dark/light/brand/success/warning/error | N @@ -55,8 +71,8 @@ visible-change | `(visible: boolean)` | 确认框显示或隐藏时触发 名称 | 描述 -- | -- -\- | 自定义 `` 显示内容 -content \| 自定义 `content` 显示内容 +\- | 默认插槽,作用同 `content` 插槽 +content | 自定义 `content` 显示内容 ### Popover External Classes @@ -70,4 +86,4 @@ t-class-content | 内容样式类 组件提供了下列 CSS 变量,可用于自定义样式。 名称 | 默认值 | 描述 -- | -- | -- ---td-popover-padding | 24rpx | - \ No newline at end of file +--td-popover-padding | 24rpx | - diff --git a/packages/components/popover/popover.less b/packages/components/popover/popover.less index 3a8fb3370..40e474764 100644 --- a/packages/components/popover/popover.less +++ b/packages/components/popover/popover.less @@ -32,6 +32,10 @@ overflow: visible; transition: 0.2s ease-in-out all; + &--fixed { + position: fixed; + } + &__content { position: relative; padding: @popover-padding; diff --git a/packages/components/popover/popover.wxml b/packages/components/popover/popover.wxml index 9651c2c13..cac5a49c7 100644 --- a/packages/components/popover/popover.wxml +++ b/packages/components/popover/popover.wxml @@ -16,7 +16,7 @@ wx:if="{{realVisible}}" id="{{classPrefix}}-content" style="{{style}} {{contentStyle}} {{customStyle}}" - class="{{class}} {{classPrefix}} {{transitionClass}} {{prefix}}-class" + class="{{class}} {{classPrefix}} {{transitionClass}} {{prefix}}-class {{fixed ? classPrefix + '--fixed' : ''}}" data-placement="{{_placement}}" > Date: Thu, 11 Dec 2025 15:27:17 +0800 Subject: [PATCH 5/7] fix: trigger fixed --- packages/components/popover/popover.ts | 120 ++++++++++++++----------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/packages/components/popover/popover.ts b/packages/components/popover/popover.ts index 1ec0d3dc7..951ae2cf5 100644 --- a/packages/components/popover/popover.ts +++ b/packages/components/popover/popover.ts @@ -161,50 +161,59 @@ export default class Popover extends SuperComponent { return start + triggerSize / 2 - contentSize / 2; }, - calcPlacement(placement: string, triggerRect: any, contentRect: any) { - const { isHorizontal, isVertical } = this.getToward(placement); - // 获取内容大小 - const { width: contentWidth, height: contentHeight } = contentRect; - // 获取所在位置 - const { left: triggerLeft, top: triggerTop, right: triggerRight, bottom: triggerBottom } = triggerRect; - // 是否能正常放置 - let canPlace = true; - const { windowWidth, windowHeight } = getWindowInfo(); - let finalPlacement = placement; - - if (isHorizontal) { - if (placement.startsWith('top')) { - canPlace = triggerTop - contentHeight >= 0; - } else if (placement.startsWith('bottom')) { - canPlace = triggerBottom + contentHeight <= windowHeight; - } - } else if (isVertical) { - if (placement.startsWith('left')) { - canPlace = triggerLeft - contentWidth >= 0; - } else if (placement.startsWith('right')) { - canPlace = triggerRight + contentWidth <= windowWidth; - } - } - - if (!canPlace) { - // 反向 - if (isHorizontal) { - finalPlacement = placement.startsWith('top') - ? placement.replace('top', 'bottom') - : placement.replace('bottom', 'top'); - } else if (isVertical) { - finalPlacement = placement.startsWith('left') - ? placement.replace('left', 'right') - : placement.replace('right', 'left'); - } - } - - const basePos = this.calcContentPosition(finalPlacement, triggerRect, contentRect); - - return { - placement: finalPlacement, - ...basePos, - }; + calcPlacement(isFixed: boolean, placement: string, triggerRect: any, contentRect: any) { + return new Promise<{ placement: string; top: number; left: number }>((resolve) => { + // 选取当前组件节点所在的组件实例,以支持 fixed 定位的元素计算位置 + const owner = this.selectOwnerComponent().createSelectorQuery(); + owner.select(`.${name}-wrapper--fixed`).boundingClientRect(); + owner.exec((b) => { + const [triggerChildRect] = b; + if (triggerChildRect && isFixed) { + triggerRect = triggerChildRect; + } + + const { isHorizontal, isVertical } = this.getToward(placement); + // 获取内容大小 + const { width: contentWidth, height: contentHeight } = contentRect; + // 获取所在位置 + const { left: triggerLeft, top: triggerTop, right: triggerRight, bottom: triggerBottom } = triggerRect; + // 是否能正常放置 + let canPlace = true; + const { windowWidth, windowHeight } = getWindowInfo(); + let finalPlacement = placement; + + if (isHorizontal) { + if (placement.startsWith('top')) { + canPlace = triggerTop - contentHeight >= 0; + } else if (placement.startsWith('bottom')) { + canPlace = triggerBottom + contentHeight <= windowHeight; + } + } else if (isVertical) { + if (placement.startsWith('left')) { + canPlace = triggerLeft - contentWidth >= 0; + } else if (placement.startsWith('right')) { + canPlace = triggerRight + contentWidth <= windowWidth; + } + } + + if (!canPlace) { + // 反向 + if (isHorizontal) { + finalPlacement = placement.startsWith('top') + ? placement.replace('top', 'bottom') + : placement.replace('bottom', 'top'); + } else if (isVertical) { + finalPlacement = placement.startsWith('left') + ? placement.replace('left', 'right') + : placement.replace('right', 'left'); + } + } + + const basePos = this.calcContentPosition(finalPlacement, triggerRect, contentRect); + + resolve({ placement: finalPlacement, ...basePos }); + }); + }); }, async computePosition() { @@ -215,22 +224,27 @@ export default class Popover extends SuperComponent { const query = this.createSelectorQuery(); query.select(`#${name}-wrapper`).boundingClientRect(); query.select(`#${name}-content`).boundingClientRect(); - query.select(`#${name}-wrapper`).fields({ computedStyle: ['position'] }); query.selectViewport().scrollOffset(); - query.exec((res) => { - // res 数组顺序: [triggerRect, contentRect, triggerStyle, viewportOffset] - const [triggerRect, contentRect, triggerStyle, viewportOffset] = res; + query.exec(async (res) => { + const [triggerRect, contentRect, viewportOffset] = res; if (!triggerRect || !contentRect) return; + // 如果 fixed 定位,不需要加上滚动偏移量 + const isFixed = this.properties.fixed; // 最终放置位置 - const { placement: finalPlacement, ...basePos } = this.calcPlacement(_placement, triggerRect, contentRect); - // TODO 优化:滚动时可能导致箭头闪烁 + const { placement: finalPlacement, ...basePos } = await this.calcPlacement( + isFixed, + _placement, + triggerRect, + contentRect, + ); + + // TODO 优化:滚动时切换placement可能导致箭头闪烁 this.setData({ _placement: finalPlacement }); - const { scrollTop = 0, scrollLeft = 0 } = viewportOffset; - // 如果触发元素是 fixed 定位,不需要加上滚动偏移量 - const isFixed = triggerStyle?.position === 'fixed'; + const { scrollTop = 0, scrollLeft = 0 } = viewportOffset || {}; + const top = isFixed ? basePos.top : basePos.top + scrollTop; const left = isFixed ? basePos.left : basePos.left + scrollLeft; From 389bd745eb50425b3dc9dfabdcda35395ce62915 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Thu, 11 Dec 2025 15:40:55 +0800 Subject: [PATCH 6/7] chore: docs --- packages/components/popover/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/components/popover/README.md b/packages/components/popover/README.md index 6722de89f..5e554b4b8 100644 --- a/packages/components/popover/README.md +++ b/packages/components/popover/README.md @@ -31,8 +31,10 @@ isComponent: true ### 组件样式 +#### 气泡主题 {{ theme }} +#### 气泡位置 {{ placement }} ## FAQ @@ -40,9 +42,9 @@ isComponent: true 如果使用场景为 `fixed`,除了需要显示指定 `fixed` 属性为 `true`,还需在触发元素的顶层添加`t-popover-wrapper--fixed` 类,用于定位触发元素。 ```html - - - + + + ``` ## API From e6beedb7ceeebea86f5d68fd357cb0e67037d3ca Mon Sep 17 00:00:00 2001 From: anlyyao Date: Thu, 25 Dec 2025 16:01:11 +0800 Subject: [PATCH 7/7] docs(Popover): update fixed attribute description --- packages/components/popover/README.en-US.md | 2 +- packages/components/popover/README.md | 13 ++----------- packages/components/popover/props.ts | 2 +- packages/components/popover/type.ts | 2 +- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/components/popover/README.en-US.md b/packages/components/popover/README.en-US.md index 6dd7c6592..afa51941c 100644 --- a/packages/components/popover/README.en-US.md +++ b/packages/components/popover/README.en-US.md @@ -10,7 +10,7 @@ style | Object | - | CSS(Cascading Style Sheets) | N custom-style | Object | - | CSS(Cascading Style Sheets),used to set style on virtual component | N close-on-click-outside | Boolean | true | \- | N content | String | - | \- | N -fixed | Boolean | false | \- | N +fixed | Boolean | false | `1.12.1` | N placement | String | top | options: top/left/right/bottom/top-left/top-right/bottom-left/bottom-right/left-top/left-bottom/right-top/right-bottom | N show-arrow | Boolean | true | \- | N theme | String | dark | options: dark/light/brand/success/warning/error | N diff --git a/packages/components/popover/README.md b/packages/components/popover/README.md index 5e554b4b8..a70ff1034 100644 --- a/packages/components/popover/README.md +++ b/packages/components/popover/README.md @@ -37,15 +37,6 @@ isComponent: true #### 气泡位置 {{ placement }} -## FAQ - -如果使用场景为 `fixed`,除了需要显示指定 `fixed` 属性为 `true`,还需在触发元素的顶层添加`t-popover-wrapper--fixed` 类,用于定位触发元素。 - -```html - - - -``` ## API @@ -57,7 +48,7 @@ style | Object | - | 样式 | N custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N close-on-click-outside | Boolean | true | 是否在点击外部元素后关闭菜单 | N content | String | - | 确认框内容 | N -fixed | Boolean | false | 如果 popover 是在一个 `position:fixed` 的区域,需要显式指定属性 fixed 为 true | N +fixed | Boolean | false | `1.12.1`。如果触发元素为 `fixed` 场景,需要显示指定 `fixed` 属性为 `true`,同时需在触发元素层添加 `t-popover-wrapper--fixed` 类,用于定位触发元素 | N placement | String | top | 浮层出现位置。可选项:top/left/right/bottom/top-left/top-right/bottom-left/bottom-right/left-top/left-bottom/right-top/right-bottom | N show-arrow | Boolean | true | 是否显示浮层箭头 | N theme | String | dark | 弹出气泡主题。可选项:dark/light/brand/success/warning/error | N @@ -73,7 +64,7 @@ visible-change | `(visible: boolean)` | 确认框显示或隐藏时触发 名称 | 描述 -- | -- -\- | 默认插槽,作用同 `content` 插槽 +\- | 默认插槽,用于自定义触发元素 content | 自定义 `content` 显示内容 ### Popover External Classes diff --git a/packages/components/popover/props.ts b/packages/components/popover/props.ts index dc3ca3db4..43360c960 100644 --- a/packages/components/popover/props.ts +++ b/packages/components/popover/props.ts @@ -15,7 +15,7 @@ const props: TdPopoverProps = { content: { type: String, }, - /** 如果 popover 是在一个 `position:fixed` 的区域,需要显式指定属性 fixed 为 true */ + /** 如果触发元素为 `fixed` 场景,需要显示指定 `fixed` 属性为 `true`,同时需在触发元素层添加 `t-popover-wrapper--fixed` 类,用于定位触发元素 */ fixed: { type: Boolean, value: false, diff --git a/packages/components/popover/type.ts b/packages/components/popover/type.ts index 1c7c723f1..f70400f14 100644 --- a/packages/components/popover/type.ts +++ b/packages/components/popover/type.ts @@ -21,7 +21,7 @@ export interface TdPopoverProps { value?: string; }; /** - * 如果 popover 是在一个 `position:fixed` 的区域,需要显式指定属性 fixed 为 true + * 如果触发元素为 `fixed` 场景,需要显示指定 `fixed` 属性为 `true`,同时需在触发元素层添加 `t-popover-wrapper--fixed` 类,用于定位触发元素 * @default false */ fixed?: {