mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [splitter] add lazy prop (#21730)
* feat(components): [splitter] add lazy prop * chore: cleanup * fix: lazy change should end dragging
This commit is contained in:
@@ -57,13 +57,24 @@ splitter/size
|
||||
|
||||
:::
|
||||
|
||||
## Lazy ^(2.11.0)
|
||||
|
||||
When `lazy` is enabled, the panel size will not update in real time during dragging, but only after the drag ends.
|
||||
|
||||
:::demo
|
||||
|
||||
splitter/lazy
|
||||
|
||||
:::
|
||||
|
||||
## Splitter API
|
||||
|
||||
### Splitter Attributes
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| ------ | -------------------------------- | ----------------------------------- | ---------- |
|
||||
| layout | Layout direction of the splitter | ^[enum]`'horizontal' \| 'vertical'` | horizontal |
|
||||
| Name | Description | Type | Default |
|
||||
| -------------- | -------------------------------- | ----------------------------------- | ---------- |
|
||||
| layout | Layout direction of the splitter | ^[enum]`'horizontal' \| 'vertical'` | horizontal |
|
||||
| lazy ^(2.11.0) | Whether to enable lazy mode | ^[boolean] | false |
|
||||
|
||||
### Splitter Events
|
||||
|
||||
|
||||
26
docs/examples/splitter/lazy.vue
Normal file
26
docs/examples/splitter/lazy.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div
|
||||
style="height: 250px; box-shadow: var(--el-border-color-light) 0px 0px 10px"
|
||||
>
|
||||
<el-splitter lazy>
|
||||
<el-splitter-panel collapsible min="50">
|
||||
<div class="demo-panel">1</div>
|
||||
</el-splitter-panel>
|
||||
<el-splitter-panel collapsible>
|
||||
<div class="demo-panel">2</div>
|
||||
</el-splitter-panel>
|
||||
<el-splitter-panel collapsible>
|
||||
<div class="demo-panel">3</div>
|
||||
</el-splitter-panel>
|
||||
</el-splitter>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.demo-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -269,4 +269,64 @@ describe('Splitter', () => {
|
||||
// Panel should be collapsed (size = 400)
|
||||
expect(panels[0].attributes('style')).toContain('flex-basis: 400px;')
|
||||
})
|
||||
|
||||
it('should not update panel size until drag ends when lazy is true', async () => {
|
||||
const wrapper = mount(() => (
|
||||
<div style={{ width: '400px', height: '400px' }}>
|
||||
<ElSplitter lazy>
|
||||
<ElSplitterPanel>Left Panel</ElSplitterPanel>
|
||||
<ElSplitterPanel>Right Panel</ElSplitterPanel>
|
||||
</ElSplitter>
|
||||
</div>
|
||||
))
|
||||
await nextTick()
|
||||
const panels = wrapper.findAll('.el-splitter-panel')
|
||||
const splitBar = wrapper.find('.el-splitter-bar__dragger')
|
||||
|
||||
const mousedown = new MouseEvent('mousedown', { bubbles: true })
|
||||
Object.defineProperty(mousedown, 'pageX', { value: 200 })
|
||||
splitBar.element.dispatchEvent(mousedown)
|
||||
|
||||
const mousemove = new MouseEvent('mousemove', { bubbles: true })
|
||||
Object.defineProperty(mousemove, 'pageX', { value: 100 })
|
||||
window.dispatchEvent(mousemove)
|
||||
await nextTick()
|
||||
expect(panels[0].attributes('style')).toContain('flex-basis: 200px;')
|
||||
|
||||
const mouseup = new MouseEvent('mouseup', { bubbles: true })
|
||||
Object.defineProperty(mouseup, 'pageX', { value: 100 })
|
||||
window.dispatchEvent(mouseup)
|
||||
await nextTick()
|
||||
expect(panels[0].attributes('style')).toContain('flex-basis: 100px;')
|
||||
})
|
||||
|
||||
it('should update panel size immediately when lazy is false', async () => {
|
||||
const wrapper = mount(() => (
|
||||
<div style={{ width: '400px', height: '400px' }}>
|
||||
<ElSplitter>
|
||||
<ElSplitterPanel>Left Panel</ElSplitterPanel>
|
||||
<ElSplitterPanel>Right Panel</ElSplitterPanel>
|
||||
</ElSplitter>
|
||||
</div>
|
||||
))
|
||||
await nextTick()
|
||||
const panels = wrapper.findAll('.el-splitter-panel')
|
||||
const splitBar = wrapper.find('.el-splitter-bar__dragger')
|
||||
|
||||
const mousedown = new MouseEvent('mousedown', { bubbles: true })
|
||||
Object.defineProperty(mousedown, 'pageX', { value: 200 })
|
||||
splitBar.element.dispatchEvent(mousedown)
|
||||
|
||||
const mousemove = new MouseEvent('mousemove', { bubbles: true })
|
||||
Object.defineProperty(mousemove, 'pageX', { value: 100 })
|
||||
window.dispatchEvent(mousemove)
|
||||
await nextTick()
|
||||
expect(panels[0].attributes('style')).toContain('flex-basis: 100px;')
|
||||
|
||||
const mouseup = new MouseEvent('mouseup', { bubbles: true })
|
||||
Object.defineProperty(mouseup, 'pageX', { value: 100 })
|
||||
window.dispatchEvent(mouseup)
|
||||
await nextTick()
|
||||
expect(panels[0].attributes('style')).toContain('flex-basis: 100px;')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { getPct, getPx, isPct, isPx } from './useSize'
|
||||
import { NOOP } from '@element-plus/utils'
|
||||
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { PanelItemState } from '../type'
|
||||
@@ -7,7 +8,8 @@ import type { PanelItemState } from '../type'
|
||||
export function useResize(
|
||||
panels: Ref<PanelItemState[]>,
|
||||
containerSize: ComputedRef<number>,
|
||||
pxSizes: ComputedRef<number[]>
|
||||
pxSizes: ComputedRef<number[]>,
|
||||
lazy: Ref<boolean>
|
||||
) {
|
||||
const ptg2px = (ptg: number) => ptg * containerSize.value || 0
|
||||
|
||||
@@ -23,18 +25,28 @@ export function useResize(
|
||||
return str ?? defaultLimit
|
||||
}
|
||||
|
||||
const lazyOffset = ref(0)
|
||||
const movingIndex = ref<{
|
||||
index: number
|
||||
confirmed: boolean
|
||||
} | null>(null)
|
||||
|
||||
let cachePxSizes: number[] = []
|
||||
let updatePanelSizes = NOOP
|
||||
|
||||
const limitSizes = computed(() =>
|
||||
panels.value.map((item) => [item.min, item.max])
|
||||
)
|
||||
|
||||
watch(lazy, () => {
|
||||
if (lazyOffset.value) {
|
||||
const mouseup = new MouseEvent('mouseup', { bubbles: true })
|
||||
window.dispatchEvent(mouseup)
|
||||
}
|
||||
})
|
||||
|
||||
const onMoveStart = (index: number) => {
|
||||
lazyOffset.value = 0
|
||||
movingIndex.value = { index, confirmed: false }
|
||||
cachePxSizes = pxSizes.value
|
||||
}
|
||||
@@ -91,13 +103,26 @@ export function useResize(
|
||||
|
||||
numSizes[mergedIndex]! += mergedOffset
|
||||
numSizes[nextIndex]! -= mergedOffset
|
||||
lazyOffset.value = mergedOffset
|
||||
|
||||
panels.value.forEach((panel, index) => {
|
||||
panel.size = numSizes[index]
|
||||
})
|
||||
updatePanelSizes = () => {
|
||||
panels.value.forEach((panel, index) => {
|
||||
panel.size = numSizes[index]
|
||||
})
|
||||
updatePanelSizes = NOOP
|
||||
}
|
||||
|
||||
if (!lazy.value) {
|
||||
updatePanelSizes()
|
||||
}
|
||||
}
|
||||
|
||||
const onMoveEnd = () => {
|
||||
if (lazy.value) {
|
||||
updatePanelSizes()
|
||||
}
|
||||
|
||||
lazyOffset.value = 0
|
||||
movingIndex.value = null
|
||||
cachePxSizes = []
|
||||
}
|
||||
@@ -135,5 +160,12 @@ export function useResize(
|
||||
})
|
||||
}
|
||||
|
||||
return { onMoveStart, onMoving, onMoveEnd, movingIndex, onCollapse }
|
||||
return {
|
||||
lazyOffset,
|
||||
onMoveStart,
|
||||
onMoving,
|
||||
onMoveEnd,
|
||||
movingIndex,
|
||||
onCollapse,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,9 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
startCollapsible: {
|
||||
type: Boolean,
|
||||
},
|
||||
endCollapsible: {
|
||||
type: Boolean,
|
||||
},
|
||||
lazy: Boolean,
|
||||
startCollapsible: Boolean,
|
||||
endCollapsible: Boolean,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['moveStart', 'moving', 'moveEnd', 'collapse'])
|
||||
@@ -143,6 +140,7 @@ const EndIcon = computed(() => (isHorizontal.value ? ArrowRight : ArrowDown))
|
||||
ns.e('dragger'),
|
||||
draggerPseudoClass,
|
||||
resizable ? '' : ns.e('disable'),
|
||||
ns.is('lazy', resizable && lazy),
|
||||
]"
|
||||
:style="draggerStyles"
|
||||
@mousedown="onMousedown"
|
||||
|
||||
@@ -37,7 +37,7 @@ if (!splitterContext)
|
||||
'usage: <el-splitter><el-splitter-panel /></el-splitter/>'
|
||||
)
|
||||
|
||||
const { panels, layout, containerSize, pxSizes } = toRefs(splitterContext)
|
||||
const { panels, layout, lazy, containerSize, pxSizes } = toRefs(splitterContext)
|
||||
|
||||
const {
|
||||
registerPanel,
|
||||
@@ -179,6 +179,7 @@ onBeforeUnmount(() => unregisterPanel(_panel))
|
||||
v-if="isShowBar"
|
||||
:index="index"
|
||||
:layout="layout"
|
||||
:lazy="lazy"
|
||||
:resizable="isResizable"
|
||||
:start-collapsible="startCollapsible"
|
||||
:end-collapsible="endCollapsible"
|
||||
|
||||
@@ -9,6 +9,7 @@ export const splitterProps = buildProps({
|
||||
default: 'horizontal',
|
||||
values: ['horizontal', 'vertical'] as const,
|
||||
},
|
||||
lazy: Boolean,
|
||||
} as const)
|
||||
|
||||
export type SplitterProps = ExtractPropTypes<typeof splitterProps>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, provide, reactive, toRef, watch } from 'vue'
|
||||
import {
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
provide,
|
||||
reactive,
|
||||
toRef,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { useNamespace, useOrderedChildren } from '@element-plus/hooks'
|
||||
import { useContainer, useResize, useSize } from './hooks'
|
||||
import { splitterProps } from './splitter'
|
||||
@@ -20,6 +27,7 @@ const emits = defineEmits<{
|
||||
|
||||
const props = defineProps(splitterProps)
|
||||
const layout = toRef(props, 'layout')
|
||||
const lazy = toRef(props, 'lazy')
|
||||
|
||||
const { containerEl, containerSize } = useContainer(layout)
|
||||
|
||||
@@ -38,11 +46,22 @@ watch(panels, () => {
|
||||
|
||||
const { percentSizes, pxSizes } = useSize(panels, containerSize)
|
||||
|
||||
const { onMoveStart, onMoving, onMoveEnd, onCollapse, movingIndex } = useResize(
|
||||
panels,
|
||||
containerSize,
|
||||
pxSizes
|
||||
)
|
||||
const {
|
||||
lazyOffset,
|
||||
movingIndex,
|
||||
onMoveStart,
|
||||
onMoving,
|
||||
onMoveEnd,
|
||||
onCollapse,
|
||||
} = useResize(panels, containerSize, pxSizes, lazy)
|
||||
|
||||
const splitterStyles = computed(() => {
|
||||
return {
|
||||
[`--${ns.b()}-bar-offset`]: lazy.value
|
||||
? `${lazyOffset.value}px`
|
||||
: undefined,
|
||||
}
|
||||
})
|
||||
|
||||
const onResizeStart = (index: number) => {
|
||||
onMoveStart(index)
|
||||
@@ -51,7 +70,10 @@ const onResizeStart = (index: number) => {
|
||||
|
||||
const onResize = (index: number, offset: number) => {
|
||||
onMoving(index, offset)
|
||||
emits('resize', index, pxSizes.value)
|
||||
|
||||
if (!lazy.value) {
|
||||
emits('resize', index, pxSizes.value)
|
||||
}
|
||||
}
|
||||
|
||||
const onResizeEnd = (index: number) => {
|
||||
@@ -71,6 +93,7 @@ provide(
|
||||
percentSizes,
|
||||
pxSizes,
|
||||
layout,
|
||||
lazy,
|
||||
movingIndex,
|
||||
containerSize,
|
||||
onMoveStart: onResizeStart,
|
||||
@@ -84,7 +107,11 @@ provide(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="containerEl" :class="[ns.b(), ns.e(layout)]">
|
||||
<div
|
||||
ref="containerEl"
|
||||
:class="[ns.b(), ns.e(layout)]"
|
||||
:style="splitterStyles"
|
||||
>
|
||||
<slot />
|
||||
<panels-sorter />
|
||||
<!-- Prevent iframe touch events from breaking -->
|
||||
|
||||
@@ -17,6 +17,7 @@ export type PanelItemState = UnwrapRef<{
|
||||
export interface SplitterRootContext {
|
||||
panels: PanelItemState[]
|
||||
layout: Layout
|
||||
lazy: boolean
|
||||
containerSize: number
|
||||
movingIndex: { index: number; confirmed: boolean } | null
|
||||
percentSizes: number[]
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
&::before {
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -58,6 +59,15 @@
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: getCssVar('border-color-light');
|
||||
}
|
||||
|
||||
&:not(.is-lazy)::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background-color: getCssVar('color-primary-light-5');
|
||||
@@ -65,21 +75,32 @@
|
||||
}
|
||||
}
|
||||
@include e(dragger-horizontal) {
|
||||
&::before {
|
||||
&::before,
|
||||
&::after {
|
||||
height: 100%;
|
||||
width: 2px;
|
||||
}
|
||||
}
|
||||
@include e(dragger-vertical) {
|
||||
&::before {
|
||||
&::before,
|
||||
&::after {
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@include e(dragger-active) {
|
||||
&::before {
|
||||
&::before,
|
||||
&::after {
|
||||
background-color: getCssVar('color-primary-light-3');
|
||||
}
|
||||
|
||||
&.#{$namespace}-splitter-bar__dragger-horizontal::after {
|
||||
transform: translate(calc(-50% + getCssVar('splitter-bar-offset')), -50%);
|
||||
}
|
||||
|
||||
&.#{$namespace}-splitter-bar__dragger-vertical::after {
|
||||
transform: translate(-50%, calc(-50% + getCssVar('splitter-bar-offset')));
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@include e(collapse-icon) {
|
||||
|
||||
Reference in New Issue
Block a user