mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-21 01:02:59 +08:00
refactor(components): refactor affix (#3368)
This commit is contained in:
parent
0eca6446e4
commit
7a4bd0f236
@ -1,8 +1,9 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { defineGetter, makeScroll } from '@element-plus/test-utils'
|
||||
import Affix from '../src/index.vue'
|
||||
import Affix from '../src/affix.vue'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
let clientHeightRestore = null
|
||||
let clientHeightRestore: () => void
|
||||
|
||||
const _mount = (template: string) =>
|
||||
mount(
|
||||
@ -35,6 +36,7 @@ describe('Affix.vue', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-affix>${AXIOM}</el-affix>
|
||||
`)
|
||||
await nextTick()
|
||||
expect(wrapper.text()).toEqual(AXIOM)
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
@ -63,6 +65,7 @@ describe('Affix.vue', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-affix :offset="30">${AXIOM}</el-affix>
|
||||
`)
|
||||
await nextTick()
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
@ -92,6 +95,7 @@ describe('Affix.vue', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-affix position="bottom" :offset="20">${AXIOM}</el-affix>
|
||||
`)
|
||||
await nextTick()
|
||||
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
@ -125,6 +129,7 @@ describe('Affix.vue', () => {
|
||||
</div>
|
||||
<div style="height: 1000px"></div>
|
||||
`)
|
||||
await nextTick()
|
||||
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
@ -166,6 +171,7 @@ describe('Affix.vue', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-affix :z-index="1000">${AXIOM}</el-affix>
|
||||
`)
|
||||
await nextTick()
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
|
@ -1,13 +1,8 @@
|
||||
import Affix from './src/index.vue'
|
||||
import { withInstall } from '@element-plus/utils/with-install'
|
||||
|
||||
import type { App } from 'vue'
|
||||
import type { SFCWithInstall } from '@element-plus/utils/types'
|
||||
import Affix from './src/affix.vue'
|
||||
|
||||
Affix.install = (app: App): void => {
|
||||
app.component(Affix.name, Affix)
|
||||
}
|
||||
export const ElAffix = withInstall(Affix)
|
||||
export default ElAffix
|
||||
|
||||
const _Affix = Affix as SFCWithInstall<typeof Affix>
|
||||
|
||||
export default _Affix
|
||||
export const ElAffix = _Affix
|
||||
export * from './src/affix'
|
||||
|
32
packages/components/affix/src/affix.ts
Normal file
32
packages/components/affix/src/affix.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { buildProp } from '@element-plus/utils/props'
|
||||
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type { ZIndexProperty } from 'csstype'
|
||||
|
||||
export const affixProps = {
|
||||
zIndex: buildProp<ZIndexProperty>({
|
||||
type: [Number, String],
|
||||
default: 100,
|
||||
}),
|
||||
target: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
position: buildProp({
|
||||
type: String,
|
||||
values: ['top', 'bottom'],
|
||||
default: 'top',
|
||||
} as const),
|
||||
} as const
|
||||
export type AffixProps = ExtractPropTypes<typeof affixProps>
|
||||
|
||||
export const affixEmits = {
|
||||
scroll: ({ scrollTop, fixed }: { scrollTop: number; fixed: boolean }) =>
|
||||
typeof scrollTop === 'number' && typeof fixed === 'boolean',
|
||||
change: (fixed: boolean) => typeof fixed === 'boolean',
|
||||
}
|
||||
export type AffixEmits = typeof affixEmits
|
@ -9,47 +9,27 @@
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { getScrollContainer, off, on } from '@element-plus/utils/dom'
|
||||
import {
|
||||
addResizeListener,
|
||||
removeResizeListener,
|
||||
} from '@element-plus/utils/resize-event'
|
||||
import { useEventListener, useResizeObserver } from '@vueuse/core'
|
||||
import { getScrollContainer } from '@element-plus/utils/dom'
|
||||
import { affixEmits, affixProps } from './affix'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type Position = 'top' | 'bottom'
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElAffix',
|
||||
props: {
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
position: {
|
||||
type: String as PropType<Position>,
|
||||
default: 'top',
|
||||
},
|
||||
},
|
||||
emits: ['scroll', 'change'],
|
||||
|
||||
props: affixProps,
|
||||
emits: affixEmits,
|
||||
|
||||
setup(props, { emit }) {
|
||||
const target = ref(null)
|
||||
const root = ref(null)
|
||||
const scrollContainer = ref(null)
|
||||
const target = shallowRef<HTMLElement>()
|
||||
const root = shallowRef<HTMLDivElement>()
|
||||
const scrollContainer = shallowRef<HTMLElement | Window>()
|
||||
|
||||
const state = reactive({
|
||||
fixed: false,
|
||||
@ -60,17 +40,16 @@ export default defineComponent({
|
||||
transform: 0,
|
||||
})
|
||||
|
||||
const rootStyle = computed(() => {
|
||||
const rootStyle = computed<CSSProperties>(() => {
|
||||
return {
|
||||
height: state.fixed ? `${state.height}px` : '',
|
||||
width: state.fixed ? `${state.width}px` : '',
|
||||
}
|
||||
})
|
||||
|
||||
const affixStyle = computed(() => {
|
||||
if (!state.fixed) {
|
||||
return
|
||||
}
|
||||
const affixStyle = computed<CSSProperties | undefined>(() => {
|
||||
if (!state.fixed) return
|
||||
|
||||
const offset = props.offset ? `${props.offset}px` : 0
|
||||
const transform = state.transform
|
||||
? `translateY(${state.transform}px)`
|
||||
@ -87,12 +66,14 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const update = () => {
|
||||
if (!root.value || !target.value || !scrollContainer.value) return
|
||||
|
||||
const rootRect = root.value.getBoundingClientRect()
|
||||
const targetRect = target.value.getBoundingClientRect()
|
||||
state.height = rootRect.height
|
||||
state.width = rootRect.width
|
||||
state.scrollTop =
|
||||
scrollContainer.value === window
|
||||
scrollContainer.value instanceof Window
|
||||
? document.documentElement.scrollTop
|
||||
: scrollContainer.value.scrollTop
|
||||
state.clientHeight = document.documentElement.clientHeight
|
||||
@ -137,29 +118,25 @@ export default defineComponent({
|
||||
|
||||
onMounted(() => {
|
||||
if (props.target) {
|
||||
target.value = document.querySelector(props.target)
|
||||
target.value =
|
||||
document.querySelector<HTMLElement>(props.target) ?? undefined
|
||||
if (!target.value) {
|
||||
throw new Error(`target is not existed: ${props.target}`)
|
||||
}
|
||||
} else {
|
||||
target.value = document.documentElement
|
||||
}
|
||||
scrollContainer.value = getScrollContainer(root.value)
|
||||
on(scrollContainer.value, 'scroll', onScroll)
|
||||
addResizeListener(root.value, update)
|
||||
scrollContainer.value = getScrollContainer(root.value!)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
off(scrollContainer.value, 'scroll', onScroll)
|
||||
removeResizeListener(root.value, update)
|
||||
})
|
||||
useEventListener(scrollContainer, 'scroll', onScroll)
|
||||
useResizeObserver(root, () => update())
|
||||
|
||||
return {
|
||||
root,
|
||||
state,
|
||||
rootStyle,
|
||||
affixStyle,
|
||||
update,
|
||||
}
|
||||
},
|
||||
})
|
@ -4,8 +4,8 @@ import isServer from './isServer'
|
||||
import type { CustomizedHTMLElement } from './types'
|
||||
|
||||
export type ResizableElement = CustomizedHTMLElement<{
|
||||
__resizeListeners__: Array<(...args: unknown[]) => unknown>
|
||||
__ro__: ResizeObserver
|
||||
__resizeListeners__?: Array<(...args: unknown[]) => unknown>
|
||||
__ro__?: ResizeObserver
|
||||
}>
|
||||
|
||||
/* istanbul ignore next */
|
||||
@ -43,6 +43,6 @@ export const removeResizeListener = function (
|
||||
if (!element || !element.__resizeListeners__) return
|
||||
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1)
|
||||
if (!element.__resizeListeners__.length) {
|
||||
element.__ro__.disconnect()
|
||||
element.__ro__?.disconnect()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user