From 1cb6ffabd6f6c114ceca781fba3b59dff987b065 Mon Sep 17 00:00:00 2001 From: huaweidevcloud Date: Sat, 26 Aug 2023 17:07:32 +0800 Subject: [PATCH] refactor: drawer --- .../devui/drawer/__tests__/drawer.spec.tsx | 2 +- .../drawer/src/components/drawer-overlay.scss | 2 +- .../drawer/src/components/drawer-overlay.tsx | 2 +- packages/devui-vue/devui/drawer/src/const.ts | 1 + .../devui/drawer/src/drawer-types.ts | 16 ++--- .../devui-vue/devui/drawer/src/drawer.tsx | 23 +++--- .../devui-vue/devui/drawer/src/use-drawer.ts | 70 +++++++++++++------ .../devui/shared/utils/focus-stack.ts | 29 ++++++++ 8 files changed, 106 insertions(+), 39 deletions(-) create mode 100644 packages/devui-vue/devui/drawer/src/const.ts create mode 100644 packages/devui-vue/devui/shared/utils/focus-stack.ts diff --git a/packages/devui-vue/devui/drawer/__tests__/drawer.spec.tsx b/packages/devui-vue/devui/drawer/__tests__/drawer.spec.tsx index 4d11227e42..195a18fd67 100644 --- a/packages/devui-vue/devui/drawer/__tests__/drawer.spec.tsx +++ b/packages/devui-vue/devui/drawer/__tests__/drawer.spec.tsx @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import { nextTick, ref } from 'vue'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import { wait } from '../../shared/utils/wait'; import Drawer from '../src/drawer'; import DrawerService from '../src/drawer-service'; diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-overlay.scss b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.scss index cf09e7654d..cadcc19258 100644 --- a/packages/devui-vue/devui/drawer/src/components/drawer-overlay.scss +++ b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.scss @@ -1,4 +1,4 @@ -@import '../../../styles-var/devui-var.scss'; +@import '@devui/theme/styles-var/devui-var.scss'; .#{$devui-prefix}-drawer__overlay { position: fixed; diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-overlay.tsx b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.tsx index 41b38cd8f0..5c97306e84 100644 --- a/packages/devui-vue/devui/drawer/src/components/drawer-overlay.tsx +++ b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.tsx @@ -1,7 +1,7 @@ import { defineComponent, Transition } from 'vue'; import type { SetupContext } from 'vue'; import { drawerOverlayProps, DrawerOverlayProps } from '../drawer-types'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import './drawer-overlay.scss'; export default defineComponent({ diff --git a/packages/devui-vue/devui/drawer/src/const.ts b/packages/devui-vue/devui/drawer/src/const.ts new file mode 100644 index 0000000000..41698cb206 --- /dev/null +++ b/packages/devui-vue/devui/drawer/src/const.ts @@ -0,0 +1 @@ +export const ZINDEX = 1; diff --git a/packages/devui-vue/devui/drawer/src/drawer-types.ts b/packages/devui-vue/devui/drawer/src/drawer-types.ts index 5aef389cca..ef85dd58c8 100644 --- a/packages/devui-vue/devui/drawer/src/drawer-types.ts +++ b/packages/devui-vue/devui/drawer/src/drawer-types.ts @@ -1,4 +1,4 @@ -import type { ExtractPropTypes, PropType, Slot, Ref } from 'vue'; +import type { ExtractPropTypes, PropType, Slot } from 'vue'; export const drawerProps = { modelValue: { @@ -9,6 +9,9 @@ export const drawerProps = { type: Number, default: 1040, }, + width: { + type: [Number, String] + }, showOverlay: { type: Boolean, default: true, @@ -32,6 +35,10 @@ export const drawerProps = { beforeClose: { type: Function as PropType<(done: () => void) => void>, }, + appendToBody: { + type: Boolean, + default: true + } }; export const drawerOverlayProps = { @@ -53,10 +60,3 @@ export type DrawerProps = ExtractPropTypes; export type DrawerOverlayProps = ExtractPropTypes; export type DrawerOptions = Partial & { content?: string | Slot }; - -export type UseDrawerFn = { - overlayRef: Ref; - drawerRef: Ref; - drawerClasses: Ref>; - handleOverlayClick: () => void; -}; diff --git a/packages/devui-vue/devui/drawer/src/drawer.tsx b/packages/devui-vue/devui/drawer/src/drawer.tsx index 0dc05777f6..7656ea9350 100644 --- a/packages/devui-vue/devui/drawer/src/drawer.tsx +++ b/packages/devui-vue/devui/drawer/src/drawer.tsx @@ -1,4 +1,4 @@ -import { defineComponent, Teleport, Transition } from 'vue'; +import { defineComponent, toRefs, Teleport, Transition, computed } from 'vue'; import { drawerProps, DrawerProps } from './drawer-types'; import DrawerOverlay from './components/drawer-overlay'; import { useDrawer } from './use-drawer'; @@ -10,15 +10,22 @@ export default defineComponent({ props: drawerProps, emits: ['close', 'update:modelValue', 'open'], setup(props: DrawerProps, { emit, slots, attrs }) { - const { overlayRef, drawerRef, drawerClasses, handleOverlayClick } = useDrawer(props, emit); + const { showOverlay, modelValue, position, appendToBody, width } = toRefs(props); + const drawerWidth = computed(() => { + if (width?.value !== undefined) { + return typeof width.value === 'number' ? `${width.value}px` : width.value; + } + return undefined; + }); + const { overlayRef, drawerRef, drawerClasses, overlayZIndex, drawerZIndex, handleOverlayClick } = useDrawer(props, emit); return () => ( - - {props.showOverlay && ( - + + {showOverlay.value && ( + )} - - {props.modelValue && ( -
+ + {modelValue.value && ( +
{slots.default?.()}
)} diff --git a/packages/devui-vue/devui/drawer/src/use-drawer.ts b/packages/devui-vue/devui/drawer/src/use-drawer.ts index bf3dac018e..61022c7652 100644 --- a/packages/devui-vue/devui/drawer/src/use-drawer.ts +++ b/packages/devui-vue/devui/drawer/src/use-drawer.ts @@ -1,18 +1,32 @@ -import { computed, onUnmounted, ref, watch } from 'vue'; -import { onClickOutside } from '@vueuse/core'; -import { DrawerEmit, DrawerProps, UseDrawerFn } from './drawer-types'; -import { lockScroll } from '../../shared/utils/lock-scroll'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { computed, onUnmounted, ref, watch, toRefs } from 'vue'; +import { onClickOutside, OnClickOutsideOptions } from '@vueuse/core'; +import { DrawerEmit, DrawerProps } from './drawer-types'; +import { lockScroll, } from '../../shared/utils/lock-scroll'; +import { focusStack } from '../../shared/utils/focus-stack'; +import type { FoucusLayer } from '../../shared/utils/focus-stack'; +import { useNamespace } from '@devui/shared/utils'; +import { ZINDEX } from './const'; -export function useDrawer(props: DrawerProps, emit: DrawerEmit): UseDrawerFn { +export function useDrawer(props: DrawerProps, emit: DrawerEmit) { const ns = useNamespace('drawer'); - const modalNs = useNamespace('modal', true); + const { zIndex } = toRefs(props); const drawerRef = ref(); const overlayRef = ref(); + const overlayZIndex = ref(zIndex.value - 1); + const drawerZIndex = ref(zIndex.value); + const paused = ref(false); const drawerClasses = computed(() => ({ [ns.b()]: true, [ns.m(props.position)]: true, })); + const layer: FoucusLayer = { + pause() { + paused.value = true; + }, + resume() { + paused.value = false; + } + }; const close = () => { emit('update:modelValue', false); emit('close'); @@ -22,46 +36,62 @@ export function useDrawer(props: DrawerProps, emit: DrawerEmit): UseDrawerFn { props.beforeClose ? props.beforeClose(close) : close(); }; const handleOverlayClick = () => { - props.closeOnClickOverlay && execClose(); + if (props.closeOnClickOverlay && !paused.value) { + execClose(); + } }; const handleEscClose = (e: KeyboardEvent) => { - e.code === 'Escape' && execClose(); + if (e.code === 'Escape' && !paused.value) { + execClose(); + } }; - const handleClickOutside = (e: PointerEvent) => { - const composedPath = e.composedPath(); - const modalOverlay = document.querySelectorAll(modalNs.e('overlay')); - const modal = document.querySelectorAll(modalNs.b()); - const isClickModalOverlay = Array.from(modalOverlay).filter((item) => composedPath.includes(item)); - const isClickModal = Array.from(modal).filter((item) => composedPath.includes(item)); - if (isClickModalOverlay.length || isClickModal.length) { + const handleClickOutside = () => { + if (paused.value) { return; } execClose(); }; setTimeout(() => { - onClickOutside(drawerRef, handleClickOutside, { capture: false, ignore: [overlayRef] }); + onClickOutside(drawerRef, handleClickOutside, { capture: false, ignore: [overlayRef] } as OnClickOutsideOptions); }); + const calculateZIndex = () => { + const drawerDomLength = document.querySelectorAll(`.${ns.b()}`).length; + overlayZIndex.value = zIndex.value + (drawerDomLength - 1) * ZINDEX - 1; + drawerZIndex.value = zIndex.value + (drawerDomLength - 1) * ZINDEX; + }; + const removeBodyAdditions = () => { + window._dvDrawerIsOpen = false; lockScrollCb?.(); document.removeEventListener('keyup', handleEscClose); + setTimeout(() => { + focusStack.remove(layer); + }); }; watch( () => props.modelValue, - (val) => { + (val, oldVal) => { if (val) { emit('open'); props.lockScroll && (lockScrollCb = lockScroll()); props.escKeyCloseable && document.addEventListener('keyup', handleEscClose); + window._dvDrawerIsOpen = true; + calculateZIndex(); + focusStack.push(layer); } else { - removeBodyAdditions(); + oldVal !== undefined && removeBodyAdditions(); } + }, + { + immediate: true, + flush: 'post' } ); onUnmounted(removeBodyAdditions); - return { overlayRef, drawerRef, drawerClasses, handleOverlayClick }; + return { overlayRef, drawerRef, drawerClasses, overlayZIndex, drawerZIndex, handleOverlayClick }; } diff --git a/packages/devui-vue/devui/shared/utils/focus-stack.ts b/packages/devui-vue/devui/shared/utils/focus-stack.ts new file mode 100644 index 0000000000..5bd2f9a09c --- /dev/null +++ b/packages/devui-vue/devui/shared/utils/focus-stack.ts @@ -0,0 +1,29 @@ +export interface FoucusLayer { + pause: () => void; + resume: () => void; +} +const removeFromStack = (stack: FoucusLayer[], layer: FoucusLayer) => { + const temp = [...stack]; + const index = temp.findIndex((item) => item === layer); + if (index !== -1) { + temp.splice(index, 1); + } + return temp; +}; +const createFocusStack = () => { + let stack: FoucusLayer[] = []; + const push = (layer: FoucusLayer) => { + const len = stack.length; + const curLayer = stack[len - 1]; + if (curLayer && curLayer !== layer) { + curLayer.pause(); + } + stack.push(layer); + }; + const remove = (layer: FoucusLayer) => { + stack = removeFromStack(stack, layer); + stack[stack.length - 1]?.resume(); + }; + return { push, remove }; +}; +export const focusStack = createFocusStack();