From 1fcd938cf9bae62673340684cc5aa7296fdcd120 Mon Sep 17 00:00:00 2001 From: zouzhibing Date: Mon, 12 Sep 2022 13:26:37 +0800 Subject: [PATCH] =?UTF-8?q?update:antdesign-vue=E9=87=8D=E6=9E=84=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Scrollbar/index.ts | 8 + src/components/Scrollbar/src/Scrollbar.vue | 207 +++++++++++++++++++++ src/components/Scrollbar/src/bar.ts | 110 +++++++++++ src/components/Scrollbar/src/domUtils.ts | 180 ++++++++++++++++++ src/components/Scrollbar/src/event.ts | 42 +++++ src/components/Scrollbar/src/types.d.ts | 18 ++ src/components/Scrollbar/src/util.ts | 50 +++++ src/layout/Sidebar/components/Menu.vue | 2 +- src/layout/Sidebar/index.vue | 5 +- 9 files changed, 619 insertions(+), 3 deletions(-) create mode 100644 src/components/Scrollbar/index.ts create mode 100644 src/components/Scrollbar/src/Scrollbar.vue create mode 100644 src/components/Scrollbar/src/bar.ts create mode 100644 src/components/Scrollbar/src/domUtils.ts create mode 100644 src/components/Scrollbar/src/event.ts create mode 100644 src/components/Scrollbar/src/types.d.ts create mode 100644 src/components/Scrollbar/src/util.ts diff --git a/src/components/Scrollbar/index.ts b/src/components/Scrollbar/index.ts new file mode 100644 index 0000000..e5b2cb2 --- /dev/null +++ b/src/components/Scrollbar/index.ts @@ -0,0 +1,8 @@ +/** + * copy from element-ui + */ + +import Scrollbar from './src/Scrollbar.vue'; + +export { Scrollbar }; +export type { ScrollbarType } from './src/types'; diff --git a/src/components/Scrollbar/src/Scrollbar.vue b/src/components/Scrollbar/src/Scrollbar.vue new file mode 100644 index 0000000..d668ed9 --- /dev/null +++ b/src/components/Scrollbar/src/Scrollbar.vue @@ -0,0 +1,207 @@ + + + diff --git a/src/components/Scrollbar/src/bar.ts b/src/components/Scrollbar/src/bar.ts new file mode 100644 index 0000000..5e982e0 --- /dev/null +++ b/src/components/Scrollbar/src/bar.ts @@ -0,0 +1,110 @@ +import { + defineComponent, + h, + computed, + ref, + getCurrentInstance, + onUnmounted, + inject, + Ref, +} from 'vue'; +import { on, off } from './domUtils'; + +import { renderThumbStyle, BAR_MAP } from './util'; + +export default defineComponent({ + name: 'Bar', + + props: { + vertical: Boolean, + size: String, + move: Number, + }, + + setup(props) { + const instance = getCurrentInstance(); + const thumb = ref(); + const wrap = inject('scroll-bar-wrap', {} as Ref>) as any; + const bar = computed(() => { + return BAR_MAP[props.vertical ? 'vertical' : 'horizontal']; + }); + const barStore = ref({}); + const cursorDown = ref(); + const clickThumbHandler = (e: any) => { + // prevent click event of right button + if (e.ctrlKey || e.button === 2) { + return; + } + window.getSelection()?.removeAllRanges(); + startDrag(e); + barStore.value[bar.value.axis] = + e.currentTarget[bar.value.offset] - + (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]); + }; + + const clickTrackHandler = (e: any) => { + const offset = Math.abs( + e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client], + ); + const thumbHalf = thumb.value[bar.value.offset] / 2; + const thumbPositionPercentage = + ((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset]; + + wrap.value[bar.value.scroll] = + (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100; + }; + const startDrag = (e: any) => { + e.stopImmediatePropagation(); + cursorDown.value = true; + on(document, 'mousemove', mouseMoveDocumentHandler); + on(document, 'mouseup', mouseUpDocumentHandler); + document.onselectstart = () => false; + }; + + const mouseMoveDocumentHandler = (e: any) => { + if (cursorDown.value === false) return; + const prevPage = barStore.value[bar.value.axis]; + + if (!prevPage) return; + + const offset = + (instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * + -1; + const thumbClickPosition = thumb.value[bar.value.offset] - prevPage; + const thumbPositionPercentage = + ((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset]; + wrap.value[bar.value.scroll] = + (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100; + }; + + function mouseUpDocumentHandler() { + cursorDown.value = false; + barStore.value[bar.value.axis] = 0; + off(document, 'mousemove', mouseMoveDocumentHandler); + document.onselectstart = null; + } + + onUnmounted(() => { + off(document, 'mouseup', mouseUpDocumentHandler); + }); + + return () => + h( + 'div', + { + class: ['scrollbar__bar', 'is-' + bar.value.key], + onMousedown: clickTrackHandler, + }, + h('div', { + ref: thumb, + class: 'scrollbar__thumb', + onMousedown: clickThumbHandler, + style: renderThumbStyle({ + size: props.size, + move: props.move, + bar: bar.value, + }), + }), + ); + }, +}); diff --git a/src/components/Scrollbar/src/domUtils.ts b/src/components/Scrollbar/src/domUtils.ts new file mode 100644 index 0000000..7efe9cb --- /dev/null +++ b/src/components/Scrollbar/src/domUtils.ts @@ -0,0 +1,180 @@ +import type { FunctionArgs } from '@vueuse/core'; +import { upperFirst } from 'lodash-es'; + +export interface ViewportOffsetResult { + left: number; + top: number; + right: number; + bottom: number; + rightIncludeBody: number; + bottomIncludeBody: number; +} + +export function getBoundingClientRect(element: Element): DOMRect | number { + if (!element || !element.getBoundingClientRect) { + return 0; + } + return element.getBoundingClientRect(); +} + +function trim(string: string) { + return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); +} + +/* istanbul ignore next */ +export function hasClass(el: Element, cls: string) { + if (!el || !cls) return false; + if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.'); + if (el.classList) { + return el.classList.contains(cls); + } else { + return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; + } +} + +/* istanbul ignore next */ +export function addClass(el: Element, cls: string) { + if (!el) return; + let curClass = el.className; + const classes = (cls || '').split(' '); + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.add(clsName); + } else if (!hasClass(el, clsName)) { + curClass += ' ' + clsName; + } + } + if (!el.classList) { + el.className = curClass; + } +} + +/* istanbul ignore next */ +export function removeClass(el: Element, cls: string) { + if (!el || !cls) return; + const classes = cls.split(' '); + let curClass = ' ' + el.className + ' '; + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.remove(clsName); + } else if (hasClass(el, clsName)) { + curClass = curClass.replace(' ' + clsName + ' ', ' '); + } + } + if (!el.classList) { + el.className = trim(curClass); + } +} +/** + * Get the left and top offset of the current element + * left: the distance between the leftmost element and the left side of the document + * top: the distance from the top of the element to the top of the document + * right: the distance from the far right of the element to the right of the document + * bottom: the distance from the bottom of the element to the bottom of the document + * rightIncludeBody: the distance between the leftmost element and the right side of the document + * bottomIncludeBody: the distance from the bottom of the element to the bottom of the document + * + * @description: + */ +export function getViewportOffset(element: Element): ViewportOffsetResult { + const doc = document.documentElement; + + const docScrollLeft = doc.scrollLeft; + const docScrollTop = doc.scrollTop; + const docClientLeft = doc.clientLeft; + const docClientTop = doc.clientTop; + + const pageXOffset = window.pageXOffset; + const pageYOffset = window.pageYOffset; + + const box = getBoundingClientRect(element); + + const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect; + + const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0); + const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0); + const offsetLeft = retLeft + pageXOffset; + const offsetTop = rectTop + pageYOffset; + + const left = offsetLeft - scrollLeft; + const top = offsetTop - scrollTop; + + const clientWidth = window.document.documentElement.clientWidth; + const clientHeight = window.document.documentElement.clientHeight; + return { + left: left, + top: top, + right: clientWidth - rectWidth - left, + bottom: clientHeight - rectHeight - top, + rightIncludeBody: clientWidth - left, + bottomIncludeBody: clientHeight - top, + }; +} + +export function hackCss(attr: string, value: string) { + const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT']; + + const styleObj: any = {}; + prefix.forEach((item) => { + styleObj[`${item}${upperFirst(attr)}`] = value; + }); + return { + ...styleObj, + [attr]: value, + }; +} + +/* istanbul ignore next */ +export function on( + element: Element | HTMLElement | Document | Window, + event: string, + handler: EventListenerOrEventListenerObject, +): void { + if (element && event && handler) { + element.addEventListener(event, handler, false); + } +} + +/* istanbul ignore next */ +export function off( + element: Element | HTMLElement | Document | Window, + event: string, + handler: Fn, +): void { + if (element && event && handler) { + element.removeEventListener(event, handler, false); + } +} + +/* istanbul ignore next */ +export function once(el: HTMLElement, event: string, fn: EventListener): void { + const listener = function (this: any, ...args: unknown[]) { + if (fn) { + fn.apply(this, args); + } + off(el, event, listener); + }; + on(el, event, listener); +} + +export function useRafThrottle(fn: T): T { + let locked = false; + // @ts-ignore + return function (...args: any[]) { + if (locked) return; + locked = true; + window.requestAnimationFrame(() => { + // @ts-ignore + fn.apply(this, args); + locked = false; + }); + }; +} diff --git a/src/components/Scrollbar/src/event.ts b/src/components/Scrollbar/src/event.ts new file mode 100644 index 0000000..3a60d7c --- /dev/null +++ b/src/components/Scrollbar/src/event.ts @@ -0,0 +1,42 @@ +import ResizeObserver from 'resize-observer-polyfill'; + +const isServer = typeof window === 'undefined'; + +/* istanbul ignore next */ +function resizeHandler(entries: any[]) { + for (const entry of entries) { + const listeners = entry.target.__resizeListeners__ || []; + if (listeners.length) { + listeners.forEach((fn: () => any) => { + fn(); + }); + } + } +} + +/* istanbul ignore next */ +export function addResizeListener(element: any, fn: () => any) { + if (isServer) return; + if (!element.__resizeListeners__) { + element.__resizeListeners__ = []; + element.__ro__ = new ResizeObserver(resizeHandler); + element.__ro__.observe(element); + } + element.__resizeListeners__.push(fn); +} + +/* istanbul ignore next */ +export function removeResizeListener(element: any, fn: () => any) { + if (!element || !element.__resizeListeners__) return; + element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); + if (!element.__resizeListeners__.length) { + element.__ro__.disconnect(); + } +} + +export function triggerWindowResize() { + const event = document.createEvent('HTMLEvents'); + event.initEvent('resize', true, true); + (event as any).eventType = 'message'; + window.dispatchEvent(event); +} diff --git a/src/components/Scrollbar/src/types.d.ts b/src/components/Scrollbar/src/types.d.ts new file mode 100644 index 0000000..4c7eeea --- /dev/null +++ b/src/components/Scrollbar/src/types.d.ts @@ -0,0 +1,18 @@ +export interface BarMapItem { + offset: string; + scroll: string; + scrollSize: string; + size: string; + key: string; + axis: string; + client: string; + direction: string; +} +export interface BarMap { + vertical: BarMapItem; + horizontal: BarMapItem; +} + +export interface ScrollbarType { + wrap: ElRef; +} diff --git a/src/components/Scrollbar/src/util.ts b/src/components/Scrollbar/src/util.ts new file mode 100644 index 0000000..b7c4845 --- /dev/null +++ b/src/components/Scrollbar/src/util.ts @@ -0,0 +1,50 @@ +import type { BarMap } from './types'; +export const BAR_MAP: BarMap = { + vertical: { + offset: 'offsetHeight', + scroll: 'scrollTop', + scrollSize: 'scrollHeight', + size: 'height', + key: 'vertical', + axis: 'Y', + client: 'clientY', + direction: 'top', + }, + horizontal: { + offset: 'offsetWidth', + scroll: 'scrollLeft', + scrollSize: 'scrollWidth', + size: 'width', + key: 'horizontal', + axis: 'X', + client: 'clientX', + direction: 'left', + }, +}; + +// @ts-ignore +export function renderThumbStyle({ move, size, bar }) { + const style = {} as any; + const translate = `translate${bar.axis}(${move}%)`; + + style[bar.size] = size; + style.transform = translate; + style.msTransform = translate; + style.webkitTransform = translate; + + return style; +} + +function extend(to: T, _from: K): T & K { + return Object.assign(to, _from); +} + +export function toObject(arr: Array): Recordable { + const res = {}; + for (let i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res; +} diff --git a/src/layout/Sidebar/components/Menu.vue b/src/layout/Sidebar/components/Menu.vue index d9b3f64..f010f61 100644 --- a/src/layout/Sidebar/components/Menu.vue +++ b/src/layout/Sidebar/components/Menu.vue @@ -1,5 +1,5 @@