From 0859f4c11d19ae69994d006d1f01770c3c5638a8 Mon Sep 17 00:00:00 2001 From: devin Date: Fri, 23 Dec 2022 16:17:06 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0popup=20=E8=BE=B9?= =?UTF-8?q?=E7=BC=98=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../directves/click-outside/index.ts | 56 +++++ .../src/components/directves/index.ts | 2 + .../src/components/popup/OPopup.vue | 182 ++++++++++++++ .../popup/__demo__/PopupContainer.vue | 125 ++++++++++ .../popup/__demo__/PopupPosition.vue | 87 +++++++ .../popup/__demo__/PopupVisible.vue | 31 +++ .../opendesign/src/components/popup/index.ts | 14 ++ .../opendesign/src/components/popup/popup.ts | 232 ++++++++++++++++++ .../src/components/popup/style/index.scss | 59 +++++ .../src/components/popup/style/index.ts | 1 + .../src/components/popup/style/var.scss | 6 + .../opendesign/src/components/popup/types.ts | 22 ++ packages/portal/src/pages/ThePopup.vue | 15 ++ packages/portal/src/router.ts | 12 + 14 files changed, 844 insertions(+) create mode 100644 packages/opendesign/src/components/directves/click-outside/index.ts create mode 100644 packages/opendesign/src/components/directves/index.ts create mode 100644 packages/opendesign/src/components/popup/OPopup.vue create mode 100644 packages/opendesign/src/components/popup/__demo__/PopupContainer.vue create mode 100644 packages/opendesign/src/components/popup/__demo__/PopupPosition.vue create mode 100644 packages/opendesign/src/components/popup/__demo__/PopupVisible.vue create mode 100644 packages/opendesign/src/components/popup/index.ts create mode 100644 packages/opendesign/src/components/popup/popup.ts create mode 100644 packages/opendesign/src/components/popup/style/index.scss create mode 100644 packages/opendesign/src/components/popup/style/index.ts create mode 100644 packages/opendesign/src/components/popup/style/var.scss create mode 100644 packages/opendesign/src/components/popup/types.ts create mode 100644 packages/portal/src/pages/ThePopup.vue diff --git a/packages/opendesign/src/components/directves/click-outside/index.ts b/packages/opendesign/src/components/directves/click-outside/index.ts new file mode 100644 index 00000000..c12824e7 --- /dev/null +++ b/packages/opendesign/src/components/directves/click-outside/index.ts @@ -0,0 +1,56 @@ +import { ObjectDirective, DirectiveBinding } from 'vue'; +interface handlerItemT { + handler: () => void +} + +const elList = new Map>(); + +window.addEventListener('mousedown', (e) => { + elList.forEach((handlers, el) => { + if (!el.contains(e.target as HTMLElement)) { + + handlers.forEach(item => { + item.handler(); + }); + } + }); +}); + +function addListener(el: HTMLElement, fn: () => void) { + if (!elList.has(el)) { + elList.set(el, []); + } + + const handlers = elList.get(el); + if (handlers) { + handlers.push({ + handler: fn + }); + } +} + +function removeListener(el: HTMLElement) { + elList.delete(el); + +} + +const vOutClick: ObjectDirective = { + beforeMount(el: HTMLElement, binding: DirectiveBinding) { + addListener(el, binding.value); + }, + unmounted(el: HTMLElement) { + removeListener(el); + } +}; + +export function listenOutClick(el: HTMLElement, fn: () => void) { + addListener(el, fn); + return () => { + removeListener(el); + }; +} + + +export { + vOutClick +}; \ No newline at end of file diff --git a/packages/opendesign/src/components/directves/index.ts b/packages/opendesign/src/components/directves/index.ts new file mode 100644 index 00000000..c6ac1fa8 --- /dev/null +++ b/packages/opendesign/src/components/directves/index.ts @@ -0,0 +1,2 @@ +export * from './click-outside'; +export * from './focus'; \ No newline at end of file diff --git a/packages/opendesign/src/components/popup/OPopup.vue b/packages/opendesign/src/components/popup/OPopup.vue new file mode 100644 index 00000000..c90fb194 --- /dev/null +++ b/packages/opendesign/src/components/popup/OPopup.vue @@ -0,0 +1,182 @@ + + diff --git a/packages/opendesign/src/components/popup/__demo__/PopupContainer.vue b/packages/opendesign/src/components/popup/__demo__/PopupContainer.vue new file mode 100644 index 00000000..1361e38b --- /dev/null +++ b/packages/opendesign/src/components/popup/__demo__/PopupContainer.vue @@ -0,0 +1,125 @@ + + + diff --git a/packages/opendesign/src/components/popup/__demo__/PopupPosition.vue b/packages/opendesign/src/components/popup/__demo__/PopupPosition.vue new file mode 100644 index 00000000..ac28e33a --- /dev/null +++ b/packages/opendesign/src/components/popup/__demo__/PopupPosition.vue @@ -0,0 +1,87 @@ + + + diff --git a/packages/opendesign/src/components/popup/__demo__/PopupVisible.vue b/packages/opendesign/src/components/popup/__demo__/PopupVisible.vue new file mode 100644 index 00000000..8da50230 --- /dev/null +++ b/packages/opendesign/src/components/popup/__demo__/PopupVisible.vue @@ -0,0 +1,31 @@ + + + diff --git a/packages/opendesign/src/components/popup/index.ts b/packages/opendesign/src/components/popup/index.ts new file mode 100644 index 00000000..1da05317 --- /dev/null +++ b/packages/opendesign/src/components/popup/index.ts @@ -0,0 +1,14 @@ +import _OPopup from './OPopup.vue'; +import type { App } from 'vue'; + +export * from './types'; + +const OPopup = Object.assign(_OPopup, { + install(app: App) { + app.component(_OPopup.name, _OPopup); + }, +}); + +export { + OPopup, +}; diff --git a/packages/opendesign/src/components/popup/popup.ts b/packages/opendesign/src/components/popup/popup.ts new file mode 100644 index 00000000..aae26b6f --- /dev/null +++ b/packages/opendesign/src/components/popup/popup.ts @@ -0,0 +1,232 @@ +import { getRelativeBounding, getScroll, isRootEl } from '../_shared/dom'; +import { PopupPosition } from './types'; + +enum Direction { + LEFT = 'left', + RIGHT = 'right', + TOP = 'top', + BOTTOM = 'bottom', +} +function getDirection(position: PopupPosition): Direction { + switch (position) { + case PopupPosition.TL: + case PopupPosition.TR: + case PopupPosition.TOP: + return Direction.TOP; + case PopupPosition.BL: + case PopupPosition.BR: + case PopupPosition.BOTTOM: + return Direction.BOTTOM; + case PopupPosition.LT: + case PopupPosition.LB: + case PopupPosition.LEFT: + return Direction.LEFT; + case PopupPosition.RT: + case PopupPosition.RB: + case PopupPosition.RIGHT: + return Direction.RIGHT; + } +} + +function fixPosition(position: PopupPosition, direction: Direction) { + const fixFn = { + [Direction.TOP]: (p: PopupPosition) => { + if (p === PopupPosition.BOTTOM) { + return PopupPosition.TOP; + } else if (p === PopupPosition.BL) { + return PopupPosition.TL; + } else if (p === PopupPosition.BR) { + return PopupPosition.TR; + } + return p; + }, + [Direction.BOTTOM]: (p: PopupPosition) => { + if (p === PopupPosition.TOP) { + return PopupPosition.BOTTOM; + } else if (p === PopupPosition.TL) { + return PopupPosition.BL; + } else if (p === PopupPosition.TR) { + return PopupPosition.BR; + } + return p; + }, + [Direction.LEFT]: (p: PopupPosition) => { + if (p === PopupPosition.RIGHT) { + return PopupPosition.LEFT; + } else if (p === PopupPosition.RT) { + return PopupPosition.LT; + } else if (p === PopupPosition.RB) { + return PopupPosition.LB; + } + return p; + }, + [Direction.RIGHT]: (p: PopupPosition) => { + if (p === PopupPosition.LEFT) { + return PopupPosition.RIGHT; + } else if (p === PopupPosition.LT) { + return PopupPosition.RT; + } else if (p === PopupPosition.LB) { + return PopupPosition.RB; + } + return p; + } + }; + + const fn = fixFn[direction]; + return fn ? fn(position) : position; +} + +function getPopupOffset(targetEl: HTMLElement, position: PopupPosition, popupRect: DOMRect, containerRect: DOMRect) { + const t = getRelativeBounding(targetEl, containerRect); + const p = popupRect; + switch (position) { + case PopupPosition.LT: { + return { + top: t.offsetTop, + left: t.offsetLeft - p.width, + }; + } + case PopupPosition.LB: { + return { + top: t.offsetTop + t.height - p.height, + left: t.offsetLeft - p.width, + }; + } + case PopupPosition.LEFT: { + return { + top: t.offsetTop + Math.round(t.height / 2) - Math.round(p.height / 2), + left: t.offsetLeft - p.width, + }; + } + case PopupPosition.RT: { + return { + top: t.offsetTop, + left: t.offsetLeft + t.width, + }; + } + case PopupPosition.RB: { + return { + top: t.offsetTop + t.height - p.height, + left: t.offsetLeft + t.width, + }; + } + case PopupPosition.RIGHT: { + return { + top: t.offsetTop + Math.round(t.height / 2) - Math.round(p.height / 2), + left: t.offsetLeft + t.width, + }; + } + case PopupPosition.TL: { + return { + top: t.offsetTop - p.height, + left: t.offsetLeft, + }; + } + case PopupPosition.TR: { + return { + top: t.offsetTop - p.height, + left: t.offsetLeft + t.width - p.width, + }; + } + case PopupPosition.TOP: { + return { + top: t.offsetTop - p.height, + left: t.offsetLeft + Math.round(t.width / 2) - Math.round(p.width / 2), + }; + } + case PopupPosition.BL: { + return { + top: t.offsetTop + t.height, + left: t.offsetLeft, + }; + } + case PopupPosition.BR: { + return { + top: t.offsetTop + t.height, + left: t.offsetLeft + t.width - p.width + }; + } + case PopupPosition.BOTTOM: { + return { + top: t.offsetTop + t.height, + left: t.offsetLeft + Math.round(t.width / 2) - Math.round(p.width / 2), + }; + } + default: { + return { + top: 0, + left: 0 + }; + } + } +} + + +// 处理popup位置 +export function calcPopupStyle(popupEl: HTMLElement, targetEl: HTMLElement, container: HTMLElement, position: PopupPosition, + { adaptive = true }: { adaptive?: boolean } = {}) { + + const cRect = container.getBoundingClientRect(); + const pRect = popupEl.getBoundingClientRect(); + + const { left, top } = getPopupOffset(targetEl, position, pRect, cRect); + + let fixedPosition = position; + let style = { + left, + top + }; + // 自适应容器边缘 + if (adaptive) { + const edge = { + left: 0, + top: 0, + right: Math.floor(cRect.width) - Math.ceil(pRect.width), + bottom: Math.floor(cRect.height) - Math.ceil(pRect.height), + }; + const d = getDirection(position); + if (d === Direction.TOP && edge.top > top) { + fixedPosition = fixPosition(position, Direction.BOTTOM); + style = getPopupOffset(targetEl, fixedPosition, pRect, cRect); + } + if (d === Direction.LEFT && edge.left > left) { + fixedPosition = fixPosition(position, Direction.RIGHT); + style = getPopupOffset(targetEl, fixedPosition, pRect, cRect); + } + if (d === Direction.RIGHT && edge.right < left) { + fixedPosition = fixPosition(position, Direction.LEFT); + style = getPopupOffset(targetEl, fixedPosition, pRect, cRect); + } + if (d === Direction.BOTTOM && edge.bottom < top) { + fixedPosition = fixPosition(position, Direction.TOP); + style = getPopupOffset(targetEl, fixedPosition, pRect, cRect); + } + + if ([Direction.TOP, Direction.BOTTOM].includes(d)) { + if (edge.left > left) { + style.left = 0; + } else if (edge.right < left) { + style.left = edge.right; + } + } + if ([Direction.LEFT, Direction.RIGHT].includes(d)) { + if (edge.top > top) { + style.top = 0; + } else if (edge.bottom < top) { + style.top = edge.bottom; + } + } + } + + // 需要加上滚动值,如果为body,则不需要 + if (!isRootEl(container)) { + const cs = getScroll(container); + style.left += cs.left; + style.top += cs.top; + } + + return { + style, + position: fixedPosition + }; +}; \ No newline at end of file diff --git a/packages/opendesign/src/components/popup/style/index.scss b/packages/opendesign/src/components/popup/style/index.scss new file mode 100644 index 00000000..e6a48f84 --- /dev/null +++ b/packages/opendesign/src/components/popup/style/index.scss @@ -0,0 +1,59 @@ +@import './var.scss'; + +.o-popup { + position: absolute; + z-index: 100; +} +.o-popup-wrap { + background-color: var(--popup-bg); + box-shadow: var(--popup-shadow); + padding: 4px 8px; + border-radius: var(--popup-radius); + border: 1px solid red; +} + +.o-popup-pos-bl { + margin-top: var(--popup-gap); +} +.o-popup-pos-bottom { + margin-top: var(--popup-gap); +} +.o-popup-pos-br { + margin-top: var(--popup-gap); +} + +.o-popup-pos-tl { + margin-bottom: var(--popup-gap); +} + +.o-popup-pos-top { + margin-bottom: var(--popup-gap); +} + +.o-popup-pos-tr { + margin-bottom: var(--popup-gap); +} + +.o-popup-pos-lt { + margin-right: var(--popup-gap); +} + +.o-popup-pos-left { + margin-right: var(--popup-gap); +} + +.o-popup-pos-lb { + margin-right: var(--popup-gap); +} + +.o-popup-pos-rt { + margin-left: var(--popup-gap); +} + +.o-popup-pos-right { + margin-left: var(--popup-gap); +} + +.o-popup-pos-rb { + margin-left: var(--popup-gap); +} diff --git a/packages/opendesign/src/components/popup/style/index.ts b/packages/opendesign/src/components/popup/style/index.ts new file mode 100644 index 00000000..67aac616 --- /dev/null +++ b/packages/opendesign/src/components/popup/style/index.ts @@ -0,0 +1 @@ +import './index.scss'; diff --git a/packages/opendesign/src/components/popup/style/var.scss b/packages/opendesign/src/components/popup/style/var.scss new file mode 100644 index 00000000..0183f05a --- /dev/null +++ b/packages/opendesign/src/components/popup/style/var.scss @@ -0,0 +1,6 @@ +.o-popup { + --popup-bg: var(--o-color-bg2); + --popup-shadow: var(--o-shadow-1); + --popup-gap: 4px; + --popup-radius: var(--o-radius-s); +} diff --git a/packages/opendesign/src/components/popup/types.ts b/packages/opendesign/src/components/popup/types.ts new file mode 100644 index 00000000..20286519 --- /dev/null +++ b/packages/opendesign/src/components/popup/types.ts @@ -0,0 +1,22 @@ +export enum PopupPosition { + TOP = 'top', // top + BOTTOM = 'bottom', // bottom + LEFT = 'left', // left + RIGHT = 'right', // right + LT = 'lt', //left-top + TL = 'tl', //top-left + LB = 'lb', //left-bottom, + BL = 'bl', //bottom-left, + RT = 'rt', //right-top, + TR = 'tr', //top-right, + RB = 'rb', //right-bottom, + BR = 'br', //bottom-right, +} + +export enum PopupTrigger { + NULL = 'null', + HOVER = 'hover', + CLICK = 'click', + FOUCS = 'foucs', + CONTEXT_MENU = 'contextmenu' +} \ No newline at end of file diff --git a/packages/portal/src/pages/ThePopup.vue b/packages/portal/src/pages/ThePopup.vue new file mode 100644 index 00000000..13881187 --- /dev/null +++ b/packages/portal/src/pages/ThePopup.vue @@ -0,0 +1,15 @@ + + + diff --git a/packages/portal/src/router.ts b/packages/portal/src/router.ts index e5911eed..1a2ac25e 100644 --- a/packages/portal/src/router.ts +++ b/packages/portal/src/router.ts @@ -20,6 +20,18 @@ export const routes = [ label: '开关', component: () => import('./pages/TheSwitch.vue'), }, + { + path: '/popup', + name: 'Popup', + label: '浮层', + component: () => import('./pages/ThePopup.vue'), + }, + { + path: '/select', + name: 'Select', + label: '下拉框', + component: () => import('./pages/TheSelect.vue'), + }, ]; export const router = createRouter({ -- Gitee From 9cac9c7bfaaee34b6dc67287a76619231b0cf1fd Mon Sep 17 00:00:00 2001 From: devin Date: Fri, 23 Dec 2022 22:14:21 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0resizeobserver=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=8F=8Ahook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/opendesign/package.json | 3 +- .../src/components/_shared/utils.ts | 3 ++ .../opendesign/src/components/hooks/index.ts | 1 + .../components/hooks/use-resize-observer.ts | 32 ++++++++++++++++ packages/opendesign/src/components/index.ts | 2 + .../__demo__/ResizeObserver.vue | 37 +++++++++++++++++++ .../src/components/resize-observer/index.ts | 1 + .../resize-observer/resize-observer.ts | 34 +++++++++++++++++ .../portal/src/pages/TheResizeObserver.vue | 11 ++++++ packages/portal/src/router.ts | 6 +++ 10 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 packages/opendesign/src/components/hooks/index.ts create mode 100644 packages/opendesign/src/components/hooks/use-resize-observer.ts create mode 100644 packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue create mode 100644 packages/opendesign/src/components/resize-observer/index.ts create mode 100644 packages/opendesign/src/components/resize-observer/resize-observer.ts create mode 100644 packages/portal/src/pages/TheResizeObserver.vue diff --git a/packages/opendesign/package.json b/packages/opendesign/package.json index 038394c8..4e19c363 100644 --- a/packages/opendesign/package.json +++ b/packages/opendesign/package.json @@ -31,6 +31,7 @@ "vue-tsc": "^1.0.13" }, "dependencies": { - "@vue/shared": "^3.2.45" + "@vue/shared": "^3.2.45", + "resize-observer-polyfill": "^1.5.1" } } \ No newline at end of file diff --git a/packages/opendesign/src/components/_shared/utils.ts b/packages/opendesign/src/components/_shared/utils.ts index e69de29b..bd67757e 100644 --- a/packages/opendesign/src/components/_shared/utils.ts +++ b/packages/opendesign/src/components/_shared/utils.ts @@ -0,0 +1,3 @@ +export function isFunction(fn: any) { + return typeof fn === 'function'; +} \ No newline at end of file diff --git a/packages/opendesign/src/components/hooks/index.ts b/packages/opendesign/src/components/hooks/index.ts new file mode 100644 index 00000000..31fd89e8 --- /dev/null +++ b/packages/opendesign/src/components/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-resize-observer'; \ No newline at end of file diff --git a/packages/opendesign/src/components/hooks/use-resize-observer.ts b/packages/opendesign/src/components/hooks/use-resize-observer.ts new file mode 100644 index 00000000..af556538 --- /dev/null +++ b/packages/opendesign/src/components/hooks/use-resize-observer.ts @@ -0,0 +1,32 @@ +import ResizeObserver from 'resize-observer-polyfill'; +import { isFunction } from '../_shared/utils'; + +export function useResizeObserver() { + let ro: ResizeObserver | null = null; + + // 创建监听实例 + const createResizeObserver = (el: HTMLElement, onResize: (en: ResizeObserverEntry) => void) => { + if (!el) { + return; + } + ro = new ResizeObserver((entries: ResizeObserverEntry[]) => { + if (isFunction(onResize)) { + onResize(entries[0]); + } + }); + ro.observe(el); + }; + + // 销毁监听 + const destoryResizeObserver = () => { + if (ro) { + ro.disconnect(); + ro = null; + } + }; + + return { + createResizeObserver, + destoryResizeObserver + }; +}; \ No newline at end of file diff --git a/packages/opendesign/src/components/index.ts b/packages/opendesign/src/components/index.ts index fa182398..2fecc76c 100644 --- a/packages/opendesign/src/components/index.ts +++ b/packages/opendesign/src/components/index.ts @@ -1,5 +1,7 @@ export * from './_shared/global'; +export * from './hooks'; + export * from './button'; export * from './switch'; diff --git a/packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue b/packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue new file mode 100644 index 00000000..3678619f --- /dev/null +++ b/packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue @@ -0,0 +1,37 @@ + + + diff --git a/packages/opendesign/src/components/resize-observer/index.ts b/packages/opendesign/src/components/resize-observer/index.ts new file mode 100644 index 00000000..6853e703 --- /dev/null +++ b/packages/opendesign/src/components/resize-observer/index.ts @@ -0,0 +1 @@ +export { default as ResizeObserver } from './resize-observer'; \ No newline at end of file diff --git a/packages/opendesign/src/components/resize-observer/resize-observer.ts b/packages/opendesign/src/components/resize-observer/resize-observer.ts new file mode 100644 index 00000000..e93b1821 --- /dev/null +++ b/packages/opendesign/src/components/resize-observer/resize-observer.ts @@ -0,0 +1,34 @@ +import { defineComponent, onBeforeUnmount, onMounted, ref, cloneVNode, ComponentPublicInstance } from 'vue'; +import { useResizeObserver } from '../hooks'; + +export default defineComponent({ + name: 'ResizeObserver', + emits: ['resize'], + setup(props, { emit, slots }) { + const { createResizeObserver, destoryResizeObserver } = useResizeObserver(); + + const childRef = ref(null); + + onMounted(() => { + if (childRef.value) { + createResizeObserver((childRef.value as ComponentPublicInstance).$el || childRef.value, (entry: ResizeObserverEntry) => { + emit('resize', entry); + }); + } + + }); + + onBeforeUnmount(() => { + destoryResizeObserver(); + }); + + return () => { + const children = slots.default?.(); + if (children) { + return cloneVNode(children[0], { ref: childRef }); + } else { + return null; + } + }; + }, +}); diff --git a/packages/portal/src/pages/TheResizeObserver.vue b/packages/portal/src/pages/TheResizeObserver.vue new file mode 100644 index 00000000..1216b237 --- /dev/null +++ b/packages/portal/src/pages/TheResizeObserver.vue @@ -0,0 +1,11 @@ + + + diff --git a/packages/portal/src/router.ts b/packages/portal/src/router.ts index 1a2ac25e..f3204227 100644 --- a/packages/portal/src/router.ts +++ b/packages/portal/src/router.ts @@ -26,6 +26,12 @@ export const routes = [ label: '浮层', component: () => import('./pages/ThePopup.vue'), }, + { + path: '/resize-observer', + name: 'ResizeObserver', + label: 'resize监听', + component: () => import('./pages/TheResizeObserver.vue'), + }, { path: '/select', name: 'Select', -- Gitee From 1154f3e95215323f1fd5da3e85b6725e9b3e07da Mon Sep 17 00:00:00 2001 From: devin Date: Fri, 23 Dec 2022 22:15:58 +0800 Subject: [PATCH 03/28] =?UTF-8?q?demo=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/resize-observer/__demo__/ResizeObserver.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue b/packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue index 3678619f..fb48a546 100644 --- a/packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue +++ b/packages/opendesign/src/components/resize-observer/__demo__/ResizeObserver.vue @@ -18,12 +18,12 @@ const onResize = () => {