Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support drawer size drag limit and onSizeDragEnd event #4009

Merged
merged 11 commits into from
May 30, 2024
2 changes: 1 addition & 1 deletion src/_common
82 changes: 82 additions & 0 deletions src/drawer/__tests__/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ import Drawer from '@/src/drawer/index.ts';

const text = '这是一段内容';

// 事件兼容问题,使其支持 x, y 值
class FakeMouseEvent extends MouseEvent {
constructor(type, values) {
const { x, y, ...mouseValues } = values;
super(type, mouseValues);

Object.assign(this, {
x: x || 0,
y: y || 0,
});
}
}

// 模拟拖拽事件
function moveElement(element, x, y) {
element.dispatchEvent(new FakeMouseEvent('mousedown', {}));
window.document.dispatchEvent(new FakeMouseEvent('mousemove', { x, y }));
window.document.dispatchEvent(new FakeMouseEvent('mouseup', { x, y }));
}

describe('Drawer', () => {
describe(':props', () => {
it(':attach', async () => {
Expand Down Expand Up @@ -118,6 +138,68 @@ describe('Drawer', () => {
expect(getComputedStyle(content.element.lastChild, null).cursor).toBe('col-resize');
});

it(':sizeDraggable value', async () => {
const visible = ref(true);
const sizeDraggableValue = {
min: 100,
max: 300,
};
const placement = ref('left');
function updatePlacement(placementVal) {
placement.value = placementVal;
}

const wrapper = mount(() => (
<Drawer
visible={visible.value}
placement={placement.value}
size={`200`}
body={text}
sizeDraggable={sizeDraggableValue}
/>
));
const content = wrapper.find('.t-drawer__content-wrapper');
moveElement(content.element.lastChild, 400, 100);
await nextTick();
expect(getComputedStyle(content.element, null).width).toBe('300px');
updatePlacement('right');
await nextTick();
moveElement(content.element.lastChild, -300, 80);
await nextTick();
expect(getComputedStyle(content.element, null).width).toBe('300px');

updatePlacement('top');
await nextTick();
moveElement(content.element.lastChild, 80, 300);
await nextTick();
expect(getComputedStyle(content.element, null).height).toBe('300px');

updatePlacement('bottom');
await nextTick();
moveElement(content.element.lastChild, 80, -300);
await nextTick();
expect(getComputedStyle(content.element, null).height).toBe('300px');
});

it(':sizeDragEnd event', async () => {
const visible = ref(true);
const sizeDragEnd = vi.fn();
const wrapper = mount(() => (
<Drawer
sizeDraggable
visible={visible.value}
placement={`left`}
size={`200`}
body={text}
onSizeDragEnd={sizeDragEnd}
/>
));
const content = wrapper.find('.t-drawer__content-wrapper');
moveElement(content.element.lastChild, 400, 100);
await nextTick();
expect(sizeDragEnd).toBeCalled();
});

it(':zIndex', async () => {
const visible = ref(true);
const wrapper = mount(() => <Drawer visible={visible.value} body={text} zIndex={2022} />);
Expand Down
5 changes: 5 additions & 0 deletions src/drawer/_example/size-draggable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:on-overlay-click="() => (visible = false)"
:placement="placement"
:size-draggable="true"
:on-size-drag-end="handleSizeDrag"
@cancel="visible = false"
>
<p>抽屉的内容</p>
Expand All @@ -28,4 +29,8 @@ import { ref } from 'vue';

const visible = ref(false);
const placement = ref('right');

function handleSizeDrag({ size }) {
console.log('size drag size: ', size);
}
</script>
19 changes: 11 additions & 8 deletions src/drawer/drawer.en-US.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
:: BASE_DOC ::

## API

### Drawer Props

name | type | default | description | required
-- | -- | -- | -- | --
attach | String / Function | '' | Typescript:`AttachNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
attach | String / Function | - | Typescript:`AttachNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
body | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
cancelBtn | String / Object / Slot / Function | '' | Typescript:`FooterButton` | N
cancelBtn | String / Object / Slot / Function | - | Typescript:`FooterButton` | N
closeBtn | String / Boolean / Slot / Function | - | Typescript:`string \| boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
closeOnEscKeydown | Boolean | true | \- | N
closeOnEscKeydown | Boolean | true | trigger drawer close event on `ESC` keydown | N
closeOnOverlayClick | Boolean | true | \- | N
confirmBtn | String / Object / Slot / Function | '' | Typescript:`FooterButton` `type FooterButton = string \| ButtonProps \| TNode`,[Button API Documents](./button?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/drawer/type.ts) | N
confirmBtn | String / Object / Slot / Function | - | Typescript:`FooterButton` `type FooterButton = string \| ButtonProps \| TNode`,[Button API Documents](./button?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/drawer/type.ts) | N
default | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
destroyOnClose | Boolean | false | \- | N
footer | Boolean / Slot / Function | true | Typescript:`boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
header | String / Boolean / Slot / Function | true | Typescript:`string \| boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
mode | String | overlay | optionsoverlay/push | N
placement | String | right | optionsleft/right/top/bottom | N
mode | String | overlay | options: overlay/push | N
placement | String | right | options: left/right/top/bottom | N
preventScrollThrough | Boolean | true | \- | N
showInAttachedElement | Boolean | false | \- | N
showOverlay | Boolean | true | \- | N
size | String | 'small' | \- | N
sizeDraggable | Boolean | false | \- | N
sizeDraggable | Boolean / Object | false | allow resizing drawer width/height, set `max` or `min` to limit size。Typescript:`boolean \| SizeDragLimit` `interface SizeDragLimit { max: number, min: number }`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/drawer/type.ts) | N
visible | Boolean | false | \- | N
zIndex | Number | - | \- | N
onCancel | Function | | Typescript:`(context: { e: MouseEvent }) => void`<br/> | N
Expand All @@ -31,6 +32,7 @@ onCloseBtnClick | Function | | Typescript:`(context: { e: MouseEvent }) => vo
onConfirm | Function | | Typescript:`(context: { e: MouseEvent }) => void`<br/> | N
onEscKeydown | Function | | Typescript:`(context: { e: KeyboardEvent }) => void`<br/> | N
onOverlayClick | Function | | Typescript:`(context: { e: MouseEvent }) => void`<br/> | N
onSizeDragEnd | Function | | Typescript:`(context: { e: MouseEvent; size: number }) => void`<br/>trigger on size drag end | N

### Drawer Events

Expand All @@ -42,6 +44,7 @@ close-btn-click | `(context: { e: MouseEvent })` | \-
confirm | `(context: { e: MouseEvent })` | \-
esc-keydown | `(context: { e: KeyboardEvent })` | \-
overlay-click | `(context: { e: MouseEvent })` | \-
size-drag-end | `(context: { e: MouseEvent; size: number })` | trigger on size drag end

### DrawerOptions

Expand All @@ -50,7 +53,7 @@ name | type | default | description | required
attach | String / Function | 'body' | Typescript:`AttachNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
className | String | - | \- | N
style | String / Object | - | Typescript:`string \| Styles`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
`Omit<DrawerProps, 'attach'>` | \- | - | \- | N
`Omit<DrawerProps, 'attach'>` | \- | - | extends `Omit<DrawerProps, 'attach'>` | N

### DrawerInstance

Expand Down
19 changes: 11 additions & 8 deletions src/drawer/drawer.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@
如果您不确定问题是否是由该规则引起的,或者确定该规则不是问题的根本原因,请在 `GitHub` 上提出一个 `issue`,并提供可以重现问题的代码。这将有助于我们更好地了解您的问题并提供更好的帮助。

## API

### Drawer Props

名称 | 类型 | 默认值 | 说明 | 必传
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
attach | String / Function | '' | 抽屉挂载的节点,默认挂在组件本身的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`AttachNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
attach | String / Function | - | 抽屉挂载的节点,默认挂在组件本身的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`AttachNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
body | String / Slot / Function | - | 抽屉内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
cancelBtn | String / Object / Slot / Function | '' | 取消按钮,可自定义。值为 null 则不显示取消按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制取消事件。TS 类型:`FooterButton` | N
cancelBtn | String / Object / Slot / Function | - | 取消按钮,可自定义。值为 null 则不显示取消按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制取消事件。TS 类型:`FooterButton` | N
closeBtn | String / Boolean / Slot / Function | - | 关闭按钮,可以自定义。值为 true 显示默认关闭按钮,值为 false 不显示关闭按钮。值类型为 string 则直接显示值,如:“关闭”。值类型为 TNode,则表示呈现自定义按钮示例。TS 类型:`string \| boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
closeOnEscKeydown | Boolean | true | 按下 ESC 时是否触发抽屉关闭事件 | N
closeOnOverlayClick | Boolean | true | 点击蒙层时是否触发抽屉关闭事件 | N
confirmBtn | String / Object / Slot / Function | '' | 确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制确认事件。TS 类型:`FooterButton` `type FooterButton = string \| ButtonProps \| TNode`,[Button API Documents](./button?tab=api)。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/drawer/type.ts) | N
confirmBtn | String / Object / Slot / Function | - | 确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制确认事件。TS 类型:`FooterButton` `type FooterButton = string \| ButtonProps \| TNode`,[Button API Documents](./button?tab=api)。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/drawer/type.ts) | N
default | String / Slot / Function | - | 抽屉内容,同 body。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
destroyOnClose | Boolean | false | 抽屉关闭时是否销毁节点 | N
footer | Boolean / Slot / Function | true | 底部操作栏,默认会有“确认”和“取消”两个按钮。值为 true 显示默认操作按钮,值为 false 或 null 不显示任何内容,值类型为 TNode 表示自定义底部内容。TS 类型:`boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
Expand All @@ -58,7 +59,7 @@ preventScrollThrough | Boolean | true | 防止滚动穿透 | N
showInAttachedElement | Boolean | false | 仅在挂载元素中显示抽屉,默认在浏览器可视区域显示。父元素需要有定位属性,如:position: relative | N
showOverlay | Boolean | true | 是否显示遮罩层 | N
size | String | 'small' | 尺寸,支持 'small', 'medium', 'large','35px', '30%', '3em' 等。纵向抽屉调整的是抽屉宽度,横向抽屉调整的是抽屉高度 | N
sizeDraggable | Boolean | false | 抽屉大小可拖拽调整,横向抽屉调整宽度,纵向抽屉调整高度 | N
sizeDraggable | Boolean / Object | false | 抽屉大小可拖拽调整,横向抽屉调整宽度,纵向抽屉调整高度。`sizeDraggable.max` 和 `sizeDraggable.min` 用于控制拖拽尺寸大小限制。TS 类型:`boolean \| SizeDragLimit` `interface SizeDragLimit { max: number, min: number }`。[详细类型定义](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/drawer/type.ts) | N
visible | Boolean | false | 组件是否可见 | N
zIndex | Number | - | 抽屉层级,样式默认为 1500 | N
onCancel | Function | | TS 类型:`(context: { e: MouseEvent }) => void`<br/>如果“取消”按钮存在,点击“取消”按钮时触发,同时触发关闭事件 | N
Expand All @@ -67,6 +68,7 @@ onCloseBtnClick | Function | | TS 类型:`(context: { e: MouseEvent }) => voi
onConfirm | Function | | TS 类型:`(context: { e: MouseEvent }) => void`<br/>如果“确认”按钮存在,则点击“确认”按钮时触发 | N
onEscKeydown | Function | | TS 类型:`(context: { e: KeyboardEvent }) => void`<br/>按下 ESC 键时触发 | N
onOverlayClick | Function | | TS 类型:`(context: { e: MouseEvent }) => void`<br/>如果蒙层存在,点击蒙层时触发 | N
onSizeDragEnd | Function | | TS 类型:`(context: { e: MouseEvent; size: number }) => void`<br/>抽屉大小拖拽结束时触发,事件参数 `size` 在横向抽屉中表示宽度,在纵向抽屉中表示高度 | N

### Drawer Events

Expand All @@ -78,15 +80,16 @@ close-btn-click | `(context: { e: MouseEvent })` | 如果关闭按钮存在,
confirm | `(context: { e: MouseEvent })` | 如果“确认”按钮存在,则点击“确认”按钮时触发
esc-keydown | `(context: { e: KeyboardEvent })` | 按下 ESC 键时触发
overlay-click | `(context: { e: MouseEvent })` | 如果蒙层存在,点击蒙层时触发
size-drag-end | `(context: { e: MouseEvent; size: number })` | 抽屉大小拖拽结束时触发,事件参数 `size` 在横向抽屉中表示宽度,在纵向抽屉中表示高度

### DrawerOptions

名称 | 类型 | 默认值 | 说明 | 必传
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
attach | String / Function | 'body' | 抽屉挂载的节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`AttachNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
className | String | - | 抽屉类名,示例:'t-class-drawer-first t-class-drawer-second' | N
style | String / Object | - | 弹框 style 属性,输入 [CSSStyleDeclaration.cssText](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/cssText)。TS 类型:`string \| Styles`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
`Omit<DrawerProps, 'attach'>` | \- | - | 继承 `Omit<DrawerProps, 'attach'>` 中的全部 API | N
`Omit<DrawerProps, 'attach'>` | \- | - | 继承 `Omit<DrawerProps, 'attach'>` 中的全部属性 | N

### DrawerInstance

Expand All @@ -101,6 +104,6 @@ update | `(props: DrawerOptions)` | \- | 更新抽屉内容

同时也支持 `this.$drawer`。

参数名称 | 参数类型 | 参数默认值 | 参数说明
参数名称 | 参数类型 | 参数默认值 | 参数描述
-- | -- | -- | --
options | \- | - | TS 类型:`DrawerOptions`
43 changes: 25 additions & 18 deletions src/drawer/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { computed, ref } from 'vue';
import { Styles } from '../common';
import { getSizeDraggable, calcMoveSize } from '../_common/js/drawer/utils';
import type { TdDrawerProps } from './type';

export const useDrag = (props: TdDrawerProps) => {
Expand Down Expand Up @@ -27,25 +28,31 @@ export const useDrag = (props: TdDrawerProps) => {
const maxWidth = document.documentElement.clientWidth;
const offsetHeight = 8;
const offsetWidth = 8;
// x 轴方向使用最大宽度,y轴方向使用最大高度
const max = props.placement === 'left' || props.placement === 'right' ? maxWidth : maxHeight;
// x 轴方向使用默认最小宽度,y轴方向使用默认最小高度
const min = props.placement === 'left' || props.placement === 'right' ? offsetWidth : offsetHeight;
const { allowSizeDraggable, max: limitMax, min: limitMin } = getSizeDraggable(props.sizeDraggable, { max, min });

if (isSizeDragging.value && props.sizeDraggable) {
if (props.placement === 'right') {
const moveLeft = Math.min(Math.max(maxWidth - x + offsetWidth, offsetWidth), maxWidth);
draggedSizeValue.value = `${moveLeft}px`;
}
if (props.placement === 'left') {
const moveRight = Math.min(Math.max(x + offsetWidth, offsetWidth), maxWidth);
draggedSizeValue.value = `${moveRight}px`;
}
if (props.placement === 'top') {
const moveBottom = Math.min(Math.max(y + offsetHeight, offsetHeight), maxHeight);
draggedSizeValue.value = `${moveBottom}px`;
}
if (props.placement === 'bottom') {
const moveTop = Math.min(Math.max(maxHeight - y + offsetHeight, offsetHeight), maxHeight);
draggedSizeValue.value = `${moveTop}px`;
}
}
// 不支持拖拽就直接返回
if (!allowSizeDraggable || !isSizeDragging.value) return;

const moveSize = calcMoveSize(props.placement, {
x,
y,
maxWidth,
maxHeight,
max: limitMax,
min: limitMin,
});

if (typeof moveSize === 'undefined') return;

draggedSizeValue.value = `${moveSize}px`;
props.onSizeDragEnd?.({
e,
size: moveSize,
});
};

const draggableLineStyles = computed(() => {
Expand Down
Loading
Loading