feat(components): [dialog] Dialog can drag overflow the viewport (#15643)

* style(components): [dialog] Modify dialog style and docs

* chore: update

* feat(components): [dialog] add overflow prop

* feat(components): update
This commit is contained in:
kooriookami 2024-01-25 15:03:34 +08:00 committed by GitHub
parent c340c96fe8
commit 67cd7e95e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 110 additions and 208 deletions

View File

@ -99,7 +99,7 @@ dialog/destroy-on-close
Try to drag the `header` part.
:::demo Set `draggable` to `true` to drag.
:::demo Set `draggable` to `true` to drag. Set `overflow` ^(2.5.4) to `true` can drag overflow the viewport.
dialog/draggable-dialog
@ -116,7 +116,7 @@ When using `modal` = false, please make sure that `append-to-body` was set to **
### Attributes
| Name | Description | Type | Default |
| -------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------- | ------- |
|----------------------------|------------------------------------------------------------------------------------------------------| ----------------------------------- | ------- |
| model-value / v-model | visibility of Dialog | ^[boolean] | — |
| title | title of Dialog. Can also be passed with a named slot (see the following table) | ^[string] | '' |
| width | width of Dialog, default is 50% | ^[string] / ^[number] | '' |
@ -135,6 +135,7 @@ When using `modal` = false, please make sure that `append-to-body` was set to **
| show-close | whether to show a close button | ^[boolean] | true |
| before-close | callback before Dialog closes, and it will prevent Dialog from closing, use done to close the dialog | ^[Function]`(done: DoneFn) => void` | — |
| draggable | enable dragging feature for Dialog | ^[boolean] | false |
| overflow ^(2.5.4) | draggable Dialog can overflow the viewport | ^[boolean] | false |
| center | whether to align the header and footer in center | ^[boolean] | false |
| align-center ^(2.2.16) | whether to align the dialog both horizontally and vertically | ^[boolean] | false |
| destroy-on-close | destroy elements in Dialog when closed | ^[boolean] | false |

View File

@ -1,22 +1,22 @@
<template>
<el-button text @click="centerDialogVisible = true">
<el-button plain @click="centerDialogVisible = true">
Click to open the Dialog
</el-button>
<el-dialog
v-model="centerDialogVisible"
title="Warning"
width="30%"
width="500"
align-center
>
<span>Open the dialog from the center from the screen</span>
<template #footer>
<span class="dialog-footer">
<div class="dialog-footer">
<el-button @click="centerDialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="centerDialogVisible = false">
Confirm
</el-button>
</span>
</div>
</template>
</el-dialog>
</template>
@ -25,8 +25,3 @@ import { ref } from 'vue'
const centerDialogVisible = ref(false)
</script>
<style scoped>
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

View File

@ -1,22 +1,22 @@
<template>
<el-button text @click="dialogVisible = true">
click to open the Dialog
<el-button plain @click="dialogVisible = true">
Click to open the Dialog
</el-button>
<el-dialog
v-model="dialogVisible"
title="Tips"
width="30%"
width="500"
:before-close="handleClose"
>
<span>This is a message</span>
<template #footer>
<span class="dialog-footer">
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</span>
</div>
</template>
</el-dialog>
</template>
@ -37,8 +37,3 @@ const handleClose = (done: () => void) => {
})
}
</script>
<style scoped>
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

View File

@ -1,20 +1,20 @@
<template>
<el-button text @click="centerDialogVisible = true">
<el-button plain @click="centerDialogVisible = true">
Click to open the Dialog
</el-button>
<el-dialog v-model="centerDialogVisible" title="Warning" width="30%" center>
<el-dialog v-model="centerDialogVisible" title="Warning" width="500" center>
<span>
It should be noted that the content will not be aligned in center by
default
</span>
<template #footer>
<span class="dialog-footer">
<div class="dialog-footer">
<el-button @click="centerDialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="centerDialogVisible = false">
Confirm
</el-button>
</span>
</div>
</template>
</el-dialog>
</template>
@ -23,8 +23,3 @@ import { ref } from 'vue'
const centerDialogVisible = ref(false)
</script>
<style scoped>
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

View File

