fix(popper): fix popper related style (#562)

* fix(popper): fix popper related style

* fix(popper): restore `onMount` hook, rewrite `Teleport` usage

* fix(popper): fix failed tests due to change

* fix(popper): - Fix popper initialiation method

* fix(popper): - Replace unref with internal defined unwrap function
This commit is contained in:
jeremywu 2020-11-10 14:55:03 +08:00 committed by GitHub
parent efe892109c
commit f3d24d6109
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1062 additions and 214 deletions

View File

@ -53,6 +53,7 @@
"cp-cli": "^2.0.0",
"cross-env": "^7.0.2",
"css-loader": "^4.2.2",
"css-minimizer-webpack-plugin": "^1.1.5",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^7.7.0",
"eslint-plugin-vue": "^7.0.0-beta.0",

View File

@ -83,16 +83,6 @@ describe('Autocomplete.vue', () => {
expect(document.body.querySelector('.el-popper').classList.contains('success')).toBe(true)
})
test('placement', async () => {
const wrapper = _mount()
await wrapper.setProps({ placement: 'top' })
expect(document.body.querySelector('.el-popper').getAttribute('data-popper-placement')).toBe('top')
await wrapper.setProps({ placement: 'bottom' })
expect(document.body.querySelector('.el-popper').getAttribute('data-popper-placement')).toBe('bottom')
})
test('popperAppendToBody', async () => {
_mount({ popperAppendToBody: false })
expect(document.body.querySelector('.el-popper__mask')).toBeNull()

View File

@ -35,6 +35,8 @@ const _mount: typeof mount = options => mount({
Cascader,
},
...options,
}, {
attachTo: 'body',
})
afterEach(() => {
@ -307,11 +309,11 @@ describe('Cascader.vue', () => {
})
const input = wrapper.find('input')
const dropdown = document.querySelector(DROPDOWN)
const dropdown = wrapper.find(DROPDOWN)
input.element.value = 'ha'
await input.trigger('input')
const hzSuggestion = dropdown.querySelector(SUGGESTION_ITEM) as HTMLElement
const hzSuggestion = dropdown.find(SUGGESTION_ITEM)
expect(filterMethod).toBeCalled()
expect(hzSuggestion.textContent).toBe('Zhejiang / Hangzhou')
expect(hzSuggestion.text()).toBe('Zhejiang / Hangzhou')
})
})

View File

