mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-24 12:45:18 +08:00
refactor(scrollbar): ts
This commit is contained in:
parent
9e28bb7fd8
commit
9e095c1a6a
@ -1,2 +0,0 @@
|
|||||||
/* istanbul ignore file */
|
|
||||||
export { default as NScrollbar } from './src/ScrollBar.vue'
|
|
3
src/scrollbar/index.ts
Normal file
3
src/scrollbar/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
export { default as NScrollbar } from './src/ScrollBar'
|
||||||
|
export type { ScrollbarRef } from './src/ScrollBar'
|
685
src/scrollbar/src/ScrollBar.tsx
Normal file
685
src/scrollbar/src/ScrollBar.tsx
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
import {
|
||||||
|
h,
|
||||||
|
ref,
|
||||||
|
defineComponent,
|
||||||
|
computed,
|
||||||
|
PropType,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
mergeProps,
|
||||||
|
renderSlot,
|
||||||
|
Transition,
|
||||||
|
VNode
|
||||||
|
} from 'vue'
|
||||||
|
import { on, off } from 'evtd'
|
||||||
|
import { VResizeObserver } from 'vueuc'
|
||||||
|
import { useIsIos } from 'vooks'
|
||||||
|
import { useTheme } from '../../_mixins'
|
||||||
|
import type { ThemeProps } from '../../_mixins'
|
||||||
|
import { scrollbarLight } from '../styles'
|
||||||
|
import type { ScrollbarTheme } from '../styles'
|
||||||
|
import style from './styles/index.cssr'
|
||||||
|
|
||||||
|
interface ScrollTo {
|
||||||
|
(x: number, y: number): void
|
||||||
|
(
|
||||||
|
x: {
|
||||||
|
left?: number
|
||||||
|
top?: number
|
||||||
|
behavior?: ScrollBehavior
|
||||||
|
debounce?: boolean
|
||||||
|
},
|
||||||
|
y: number
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
x: {
|
||||||
|
el: HTMLElement
|
||||||
|
behavior?: ScrollBehavior
|
||||||
|
debounce?: boolean
|
||||||
|
},
|
||||||
|
y: number
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
x: {
|
||||||
|
index: number
|
||||||
|
elSize: number
|
||||||
|
behavior?: ScrollBehavior
|
||||||
|
debounce?: boolean
|
||||||
|
},
|
||||||
|
y: number
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
x: {
|
||||||
|
position: 'top' | 'bottom'
|
||||||
|
behavior?: ScrollBehavior
|
||||||
|
debounce?: boolean
|
||||||
|
},
|
||||||
|
y: number
|
||||||
|
): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollbarRef {
|
||||||
|
scrollTo: ScrollTo
|
||||||
|
sync: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Scrollbar',
|
||||||
|
props: {
|
||||||
|
...(useTheme.props as ThemeProps<ScrollbarTheme>),
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
scrollable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
xScrollable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
type: Function as PropType<undefined | (() => HTMLElement)>,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: Function as PropType<undefined | (() => HTMLElement)>,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
containerStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
contentClass: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
contentStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
horizontalRailStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
verticalRailStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
onScroll: {
|
||||||
|
type: Function as PropType<((e: Event) => void) | undefined>,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup (props) {
|
||||||
|
// dom ref
|
||||||
|
const containerRef = ref<HTMLElement | null>(null)
|
||||||
|
const contentRef = ref<HTMLElement | null>(null)
|
||||||
|
const yRailRef = ref<HTMLElement | null>(null)
|
||||||
|
const xRailRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
// data ref
|
||||||
|
const contentHeightRef = ref<number | null>(null)
|
||||||
|
const contentWidthRef = ref<number | null>(null)
|
||||||
|
const containerHeightRef = ref<number | null>(null)
|
||||||
|
const containerWidthRef = ref<number | null>(null)
|
||||||
|
const yRailSizeRef = ref<number | null>(null)
|
||||||
|
const xRailSizeRef = ref<number | null>(null)
|
||||||
|
const containerScrollTopRef = ref<number | null>(null)
|
||||||
|
const containerScrollLeftRef = ref<number | null>(null)
|
||||||
|
const isShowXBarRef = ref(false)
|
||||||
|
const isShowYBarRef = ref(false)
|
||||||
|
|
||||||
|
let yBarPressed = false
|
||||||
|
let xBarPressed = false
|
||||||
|
let xBarVanishTimerId: number | undefined
|
||||||
|
let yBarVanishTimerId: number | undefined
|
||||||
|
let memoYTop: number = 0
|
||||||
|
let memoXLeft: number = 0
|
||||||
|
let memoMouseX: number = 0
|
||||||
|
let memoMouseY: number = 0
|
||||||
|
const isIos = useIsIos()
|
||||||
|
|
||||||
|
const yBarSizeRef = computed(() => {
|
||||||
|
const { value: containerHeight } = containerHeightRef
|
||||||
|
const { value: contentHeight } = contentHeightRef
|
||||||
|
const { value: yRailSize } = yRailSizeRef
|
||||||
|
if (
|
||||||
|
containerHeight === null ||
|
||||||
|
contentHeight === null ||
|
||||||
|
yRailSize === null
|
||||||
|
) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return Math.min(
|
||||||
|
containerHeight,
|
||||||
|
(yRailSize * containerHeight) / contentHeight + props.size * 1.5
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const yBarSizePxRef = computed(() => {
|
||||||
|
return `${yBarSizeRef.value}px`
|
||||||
|
})
|
||||||
|
const xBarSizeRef = computed(() => {
|
||||||
|
const { value: containerWidth } = containerWidthRef
|
||||||
|
const { value: contentWidth } = contentWidthRef
|
||||||
|
const { value: xRailSize } = xRailSizeRef
|
||||||
|
if (
|
||||||
|
containerWidth === null ||
|
||||||
|
contentWidth === null ||
|
||||||
|
xRailSize === null
|
||||||
|
) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return (xRailSize * containerWidth) / contentWidth + props.size * 1.5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const xBarSizePxRef = computed(() => {
|
||||||
|
return `${xBarSizeRef.value}px`
|
||||||
|
})
|
||||||
|
const yBarTopRef = computed(() => {
|
||||||
|
const { value: containerHeight } = containerHeightRef
|
||||||
|
const { value: containerScrollTop } = containerScrollTopRef
|
||||||
|
const { value: contentHeight } = contentHeightRef
|
||||||
|
const { value: yRailSize } = yRailSizeRef
|
||||||
|
if (
|
||||||
|
containerHeight === null ||
|
||||||
|
containerScrollTop === null ||
|
||||||
|
contentHeight === null ||
|
||||||
|
yRailSize === null
|
||||||
|
) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
(containerScrollTop / (contentHeight - containerHeight)) *
|
||||||
|
(yRailSize - yBarSizeRef.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const yBarTopPxRef = computed(() => {
|
||||||
|
return `${yBarTopRef.value}px`
|
||||||
|
})
|
||||||
|
const xBarLeftRef = computed(() => {
|
||||||
|
const { value: containerWidth } = containerWidthRef
|
||||||
|
const { value: containerScrollLeft } = containerScrollLeftRef
|
||||||
|
const { value: contentWidth } = contentWidthRef
|
||||||
|
const { value: xRailSize } = xRailSizeRef
|
||||||
|
if (
|
||||||
|
containerWidth === null ||
|
||||||
|
containerScrollLeft === null ||
|
||||||
|
contentWidth === null ||
|
||||||
|
xRailSize === null
|
||||||
|
) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
(containerScrollLeft / (contentWidth - containerWidth)) *
|
||||||
|
(xRailSize - xBarSizeRef.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const xBarLeftPxRef = computed(() => {
|
||||||
|
return `${xBarLeftRef.value}px`
|
||||||
|
})
|
||||||
|
const sizePxRef = computed(() => {
|
||||||
|
return `${props.size}px`
|
||||||
|
})
|
||||||
|
const needYBarRef = computed(() => {
|
||||||
|
const { value: containerHeight } = containerHeightRef
|
||||||
|
const { value: contentHeight } = contentHeightRef
|
||||||
|
return (
|
||||||
|
containerHeight !== null &&
|
||||||
|
contentHeight !== null &&
|
||||||
|
contentHeight > containerHeight
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const needXBarRef = computed(() => {
|
||||||
|
const { value: containerWidth } = containerWidthRef
|
||||||
|
const { value: contentWidth } = contentWidthRef
|
||||||
|
return (
|
||||||
|
containerWidth !== null &&
|
||||||
|
contentWidth !== null &&
|
||||||
|
contentWidth > containerWidth
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const mergedContainerRef = computed(() => {
|
||||||
|
const { container } = props
|
||||||
|
if (container) return container()
|
||||||
|
return containerRef.value
|
||||||
|
})
|
||||||
|
const mergedContentRef = computed(() => {
|
||||||
|
const { content } = props
|
||||||
|
if (content) return content()
|
||||||
|
return contentRef.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// methods
|
||||||
|
function handleContentResize (): void {
|
||||||
|
sync()
|
||||||
|
}
|
||||||
|
interface MergedScrollOptions {
|
||||||
|
left?: number
|
||||||
|
top?: number
|
||||||
|
el?: HTMLElement
|
||||||
|
position?: 'top' | 'bottom'
|
||||||
|
behavior?: ScrollBehavior
|
||||||
|
debounce?: boolean
|
||||||
|
index?: number
|
||||||
|
elSize?: number
|
||||||
|
}
|
||||||
|
const scrollTo: ScrollTo = (
|
||||||
|
options: MergedScrollOptions | number,
|
||||||
|
y?: number
|
||||||
|
): void => {
|
||||||
|
if (!props.scrollable) return
|
||||||
|
if (typeof options === 'number') {
|
||||||
|
scrollToPosition(options, y ?? 0, 0, false, 'auto')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
index,
|
||||||
|
elSize,
|
||||||
|
position,
|
||||||
|
behavior,
|
||||||
|
el,
|
||||||
|
debounce = true
|
||||||
|
} = options
|
||||||
|
if (left !== undefined || top !== undefined) {
|
||||||
|
scrollToPosition(left ?? 0, top ?? 0, 0, false, behavior)
|
||||||
|
}
|
||||||
|
if (el !== undefined) {
|
||||||
|
scrollToPosition(0, el.offsetTop, el.offsetHeight, debounce, behavior)
|
||||||
|
} else if (index !== undefined && elSize !== undefined) {
|
||||||
|
scrollToPosition(0, index * elSize, elSize, debounce, behavior)
|
||||||
|
} else if (position === 'bottom') {
|
||||||
|
scrollToPosition(0, Number.MAX_SAFE_INTEGER, 0, false, behavior)
|
||||||
|
} else if (position === 'top') {
|
||||||
|
scrollToPosition(0, 0, 0, false, behavior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function scrollToPosition (
|
||||||
|
left: number,
|
||||||
|
top: number,
|
||||||
|
elSize: number,
|
||||||
|
debounce: boolean,
|
||||||
|
behavior?: ScrollBehavior
|
||||||
|
): void {
|
||||||
|
const { value: container } = mergedContainerRef
|
||||||
|
if (!container) return
|
||||||
|
if (debounce) {
|
||||||
|
const { scrollTop, offsetHeight } = container
|
||||||
|
if (top > scrollTop) {
|
||||||
|
if (top + elSize <= scrollTop + offsetHeight) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
container.scrollTo({
|
||||||
|
left,
|
||||||
|
top: top + elSize - offsetHeight,
|
||||||
|
behavior
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.scrollTo({
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
behavior
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleMouseEnterWrapper (): void {
|
||||||
|
showXBar()
|
||||||
|
showYBar()
|
||||||
|
sync()
|
||||||
|
}
|
||||||
|
function handleMouseLeaveWrapper (): void {
|
||||||
|
hideBar()
|
||||||
|
}
|
||||||
|
function hideBar (): void {
|
||||||
|
hideYBar()
|
||||||
|
hideXBar()
|
||||||
|
}
|
||||||
|
function hideYBar (): void {
|
||||||
|
if (yBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(yBarVanishTimerId)
|
||||||
|
}
|
||||||
|
yBarVanishTimerId = window.setTimeout(() => {
|
||||||
|
isShowYBarRef.value = false
|
||||||
|
}, props.duration)
|
||||||
|
}
|
||||||
|
function hideXBar (): void {
|
||||||
|
if (xBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(xBarVanishTimerId)
|
||||||
|
}
|
||||||
|
xBarVanishTimerId = window.setTimeout(() => {
|
||||||
|
isShowXBarRef.value = false
|
||||||
|
}, props.duration)
|
||||||
|
}
|
||||||
|
function showXBar (): void {
|
||||||
|
if (xBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(xBarVanishTimerId)
|
||||||
|
}
|
||||||
|
isShowXBarRef.value = true
|
||||||
|
}
|
||||||
|
function showYBar (): void {
|
||||||
|
if (yBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(yBarVanishTimerId)
|
||||||
|
}
|
||||||
|
isShowYBarRef.value = true
|
||||||
|
}
|
||||||
|
function handleScroll (e: Event): void {
|
||||||
|
const { onScroll } = props
|
||||||
|
if (onScroll) onScroll(e)
|
||||||
|
syncScrollState()
|
||||||
|
}
|
||||||
|
function syncScrollState (): void {
|
||||||
|
// only collect scroll state, do not trigger any dom event
|
||||||
|
const { value: container } = mergedContainerRef
|
||||||
|
if (container) {
|
||||||
|
containerScrollTopRef.value = container.scrollTop
|
||||||
|
containerScrollLeftRef.value = container.scrollLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function syncPositionState (): void {
|
||||||
|
// only collect position state, do not trigger any dom event
|
||||||
|
// Don't use getClientBoundingRect because element may be scale transformed
|
||||||
|
const { value: content } = mergedContentRef
|
||||||
|
if (content) {
|
||||||
|
contentHeightRef.value = content.offsetHeight
|
||||||
|
contentWidthRef.value = content.offsetWidth
|
||||||
|
}
|
||||||
|
const { value: container } = mergedContainerRef
|
||||||
|
if (container) {
|
||||||
|
containerHeightRef.value = container.offsetHeight
|
||||||
|
containerWidthRef.value = container.offsetWidth
|
||||||
|
}
|
||||||
|
const { value: xRailEl } = xRailRef
|
||||||
|
const { value: yRailEl } = yRailRef
|
||||||
|
if (xRailEl) {
|
||||||
|
xRailSizeRef.value = xRailEl.offsetWidth
|
||||||
|
}
|
||||||
|
if (yRailEl) {
|
||||||
|
yRailSizeRef.value = yRailEl.offsetHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sync (): void {
|
||||||
|
if (!props.scrollable) return
|
||||||
|
syncPositionState()
|
||||||
|
syncScrollState()
|
||||||
|
}
|
||||||
|
function handleXScrollMouseDown (e: MouseEvent): void {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
xBarPressed = true
|
||||||
|
on('mousemove', window, handleXScrollMouseMove, true)
|
||||||
|
on('mouseup', window, handleXScrollMouseUp, true)
|
||||||
|
memoXLeft = containerScrollLeftRef.value as number
|
||||||
|
memoMouseX = e.clientX
|
||||||
|
}
|
||||||
|
function handleXScrollMouseMove (e: MouseEvent): void {
|
||||||
|
if (!xBarPressed) return
|
||||||
|
if (xBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(xBarVanishTimerId)
|
||||||
|
}
|
||||||
|
if (yBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(yBarVanishTimerId)
|
||||||
|
}
|
||||||
|
const { value: containerWidth } = containerWidthRef
|
||||||
|
const { value: contentWidth } = contentWidthRef
|
||||||
|
const { value: xBarSize } = xBarSizeRef
|
||||||
|
if (containerWidth === null || contentWidth === null) return
|
||||||
|
const dX = e.clientX - memoMouseX
|
||||||
|
const dScrollLeft =
|
||||||
|
(dX * (contentWidth - containerWidth)) / (containerWidth - xBarSize)
|
||||||
|
const toScrollLeftUpperBound = contentWidth - containerWidth
|
||||||
|
let toScrollLeft = memoXLeft + dScrollLeft
|
||||||
|
toScrollLeft = Math.min(toScrollLeftUpperBound, toScrollLeft)
|
||||||
|
toScrollLeft = Math.max(toScrollLeft, 0)
|
||||||
|
const { value: container } = mergedContainerRef
|
||||||
|
if (container) {
|
||||||
|
container.scrollLeft = toScrollLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleXScrollMouseUp (e: MouseEvent): void {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
off('mousemove', window, handleXScrollMouseMove, true)
|
||||||
|
off('mouseup', window, handleXScrollMouseUp, true)
|
||||||
|
xBarPressed = false
|
||||||
|
sync()
|
||||||
|
const { value: container } = mergedContainerRef
|
||||||
|
if (!container?.contains(e.target as any)) {
|
||||||
|
hideBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleYScrollMouseDown (e: MouseEvent): void {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
yBarPressed = true
|
||||||
|
on('mousemove', window, handleYScrollMouseMove, true)
|
||||||
|
on('mouseup', window, handleYScrollMouseUp, true)
|
||||||
|
memoYTop = containerScrollTopRef.value as number
|
||||||
|
memoMouseY = e.clientY
|
||||||
|
}
|
||||||
|
function handleYScrollMouseMove (e: MouseEvent): void {
|
||||||
|
if (!yBarPressed) return
|
||||||
|
if (xBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(xBarVanishTimerId)
|
||||||
|
}
|
||||||
|
if (yBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(yBarVanishTimerId)
|
||||||
|
}
|
||||||
|
const { value: containerHeight } = containerHeightRef
|
||||||
|
const { value: contentHeight } = contentHeightRef
|
||||||
|
const { value: yBarSize } = yBarSizeRef
|
||||||
|
if (containerHeight === null || contentHeight === null) return
|
||||||
|
const dY = e.clientY - memoMouseY
|
||||||
|
const dScrollTop =
|
||||||
|
(dY * (contentHeight - containerHeight)) / (containerHeight - yBarSize)
|
||||||
|
const toScrollTopUpperBound = contentHeight - containerHeight
|
||||||
|
let toScrollTop = memoYTop + dScrollTop
|
||||||
|
toScrollTop = Math.min(toScrollTopUpperBound, toScrollTop)
|
||||||
|
toScrollTop = Math.max(toScrollTop, 0)
|
||||||
|
const { value: container } = mergedContainerRef
|
||||||
|
if (container) {
|
||||||
|
container.scrollTop = toScrollTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleYScrollMouseUp (e: MouseEvent): void {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
off('mousemove', window, handleYScrollMouseMove, true)
|
||||||
|
off('mouseup', window, handleYScrollMouseUp, true)
|
||||||
|
yBarPressed = false
|
||||||
|
sync()
|
||||||
|
const { value: container } = mergedContainerRef
|
||||||
|
if (!container?.contains(e.target as any)) {
|
||||||
|
hideBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
// if container exist, it always can't be resolved when scrollbar is mounted
|
||||||
|
// for example:
|
||||||
|
// - component
|
||||||
|
// - scrollbar
|
||||||
|
// - inner
|
||||||
|
// if you pass inner to scrollbar, you may use a ref inside component
|
||||||
|
// however, when scrollbar is mounted, ref is not ready at component
|
||||||
|
// you need to init by yourself
|
||||||
|
if (props.container) return
|
||||||
|
sync()
|
||||||
|
})
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (xBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(xBarVanishTimerId)
|
||||||
|
}
|
||||||
|
if (yBarVanishTimerId !== undefined) {
|
||||||
|
window.clearTimeout(yBarVanishTimerId)
|
||||||
|
}
|
||||||
|
off('mousemove', window, handleYScrollMouseMove, true)
|
||||||
|
off('mouseup', window, handleYScrollMouseUp, true)
|
||||||
|
})
|
||||||
|
const themeRef = useTheme(
|
||||||
|
'Scrollbar',
|
||||||
|
'Scrollbar',
|
||||||
|
style,
|
||||||
|
scrollbarLight,
|
||||||
|
props
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
sync,
|
||||||
|
scrollTo,
|
||||||
|
containerRef,
|
||||||
|
contentRef,
|
||||||
|
yRailRef,
|
||||||
|
xRailRef,
|
||||||
|
needYBar: needYBarRef,
|
||||||
|
needXBar: needXBarRef,
|
||||||
|
sizePx: sizePxRef,
|
||||||
|
yBarSizePx: yBarSizePxRef,
|
||||||
|
xBarSizePx: xBarSizePxRef,
|
||||||
|
yBarTopPx: yBarTopPxRef,
|
||||||
|
xBarLeftPx: xBarLeftPxRef,
|
||||||
|
isShowXBar: isShowXBarRef,
|
||||||
|
isShowYBar: isShowYBarRef,
|
||||||
|
isIos,
|
||||||
|
handleScroll,
|
||||||
|
handleContentResize,
|
||||||
|
handleMouseEnterWrapper,
|
||||||
|
handleMouseLeaveWrapper,
|
||||||
|
handleYScrollMouseDown,
|
||||||
|
handleXScrollMouseDown,
|
||||||
|
cssVars: computed(() => {
|
||||||
|
const {
|
||||||
|
common: { cubicBezierEaseInOut },
|
||||||
|
self: { color, colorHover }
|
||||||
|
} = themeRef.value
|
||||||
|
return {
|
||||||
|
'--bezier': cubicBezierEaseInOut,
|
||||||
|
'--color': color,
|
||||||
|
'--color-hover': colorHover
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const { $slots } = this
|
||||||
|
if (!this.scrollable) return renderSlot($slots, 'default')
|
||||||
|
return (
|
||||||
|
<VResizeObserver onResize={this.handleContentResize}>
|
||||||
|
{{
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
mergeProps(this.$attrs, {
|
||||||
|
class: 'n-scrollbar',
|
||||||
|
style: this.cssVars,
|
||||||
|
onMouseenter: this.handleMouseEnterWrapper,
|
||||||
|
onMouseleave: this.handleMouseLeaveWrapper
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
this.container ? (
|
||||||
|
renderSlot($slots, 'default')
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
ref="containerRef"
|
||||||
|
class="n-scrollbar-container"
|
||||||
|
style={this.containerStyle}
|
||||||
|
onScroll={this.handleScroll}
|
||||||
|
>
|
||||||
|
<VResizeObserver onResize={this.handleContentResize}>
|
||||||
|
{{
|
||||||
|
default: () => (
|
||||||
|
<div
|
||||||
|
ref="contentRef"
|
||||||
|
style={
|
||||||
|
[
|
||||||
|
this.contentStyle,
|
||||||
|
{
|
||||||
|
width: this.xScrollable ? 'fit-content' : null
|
||||||
|
}
|
||||||
|
] as any
|
||||||
|
}
|
||||||
|
class={['n-scrollbar-content', this.contentClass]}
|
||||||
|
>
|
||||||
|
{renderSlot($slots, 'default')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</VResizeObserver>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
<div
|
||||||
|
ref="yRailRef"
|
||||||
|
class={[
|
||||||
|
'n-scrollbar-rail n-scrollbar-rail--vertical',
|
||||||
|
{
|
||||||
|
'n-scrollbar-rail--disabled': !this.needYBar
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
style={
|
||||||
|
[this.horizontalRailStyle, { width: this.sizePx }] as any
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Transition name="n-fade-in-transition">
|
||||||
|
{{
|
||||||
|
default: () =>
|
||||||
|
this.needYBar && this.isShowYBar && !this.isIos ? (
|
||||||
|
<div
|
||||||
|
class="n-scrollbar-rail__scrollbar"
|
||||||
|
style={{
|
||||||
|
height: this.yBarSizePx,
|
||||||
|
top: this.yBarTopPx,
|
||||||
|
width: this.sizePx,
|
||||||
|
borderRadius: this.sizePx
|
||||||
|
}}
|
||||||
|
onMousedown={this.handleYScrollMouseDown}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}}
|
||||||
|
</Transition>
|
||||||
|
</div>,
|
||||||
|
<div
|
||||||
|
ref="xRailRef"
|
||||||
|
class={[
|
||||||
|
'n-scrollbar-rail n-scrollbar-rail--horizontal',
|
||||||
|
{
|
||||||
|
'n-scrollbar-rail--disabled': !this.needXBar
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
style={
|
||||||
|
[this.verticalRailStyle, { height: this.sizePx }] as any
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Transition name="n-fade-in-transition">
|
||||||
|
{{
|
||||||
|
default: () =>
|
||||||
|
this.needXBar && this.isShowXBar && !this.isIos ? (
|
||||||
|
<div
|
||||||
|
class="n-scrollbar-rail__scrollbar"
|
||||||
|
style={{
|
||||||
|
height: this.sizePx,
|
||||||
|
width: this.xBarSizePx,
|
||||||
|
left: this.xBarLeftPx,
|
||||||
|
borderRadius: this.sizePx
|
||||||
|
}}
|
||||||
|
onMousedown={this.handleXScrollMouseDown}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}}
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
] as VNode[]
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</VResizeObserver>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
@ -1,537 +0,0 @@
|
|||||||
<template>
|
|
||||||
<slot v-if="!scrollable" />
|
|
||||||
<v-resize-observer v-else @resize="handleContentResize">
|
|
||||||
<div
|
|
||||||
v-bind="$attrs"
|
|
||||||
class="n-scrollbar"
|
|
||||||
:style="cssVars"
|
|
||||||
@mouseenter="handleMouseEnterWrapper"
|
|
||||||
@mouseleave="handleMouseLeaveWrapper"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="!container"
|
|
||||||
ref="containerRef"
|
|
||||||
class="n-scrollbar-container"
|
|
||||||
:style="containerStyle"
|
|
||||||
@scroll="handleScroll"
|
|
||||||
>
|
|
||||||
<v-resize-observer @resize="handleContentResize">
|
|
||||||
<div
|
|
||||||
ref="contentRef"
|
|
||||||
:style="[
|
|
||||||
contentStyle,
|
|
||||||
{
|
|
||||||
width: xScrollable ? 'fit-content' : null
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
:class="['n-scrollbar-content', contentClass]"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</v-resize-observer>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<slot />
|
|
||||||
</template>
|
|
||||||
<div
|
|
||||||
ref="yRailRef"
|
|
||||||
class="n-scrollbar-rail n-scrollbar-rail--vertical"
|
|
||||||
:class="{
|
|
||||||
'n-scrollbar-rail--disabled': !needyBar
|
|
||||||
}"
|
|
||||||
:style="[horizontalRailStyle, { width: sizePx }]"
|
|
||||||
>
|
|
||||||
<transition name="n-fade-in-transition">
|
|
||||||
<div
|
|
||||||
v-if="needyBar && isShowYBar && !isIos"
|
|
||||||
class="n-scrollbar-rail__scrollbar"
|
|
||||||
:style="{
|
|
||||||
height: yBarSizePx,
|
|
||||||
top: yBarTopPx,
|
|
||||||
width: sizePx,
|
|
||||||
borderRadius: scrollbarBorderRadius
|
|
||||||
}"
|
|
||||||
@mousedown="handleYScrollMouseDown"
|
|
||||||
@mouseup="handleYScrollMouseUp"
|
|
||||||
@mousemove="handleYScrollMouseMove"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
ref="xRailRef"
|
|
||||||
class="n-scrollbar-rail n-scrollbar-rail--horizontal"
|
|
||||||
:class="{
|
|
||||||
'n-scrollbar-rail--disabled': !needxBar
|
|
||||||
}"
|
|
||||||
:style="{ ...verticalRailStyle, height: sizePx }"
|
|
||||||
>
|
|
||||||
<transition name="n-fade-in-transition">
|
|
||||||
<div
|
|
||||||
v-if="needxBar && isShowXBar && !isIos"
|
|
||||||
class="n-scrollbar-rail__scrollbar"
|
|
||||||
:style="{
|
|
||||||
height: sizePx,
|
|
||||||
width: xBarSizePx,
|
|
||||||
left: xBarLeftPx,
|
|
||||||
borderRadius: scrollbarBorderRadius
|
|
||||||
}"
|
|
||||||
@mousedown="handleXScrollMouseDown"
|
|
||||||
@mouseup="handleXScrollMouseUp"
|
|
||||||
@mousemove="handleXScrollMouseMove"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-resize-observer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { ref, defineComponent, computed } from 'vue'
|
|
||||||
import { on, off } from 'evtd'
|
|
||||||
import { VResizeObserver } from 'vueuc'
|
|
||||||
import { useIsIos } from 'vooks'
|
|
||||||
import { useTheme } from '../../_mixins'
|
|
||||||
import { scrollbarLight } from '../styles'
|
|
||||||
import style from './styles/index.cssr.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Scrollbar',
|
|
||||||
components: {
|
|
||||||
VResizeObserver
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
...useTheme.props,
|
|
||||||
size: {
|
|
||||||
type: Number,
|
|
||||||
default: 5
|
|
||||||
},
|
|
||||||
duration: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
scrollable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
xScrollable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
type: Function,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: Function,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
containerStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
contentClass: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
contentStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
horizontalRailStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
verticalRailStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
onScroll: {
|
|
||||||
type: Function,
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup (props) {
|
|
||||||
const themeRef = useTheme(
|
|
||||||
'Scrollbar',
|
|
||||||
'Scrollbar',
|
|
||||||
style,
|
|
||||||
scrollbarLight,
|
|
||||||
props
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
containerRef: ref(null),
|
|
||||||
contentRef: ref(null),
|
|
||||||
yRailRef: ref(null),
|
|
||||||
xRailRef: ref(null),
|
|
||||||
cssVars: computed(() => {
|
|
||||||
const {
|
|
||||||
common: { cubicBezierEaseInOut },
|
|
||||||
self: { color, colorHover }
|
|
||||||
} = themeRef.value
|
|
||||||
return {
|
|
||||||
'--bezier': cubicBezierEaseInOut,
|
|
||||||
'--color': color,
|
|
||||||
'--color-hover': colorHover
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
contentHeight: null,
|
|
||||||
contentWidth: null,
|
|
||||||
containerHeight: null,
|
|
||||||
containerWidth: null,
|
|
||||||
yRailSize: null,
|
|
||||||
xRailWidth: null,
|
|
||||||
containerScrollTop: null,
|
|
||||||
containerScrollLeft: null,
|
|
||||||
xBarVanishTimerId: null,
|
|
||||||
yBarVanishTimerId: null,
|
|
||||||
isShowXBar: false,
|
|
||||||
isShowYBar: false,
|
|
||||||
yBarPressed: false,
|
|
||||||
xBarPressed: false,
|
|
||||||
memoYTop: null,
|
|
||||||
memoXLeft: null,
|
|
||||||
memoMouseX: null,
|
|
||||||
memoMouseY: null,
|
|
||||||
isIos: useIsIos()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
yBarSize () {
|
|
||||||
if (
|
|
||||||
this.containerHeight === null ||
|
|
||||||
this.contentHeight === null ||
|
|
||||||
this.yRailSize === null
|
|
||||||
) {
|
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
return Math.min(
|
|
||||||
this.containerHeight,
|
|
||||||
(this.yRailSize * this.containerHeight) / this.contentHeight +
|
|
||||||
this.size * 1.5
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yBarSizePx () {
|
|
||||||
return this.yBarSize + 'px'
|
|
||||||
},
|
|
||||||
xBarSize () {
|
|
||||||
if (
|
|
||||||
this.containerWidth === null ||
|
|
||||||
this.contentWidth === null ||
|
|
||||||
this.xRailSize === null
|
|
||||||
) {
|
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
(this.xRailSize * this.containerWidth) / this.contentWidth +
|
|
||||||
this.size * 1.5
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xBarSizePx () {
|
|
||||||
return this.xBarSize + 'px'
|
|
||||||
},
|
|
||||||
yBarTop () {
|
|
||||||
if (
|
|
||||||
this.containerHeight === null ||
|
|
||||||
this.containerScrollTop === null ||
|
|
||||||
this.contentHeight === null ||
|
|
||||||
this.yRailSize === null
|
|
||||||
) {
|
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
(this.containerScrollTop /
|
|
||||||
(this.contentHeight - this.containerHeight)) *
|
|
||||||
(this.yRailSize - this.yBarSize)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yBarTopPx () {
|
|
||||||
return this.yBarTop + 'px'
|
|
||||||
},
|
|
||||||
xBarLeft () {
|
|
||||||
if (
|
|
||||||
this.containerWidth === null ||
|
|
||||||
this.containerScrollLeft === null ||
|
|
||||||
this.contentWidth === null
|
|
||||||
) {
|
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
(this.containerScrollLeft /
|
|
||||||
(this.contentWidth - this.containerWidth)) *
|
|
||||||
(this.xRailSize - this.xBarSize)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xBarLeftPx () {
|
|
||||||
return this.xBarLeft + 'px'
|
|
||||||
},
|
|
||||||
sizePx () {
|
|
||||||
return this.size + 'px'
|
|
||||||
},
|
|
||||||
scrollbarBorderRadius () {
|
|
||||||
return this.size / 2 + 'px'
|
|
||||||
},
|
|
||||||
needyBar () {
|
|
||||||
return (
|
|
||||||
this.containerHeight !== null &&
|
|
||||||
this.contentHeight !== null &&
|
|
||||||
this.contentHeight > this.containerHeight
|
|
||||||
)
|
|
||||||
},
|
|
||||||
needxBar () {
|
|
||||||
return (
|
|
||||||
this.containerWidth !== null &&
|
|
||||||
this.contentWidth !== null &&
|
|
||||||
this.contentWidth > this.containerWidth
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeUnmount () {
|
|
||||||
window.clearTimeout(this.xBarVanishTimerId)
|
|
||||||
window.clearTimeout(this.yBarVanishTimerId)
|
|
||||||
off('mousemove', window, this.handleYScrollMouseMove, true)
|
|
||||||
off('mouseup', window, this.handleYScrollMouseUp, true)
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
// if container exist, it always can't be resolved when scrollbar is mounted
|
|
||||||
// for example:
|
|
||||||
// - component
|
|
||||||
// - scrollbar
|
|
||||||
// - inner
|
|
||||||
// if you pass inner to scrollbar, you may use a ref inside component
|
|
||||||
// however, when scrollbar is mounted, ref is not ready at component
|
|
||||||
// you need to init by yourself
|
|
||||||
if (this.container) return
|
|
||||||
this.sync()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
mergedContainerRef () {
|
|
||||||
const { container } = this
|
|
||||||
if (container) return container()
|
|
||||||
return this.containerRef
|
|
||||||
},
|
|
||||||
mergedContentRef () {
|
|
||||||
const { content } = this
|
|
||||||
if (content) return content()
|
|
||||||
return this.contentRef
|
|
||||||
},
|
|
||||||
handleContentResize () {
|
|
||||||
this.sync()
|
|
||||||
},
|
|
||||||
scrollTo (options, y) {
|
|
||||||
if (!this.scrollable) return
|
|
||||||
if (typeof options === 'number') {
|
|
||||||
this.scrollToPosition(options, y, 0, false, 'auto')
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
index,
|
|
||||||
elSize,
|
|
||||||
position,
|
|
||||||
behavior,
|
|
||||||
el,
|
|
||||||
debounce = true
|
|
||||||
} = options
|
|
||||||
if (left !== undefined || top !== undefined) {
|
|
||||||
this.scrollToPosition(left, top, 0, false, behavior)
|
|
||||||
}
|
|
||||||
if (el !== undefined) {
|
|
||||||
this.scrollToPosition(
|
|
||||||
0,
|
|
||||||
el.offsetTop,
|
|
||||||
el.offsetHeight,
|
|
||||||
debounce,
|
|
||||||
behavior
|
|
||||||
)
|
|
||||||
} else if (index !== undefined && elSize !== undefined) {
|
|
||||||
this.scrollToPosition(0, index * elSize, elSize, debounce, behavior)
|
|
||||||
} else if (position === 'bottom') {
|
|
||||||
this.scrollToPosition(0, Number.MAX_SAFE_INTEGER, 0, false, behavior)
|
|
||||||
} else if (position === 'top') {
|
|
||||||
this.scrollToPosition(0, 0, 0, false, behavior)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollToPosition (left, top, elSize, debounce, behavior) {
|
|
||||||
const container = this.mergedContainerRef()
|
|
||||||
if (!container) return
|
|
||||||
if (debounce) {
|
|
||||||
const { scrollTop, offsetHeight } = container
|
|
||||||
if (top > scrollTop) {
|
|
||||||
if (top + elSize <= scrollTop + offsetHeight) {
|
|
||||||
// do nothing
|
|
||||||
} else {
|
|
||||||
container.scrollTo({
|
|
||||||
left,
|
|
||||||
top: top + elSize - offsetHeight,
|
|
||||||
behavior
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
container.scrollTo({
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
behavior
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleMouseEnterWrapper () {
|
|
||||||
this.showXBar()
|
|
||||||
this.showYBar()
|
|
||||||
this.sync()
|
|
||||||
},
|
|
||||||
handleMouseLeaveWrapper () {
|
|
||||||
this.hideBar()
|
|
||||||
},
|
|
||||||
hideBar () {
|
|
||||||
this.hideYBar()
|
|
||||||
this.hideXBar()
|
|
||||||
},
|
|
||||||
hideYBar () {
|
|
||||||
if (this.yBarVanishTimerId !== null) {
|
|
||||||
window.clearTimeout(this.yBarVanishTimerId)
|
|
||||||
}
|
|
||||||
this.yBarVanishTimerId = window.setTimeout(() => {
|
|
||||||
this.isShowYBar = false
|
|
||||||
}, this.duration)
|
|
||||||
},
|
|
||||||
hideXBar () {
|
|
||||||
if (this.xBarVanishTimerId !== null) {
|
|
||||||
window.clearTimeout(this.xBarVanishTimerId)
|
|
||||||
}
|
|
||||||
this.xBarVanishTimerId = window.setTimeout(() => {
|
|
||||||
this.isShowXBar = false
|
|
||||||
}, this.duration)
|
|
||||||
},
|
|
||||||
showXBar () {
|
|
||||||
if (this.xBarVanishTimerId !== null) {
|
|
||||||
window.clearTimeout(this.xBarVanishTimerId)
|
|
||||||
}
|
|
||||||
this.isShowXBar = true
|
|
||||||
},
|
|
||||||
showYBar () {
|
|
||||||
if (this.yBarVanishTimerId !== null) {
|
|
||||||
window.clearTimeout(this.yBarVanishTimerId)
|
|
||||||
}
|
|
||||||
this.isShowYBar = true
|
|
||||||
},
|
|
||||||
handleScroll (e) {
|
|
||||||
const { onScroll } = this
|
|
||||||
if (onScroll) onScroll(e)
|
|
||||||
this.syncScrollState()
|
|
||||||
},
|
|
||||||
syncScrollState () {
|
|
||||||
// only collect scroll state, do not trigger any dom event
|
|
||||||
const container = this.mergedContainerRef()
|
|
||||||
if (container) {
|
|
||||||
this.containerScrollTop = container.scrollTop
|
|
||||||
this.containerScrollLeft = container.scrollLeft
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncPositionState () {
|
|
||||||
// only collect position state, do not trigger any dom event
|
|
||||||
// Don't use getClientBoundingRect because element may be scale transformed
|
|
||||||
const content = this.mergedContentRef()
|
|
||||||
if (content) {
|
|
||||||
this.contentHeight = content.offsetHeight
|
|
||||||
this.contentWidth = content.offsetWidth
|
|
||||||
}
|
|
||||||
const container = this.mergedContainerRef()
|
|
||||||
if (container) {
|
|
||||||
this.containerHeight = container.offsetHeight
|
|
||||||
this.containerWidth = container.offsetWidth
|
|
||||||
}
|
|
||||||
const { xRailRef, yRailRef } = this
|
|
||||||
if (xRailRef) {
|
|
||||||
this.xRailSize = xRailRef.offsetWidth
|
|
||||||
}
|
|
||||||
if (yRailRef) {
|
|
||||||
this.yRailSize = yRailRef.offsetHeight
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sync () {
|
|
||||||
if (!this.scrollable) return
|
|
||||||
this.syncPositionState()
|
|
||||||
this.syncScrollState()
|
|
||||||
},
|
|
||||||
handleXScrollMouseDown (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
this.xBarPressed = true
|
|
||||||
on('mousemove', window, this.handleXScrollMouseMove, true)
|
|
||||||
on('mouseup', window, this.handleXScrollMouseUp, true)
|
|
||||||
this.memoXLeft = this.containerScrollLeft
|
|
||||||
this.memoMouseX = e.clientX
|
|
||||||
},
|
|
||||||
handleXScrollMouseMove (e) {
|
|
||||||
if (!this.xBarPressed) return
|
|
||||||
window.clearTimeout(this.xBarVanishTimerId)
|
|
||||||
window.clearTimeout(this.yBarVanishTimerId)
|
|
||||||
const dX = e.clientX - this.memoMouseX
|
|
||||||
const dScrollLeft =
|
|
||||||
(dX * (this.contentWidth - this.containerWidth)) /
|
|
||||||
(this.containerWidth - this.xBarSize)
|
|
||||||
const toScrollLeftUpperBound = this.contentWidth - this.containerWidth
|
|
||||||
let toScrollLeft = this.memoXLeft + dScrollLeft
|
|
||||||
toScrollLeft = Math.min(toScrollLeftUpperBound, toScrollLeft)
|
|
||||||
toScrollLeft = Math.max(toScrollLeft, 0)
|
|
||||||
this.mergedContainerRef().scrollLeft = toScrollLeft
|
|
||||||
},
|
|
||||||
handleXScrollMouseUp (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
off('mousemove', window, this.handleXScrollMouseMove, true)
|
|
||||||
off('mouseup', window, this.handleXScrollMouseUp, true)
|
|
||||||
this.xBarPressed = false
|
|
||||||
this.sync()
|
|
||||||
if (!this.mergedContainerRef().contains(e.target)) {
|
|
||||||
this.hideBar()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleYScrollMouseDown (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
this.yBarPressed = true
|
|
||||||
on('mousemove', window, this.handleYScrollMouseMove, true)
|
|
||||||
on('mouseup', window, this.handleYScrollMouseUp, true)
|
|
||||||
this.memoYTop = this.containerScrollTop
|
|
||||||
this.memoMouseY = e.clientY
|
|
||||||
},
|
|
||||||
handleYScrollMouseMove (e) {
|
|
||||||
if (!this.yBarPressed) return
|
|
||||||
window.clearTimeout(this.xBarVanishTimerId)
|
|
||||||
window.clearTimeout(this.yBarVanishTimerId)
|
|
||||||
const dY = e.clientY - this.memoMouseY
|
|
||||||
const dScrollTop =
|
|
||||||
(dY * (this.contentHeight - this.containerHeight)) /
|
|
||||||
(this.containerHeight - this.yBarSize)
|
|
||||||
const toScrollTopUpperBound = this.contentHeight - this.containerHeight
|
|
||||||
let toScrollTop = this.memoYTop + dScrollTop
|
|
||||||
toScrollTop = Math.min(toScrollTopUpperBound, toScrollTop)
|
|
||||||
toScrollTop = Math.max(toScrollTop, 0)
|
|
||||||
this.mergedContainerRef().scrollTop = toScrollTop
|
|
||||||
},
|
|
||||||
handleYScrollMouseUp (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
const { onScrollEnd } = this
|
|
||||||
if (onScrollEnd) onScrollEnd()
|
|
||||||
off('mousemove', window, this.handleYScrollMouseMove, true)
|
|
||||||
off('mouseup', window, this.handleYScrollMouseUp, true)
|
|
||||||
this.yBarPressed = false
|
|
||||||
this.sync()
|
|
||||||
if (!this.mergedContainerRef().contains(e.target)) {
|
|
||||||
this.hideBar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -1,6 +1,7 @@
|
|||||||
import { commonDark } from '../../_styles/new-common'
|
import { commonDark } from '../../_styles/new-common'
|
||||||
|
import type { ScrollbarTheme } from './light'
|
||||||
|
|
||||||
export default {
|
const scrollbarDark: ScrollbarTheme = {
|
||||||
name: 'Scrollbar',
|
name: 'Scrollbar',
|
||||||
common: commonDark,
|
common: commonDark,
|
||||||
self (vars) {
|
self (vars) {
|
||||||
@ -11,3 +12,5 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default scrollbarDark
|
@ -1,2 +0,0 @@
|
|||||||
export { default as scrollbarDark } from './dark.js'
|
|
||||||
export { default as scrollbarLight } from './light.js'
|
|
3
src/scrollbar/styles/index.ts
Normal file
3
src/scrollbar/styles/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as scrollbarDark } from './dark'
|
||||||
|
export { default as scrollbarLight } from './light'
|
||||||
|
export type { ScrollbarTheme, ScrollbarThemeVars } from './light'
|
@ -1,13 +0,0 @@
|
|||||||
import { commonLight } from '../../_styles/new-common'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Scrollbar',
|
|
||||||
common: commonLight,
|
|
||||||
self (vars) {
|
|
||||||
const { scrollbarColorOverlay, scrollbarColorHoverOverlay } = vars
|
|
||||||
return {
|
|
||||||
color: scrollbarColorOverlay,
|
|
||||||
colorHover: scrollbarColorHoverOverlay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
22
src/scrollbar/styles/light.ts
Normal file
22
src/scrollbar/styles/light.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { commonLight } from '../../_styles/new-common'
|
||||||
|
import type { ThemeCommonVars } from '../../_styles/new-common'
|
||||||
|
import type { Theme } from '../../_mixins'
|
||||||
|
|
||||||
|
const self = (vars: ThemeCommonVars) => {
|
||||||
|
const { scrollbarColorOverlay, scrollbarColorHoverOverlay } = vars
|
||||||
|
return {
|
||||||
|
color: scrollbarColorOverlay,
|
||||||
|
colorHover: scrollbarColorHoverOverlay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScrollbarThemeVars = ReturnType<typeof self>
|
||||||
|
|
||||||
|
const scrollbarLight: Theme<ScrollbarThemeVars> = {
|
||||||
|
name: 'Scrollbar',
|
||||||
|
common: commonLight,
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
export default scrollbarLight
|
||||||
|
export type ScrollbarTheme = typeof scrollbarLight
|
Loading…
Reference in New Issue
Block a user