@ -1,9 +1,13 @@
<template>
<el-button text @click="dialogTableVisible = true">
open a Table nested Dialog
<el-button plain @click="dialogTableVisible = true">
Open a Table nested Dialog
</el-button>
<el-dialog v-model="dialogTableVisible" title="Shipping address">
<el-button plain @click="dialogFormVisible = true">
Open a Form nested Dialog
</el-button>
<el-dialog v-model="dialogTableVisible" title="Shipping address" width="800">
<el-table :data="gridData">
<el-table-column property="date" label="Date" width="150" />
<el-table-column property="name" label="Name" width="200" />
@ -11,12 +15,7 @@
</el-table>
</el-dialog>
<!-- Form -->
<el-button text @click="dialogFormVisible = true">
open a Form nested Dialog
</el-button>
<el-dialog v-model="dialogFormVisible" title="Shipping address">
<el-dialog v-model="dialogFormVisible" title="Shipping address" width="500">
<el-form :model="form">
<el-form-item label="Promotion name" :label-width="formLabelWidth">
<el-input v-model="form.name" autocomplete="off" />
@ -29,12 +28,12 @@
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<div class="dialog-footer">
<el-button @click="dialogFormVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogFormVisible = false">
Confirm
</el-button>
</span>
</div>
</template>
</el-dialog>
</template>
@ -80,17 +79,3 @@ const gridData = [
},
]
</script>
<style scoped>
.el-button--text {
margin-right: 15px;
}
.el-select {
width: 300px;
}
.el-input {
width: 300px;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

View File

@ -1,8 +1,9 @@
<template>
<el-button @click="visible = true">
<el-button plain @click="visible = true">
Open Dialog with customized header
</el-button>
<el-dialog v-model="visible" :show-close="false">
<el-dialog v-model="visible" :show-close="false" width="500">
<template #header="{ close, titleId, titleClass }">
<div class="my-header">
<h4 :id="titleId" :class="titleClass">This is a custom header!</h4>
@ -29,5 +30,6 @@ const visible = ref(false)
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 16px;
}
</style>

View File

@ -1,12 +1,12 @@
<template>
<el-button text @click="centerDialogVisible = true">
<el-button plain @click="centerDialogVisible = true">
Click to open Dialog
</el-button>
<el-dialog
v-model="centerDialogVisible"
title="Notice"
width="30%"
width="500"
destroy-on-close
center
>
@ -18,12 +18,12 @@
<strong>Extra content (Not rendered)</strong>
</div>
<template #footer>
<span class="dialog-footer">
<div class="dialog-footer">
<el-button @click="centerDialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="centerDialogVisible = false">
Confirm
</el-button>
</span>
</div>
</template>
</el-dialog>
</template>
@ -33,8 +33,3 @@ import { ref } from 'vue'
const centerDialogVisible = ref(false)
</script>
<style scoped>
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

View File

@ -1,17 +1,39 @@
<template>
<el-button text @click="dialogVisible = true">
Click to open Dialog
<el-button plain @click="dialogVisible = true">
Open a draggable Dialog
</el-button>
<el-dialog v-model="dialogVisible" title="Tips" width="30%" draggable>
<el-button plain @click="dialogOverflowVisible = true">
Open a overflow draggable Dialog
</el-button>
<el-dialog v-model="dialogVisible" title="Tips" width="500" draggable>
<span>It's a draggable Dialog</span>
<template #footer>
<span class="dialog-footer">
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</span>
</div>
</template>
</el-dialog>
<el-dialog
v-model="dialogOverflowVisible"
title="Tips"
width="500"
draggable
overflow
>
<span>It's a overflow draggable Dialog</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogOverflowVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogOverflowVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
@ -20,4 +42,5 @@
import { ref } from 'vue'
const dialogVisible = ref(false)
const dialogOverflowVisible = ref(false)
</script>

View File

@ -1,47 +0,0 @@
<template>
<el-button text @click="dialogVisible = true">
click to open the Dialog
</el-button>
<div>
<p>Close dialog and the input will be focused</p>
<el-input ref="inputRef" placeholder="Please input" />
</div>
<el-dialog
v-model="dialogVisible"
destroy-on-close
title="Tips"
width="30%"
@close-auto-focus="handleCloseAutoFocus"
>
<span>This is a message</span>
<el-divider />
<el-input placeholder="Initially focused" />
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ElInput } from 'element-plus'
const dialogVisible = ref(false)
const inputRef = ref<InstanceType<typeof ElInput>>()
const handleCloseAutoFocus = () => {
inputRef.value?.focus()
}
</script>
<style scoped>
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