@ -10,7 +10,9 @@ const _mount = (template: string, data: () => ({[key:string]: any;})) => {
data,
template,
})
return mount(Component)
return mount(Component, {
attachTo: 'body',
})
}
type ColorPickerVM = ComponentPublicInstance<{

View File

@ -18,6 +18,7 @@ const _mount = (template: string, data = () => ({}), otherObj?) => mount({
elFormItem: {},
},
},
attachTo: 'body',
})
afterEach(() => {

View File

@ -26,6 +26,7 @@ const _mount = (template: string, data = () => ({}), otherObj?) => mount({
elFormItem: {},
},
},
attachTo: 'body',
})
afterEach(() => {

View File

@ -6,7 +6,7 @@
:effect="effect"
:manual-mode="true"
:trigger="[trigger]"
popper-class="el-dropdown-popper"
popper-class="el-dropdown__popper"
>
<template #default>
<slot name="dropdown"></slot>

View File

@ -9,6 +9,7 @@ const _mount = (props: any = {}) => mount(ElPopconfirm, {
class: 'reference',
}),
},
attachTo: 'body',
})
describe('Popconfirm.vue', () => {

View File

@ -72,14 +72,13 @@ export default defineComponent({
popperStyle,
popperId,
popperClass,
pure,
showArrow,
transition,
visibility,
} = this
const kls = [
this.content ? 'el-popover__plain' : '',
this.content ? 'el-popover--plain' : '',
'el-popover',
popperClass,
].join(' ')
@ -90,7 +89,7 @@ export default defineComponent({
popperClass: kls,
popperStyle: popperStyle,
popperId,
pure,
pure: true,
visibility,
onMouseEnter: onPopperMouseEnter,
onMouseLeave: onPopperMouseLeave,

View File

@ -36,6 +36,7 @@ const _mount = (props: UnknownProps = {}, slots = {}): VueWrapper<any> =>
}),
...slots,
},
attachTo: 'body',
})
const popperMock = jest
@ -85,7 +86,7 @@ describe('Popper.vue', () => {
test('append to body', () => {
let wrapper = _mount()
expect(wrapper.find(selector).exists()).toBe(false)
expect(wrapper.find(selector).exists()).toBe(true)
/**
* Current layout of `ElPopper`
@ -96,10 +97,10 @@ describe('Popper.vue', () => {
*/
wrapper = _mount({
appendToBody: false,
appendToBody: true,
})
expect(wrapper.find(selector).exists()).toBe(true)
expect(wrapper.find(selector).exists()).toBe(false)
})
test('should show popper when mouse entered and hide when popper left', async () => {

View File

@ -4,17 +4,17 @@ import {
defineComponent,
Fragment,
Teleport,
onMounted,
onBeforeUnmount,
onDeactivated,
onActivated,
onMounted,
renderSlot,
toDisplayString,
withDirectives,
} from 'vue'
import throwError from '@element-plus/utils/error'
import { renderBlock } from '@element-plus/utils/vnode'
import { PatchFlags, renderBlock } from '@element-plus/utils/vnode'
import usePopper from './use-popper/index'
import defaultProps from './use-popper/defaults'
@ -107,18 +107,16 @@ export default defineComponent({
return renderBlock(Fragment, null, [
trigger,
appendToBody
? createVNode(
Teleport as any, // Vue did not support createVNode for Teleport
{
to: 'body',
key: 0,
},
[
popper,
],
)
: renderBlock(Fragment, { key: 1 }, [popper]),
createVNode(
Teleport as any, // Vue did not support createVNode for Teleport
{
to: 'body',
disabled: !appendToBody,
},
[popper],
PatchFlags.PROPS,
['disabled'],
),
])
},
})

View File

@ -40,10 +40,10 @@ export default function renderPopper(
} = props
const kls = [
popperClass,
'el-popper',
'is-' + effect,
popperClass,
pure ? 'el-popper__pure' : '',
pure ? 'is-pure' : '',
]
/**
* Equivalent to

View File

@ -46,7 +46,7 @@ export default {
},
appendToBody: {
type: Boolean,
default: true,
default: false,
},
boundariesPadding: {
type: Number,

View File

@ -12,18 +12,21 @@ import {
isHTMLElement,
isArray,
isString,
$,
} from '@element-plus/utils/util'
import usePopperOptions from './popper-options'
import type { ComponentPublicInstance, SetupContext } from 'vue'
import type { ComponentPublicInstance, SetupContext, Ref } from 'vue'
import type { IPopperOptions, TriggerType, PopperInstance, RefElement } from './defaults'
type ElementType = ComponentPublicInstance | HTMLElement
export const DEFAULT_TRIGGER = ['hover']
export const UPDATE_VISIBLE_EVENT = 'update:visible'
export default function (props: IPopperOptions, { emit }: SetupContext<string[]>) {
const arrowRef = ref<RefElement>(null)
const triggerRef = ref<ComponentPublicInstance | HTMLElement>(null)
const triggerRef = ref(null) as Ref<ElementType>
const popperRef = ref<RefElement>(null)
const popperId = `el-popper-${generateId()}`
@ -64,7 +67,6 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
}, props.hideAfter)
}
visibility.value = true
update()
}
function _hide() {
@ -131,14 +133,17 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
}
function initializePopper() {
const _trigger = isHTMLElement(triggerRef.value)
? triggerRef.value
: (triggerRef.value as ComponentPublicInstance).$el
detachPopper()
if (!$(visibility)) {
return
}
const unwrappedTrigger = $(triggerRef)
const _trigger = isHTMLElement(unwrappedTrigger)
? unwrappedTrigger
: (unwrappedTrigger as ComponentPublicInstance).$el
popperInstance = createPopper(
_trigger,
popperRef.value,
popperOptions.value,
$(popperRef),
$(popperOptions),
)
popperInstance.update()
@ -146,7 +151,7 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
function doDestroy(forceDestroy?: boolean) {
/* istanbul ignore if */
if (!popperInstance || (visibility.value && !forceDestroy)) return
if (!popperInstance || ($(visibility) && !forceDestroy)) return
detachPopper()
}
@ -164,6 +169,9 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
}
function update() {
if (!$(visibility)) {
return
}
if (popperInstance) {
popperInstance.update()
} else {
@ -171,9 +179,15 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
}
}
function onVisibilityChange(toState: boolean) {
if (toState) {
initializePopper()
}
}
if (!isManualMode()) {
const toggleState = () => {
if (visibility.value) {
if ($(visibility)) {
hide()
} else {
show()
@ -247,7 +261,7 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
popperInstance.update()
})
watch(visibility, update)
watch(visibility, onVisibilityChange)
return {
update,
@ -260,6 +274,7 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
emit('after-enter')
},
onAfterLeave: () => {
detachPopper()
emit('after-leave')
},
initializePopper,

View File

@ -12,11 +12,11 @@
placement="bottom-start"
:show-arrow="true"
:append-to-body="popperAppendToBody"
pure
popper-class="el-select--popper"
manual-mode
effect="light"
trigger="click"
:offset="6"
:offset="11"
>
<template #trigger>
<div class="select-trigger">

View File

@ -399,6 +399,7 @@ export const useSelect = (props, states: States, ctx) => {
const handleResize = () => {
resetInputWidth()
popper.value?.update()
if (props.multiple) resetInputHeight()
}

View File

@ -1,4 +1,4 @@
import { mount, VueWrapper } from '@vue/test-utils'
import { mount as _mount, VueWrapper } from '@vue/test-utils'
import { ComponentPublicInstance, nextTick } from 'vue'
import ElTable from '../src/table.vue'
import ElTableColumn from '../src/table-column/index'
@ -8,6 +8,11 @@ const testDataArr = []
const toArray = function(obj) {
return [].slice.call(obj)
}
const mount = (opt: any) => _mount(opt, {
attachTo: 'body',
})
const triggerEvent = function(elm, name, ...opts) {
let eventName

View File

@ -1,7 +1,6 @@
@import "mixins/mixins";
@import "common/var";
@import "button";
@import "./popper";
@include b(dropdown) {
display: inline-block;
@ -9,6 +8,30 @@
color: $--color-text-regular;
font-size: $--font-size-base;
@include e(popper) {
// using attributes selector to override
&[role="tooltip"] {
padding: 0px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
&.is-light[role="tooltip"] {
border: 1px solid #EBEEF5;
& .el-popper__arrow::before {
border: 1px solid #EBEEF5;
background: #FFF;
}
}
}
.el-dropdown-menu {
border: none;
}
#{& + '-selfdefine'} {
outline: none;
}
}
.el-button-group {
display: block;
.el-button {

View File

@ -1,40 +1,49 @@
@import "mixins/mixins";
@import "common/var";
@import "./popper";
@import 'mixins/mixins';
@import 'common/var';
@include b(popover) {
position: absolute;
background: $--popover-background-color;
min-width: 150px;
border-radius: 4px;
border: 1px solid $--popover-border-color;
padding: $--popover-padding;
z-index: $--index-popper;
color: $--color-text-regular;
line-height: 1.4;
text-align: justify;
font-size: $--popover-font-size;
box-shadow: $--box-shadow-light;
word-break: break-all;
&.el-popper {
background: $--popover-background-color;
min-width: 150px;
border-radius: 4px;
border: 1px solid $--popover-border-color;
padding: $--popover-padding;
z-index: $--index-popper;
color: $--color-text-regular;
line-height: 1.4;
text-align: justify;
font-size: $--popover-font-size;
box-shadow: $--box-shadow-light;
word-break: break-all;
@include m(plain) {
padding: $--popover-padding-large;
}
&.el-popper[role="tooltip"] {
border: none;
.el-popper__arrow::before {
border: none;
}
}
@include e(title) {
color: $--popover-title-font-color;
font-size: $--popover-title-font-size;
line-height: 1;
margin-bottom: 12px;
}
@include m(plain) {
padding: $--popover-padding-large;
}
@include e(reference) {
&:focus:not(.focusing), &:focus:hover {
@include e(title) {
color: $--popover-title-font-color;
font-size: $--popover-title-font-size;
line-height: 1;
margin-bottom: 12px;
}
@include e(reference) {
&:focus:not(.focusing),
&:focus:hover {
outline-width: 0;
}
}
&:focus:active,
&:focus {
outline-width: 0;
}
}
&:focus:active, &:focus {
outline-width: 0;
}
}

View File

@ -1,101 +1,86 @@
@import "mixins/mixins";
@import "common/var";
@import 'mixins/mixins';
@import 'common/var';
@include b(popper) {
.popper__arrow,
.popper__arrow::after {
position: absolute;
border-radius: 4px;
padding: 10px;
z-index: 2000;
font-size: 12px;
line-height: 1.2;
min-width: 10px;
word-wrap: break-word;
visibility: visible;
$arrow-selector: #{& + '__arrow'};
@include when(dark) {
color: $--color-white;
background: $--color-text-primary;
#{$arrow-selector}::before {
background: $--color-text-primary;
}
}
@include when(light) {
background: $--color-white;
border: 1px solid $--color-text-primary;
#{$arrow-selector}::before {
border: 1px solid $--color-text-primary;
background: $--color-white;
}
}
@include m(pure) {
padding: 0;
border: none;
#{$arrow-selector}::before {
border: none;
}
}
@include e(arrow) {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
width: 10px;
height: 10px;
z-index: -1;
.popper__arrow {
border-width: $--popover-arrow-size;
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03))
}
.popper__arrow::after {
content: " ";
border-width: $--popover-arrow-size;
}
&[x-placement^="top"] {
margin-bottom: #{$--popover-arrow-size + 6};
}
&[x-placement^="top"] .popper__arrow {
bottom: -$--popover-arrow-size;
left: 50%;
margin-right: #{$--tooltip-arrow-size / 2};
border-top-color: $--popover-border-color;
border-bottom-width: 0;
&::after {
bottom: 1px;
margin-left: -$--popover-arrow-size;
border-top-color: $--popover-background-color;
border-bottom-width: 0;
&::before {
position: absolute;
width: 10px;
height: 10px;
z-index: -1;
content: ' ';
transform: rotate(45deg);
background: $--color-text-primary;
box-sizing: border-box;
}
}
&[x-placement^="bottom"] {
margin-top: #{$--popover-arrow-size + 6};
}
$placements: (
'top': 'bottom',
'bottom': 'top',
'left': 'right',
'right': 'left',
);
&[x-placement^="bottom"] .popper__arrow {
top: -$--popover-arrow-size;
left: 50%;
margin-right: #{$--tooltip-arrow-size / 2};
border-top-width: 0;
border-bottom-color: $--popover-border-color;
&::after {
top: 1px;
margin-left: -$--popover-arrow-size;
border-top-width: 0;
border-bottom-color: $--popover-background-color;
@each $placement, $opposite in $placements {
&[data-popper-placement^='#{$placement}'] > #{$arrow-selector} {
#{$opposite}: -5px;
}
}
&[x-placement^="right"] {
margin-left: #{$--popover-arrow-size + 6};
}
&[x-placement^="right"] .popper__arrow {
top: 50%;
left: -$--popover-arrow-size;
margin-bottom: #{$--tooltip-arrow-size / 2};
border-right-color: $--popover-border-color;
border-left-width: 0;
&::after {
bottom: -$--popover-arrow-size;
left: 1px;
border-right-color: $--popover-background-color;
border-left-width: 0;
@each $placement,
$adjacency
in ('top': 'left', 'bottom': 'right', 'left': 'bottom', 'right': 'top')
{
&.is-light[data-popper-placement^='#{$placement}'] {
#{$arrow-selector}::before {
border-#{$placement}-color: transparent;
border-#{$adjacency}-color: transparent;
}
}
}
&[x-placement^="left"] {
margin-right: #{$--popover-arrow-size + 6};
}
&[x-placement^="left"] .popper__arrow {
top: 50%;
right: -$--popover-arrow-size;
margin-bottom: #{$--tooltip-arrow-size / 2};
border-right-width: 0;
border-left-color: $--popover-border-color;
&::after {
right: 1px;
bottom: -$--popover-arrow-size;
margin-left: -$--popover-arrow-size;
border-right-width: 0;
border-left-color: $--popover-background-color;
}
}
}
}

View File

@ -9,7 +9,6 @@
background-color: $--select-dropdown-background;
box-shadow: $--select-dropdown-shadow;
box-sizing: border-box;
margin: 5px 0;
@include when(multiple) {
& .el-select-dropdown__item.selected {

View File

@ -1,17 +1,36 @@
@import "mixins/mixins";
@import "mixins/utils";
@import "common/var";
@import "select-dropdown";
@import "input";
@import "tag";
@import "option";
@import "option-group";
@import "scrollbar";
@import 'mixins/mixins';
@import 'mixins/utils';
@import 'common/var';
@import 'select-dropdown';
@import 'input';
@import 'tag';
@import 'option';
@import 'option-group';
@import 'scrollbar';
@include b(select) {
display: inline-block;
position: relative;
// using attributes selector to override
@include m(popper) {
// using attributes selector to override
&[role='tooltip'] {
padding: 0px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
&.is-light[role='tooltip'] {
border: none;
& .el-popper__arrow {
z-index: 0;
}
& .el-popper__arrow::before {
border: none;
background: #fff;
}
}
}
}
.el-select__tags > span {
display: inline-block;
}
@ -35,7 +54,7 @@
& .el-select__caret {
color: $--select-input-color;
font-size: $--select-input-font-size;
transition: transform .3s;
transition: transform 0.3s;
transform: rotateZ(180deg);
cursor: pointer;
@ -140,7 +159,7 @@
&::before {
display: block;
transform: translate(0, .5px);
transform: translate(0, 0.5px);
}
}
}

View File

@ -16,6 +16,7 @@ const _mount = (template: string, data, otherObj?) => mount({
elFormItem: {},
},
},
attachTo: 'body',
})
const makeRange = (start, end) => {

View File

@ -12,6 +12,7 @@ const _mount = (props: any = {}, content: string | VNode = '') => mount(Tooltip,
content: () => content,
},
props,
attachTo: 'body',
})
const selector = '.el-popper'

View File

@ -211,3 +211,11 @@ export function arrayFlat(arr: unknown[]) {
export function deduplicate<T>(arr: T[]) {
return [...new Set(arr)]
}
/**
* Unwraps refed value
* @param ref Refed value
*/
export function $<T>(ref: Ref<T>) {
return ref.value
}

View File

@ -9,7 +9,7 @@ import FooterNav from './components/footer-nav'
import title from './i18n/title'
import 'highlight.js/styles/color-brewer.css'
import './demo-styles/index.scss'
import './assets/styles/common.css'
import './assets/styles/common.scss'
import './assets/styles/fonts/style.css'
import icon from './icon.json'
import dayjs from 'dayjs'

View File

@ -3,6 +3,8 @@ const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const isProd = process.env.NODE_ENV === 'production'
@ -11,7 +13,9 @@ const isPlay = !!process.env.PLAY_ENV
const config = {
mode: isProd ? 'production' : 'development',
devtool: !isProd && 'cheap-module-eval-source-map',
entry: isPlay ? path.resolve(__dirname, './play.js') : path.resolve(__dirname, './entry.js'),
entry: isPlay
? path.resolve(__dirname, './play.js')
: path.resolve(__dirname, './entry.js'),
output: {
path: path.resolve(__dirname, '../website-dist'),
publicPath: '/',
@ -59,7 +63,7 @@ const config = {
resolve: {
extensions: ['.ts', '.tsx', '.js', '.vue', '.json'],
alias: {
'vue': 'vue/dist/vue.esm-browser.js',
vue: 'vue/dist/vue.esm-browser.js',
examples: path.resolve(__dirname),
},
},
@ -80,6 +84,12 @@ const config = {
contentBase: __dirname,
overlay: true,
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
},
}
const cssRule = {
@ -95,14 +105,16 @@ const cssRule = {
],
}
if (isProd) {
config.plugins.push(new MiniCssExtractPlugin({
// if (isProd) {
config.plugins.push(
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}))
cssRule.use.unshift(MiniCssExtractPlugin.loader)
} else {
cssRule.use.unshift('style-loader')
}
}),
)
cssRule.use.unshift(MiniCssExtractPlugin.loader)
// } else {
cssRule.use.unshift('style-loader')
// }
config.module.rules.push(cssRule)
module.exports = config

812
yarn.lock

File diff suppressed because it is too large Load Diff