mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-02-17 13:20:52 +08:00
refactor(placable): use a measure element for better
positioning robustness
This commit is contained in:
parent
3d2708ebdc
commit
f8e76dd7cd
@ -6,7 +6,6 @@
|
|||||||
'n-base-select-menu--multiple': multiple,
|
'n-base-select-menu--multiple': multiple,
|
||||||
[`n-${theme}-theme`]: theme
|
[`n-${theme}-theme`]: theme
|
||||||
}"
|
}"
|
||||||
|
|
||||||
:style="{
|
:style="{
|
||||||
width: width && (width + 'px')
|
width: width && (width + 'px')
|
||||||
}"
|
}"
|
||||||
|
@ -11,20 +11,20 @@
|
|||||||
</slot>
|
</slot>
|
||||||
<div
|
<div
|
||||||
ref="contentContainer"
|
ref="contentContainer"
|
||||||
v-clickoutside="handleClickOutsideMenu"
|
class="n-positioning-container"
|
||||||
class="n-detached-content-container n-select-detached-content-container"
|
|
||||||
:class="{
|
:class="{
|
||||||
[namespace]: namespace
|
[namespace]: namespace
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="content"
|
ref="content"
|
||||||
class="n-detached-content-content"
|
class="n-positioning-content"
|
||||||
>
|
>
|
||||||
<transition name="n-select-menu--transition">
|
<transition name="n-select-menu--transition">
|
||||||
<n-base-select-menu
|
<n-base-select-menu
|
||||||
v-if="active"
|
v-if="active"
|
||||||
ref="contentInner"
|
ref="contentInner"
|
||||||
|
v-clickoutside="handleClickOutsideMenu"
|
||||||
auto-pending-first-option
|
auto-pending-first-option
|
||||||
class="n-select-menu"
|
class="n-select-menu"
|
||||||
:theme="synthesizedTheme"
|
:theme="synthesizedTheme"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="n-detached-content-container"
|
class="n-positioning-container"
|
||||||
>
|
>
|
||||||
<div ref="content" class="n-detached-content">
|
<div ref="content" class="n-positioning-content">
|
||||||
<transition name="n-cascader-menu--transition">
|
<transition name="n-cascader-menu--transition">
|
||||||
<div
|
<div
|
||||||
v-if="active"
|
v-if="active"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="n-detached-content-container">
|
<div class="n-positioning-container">
|
||||||
<div ref="content" class="n-detached-content">
|
<div ref="content" class="n-positioning-content">
|
||||||
<transition name="n-cascader-menu--transition">
|
<transition name="n-cascader-menu--transition">
|
||||||
<n-base-select-menu
|
<n-base-select-menu
|
||||||
v-if="active"
|
v-if="active"
|
||||||
@ -15,6 +15,7 @@
|
|||||||
:is-option-selected="isSelected"
|
:is-option-selected="isSelected"
|
||||||
@mousedown.native.prevent="() => {}"
|
@mousedown.native.prevent="() => {}"
|
||||||
@menu-toggle-option="handleSelectMenuToggleOption"
|
@menu-toggle-option="handleSelectMenuToggleOption"
|
||||||
|
@menu-visible="handleMenuVisible"
|
||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@ -188,6 +189,9 @@ export default {
|
|||||||
const pendingOptionData = this.$refs.contentInner.getPendingOptionData()
|
const pendingOptionData = this.$refs.contentInner.getPendingOptionData()
|
||||||
this.handleSelectOptionCheck(pendingOptionData)
|
this.handleSelectOptionCheck(pendingOptionData)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
handleMenuVisible () {
|
||||||
|
this.updatePosition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,19 +54,19 @@
|
|||||||
</n-input>
|
</n-input>
|
||||||
<div
|
<div
|
||||||
ref="contentContainer"
|
ref="contentContainer"
|
||||||
class="n-detached-content-container n-date-picker-detached-content-container"
|
class="n-positioning-container"
|
||||||
:class="{
|
:class="{
|
||||||
[namespace]: namespace
|
[namespace]: namespace
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="content"
|
ref="content"
|
||||||
v-clickoutside="handleClickOutside"
|
class="n-positioning-content"
|
||||||
class="n-dateched-content"
|
|
||||||
>
|
>
|
||||||
<datetime-panel
|
<datetime-panel
|
||||||
v-if="type === 'datetime'"
|
v-if="type === 'datetime'"
|
||||||
ref="panel"
|
ref="panel"
|
||||||
|
v-clickoutside="handleClickOutside"
|
||||||
:value="value"
|
:value="value"
|
||||||
:active="active"
|
:active="active"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
@ -80,6 +80,7 @@
|
|||||||
<date-panel
|
<date-panel
|
||||||
v-else-if="type === 'date'"
|
v-else-if="type === 'date'"
|
||||||
ref="panel"
|
ref="panel"
|
||||||
|
v-clickoutside="handleClickOutside"
|
||||||
:value="value"
|
:value="value"
|
||||||
:active="active"
|
:active="active"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
@ -92,6 +93,7 @@
|
|||||||
<daterange-panel
|
<daterange-panel
|
||||||
v-else-if="type === 'daterange'"
|
v-else-if="type === 'daterange'"
|
||||||
ref="panel"
|
ref="panel"
|
||||||
|
v-clickoutside="handleClickOutside"
|
||||||
:value="value"
|
:value="value"
|
||||||
:active="active"
|
:active="active"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
@ -105,6 +107,7 @@
|
|||||||
<datetimerange-panel
|
<datetimerange-panel
|
||||||
v-else-if="type === 'datetimerange'"
|
v-else-if="type === 'datetimerange'"
|
||||||
ref="panel"
|
ref="panel"
|
||||||
|
v-clickoutside="handleClickOutside"
|
||||||
:value="value"
|
:value="value"
|
||||||
:active="active"
|
:active="active"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-base-portal ref="portal">
|
<n-base-portal ref="portal">
|
||||||
<div class="n-detached-content-container">
|
<div class="n-detached-content-container" style="z-index: 1000;">
|
||||||
<div
|
<div
|
||||||
class="n-drawer-container"
|
class="n-drawer-container"
|
||||||
:class="{
|
:class="{
|
||||||
|
@ -97,17 +97,17 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
// created () {
|
||||||
console.log('dropdown menu created')
|
// console.log('dropdown menu created')
|
||||||
},
|
// },
|
||||||
beforeDestroy () {
|
// beforeDestroy () {
|
||||||
console.log('dropdown menu beforeDestroy')
|
// console.log('dropdown menu beforeDestroy')
|
||||||
},
|
// },
|
||||||
destroyed () {
|
// destroyed () {
|
||||||
console.log('dropdown menu destroyed')
|
// console.log('dropdown menu destroyed')
|
||||||
},
|
// },
|
||||||
mounted () {
|
mounted () {
|
||||||
console.log('dropdown menu mounted')
|
// console.log('dropdown menu mounted')
|
||||||
if (this.defaultFocus && this.focusable) {
|
if (this.defaultFocus && this.focusable) {
|
||||||
this.$el.focus()
|
this.$el.focus()
|
||||||
}
|
}
|
||||||
@ -220,6 +220,9 @@ export default {
|
|||||||
isOptionSelected: () => false,
|
isOptionSelected: () => false,
|
||||||
theme: this.synthesizedTheme
|
theme: this.synthesizedTheme
|
||||||
},
|
},
|
||||||
|
style: {
|
||||||
|
overflow: 'visible'
|
||||||
|
},
|
||||||
on: {
|
on: {
|
||||||
'menu-toggle-option': option => {
|
'menu-toggle-option': option => {
|
||||||
this.handleSelectOption(option.value)
|
this.handleSelectOption(option.value)
|
||||||
|
@ -227,7 +227,7 @@ export default {
|
|||||||
},
|
},
|
||||||
render (h) {
|
render (h) {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
staticClass: 'n-detached-content-container',
|
staticClass: 'n-positioning-container',
|
||||||
class: {
|
class: {
|
||||||
[this.containerClass]: true,
|
[this.containerClass]: true,
|
||||||
[this.namespace]: this.namespace
|
[this.namespace]: this.namespace
|
||||||
@ -235,7 +235,7 @@ export default {
|
|||||||
ref: 'contentContainer'
|
ref: 'contentContainer'
|
||||||
}, [
|
}, [
|
||||||
h('div', {
|
h('div', {
|
||||||
staticClass: 'n-detached-content',
|
staticClass: 'n-positioning-content',
|
||||||
ref: 'content'
|
ref: 'content'
|
||||||
}, [
|
}, [
|
||||||
h('transition', {
|
h('transition', {
|
||||||
|
@ -41,15 +41,14 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
ref="contentContainer"
|
ref="contentContainer"
|
||||||
v-clickoutside="handleClickOutsideMenu"
|
class="n-positioning-container"
|
||||||
class="n-detached-content-container n-select-detached-content-container"
|
|
||||||
:class="{
|
:class="{
|
||||||
[namespace]: namespace
|
[namespace]: namespace
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="content"
|
ref="content"
|
||||||
class="n-detached-content-content"
|
class="n-positioning-content"
|
||||||
>
|
>
|
||||||
<transition
|
<transition
|
||||||
name="n-select-menu--transition"
|
name="n-select-menu--transition"
|
||||||
@ -58,6 +57,7 @@
|
|||||||
<n-base-select-menu
|
<n-base-select-menu
|
||||||
v-if="active"
|
v-if="active"
|
||||||
ref="contentInner"
|
ref="contentInner"
|
||||||
|
v-clickoutside="handleClickOutsideMenu"
|
||||||
class="n-select-menu"
|
class="n-select-menu"
|
||||||
auto-pending-first-option
|
auto-pending-first-option
|
||||||
:theme="synthesizedTheme"
|
:theme="synthesizedTheme"
|
||||||
|
@ -68,14 +68,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="contentContainer"
|
ref="contentContainer"
|
||||||
class="n-detached-content-container n-slider-detached-content-container"
|
class="n-positioning-container"
|
||||||
:class="{
|
:class="{
|
||||||
[namespace]: namespace
|
[namespace]: namespace
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="content"
|
ref="content"
|
||||||
class="n-detached-content"
|
class="n-positioning-content"
|
||||||
>
|
>
|
||||||
<transition name="n-fade-in-scale-up--transition">
|
<transition name="n-fade-in-scale-up--transition">
|
||||||
<div
|
<div
|
||||||
|
@ -21,9 +21,9 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
ref="contentContainer"
|
ref="contentContainer"
|
||||||
class="n-detached-content-container n-time-picker-detached-content-container"
|
class="n-positioning-container"
|
||||||
:class="{
|
:class="{
|
||||||
'n-detached-content-container--absolute-positioned': positionModeisAbsolute,
|
'n-positioning-container--absolute': positionModeisAbsolute,
|
||||||
[namespace]: namespace
|
[namespace]: namespace
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
@ -4,12 +4,33 @@ import getParentNode from '../utils/dom/getParentNode'
|
|||||||
import getScrollParent from '../utils/dom/getScrollParent'
|
import getScrollParent from '../utils/dom/getScrollParent'
|
||||||
import { getAdjustedPlacementOfTrackingElement, getTransformOriginByPlacement, getPosition } from '../utils/dom/calcPlacementTransform'
|
import { getAdjustedPlacementOfTrackingElement, getTransformOriginByPlacement, getPosition } from '../utils/dom/calcPlacementTransform'
|
||||||
|
|
||||||
|
let viewMeasurerInitialized = false
|
||||||
|
let viewMeasurer = null
|
||||||
|
|
||||||
|
if (!viewMeasurerInitialized && !document.getElementById('n-view-measurer')) {
|
||||||
|
viewMeasurer = document.createElement('div')
|
||||||
|
viewMeasurer.id = 'n-view-measurer'
|
||||||
|
viewMeasurer.style = `
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
visibility: hidden;
|
||||||
|
`
|
||||||
|
viewMeasurerInitialized = true
|
||||||
|
document.body.appendChild(viewMeasurer)
|
||||||
|
}
|
||||||
|
|
||||||
function getActivatorEl (componentInstance) {
|
function getActivatorEl (componentInstance) {
|
||||||
return componentInstance.$refs.activator.$el || componentInstance.$refs.activator
|
const refs = componentInstance.$refs
|
||||||
|
return refs.activator.$el || refs.activator
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContentEl (componentInstance) {
|
function getContentEl (componentInstance) {
|
||||||
return componentInstance.$refs.content.$el || componentInstance.$refs.content
|
const refs = componentInstance.$refs
|
||||||
|
return refs.content.$el || refs.content
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContentInner (instance) {
|
function getContentInner (instance) {
|
||||||
@ -17,25 +38,37 @@ function getContentInner (instance) {
|
|||||||
return (contentInnerRef && contentInnerRef.$el) || contentInnerRef || null
|
return (contentInnerRef && contentInnerRef.$el) || contentInnerRef || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActivatorRect (manuallyPositioned, x, y, trackedElement) {
|
function getViewBoundingRect () {
|
||||||
|
const containerBoundingRect = viewMeasurer.getBoundingClientRect()
|
||||||
|
return {
|
||||||
|
left: containerBoundingRect.left,
|
||||||
|
top: containerBoundingRect.top,
|
||||||
|
right: containerBoundingRect.right,
|
||||||
|
bottom: containerBoundingRect.bottom,
|
||||||
|
width: containerBoundingRect.width,
|
||||||
|
height: containerBoundingRect.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActivatorRect (manuallyPositioned, x, y, trackedElement, viewBoundingRect) {
|
||||||
if (manuallyPositioned) {
|
if (manuallyPositioned) {
|
||||||
return {
|
return {
|
||||||
top: y,
|
top: y,
|
||||||
left: x,
|
left: x,
|
||||||
height: 0,
|
height: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
right: window.innerWidth - x,
|
right: viewBoundingRect.width - x,
|
||||||
bottom: window.innerHeight - y
|
bottom: viewBoundingRect.height - y
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const activatorRect = trackedElement.getBoundingClientRect()
|
const activatorRect = trackedElement.getBoundingClientRect()
|
||||||
return {
|
return {
|
||||||
left: parseInt(activatorRect.left),
|
left: activatorRect.left - viewBoundingRect.left,
|
||||||
top: parseInt(activatorRect.top),
|
top: activatorRect.top - viewBoundingRect.top,
|
||||||
bottom: parseInt(window.innerHeight - activatorRect.bottom),
|
bottom: viewBoundingRect.height + viewBoundingRect.top - activatorRect.bottom,
|
||||||
right: parseInt(window.innerWidth - activatorRect.right),
|
right: viewBoundingRect.width + viewBoundingRect.left - activatorRect.right,
|
||||||
width: parseInt(activatorRect.width),
|
width: activatorRect.width,
|
||||||
height: parseInt(activatorRect.height)
|
height: activatorRect.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,14 +210,16 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
_getTrackingElement () {
|
_getTrackingElement () {
|
||||||
if (this.$refs && this.$refs.content) {
|
const refs = this.$refs
|
||||||
|
if (refs && refs.content) {
|
||||||
this.trackingElement = getContentEl(this)
|
this.trackingElement = getContentEl(this)
|
||||||
} else if (this.getTrackingElement) {
|
} else if (this.getTrackingElement) {
|
||||||
this.trackingElement = this.getTrackingElement()
|
this.trackingElement = this.getTrackingElement()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_getTrackedElement () {
|
_getTrackedElement () {
|
||||||
if (this.$refs && this.$refs.activator) {
|
const refs = this.$refs
|
||||||
|
if (refs && refs.activator) {
|
||||||
this.trackedElement = getActivatorEl(this)
|
this.trackedElement = getActivatorEl(this)
|
||||||
} else if (this.getTrackedElement) {
|
} else if (this.getTrackedElement) {
|
||||||
this.trackedElement = this.getTrackedElement()
|
this.trackedElement = this.getTrackedElement()
|
||||||
@ -221,7 +256,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// debugger
|
// debugger
|
||||||
const activatorRect = getActivatorRect(this.manuallyPositioned, this.x, this.y, this.trackedElement)
|
const viewBoundingRect = getViewBoundingRect()
|
||||||
|
const activatorRect = getActivatorRect(this.manuallyPositioned, this.x, this.y, this.trackedElement, viewBoundingRect)
|
||||||
const contentInner = getContentInner(this)
|
const contentInner = getContentInner(this)
|
||||||
if (this.widthMode === 'activator' && contentInner) {
|
if (this.widthMode === 'activator' && contentInner) {
|
||||||
contentInner.style.minWidth = activatorRect.width + 'px'
|
contentInner.style.minWidth = activatorRect.width + 'px'
|
||||||
|
28
playground/pg2.html
Normal file
28
playground/pg2.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
.fixed-el {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
.test-el {
|
||||||
|
position: absolute;
|
||||||
|
left: 400px;
|
||||||
|
top: 200px;
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
background-color: yellowgreen;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="fixed-el">123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890-12345678901234567890123456789012345678901234567890
|
||||||
|
<div class="test-el"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -44,6 +44,7 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: background-color .3s $--n-ease-in-out-cubic-bezier;
|
transition: background-color .3s $--n-ease-in-out-cubic-bezier;
|
||||||
font-size: $--n-secondary-text-color;
|
font-size: $--n-secondary-text-color;
|
||||||
|
overflow: hidden;
|
||||||
@include b(base-select-menu-option-wrapper) {
|
@include b(base-select-menu-option-wrapper) {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -42,9 +42,6 @@
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@include b(select-detached-content-container) {
|
|
||||||
transform: translateZ(0);
|
|
||||||
}
|
|
||||||
@include b(select-menu) {
|
@include b(select-menu) {
|
||||||
@include fade-in-scale-up-transition(select-menu, $original-transition: (background-color .3s $--n-ease-in-out-cubic-bezier));
|
@include fade-in-scale-up-transition(select-menu, $original-transition: (background-color .3s $--n-ease-in-out-cubic-bezier));
|
||||||
}
|
}
|
||||||
|
@ -35,24 +35,20 @@ input {
|
|||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.n-detached-content-container {
|
.n-positioning-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 0;
|
pointer-events: none;
|
||||||
overflow: visible;
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.n-detached-content-container.n-detached-content-container--absolute-positioned {
|
.n-positioning-container.n-positioning-container--absolute {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 0;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.n-detached-content {
|
.n-positioning-content {
|
||||||
position: fixed;
|
pointer-events: all;
|
||||||
}
|
}
|
@ -125,17 +125,4 @@ $theme-names: "dark", "light";
|
|||||||
@if not $common-css-attrs-generated {
|
@if not $common-css-attrs-generated {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@mixin detachedContentWrapper {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin detachedContent {
|
|
||||||
position: fixed;
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user