View File

@ -1,22 +1,23 @@
<template>
<el-button text @click="outerVisible = true">
open the outer Dialog
<el-button plain @click="outerVisible = true">
Open the outer Dialog
</el-button>
<el-dialog v-model="outerVisible" title="Outer Dialog">
<template #default>
<el-dialog
v-model="innerVisible"
width="30%"
title="Inner Dialog"
append-to-body
/>
</template>
<el-dialog v-model="outerVisible" title="Outer Dialog" width="800">
<span>This is the outer Dialog</span>
<el-dialog
v-model="innerVisible"
width="500"
title="Inner Dialog"
append-to-body
>
<span>This is the inner Dialog</span>
</el-dialog>
<template #footer>
<div class="dialog-footer">
<el-button @click="outerVisible = false">Cancel</el-button>
<el-button type="primary" @click="innerVisible = true">
open the inner Dialog
Open the inner Dialog
</el-button>
</div>
</template>
@ -29,8 +30,3 @@ import { ref } from 'vue'
const outerVisible = ref(false)
const innerVisible = ref(false)
</script>
<style scoped>
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

View File

@ -15,17 +15,14 @@ export const dialogContentProps = buildProps({
closeIcon: {
type: iconPropType,
},
/**
* @deprecated will be removed in version 2.4.0, please use class
*/
customClass: {
type: String,
default: '',
},
/**
* @description enable dragging feature for Dialog
*/
draggable: Boolean,
/**
* @description draggable Dialog can overflow the viewport
*/
overflow: Boolean,
/**
* @description whether the Dialog takes up full screen
*/

View File

@ -1,6 +1,9 @@
<template>
<div :ref="composedDialogRef" :class="dialogKls" :style="style" tabindex="-1">
<header ref="headerRef" :class="ns.e('header')">
<header
ref="headerRef"
:class="[ns.e('header'), { 'show-close': showClose }]"
>
<slot name="header">
<span role="heading" :aria-level="ariaLevel" :class="ns.e('title')">
{{ title }}
@ -52,11 +55,11 @@ const dialogKls = computed(() => [
ns.is('draggable', props.draggable),
ns.is('align-center', props.alignCenter),
{ [ns.m('center')]: props.center },
props.customClass,
])
const composedDialogRef = composeRefs(focusTrapRef, dialogRef)
const draggable = computed(() => props.draggable)
useDraggable(dialogRef, headerRef, draggable)
const overflow = computed(() => props.overflow)
useDraggable(dialogRef, headerRef, draggable, overflow)
</script>

View File

@ -41,11 +41,11 @@
v-if="rendered"
ref="dialogContentRef"
v-bind="$attrs"
:custom-class="customClass"
:center="center"
:align-center="alignCenter"
:close-icon="closeIcon"
:draggable="draggable"
:overflow="overflow"
:fullscreen="fullscreen"
:show-close="showClose"
:title="title"
@ -104,18 +104,6 @@ useDeprecated(
computed(() => !!slots.title)
)
useDeprecated(
{
scope: 'el-dialog',
from: 'custom-class',
replacement: 'class',
version: '2.3.0',
ref: 'https://element-plus.org/en-US/component/dialog.html#attributes',
type: 'Attribute',
},
computed(() => !!props.customClass)
)
const ns = useNamespace('dialog')
const dialogRef = ref<HTMLElement>()
const headerRef = ref<HTMLElement>()

View File

@ -229,23 +229,6 @@ describe('Drawer', () => {
expect(wrapper.find('.el-drawer__close-btn').exists()).toBe(false)
})
test('should have custom classes when custom classes were given', async () => {
const classes = 'some-custom-class'
const wrapper = _mount(
`
<el-drawer :title='title' v-model='visible' ref='drawer' custom-class='${classes}'>
<span>${content}</span>
</el-drawer>
`,
() => ({
title,
visible: true,
})
)
expect(wrapper.find(`.${classes}`).exists()).toBe(true)
})
test('drawer header should have slot props', async () => {
const wrapper = _mount(
`

View File

@ -27,7 +27,7 @@
:aria-labelledby="!title ? titleId : undefined"
:aria-describedby="bodyId"
v-bind="$attrs"
:class="[ns.b(), direction, visible && 'open', customClass]"
:class="[ns.b(), direction, visible && 'open']"
:style="
isHorizontal ? 'width: ' + drawerSize : 'height: ' + drawerSize
"
@ -112,17 +112,6 @@ useDeprecated(
},
computed(() => !!slots.title)
)
useDeprecated(
{
scope: 'el-drawer',
from: 'custom-class',
replacement: 'class',
version: '2.3.0',
ref: 'https://element-plus.org/en-US/component/drawer.html#attributes',
type: 'Attribute',
},
computed(() => !!props.customClass)
)
const drawerRef = ref<HTMLElement>()
const focusStartRef = ref<HTMLElement>()

View File

@ -5,7 +5,8 @@ import type { ComputedRef, Ref } from 'vue'
export const useDraggable = (
targetRef: Ref<HTMLElement | undefined>,
dragRef: Ref<HTMLElement | undefined>,
draggable: ComputedRef<boolean>
draggable: ComputedRef<boolean>,
overflow?: ComputedRef<boolean>
) => {
let transform = {
offsetX: 0,
@ -32,14 +33,13 @@ export const useDraggable = (
const maxTop = clientHeight - targetTop - targetHeight + offsetY
const onMousemove = (e: MouseEvent) => {
const moveX = Math.min(
Math.max(offsetX + e.clientX - downX, minLeft),
maxLeft
)
const moveY = Math.min(
Math.max(offsetY + e.clientY - downY, minTop),
maxTop
)
let moveX = offsetX + e.clientX - downX
let moveY = offsetY + e.clientY - downY
if (!overflow?.value) {
moveX = Math.min(Math.max(moveX, minLeft), maxLeft)
moveY = Math.min(Math.max(moveY, minTop), maxTop)
}
transform = {
offsetX: moveX,

View File

@ -786,7 +786,7 @@ $dialog: map.merge(
'title-font-size': getCssVar('font-size-large'),
'content-font-size': 14px,
'font-line-height': getCssVar('font-line-height-primary'),
'padding-primary': 20px,
'padding-primary': 16px,
'border-radius': getCssVar('border-radius-small'),
),
$dialog

View File

@ -15,6 +15,7 @@
border-radius: getCssVar('dialog', 'border-radius');
box-shadow: getCssVar('dialog', 'box-shadow');
box-sizing: border-box;
padding: getCssVar('dialog', 'padding-primary');
width: var(#{getCssVarName('dialog-width')}, 50%);
&:focus {
@ -52,18 +53,23 @@
}
@include e(header) {
padding: getCssVar('dialog', 'padding-primary');
padding-bottom: 10px;
margin-right: 16px;
padding-bottom: getCssVar('dialog', 'padding-primary');
&.show-close {
padding-right: calc(getCssVar('dialog', 'padding-primary') + var(
#{getCssVarName('message-close-size')},
map.get($message, 'close-size')
));
}
}
@include e(headerbtn) {
position: absolute;
top: 6px;
top: 0;
right: 0;
padding: 0;
width: 54px;
height: 54px;
width: 48px;
height: 48px;
background: transparent;
border: none;
outline: none;
@ -93,15 +99,12 @@
}
@include e(body) {
padding: calc(#{getCssVar('dialog-padding-primary')} + 10px)
getCssVar('dialog-padding-primary');
color: getCssVar('text-color', 'regular');
font-size: getCssVar('dialog-content-font-size');
}
@include e(footer) {
padding: getCssVar('dialog-padding-primary');
padding-top: 10px;
padding-top: getCssVar('dialog', 'padding-primary');
text-align: right;
box-sizing: border-box;
}
@ -112,7 +115,6 @@
@include e(body) {
text-align: initial;
padding: 25px calc(#{getCssVar('dialog-padding-primary')} + 5px) 30px;
}
@include e(footer) {