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..5e554b4b8 100644 --- a/packages/components/popover/README.md +++ b/packages/components/popover/README.md @@ -25,12 +25,29 @@ isComponent: true ### 组件类型 -带箭头的弹出气泡 +#### 带箭头的弹出气泡 {{ base }} -## API +### 组件样式 + +#### 气泡主题 +{{ theme }} + +#### 气泡位置 +{{ placement }} + +## FAQ +如果使用场景为 `fixed`,除了需要显示指定 `fixed` 属性为 `true`,还需在触发元素的顶层添加`t-popover-wrapper--fixed` 类,用于定位触发元素。 + +```html + + + +``` + +## API ### Popover Props @@ -40,6 +57,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 +73,8 @@ visible-change | `(visible: boolean)` | 确认框显示或隐藏时触发 名称 | 描述 -- | -- -\- | 自定义 `` 显示内容 -content \| 自定义 `content` 显示内容 +\- | 默认插槽,作用同 `content` 插槽 +content | 自定义 `content` 显示内容 ### Popover External Classes @@ -70,4 +88,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.ts b/packages/components/popover/popover.ts index 3d5f38f0b..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() { @@ -217,18 +226,27 @@ export default class Popover extends SuperComponent { query.select(`#${name}-content`).boundingClientRect(); query.selectViewport().scrollOffset(); - query.exec((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; - const top = basePos.top + scrollTop; - const left = basePos.left + scrollLeft; + const { scrollTop = 0, scrollLeft = 0 } = viewportOffset || {}; + + 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); 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}}" >