mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-30 12:52:43 +08:00
fix(modal & drawer): detachable components detached in wrong place when nested like modal > drawer > component
This commit is contained in:
parent
e9b5f35e69
commit
040579d46b
255
demo/documentation/components/modal/zhCN/drawerDebug.md
Normal file
255
demo/documentation/components/modal/zhCN/drawerDebug.md
Normal file
@ -0,0 +1,255 @@
|
||||
# Drawer Debug
|
||||
```html
|
||||
<n-button @click="modalActive = !modalActive">Toggle</n-button>
|
||||
<n-modal
|
||||
title="Dark Modal Debug"
|
||||
preset="card"
|
||||
v-model="modalActive"
|
||||
:overlay-style="{ marginTop: '24px', marginBottom: '24px', width: '800px' }"
|
||||
>
|
||||
<n-button @click="drawerActive = !drawerActive">Open Drawer</n-button>
|
||||
<n-drawer v-model="drawerActive">
|
||||
<n-radio-group v-model="size" name="top-size" style="margin-bottom: 12px;">
|
||||
<n-radio-button value="small">小</n-radio-button>
|
||||
<n-radio-button value="medium" >中</n-radio-button>
|
||||
<n-radio-button value="large">大</n-radio-button>
|
||||
</n-radio-group>
|
||||
<n-form
|
||||
:model="model"
|
||||
:rules="rules"
|
||||
:size="size"
|
||||
ref="form"
|
||||
label-placement="top"
|
||||
>
|
||||
<n-row :gutter="24">
|
||||
<n-form-item-col :span="12" label="Input" path="inputValue">
|
||||
<n-input placeholder="Input" v-model="model.inputValue" />
|
||||
</n-form-item-col :span="12">
|
||||
<n-form-item-col :span="12" label="Textarea" path="textareaValue">
|
||||
<n-input placeholder="Textarea" v-model="model.textareaValue" type="textarea"
|
||||
:autosize="{
|
||||
minRows: 3,
|
||||
maxRows: 5
|
||||
}"
|
||||
/>
|
||||
</n-form-item-col>
|
||||
</n-row>
|
||||
<n-row :gutter="24">
|
||||
<n-form-item-col :span="12" label="Select" path="selectValue">
|
||||
<n-select placeholder="Select" :options="generalOptions" v-model="model.selectValue"/>
|
||||
</n-form-item-col>
|
||||
<n-form-item-col :span="12" label="Multiple Select" path="multipleSelectValue">
|
||||
<n-select placeholder="Select" :options="generalOptions" v-model="model.multipleSelectValue" multiple/>
|
||||
</n-form-item-col>
|
||||
</n-row>
|
||||
<n-row :gutter="24">
|
||||
<n-form-item-col :span="12" label="Datetime" path="datetimeValue">
|
||||
<n-date-picker type="datetime" v-model="model.datetimeValue"/>
|
||||
</n-form-item-col>
|
||||
<n-form-item-col :span="12" label="Switch" path="switchValue">
|
||||
<n-switch v-model="model.switchValue" />
|
||||
</n-form-item-col>
|
||||
</n-row>
|
||||
<n-row :gutter="24">
|
||||
<n-form-item-col :span="12" label="Checkbox Group" path="checkboxGroupValue">
|
||||
<n-checkbox-group v-model="model.checkboxGroupValue">
|
||||
<n-checkbox value="Option 1">Option 1</n-checkbox>
|
||||
<n-checkbox value="Option 2">Option 2</n-checkbox>
|
||||
<n-checkbox value="Option 3">Option 3</n-checkbox>
|
||||
</n-checkbox-group>
|
||||
</n-form-item-col>
|
||||
<n-form-item-col :span="12" label="Radio Group" path="radioGroupValue">
|
||||
<n-radio-group v-model="model.radioGroupValue" name="radiogroup1">
|
||||
<n-radio value="Radio 1">Radio 1</n-radio>
|
||||
<n-radio value="Radio 2">Radio 2</n-radio>
|
||||
<n-radio value="Radio 3">Radio 3</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item-col>
|
||||
</n-row>
|
||||
<n-row :gutter="24">
|
||||
<n-form-item-col :span="12" label="Radio Button Group" path="radioGroupValue">
|
||||
<n-radio-group v-model="model.radioGroupValue" name="radiogroup2">
|
||||
<n-radio-button value="Radio 1">Radio 1</n-radio-button>
|
||||
<n-radio-button value="Radio 2">Radio 2</n-radio-button>
|
||||
<n-radio-button value="Radio 3">Radio 3</n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-form-item-col>
|
||||
<n-form-item-col :span="12" label="Input Number" path="inputNumberValue">
|
||||
<n-input-number v-model="model.inputNumberValue"/>
|
||||
</n-form-item-col>
|
||||
</n-row>
|
||||
<n-row :gutter="24">
|
||||
<n-form-item-col :span="12" label="Time Picker" path="timePickerValue">
|
||||
<n-time-picker v-model="model.timePickerValue" />
|
||||
</n-form-item-col>
|
||||
<n-form-item-col :span="12" label="Slider" path="sliderValue">
|
||||
<n-slider v-model="model.sliderValue" :step="5"/>
|
||||
</n-form-item-col>
|
||||
</n-row>
|
||||
<n-row :gutter="24">
|
||||
<n-form-item-col :span="14" label="Transfer" path="transferValue">
|
||||
<n-transfer
|
||||
style="width: 100%;"
|
||||
v-model="model.transferValue"
|
||||
:options="generalOptions"
|
||||
/>
|
||||
</n-form-item-col>
|
||||
<n-form-item-col :span="5" label="Nested Path" path="nestedValue.path1">
|
||||
<n-cascader placeholder="Nested Path 1" v-model="model.nestedValue.path1" :options="cascaderOptions"/>
|
||||
</n-form-item-col>
|
||||
<n-form-item-col :span="5" path="nestedValue.path2">
|
||||
<n-select placeholder="Nested Path 2" :options="generalOptions" v-model="model.nestedValue.path2"/>
|
||||
</n-form-item-col>
|
||||
</n-row>
|
||||
<n-row>
|
||||
<n-col :span="24">
|
||||
<div style="display: flex; justify-content: flex-end;">
|
||||
<n-button @click="handleValidateButtonClick" round type="primary">验证</n-button>
|
||||
</div>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-form>
|
||||
</n-drawer>
|
||||
</n-modal>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
drawerActive: false,
|
||||
modalActive: false,
|
||||
size: 'medium',
|
||||
model: {
|
||||
inputValue: null,
|
||||
textareaValue: null,
|
||||
selectValue: null,
|
||||
multipleSelectValue: null,
|
||||
datetimeValue: null,
|
||||
nestedValue: {
|
||||
path1: null,
|
||||
path2: null
|
||||
},
|
||||
switchValue: false,
|
||||
checkboxGroupValue: null,
|
||||
radioGroupValue: null,
|
||||
radioButtonGroupValue: null,
|
||||
inputNumberValue: null,
|
||||
timePickerValue: null,
|
||||
sliderValue: 0,
|
||||
transferValue: null
|
||||
},
|
||||
generalOptions: [
|
||||
'groode',
|
||||
'veli good',
|
||||
'emazing',
|
||||
'lidiculous'
|
||||
].map(v => ({
|
||||
label: v,
|
||||
value: v
|
||||
})),
|
||||
cascaderOptions: [
|
||||
{
|
||||
label: 'groode',
|
||||
value: 'groode',
|
||||
children: [
|
||||
{
|
||||
label: 'veli good',
|
||||
value: 'veli good'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
inputValue: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入 inputValue'
|
||||
},
|
||||
textareaValue: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入 textareaValue'
|
||||
},
|
||||
selectValue: {
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请选择 selectValue'
|
||||
},
|
||||
multipleSelectValue: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请选择 multipleSelectValue'
|
||||
},
|
||||
datetimeValue: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请输入 datetimeValue'
|
||||
},
|
||||
nestedValue: {
|
||||
path1: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入 nestedValue.path1'
|
||||
},
|
||||
path2: {
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请输入 nestedValue.path2'
|
||||
}
|
||||
},
|
||||
checkboxGroupValue: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
message: '请选择 checkboxGroupValue'
|
||||
},
|
||||
radioGroupValue: {
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
message: '请选择 radioGroupValue'
|
||||
},
|
||||
radioButtonGroupValue: {
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
message: '请选择 radioButtonGroupValue'
|
||||
},
|
||||
inputNumberValue: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请输入 inputNumberValue'
|
||||
},
|
||||
timePickerValue: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请输入 timePickerValue'
|
||||
},
|
||||
sliderValue: 0,
|
||||
transferValue: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
message: '请输入 transferValue'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleValidateButtonClick (e) {
|
||||
e.preventDefault()
|
||||
this.$refs.form.validate(errors => {
|
||||
if (!errors) {
|
||||
this.$NMessage.success('验证成功')
|
||||
} else {
|
||||
console.log(errors)
|
||||
this.$NMessage.error('验证失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -13,6 +13,7 @@ dark1-debug
|
||||
dark2-debug
|
||||
dark3-debug
|
||||
dark4-debug
|
||||
drawer-debug
|
||||
```
|
||||
## V-model
|
||||
|Prop|Event|
|
||||
|
@ -97,6 +97,7 @@ document.documentElement.addEventListener('click', (e) => {
|
||||
}, true)
|
||||
|
||||
export default {
|
||||
name: 'NModalContent',
|
||||
components: {
|
||||
NScrollbar,
|
||||
NConfirm,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { getPortalTarget } from '../../../_utils/component/portal'
|
||||
|
||||
function cleanUp (content, target) {
|
||||
if (content && target && target.contains(content)) {
|
||||
target.removeChild(content)
|
||||
@ -6,14 +8,6 @@ function cleanUp (content, target) {
|
||||
|
||||
export default {
|
||||
name: 'NBasePortal',
|
||||
inject: {
|
||||
NModal: {
|
||||
default: null
|
||||
},
|
||||
NDrawer: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
onMounted: {
|
||||
type: Function,
|
||||
@ -22,15 +16,7 @@ export default {
|
||||
transferTarget: {
|
||||
type: Function,
|
||||
default: function () {
|
||||
const NModal = this.NModal
|
||||
if (NModal) {
|
||||
return NModal.getDetachTarget()
|
||||
}
|
||||
const NDrawer = this.NDrawer
|
||||
if (NDrawer) {
|
||||
return NDrawer.getDetachTarget()
|
||||
}
|
||||
return document.body
|
||||
return getPortalTarget(this)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -47,7 +33,7 @@ export default {
|
||||
* Since content may be detached in modal, waiting animation done is
|
||||
* important. A more elegant solution is needed.
|
||||
*/
|
||||
if (this.NModal || this.NDrawer) {
|
||||
if (getPortalTarget(this, true)) {
|
||||
setTimeout(() => {
|
||||
cleanUp(content, target)
|
||||
}, 300)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import withapp from './withapp'
|
||||
import { getPortalTarget } from '../_utils/component/portal'
|
||||
|
||||
function cleanUp (content, target) {
|
||||
if (content && target && target.contains(content)) {
|
||||
@ -16,27 +17,11 @@ function cleanUp (content, target) {
|
||||
*/
|
||||
export default {
|
||||
mixins: [ withapp ],
|
||||
inject: {
|
||||
NModal: {
|
||||
default: null
|
||||
},
|
||||
NDrawer: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
detachTarget: {
|
||||
type: Function,
|
||||
default: function () {
|
||||
const NModal = this.NModal
|
||||
if (NModal) {
|
||||
return NModal.getDetachTarget()
|
||||
}
|
||||
const NDrawer = this.NDrawer
|
||||
if (NDrawer) {
|
||||
return NDrawer.getDetachTarget()
|
||||
}
|
||||
return document.body
|
||||
return getPortalTarget(this)
|
||||
}
|
||||
},
|
||||
detachable: {
|
||||
@ -103,7 +88,7 @@ export default {
|
||||
* Since content may be detached in modal, waiting animation done is
|
||||
* important. A more elegant solution is needed.
|
||||
*/
|
||||
if (this.NModal || this.NDrawer) {
|
||||
if (getPortalTarget(this, true)) {
|
||||
setTimeout(() => {
|
||||
cleanUp(content, target)
|
||||
}, 300)
|
||||
|
19
src/_utils/component/portal.js
Normal file
19
src/_utils/component/portal.js
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @param {VueComponentInstance} instance
|
||||
* @param {boolean} returnBoolean if set to true, it returns whether the instance
|
||||
* is inside a modal or a drawer
|
||||
*/
|
||||
export function getPortalTarget (instance, returnBoolean = false) {
|
||||
let cursor = instance.$parent
|
||||
while (cursor) {
|
||||
const componentName = cursor.$options.name
|
||||
if (
|
||||
componentName === 'NModalContent' ||
|
||||
componentName === 'NDrawerContent'
|
||||
) {
|
||||
return returnBoolean ? true : cursor.getDetachTarget()
|
||||
}
|
||||
cursor = cursor.$parent
|
||||
}
|
||||
return returnBoolean ? false : document.body
|
||||
}
|
Loading…
Reference in New Issue
Block a user