mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
refactor(components): [scrollbar] switch to script-setup syntax (#7986)
This commit is contained in:
@@ -1,19 +1,14 @@
|
||||
import { buildProps } from '@element-plus/utils'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type Bar from './bar.vue'
|
||||
|
||||
export const barProps = buildProps({
|
||||
always: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
width: String,
|
||||
height: String,
|
||||
ratioX: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
@@ -24,3 +19,5 @@ export const barProps = buildProps({
|
||||
},
|
||||
} as const)
|
||||
export type BarProps = ExtractPropTypes<typeof barProps>
|
||||
|
||||
export type BarInstance = InstanceType<typeof Bar>
|
||||
|
||||
@@ -8,36 +8,28 @@
|
||||
:always="always"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { GAP } from './util'
|
||||
import Thumb from './thumb.vue'
|
||||
import { barProps } from './bar'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Thumb,
|
||||
},
|
||||
props: barProps,
|
||||
setup(props) {
|
||||
const moveX = ref(0)
|
||||
const moveY = ref(0)
|
||||
const GAP = 4 // top 2 + bottom 2 of bar instance
|
||||
const props = defineProps(barProps)
|
||||
|
||||
const handleScroll = (wrap: HTMLDivElement) => {
|
||||
if (wrap) {
|
||||
const offsetHeight = wrap.offsetHeight - GAP
|
||||
const offsetWidth = wrap.offsetWidth - GAP
|
||||
const moveX = ref(0)
|
||||
const moveY = ref(0)
|
||||
|
||||
moveY.value = ((wrap.scrollTop * 100) / offsetHeight) * props.ratioY
|
||||
moveX.value = ((wrap.scrollLeft * 100) / offsetWidth) * props.ratioX
|
||||
}
|
||||
}
|
||||
const handleScroll = (wrap: HTMLDivElement) => {
|
||||
if (wrap) {
|
||||
const offsetHeight = wrap.offsetHeight - GAP
|
||||
const offsetWidth = wrap.offsetWidth - GAP
|
||||
|
||||
return {
|
||||
handleScroll,
|
||||
moveX,
|
||||
moveY,
|
||||
}
|
||||
},
|
||||
moveY.value = ((wrap.scrollTop * 100) / offsetHeight) * props.ratioY
|
||||
moveX.value = ((wrap.scrollLeft * 100) / offsetWidth) * props.ratioX
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleScroll,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { buildProps, definePropType, isNumber } from '@element-plus/utils'
|
||||
import type { ExtractPropTypes, StyleValue } from 'vue'
|
||||
import type Scrollbar from './scrollbar.vue'
|
||||
|
||||
export const scrollbarProps = buildProps({
|
||||
height: {
|
||||
@@ -10,10 +11,7 @@ export const scrollbarProps = buildProps({
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
native: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
native: Boolean,
|
||||
wrapStyle: {
|
||||
type: definePropType<StyleValue>([String, Object, Array]),
|
||||
default: '',
|
||||
@@ -35,16 +33,12 @@ export const scrollbarProps = buildProps({
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
always: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
always: Boolean,
|
||||
minSize: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
} as const)
|
||||
|
||||
export type ScrollbarProps = ExtractPropTypes<typeof scrollbarProps>
|
||||
|
||||
export const scrollbarEmits = {
|
||||
@@ -57,3 +51,5 @@ export const scrollbarEmits = {
|
||||
}) => isNumber(scrollTop) && isNumber(scrollLeft),
|
||||
}
|
||||
export type ScrollbarEmits = typeof scrollbarEmits
|
||||
|
||||
export type ScrollbarInstance = InstanceType<typeof Scrollbar>
|
||||
|
||||
@@ -31,10 +31,9 @@
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onUpdated,
|
||||
@@ -47,165 +46,152 @@ import { useEventListener, useResizeObserver } from '@vueuse/core'
|
||||
import { addUnit, debugWarn, isNumber, isObject } from '@element-plus/utils'
|
||||
import { scrollbarContextKey } from '@element-plus/tokens'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { GAP } from './util'
|
||||
import Bar from './bar.vue'
|
||||
|
||||
import { scrollbarEmits, scrollbarProps } from './scrollbar'
|
||||
import type { BarInstance } from './bar'
|
||||
import type { CSSProperties, StyleValue } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
defineOptions({
|
||||
name: 'ElScrollbar',
|
||||
components: {
|
||||
Bar,
|
||||
},
|
||||
props: scrollbarProps,
|
||||
emits: scrollbarEmits,
|
||||
})
|
||||
|
||||
setup(props, { emit }) {
|
||||
const ns = useNamespace('scrollbar')
|
||||
const props = defineProps(scrollbarProps)
|
||||
const emit = defineEmits(scrollbarEmits)
|
||||
|
||||
let stopResizeObserver: (() => void) | undefined = undefined
|
||||
let stopResizeListener: (() => void) | undefined = undefined
|
||||
const ns = useNamespace('scrollbar')
|
||||
|
||||
const scrollbar$ = ref<HTMLDivElement>()
|
||||
const wrap$ = ref<HTMLDivElement>()
|
||||
const resize$ = ref<HTMLElement>()
|
||||
let stopResizeObserver: (() => void) | undefined = undefined
|
||||
let stopResizeListener: (() => void) | undefined = undefined
|
||||
|
||||
const sizeWidth = ref('0')
|
||||
const sizeHeight = ref('0')
|
||||
const barRef = ref()
|
||||
const moveX = ref(0)
|
||||
const moveY = ref(0)
|
||||
const ratioY = ref(1)
|
||||
const ratioX = ref(1)
|
||||
const SCOPE = 'ElScrollbar'
|
||||
const GAP = 4 // top 2 + bottom 2 of bar instance
|
||||
const scrollbar$ = ref<HTMLDivElement>()
|
||||
const wrap$ = ref<HTMLDivElement>()
|
||||
const resize$ = ref<HTMLElement>()
|
||||
|
||||
const style = computed<StyleValue>(() => {
|
||||
const style: CSSProperties = {}
|
||||
if (props.height) style.height = addUnit(props.height)
|
||||
if (props.maxHeight) style.maxHeight = addUnit(props.maxHeight)
|
||||
return [props.wrapStyle, style]
|
||||
const sizeWidth = ref('0')
|
||||
const sizeHeight = ref('0')
|
||||
const barRef = ref<BarInstance>()
|
||||
const ratioY = ref(1)
|
||||
const ratioX = ref(1)
|
||||
const SCOPE = 'ElScrollbar'
|
||||
|
||||
const style = computed<StyleValue>(() => {
|
||||
const style: CSSProperties = {}
|
||||
if (props.height) style.height = addUnit(props.height)
|
||||
if (props.maxHeight) style.maxHeight = addUnit(props.maxHeight)
|
||||
return [props.wrapStyle, style]
|
||||
})
|
||||
|
||||
const handleScroll = () => {
|
||||
if (wrap$.value) {
|
||||
barRef.value?.handleScroll(wrap$.value)
|
||||
|
||||
emit('scroll', {
|
||||
scrollTop: wrap$.value.scrollTop,
|
||||
scrollLeft: wrap$.value.scrollLeft,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
if (wrap$.value) {
|
||||
barRef.value?.handleScroll(wrap$.value)
|
||||
function scrollTo(xCord: number, yCord?: number): void
|
||||
function scrollTo(options: ScrollToOptions): void
|
||||
function scrollTo(arg1: unknown, arg2?: number) {
|
||||
if (isObject(arg1)) {
|
||||
wrap$.value!.scrollTo(arg1)
|
||||
} else if (isNumber(arg1) && isNumber(arg2)) {
|
||||
wrap$.value!.scrollTo(arg1, arg2)
|
||||
}
|
||||
}
|
||||
|
||||
emit('scroll', {
|
||||
scrollTop: wrap$.value.scrollTop,
|
||||
scrollLeft: wrap$.value.scrollLeft,
|
||||
})
|
||||
}
|
||||
const setScrollTop = (value: number) => {
|
||||
if (!isNumber(value)) {
|
||||
debugWarn(SCOPE, 'value must be a number')
|
||||
return
|
||||
}
|
||||
wrap$.value!.scrollTop = value
|
||||
}
|
||||
|
||||
const setScrollLeft = (value: number) => {
|
||||
if (!isNumber(value)) {
|
||||
debugWarn(SCOPE, 'value must be a number')
|
||||
return
|
||||
}
|
||||
wrap$.value!.scrollLeft = value
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
if (!wrap$.value) return
|
||||
const offsetHeight = wrap$.value.offsetHeight - GAP
|
||||
const offsetWidth = wrap$.value.offsetWidth - GAP
|
||||
|
||||
const originalHeight = offsetHeight ** 2 / wrap$.value.scrollHeight
|
||||
const originalWidth = offsetWidth ** 2 / wrap$.value.scrollWidth
|
||||
const height = Math.max(originalHeight, props.minSize)
|
||||
const width = Math.max(originalWidth, props.minSize)
|
||||
|
||||
ratioY.value =
|
||||
originalHeight /
|
||||
(offsetHeight - originalHeight) /
|
||||
(height / (offsetHeight - height))
|
||||
ratioX.value =
|
||||
originalWidth /
|
||||
(offsetWidth - originalWidth) /
|
||||
(width / (offsetWidth - width))
|
||||
|
||||
sizeHeight.value = height + GAP < offsetHeight ? `${height}px` : ''
|
||||
sizeWidth.value = width + GAP < offsetWidth ? `${width}px` : ''
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.noresize,
|
||||
(noresize) => {
|
||||
if (noresize) {
|
||||
stopResizeObserver?.()
|
||||
stopResizeListener?.()
|
||||
} else {
|
||||
;({ stop: stopResizeObserver } = useResizeObserver(resize$, update))
|
||||
stopResizeListener = useEventListener('resize', update)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function scrollTo(xCord: number, yCord?: number): void
|
||||
function scrollTo(options: ScrollToOptions): void
|
||||
function scrollTo(arg1: unknown, arg2?: number) {
|
||||
if (isObject(arg1)) {
|
||||
wrap$.value!.scrollTo(arg1)
|
||||
} else if (isNumber(arg1) && isNumber(arg2)) {
|
||||
wrap$.value!.scrollTo(arg1, arg2)
|
||||
}
|
||||
}
|
||||
|
||||
const setScrollTop = (value: number) => {
|
||||
if (!isNumber(value)) {
|
||||
debugWarn(SCOPE, 'value must be a number')
|
||||
return
|
||||
}
|
||||
wrap$.value!.scrollTop = value
|
||||
}
|
||||
|
||||
const setScrollLeft = (value: number) => {
|
||||
if (!isNumber(value)) {
|
||||
debugWarn(SCOPE, 'value must be a number')
|
||||
return
|
||||
}
|
||||
wrap$.value!.scrollLeft = value
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
if (!wrap$.value) return
|
||||
const offsetHeight = wrap$.value.offsetHeight - GAP
|
||||
const offsetWidth = wrap$.value.offsetWidth - GAP
|
||||
|
||||
const originalHeight = offsetHeight ** 2 / wrap$.value.scrollHeight
|
||||
const originalWidth = offsetWidth ** 2 / wrap$.value.scrollWidth
|
||||
const height = Math.max(originalHeight, props.minSize)
|
||||
const width = Math.max(originalWidth, props.minSize)
|
||||
|
||||
ratioY.value =
|
||||
originalHeight /
|
||||
(offsetHeight - originalHeight) /
|
||||
(height / (offsetHeight - height))
|
||||
ratioX.value =
|
||||
originalWidth /
|
||||
(offsetWidth - originalWidth) /
|
||||
(width / (offsetWidth - width))
|
||||
|
||||
sizeHeight.value = height + GAP < offsetHeight ? `${height}px` : ''
|
||||
sizeWidth.value = width + GAP < offsetWidth ? `${width}px` : ''
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.noresize,
|
||||
(noresize) => {
|
||||
if (noresize) {
|
||||
stopResizeObserver?.()
|
||||
stopResizeListener?.()
|
||||
} else {
|
||||
;({ stop: stopResizeObserver } = useResizeObserver(resize$, update))
|
||||
stopResizeListener = useEventListener('resize', update)
|
||||
watch(
|
||||
() => [props.maxHeight, props.height],
|
||||
() => {
|
||||
if (!props.native)
|
||||
nextTick(() => {
|
||||
update()
|
||||
if (wrap$.value) {
|
||||
barRef.value?.handleScroll(wrap$.value)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [props.maxHeight, props.height],
|
||||
() => {
|
||||
if (!props.native)
|
||||
nextTick(() => {
|
||||
update()
|
||||
if (wrap$.value) {
|
||||
barRef.value?.handleScroll(wrap$.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
provide(
|
||||
scrollbarContextKey,
|
||||
reactive({
|
||||
scrollbarElement: scrollbar$,
|
||||
wrapElement: wrap$,
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (!props.native) nextTick(() => update())
|
||||
})
|
||||
onUpdated(() => update())
|
||||
provide(
|
||||
scrollbarContextKey,
|
||||
reactive({
|
||||
scrollbarElement: scrollbar$,
|
||||
wrapElement: wrap$,
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
ns,
|
||||
scrollbar$,
|
||||
wrap$,
|
||||
resize$,
|
||||
barRef,
|
||||
moveX,
|
||||
moveY,
|
||||
ratioX,
|
||||
ratioY,
|
||||
sizeWidth,
|
||||
sizeHeight,
|
||||
style,
|
||||
update,
|
||||
handleScroll,
|
||||
scrollTo,
|
||||
setScrollTop,
|
||||
setScrollLeft,
|
||||
}
|
||||
},
|
||||
onMounted(() => {
|
||||
if (!props.native) nextTick(() => update())
|
||||
})
|
||||
onUpdated(() => update())
|
||||
|
||||
defineExpose({
|
||||
/** @description update scrollbar state manually */
|
||||
update,
|
||||
/** @description scrolls to a particular set of coordinates */
|
||||
scrollTo,
|
||||
/** @description set distance to scroll top */
|
||||
setScrollTop,
|
||||
/** @description set distance to scroll left */
|
||||
setScrollLeft,
|
||||
/** @description handle scroll event */
|
||||
handleScroll,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { buildProps } from '@element-plus/utils'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type Thumb from './thumb.vue'
|
||||
|
||||
export const thumbProps = buildProps({
|
||||
vertical: Boolean,
|
||||
@@ -12,3 +13,5 @@ export const thumbProps = buildProps({
|
||||
always: Boolean,
|
||||
} as const)
|
||||
export type ThumbProps = ExtractPropTypes<typeof thumbProps>
|
||||
|
||||
export type ThumbInstance = InstanceType<typeof Thumb>
|
||||
|
||||
@@ -16,183 +16,154 @@
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
inject,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
toRef,
|
||||
} from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, onBeforeUnmount, ref, toRef } from 'vue'
|
||||
import { isClient, useEventListener } from '@vueuse/core'
|
||||
import { scrollbarContextKey } from '@element-plus/tokens'
|
||||
import { throwError } from '@element-plus/utils'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { BAR_MAP, renderThumbStyle } from './util'
|
||||
|
||||
import { thumbProps } from './thumb'
|
||||
|
||||
const COMPONENT_NAME = 'Thumb'
|
||||
export default defineComponent({
|
||||
name: COMPONENT_NAME,
|
||||
props: thumbProps,
|
||||
const props = defineProps(thumbProps)
|
||||
|
||||
setup(props) {
|
||||
const scrollbar = inject(scrollbarContextKey)
|
||||
const ns = useNamespace('scrollbar')
|
||||
const scrollbar = inject(scrollbarContextKey)
|
||||
const ns = useNamespace('scrollbar')
|
||||
|
||||
if (!scrollbar)
|
||||
throwError(COMPONENT_NAME, 'can not inject scrollbar context')
|
||||
if (!scrollbar) throwError(COMPONENT_NAME, 'can not inject scrollbar context')
|
||||
|
||||
const instance = ref<HTMLDivElement>()
|
||||
const thumb = ref<HTMLDivElement>()
|
||||
const instance = ref<HTMLDivElement>()
|
||||
const thumb = ref<HTMLDivElement>()
|
||||
|
||||
const thumbState = ref({})
|
||||
const visible = ref(false)
|
||||
const thumbState = ref<Partial<Record<'X' | 'Y', number>>>({})
|
||||
const visible = ref(false)
|
||||
|
||||
let cursorDown = false
|
||||
let cursorLeave = false
|
||||
let originalOnSelectStart:
|
||||
| ((this: GlobalEventHandlers, ev: Event) => any)
|
||||
| null = isClient ? document.onselectstart : null
|
||||
let cursorDown = false
|
||||
let cursorLeave = false
|
||||
let originalOnSelectStart:
|
||||
| ((this: GlobalEventHandlers, ev: Event) => any)
|
||||
| null = isClient ? document.onselectstart : null
|
||||
|
||||
const bar = computed(
|
||||
() => BAR_MAP[props.vertical ? 'vertical' : 'horizontal']
|
||||
)
|
||||
const bar = computed(() => BAR_MAP[props.vertical ? 'vertical' : 'horizontal'])
|
||||
|
||||
const thumbStyle = computed(() =>
|
||||
renderThumbStyle({
|
||||
size: props.size,
|
||||
move: props.move,
|
||||
bar: bar.value,
|
||||
})
|
||||
)
|
||||
const thumbStyle = computed(() =>
|
||||
renderThumbStyle({
|
||||
size: props.size,
|
||||
move: props.move,
|
||||
bar: bar.value,
|
||||
})
|
||||
)
|
||||
|
||||
const offsetRatio = computed(
|
||||
() =>
|
||||
// offsetRatioX = original width of thumb / current width of thumb / ratioX
|
||||
// offsetRatioY = original height of thumb / current height of thumb / ratioY
|
||||
// instance height = wrap height - GAP
|
||||
instance.value![bar.value.offset] ** 2 /
|
||||
scrollbar.wrapElement![bar.value.scrollSize] /
|
||||
props.ratio /
|
||||
thumb.value![bar.value.offset]
|
||||
)
|
||||
const offsetRatio = computed(
|
||||
() =>
|
||||
// offsetRatioX = original width of thumb / current width of thumb / ratioX
|
||||
// offsetRatioY = original height of thumb / current height of thumb / ratioY
|
||||
// instance height = wrap height - GAP
|
||||
instance.value![bar.value.offset] ** 2 /
|
||||
scrollbar.wrapElement![bar.value.scrollSize] /
|
||||
props.ratio /
|
||||
thumb.value![bar.value.offset]
|
||||
)
|
||||
|
||||
const clickThumbHandler = (e: MouseEvent) => {
|
||||
// prevent click event of middle and right button
|
||||
e.stopPropagation()
|
||||
if (e.ctrlKey || [1, 2].includes(e.button)) return
|
||||
const clickThumbHandler = (e: MouseEvent) => {
|
||||
// prevent click event of middle and right button
|
||||
e.stopPropagation()
|
||||
if (e.ctrlKey || [1, 2].includes(e.button)) return
|
||||
|
||||
window.getSelection()?.removeAllRanges()
|
||||
startDrag(e)
|
||||
window.getSelection()?.removeAllRanges()
|
||||
startDrag(e)
|
||||
|
||||
const el = e.currentTarget as HTMLDivElement
|
||||
if (!el) return
|
||||
thumbState.value[bar.value.axis] =
|
||||
el[bar.value.offset] -
|
||||
(e[bar.value.client] - el.getBoundingClientRect()[bar.value.direction])
|
||||
}
|
||||
const el = e.currentTarget as HTMLDivElement
|
||||
if (!el) return
|
||||
thumbState.value[bar.value.axis] =
|
||||
el[bar.value.offset] -
|
||||
(e[bar.value.client] - el.getBoundingClientRect()[bar.value.direction])
|
||||
}
|
||||
|
||||
const clickTrackHandler = (e: MouseEvent) => {
|
||||
if (!thumb.value || !instance.value || !scrollbar.wrapElement) return
|
||||
const clickTrackHandler = (e: MouseEvent) => {
|
||||
if (!thumb.value || !instance.value || !scrollbar.wrapElement) return
|
||||
|
||||
const offset = Math.abs(
|
||||
(e.target as HTMLElement).getBoundingClientRect()[bar.value.direction] -
|
||||
e[bar.value.client]
|
||||
)
|
||||
const thumbHalf = thumb.value[bar.value.offset] / 2
|
||||
const thumbPositionPercentage =
|
||||
((offset - thumbHalf) * 100 * offsetRatio.value) /
|
||||
instance.value[bar.value.offset]
|
||||
const offset = Math.abs(
|
||||
(e.target as HTMLElement).getBoundingClientRect()[bar.value.direction] -
|
||||
e[bar.value.client]
|
||||
)
|
||||
const thumbHalf = thumb.value[bar.value.offset] / 2
|
||||
const thumbPositionPercentage =
|
||||
((offset - thumbHalf) * 100 * offsetRatio.value) /
|
||||
instance.value[bar.value.offset]
|
||||
|
||||
scrollbar.wrapElement[bar.value.scroll] =
|
||||
(thumbPositionPercentage *
|
||||
scrollbar.wrapElement[bar.value.scrollSize]) /
|
||||
100
|
||||
}
|
||||
scrollbar.wrapElement[bar.value.scroll] =
|
||||
(thumbPositionPercentage * scrollbar.wrapElement[bar.value.scrollSize]) /
|
||||
100
|
||||
}
|
||||
|
||||
const startDrag = (e: MouseEvent) => {
|
||||
e.stopImmediatePropagation()
|
||||
cursorDown = true
|
||||
document.addEventListener('mousemove', mouseMoveDocumentHandler)
|
||||
document.addEventListener('mouseup', mouseUpDocumentHandler)
|
||||
originalOnSelectStart = document.onselectstart
|
||||
document.onselectstart = () => false
|
||||
}
|
||||
const startDrag = (e: MouseEvent) => {
|
||||
e.stopImmediatePropagation()
|
||||
cursorDown = true
|
||||
document.addEventListener('mousemove', mouseMoveDocumentHandler)
|
||||
document.addEventListener('mouseup', mouseUpDocumentHandler)
|
||||
originalOnSelectStart = document.onselectstart
|
||||
document.onselectstart = () => false
|
||||
}
|
||||
|
||||
const mouseMoveDocumentHandler = (e: MouseEvent) => {
|
||||
if (!instance.value || !thumb.value) return
|
||||
if (cursorDown === false) return
|
||||
const mouseMoveDocumentHandler = (e: MouseEvent) => {
|
||||
if (!instance.value || !thumb.value) return
|
||||
if (cursorDown === false) return
|
||||
|
||||
const prevPage = thumbState.value[bar.value.axis]
|
||||
if (!prevPage) return
|
||||
const prevPage = thumbState.value[bar.value.axis]
|
||||
if (!prevPage) return
|
||||
|
||||
const offset =
|
||||
(instance.value.getBoundingClientRect()[bar.value.direction] -
|
||||
e[bar.value.client]) *
|
||||
-1
|
||||
const thumbClickPosition = thumb.value[bar.value.offset] - prevPage
|
||||
const thumbPositionPercentage =
|
||||
((offset - thumbClickPosition) * 100 * offsetRatio.value) /
|
||||
instance.value[bar.value.offset]
|
||||
scrollbar.wrapElement[bar.value.scroll] =
|
||||
(thumbPositionPercentage *
|
||||
scrollbar.wrapElement[bar.value.scrollSize]) /
|
||||
100
|
||||
}
|
||||
const offset =
|
||||
(instance.value.getBoundingClientRect()[bar.value.direction] -
|
||||
e[bar.value.client]) *
|
||||
-1
|
||||
const thumbClickPosition = thumb.value[bar.value.offset] - prevPage
|
||||
const thumbPositionPercentage =
|
||||
((offset - thumbClickPosition) * 100 * offsetRatio.value) /
|
||||
instance.value[bar.value.offset]
|
||||
scrollbar.wrapElement[bar.value.scroll] =
|
||||
(thumbPositionPercentage * scrollbar.wrapElement[bar.value.scrollSize]) /
|
||||
100
|
||||
}
|
||||
|
||||
const mouseUpDocumentHandler = () => {
|
||||
cursorDown = false
|
||||
thumbState.value[bar.value.axis] = 0
|
||||
document.removeEventListener('mousemove', mouseMoveDocumentHandler)
|
||||
document.removeEventListener('mouseup', mouseUpDocumentHandler)
|
||||
restoreOnselectstart()
|
||||
if (cursorLeave) visible.value = false
|
||||
}
|
||||
const mouseUpDocumentHandler = () => {
|
||||
cursorDown = false
|
||||
thumbState.value[bar.value.axis] = 0
|
||||
document.removeEventListener('mousemove', mouseMoveDocumentHandler)
|
||||
document.removeEventListener('mouseup', mouseUpDocumentHandler)
|
||||
restoreOnselectstart()
|
||||
if (cursorLeave) visible.value = false
|
||||
}
|
||||
|
||||
const mouseMoveScrollbarHandler = () => {
|
||||
cursorLeave = false
|
||||
visible.value = !!props.size
|
||||
}
|
||||
const mouseMoveScrollbarHandler = () => {
|
||||
cursorLeave = false
|
||||
visible.value = !!props.size
|
||||
}
|
||||
|
||||
const mouseLeaveScrollbarHandler = () => {
|
||||
cursorLeave = true
|
||||
visible.value = cursorDown
|
||||
}
|
||||
const mouseLeaveScrollbarHandler = () => {
|
||||
cursorLeave = true
|
||||
visible.value = cursorDown
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
restoreOnselectstart()
|
||||
document.removeEventListener('mouseup', mouseUpDocumentHandler)
|
||||
})
|
||||
|
||||
const restoreOnselectstart = () => {
|
||||
if (document.onselectstart !== originalOnSelectStart)
|
||||
document.onselectstart = originalOnSelectStart
|
||||
}
|
||||
|
||||
useEventListener(
|
||||
toRef(scrollbar, 'scrollbarElement'),
|
||||
'mousemove',
|
||||
mouseMoveScrollbarHandler
|
||||
)
|
||||
useEventListener(
|
||||
toRef(scrollbar, 'scrollbarElement'),
|
||||
'mouseleave',
|
||||
mouseLeaveScrollbarHandler
|
||||
)
|
||||
|
||||
return {
|
||||
ns,
|
||||
instance,
|
||||
thumb,
|
||||
bar,
|
||||
thumbStyle,
|
||||
visible,
|
||||
clickTrackHandler,
|
||||
clickThumbHandler,
|
||||
}
|
||||
},
|
||||
onBeforeUnmount(() => {
|
||||
restoreOnselectstart()
|
||||
document.removeEventListener('mouseup', mouseUpDocumentHandler)
|
||||
})
|
||||
|
||||
const restoreOnselectstart = () => {
|
||||
if (document.onselectstart !== originalOnSelectStart)
|
||||
document.onselectstart = originalOnSelectStart
|
||||
}
|
||||
|
||||
useEventListener(
|
||||
toRef(scrollbar, 'scrollbarElement'),
|
||||
'mousemove',
|
||||
mouseMoveScrollbarHandler
|
||||
)
|
||||
useEventListener(
|
||||
toRef(scrollbar, 'scrollbarElement'),
|
||||
'mouseleave',
|
||||
mouseLeaveScrollbarHandler
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { ThumbProps } from './thumb'
|
||||
|
||||
export const GAP = 4 // top 2 + bottom 2 of bar instance
|
||||
|
||||
export const BAR_MAP = {
|
||||
vertical: {
|
||||
@@ -23,7 +26,13 @@ export const BAR_MAP = {
|
||||
},
|
||||
} as const
|
||||
|
||||
export const renderThumbStyle = ({ move, size, bar }): CSSProperties => ({
|
||||
export const renderThumbStyle = ({
|
||||
move,
|
||||
size,
|
||||
bar,
|
||||
}: Pick<ThumbProps, 'move' | 'size'> & {
|
||||
bar: typeof BAR_MAP[keyof typeof BAR_MAP]
|
||||
}): CSSProperties => ({
|
||||
[bar.size]: size,
|
||||
transform: `translate${bar.axis}(${move}%)`,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { InjectionKey } from 'vue'
|
||||
|
||||
export interface scrollbarContext {
|
||||
export interface ScrollbarContext {
|
||||
scrollbarElement: HTMLDivElement
|
||||
wrapElement: HTMLDivElement
|
||||
}
|
||||
|
||||
export const scrollbarContextKey: InjectionKey<scrollbarContext> = Symbol(
|
||||
export const scrollbarContextKey: InjectionKey<ScrollbarContext> = Symbol(
|
||||
'scrollbarContextKey'
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user