1+ <template >
2+ <Teleport v-if =" state.attached" :to =" globals?.container || 'body'" >
3+ <UiTransition
4+ name =" fade-2"
5+ @before-enter =" visibilityOfOverlay = 'showing'"
6+ @after-enter =" visibilityOfOverlay = 'shown'"
7+ @before-leave =" visibilityOfOverlay = 'hiding'"
8+ @after-leave =" visibilityOfOverlay = 'hidden'"
9+ >
10+ <div
11+ v-show =" state.shown"
12+ :id =" id"
13+ :aria-hidden =" visibility !== 'shown' ? 'true' : 'false'"
14+ :class =" [$attrs.class, {
15+ ['ui-v1-modal']: true,
16+ ['ui-v1-modal_overlapped']: state.overlapped,
17+ ['ui-v1-modal-sidebar-overlay']: true,
18+ ['ui-v1-modal-sidebar-overlay_fixed']: fixed,
19+ [`ui-v1-modal-sidebar-overlay_${direction}`]: fixed,
20+ }]"
21+ aria-modal =" true"
22+ v-bind =" $attrs"
23+ @click =" onBackdropClick"
24+ >
25+ <UiTransition
26+ :name =" `slide-${direction}`"
27+ @before-enter =" visibilityOfSidebar = 'showing'"
28+ @after-enter =" visibilityOfSidebar = 'shown'"
29+ @before-leave =" visibilityOfSidebar = 'hiding'"
30+ @after-leave =" visibilityOfSidebar = 'hidden'"
31+ >
32+ <aside
33+ v-if =" state.shown"
34+ :id =" id + '-sidebar'"
35+ :class =" {
36+ 'ui-v1-modal-sidebar': true,
37+ 'ui-v1-modal-sidebar_left': direction === DIRECTION.LEFT,
38+ 'ui-v1-modal-sidebar_size_sm': size === SIZE.SM,
39+ 'ui-v1-modal-sidebar_size_lg': size === SIZE.LG,
40+ }"
41+ >
42+ <header class =" ui-v1-modal-sidebar__header" >
43+ <div class =" ui-v1-modal-sidebar__header-inner" >
44+ <div
45+ aria-level =" 1"
46+ class =" ui-v1-modal-sidebar__title"
47+ role =" heading"
48+ >
49+ <slot name =" title" :overlapped =" state.overlapped" />
50+ </div >
51+ </div >
52+
53+ <div
54+ ref =" closer"
55+ aria-label =" Esc"
56+ class =" ui-v1-modal-sidebar__close"
57+ role =" button"
58+ @click =" onCloserClick"
59+ >
60+ <IconClear aria-hidden =" true" width =" 32" />
61+
62+ <UiTooltip :target =" closerTarget" :offset-main-axis =" 0" >
63+ Esc
64+ </UiTooltip >
65+ </div >
66+ </header >
67+
68+ <div v-if =" scrolling === SCROLLING.NONE" class =" ui-v1-modal-sidebar__body-fixed" >
69+ <slot :overlapped =" state.overlapped" />
70+ </div >
71+
72+ <UiScrollBox
73+ v-else
74+ :native =" scrolling === SCROLLING.NATIVE"
75+ class =" ui-v1-modal-sidebar__body"
76+ show-on-mac
77+ @ps-y-reach-end =" $emit('scroll:y:end')"
78+ >
79+ <slot :overlapped =" state.overlapped" />
80+ </UiScrollBox >
81+
82+ <footer
83+ v-if =" 'footer' in $slots"
84+ class =" ui-v1-modal-sidebar__footer"
85+ >
86+ <slot name =" footer" :overlapped =" state.overlapped" />
87+ </footer >
88+ </aside >
89+ </UiTransition >
90+ </div >
91+ </UiTransition >
92+ </Teleport >
93+ </template >
94+
195<script lang="ts" setup>
2- import type { PropType , VNode } from ' vue'
96+ import type { PropType } from ' vue'
397
498import type { Layer } from ' @/host/components/modal/layer'
599import type { EmbedModal } from ' @/host/components/modal/plugin'
6100
7101import UiScrollBox from ' @/host/components/scroll-box/UiScrollBox.vue'
8102import UiTransition from ' @/host/components/transition/UiTransition.vue'
103+ import UiTooltip from ' @/host/components/tooltip/UiTooltip.vue'
9104
10105import IconClear from ' ~assets/sprites/actions/clear.svg'
11106
12107import {
13- Teleport ,
14108 computed ,
15- h ,
16109 inject ,
17110 onActivated ,
18111 onBeforeUnmount ,
19112 onDeactivated ,
20113 onMounted ,
21114 reactive ,
22115 ref ,
23- useAttrs ,
24- useSlots ,
25- vShow ,
26116 watch ,
27- withDirectives ,
28117} from ' vue'
29118
30119import { ModalInjectKey } from ' @/host/components/modal/plugin'
@@ -119,7 +208,7 @@ const emit = defineEmits([
119208 ' scroll:y:end' ,
120209])
121210
122- const globals = inject <EmbedModal | null >(ModalInjectKey , null )
211+ const globals = inject <EmbedModal | null >(ModalInjectKey , null )
123212
124213const state = reactive ({
125214 attached: false ,
@@ -129,6 +218,9 @@ const state = reactive({
129218 overlapped: false ,
130219})
131220
221+ const closer = ref <HTMLElement | null >(null )
222+ const closerTarget = computed (() => closer )
223+
132224const layer = computed <Layer >(() => ({
133225 id: props .id ,
134226 isBlocker : () => ! props .fixed ,
@@ -270,121 +362,17 @@ onBeforeUnmount(() => {
270362 detach ()
271363})
272364
273- const attrs = useAttrs ()
274- const slots = useSlots ()
275-
276- const hasSlot = (slotName : string ): boolean => slotName in slots
277- const renderSlot = (slotName : string ) => {
278- const fn = slots [slotName ]
279- return fn ? fn ({ overlapped: state .overlapped }) : []
280- }
281- const normalizeSlot = (slotName : string ) => () => renderSlot (slotName )
282-
283- const renderHeader = (): VNode => {
284- return h (' header' , { class: ' ui-v1-modal-sidebar__header' }, [
285- h (' div' , { class: ' ui-v1-modal-sidebar__header-inner' }, h (' div' , {
286- role: ' heading' ,
287- ' aria-level' : ' 1' ,
288- class: ' ui-v1-modal-sidebar__title' ,
289- }, renderSlot (' title' ))),
290- h (' div' , {
291- role: ' button' ,
292- ' aria-label' : ' Esc' ,
293- class: ' ui-v1-modal-sidebar__close' ,
294- onClick : () => {
295- if (! state .overlapped ) {
296- close (CLOSE_METHOD .CLICK_CROSS )
297- }
298- },
299- }, h (IconClear , {
300- width: 32 ,
301- title: ' Esc' ,
302- })),
303- ])
304- }
305-
306- const renderBody = (): VNode => props .scrolling === SCROLLING .NONE
307- ? h (' div' , { class: ' ui-v1-modal-sidebar__body-fixed' }, renderSlot (' default' ))
308- : h (UiScrollBox , {
309- class: ' ui-v1-modal-sidebar__body' ,
310- native: props .scrolling === SCROLLING .NATIVE ,
311- showOnMac: true ,
312- onPsYReachEnd : () => emit (' scroll:y:end' ),
313- }, {
314- default: normalizeSlot (' default' ),
315- })
316-
317- const renderFooter = (): VNode [] => hasSlot (' footer' )
318- ? [h (' footer' , { class: ' ui-v1-modal-sidebar__footer' }, renderSlot (' footer' ))]
319- : []
320-
321- const renderSidebar = (): VNode => {
322- const direction = props .direction
323- const size = props .size
324-
325- const setVisibility = (visibility : Visibility ) => () => visibilityOfSidebar .value = visibility
326-
327- return h (UiTransition , {
328- name: ` slide-${direction } ` ,
329- onBeforeEnter: setVisibility (' showing' ),
330- onAfterEnter: setVisibility (' shown' ),
331- onBeforeLeave: setVisibility (' hiding' ),
332- onAfterLeave: setVisibility (' hidden' ),
333- }, {
334- default : () => state .shown ? [
335- h (' aside' , {
336- id: props .id + ' -sidebar' ,
337- class: {
338- ' ui-v1-modal-sidebar' : true ,
339- ' ui-v1-modal-sidebar_left' : direction === DIRECTION .LEFT ,
340- ' ui-v1-modal-sidebar_size_sm' : size === SIZE .SM ,
341- ' ui-v1-modal-sidebar_size_lg' : size === SIZE .LG ,
342- },
343- }, [
344- renderHeader (),
345- renderBody (),
346- ... renderFooter (),
347- ]),
348- ] : [],
349- })
365+ const onBackdropClick = (event : Event ) => {
366+ if (event .target === event .currentTarget && ! state .overlapped ) {
367+ close (CLOSE_METHOD .CLICK_OUTSIDE )
368+ }
350369}
351370
352- const EmbedModalSidebar = () => {
353- const setVisibility = (visibility : Visibility ) => () => visibilityOfOverlay .value = visibility
354-
355- return ! state .attached ? undefined : h (Teleport , {
356- to: globals ?.container ?? document .body ,
357- }, h (UiTransition , {
358- name: ' fade-2' ,
359- onBeforeEnter: setVisibility (' showing' ),
360- onAfterEnter: setVisibility (' shown' ),
361- onBeforeLeave: setVisibility (' hiding' ),
362- onAfterLeave: setVisibility (' hidden' ),
363- }, {
364- default : () => withDirectives (h (' div' , {
365- id: props .id ,
366- ' aria-hidden' : visibility .value !== ' shown' ? ' true' : ' false' ,
367- ' aria-modal' : ' true' ,
368- ... attrs ,
369- class: [attrs .class , {
370- ' ui-v1-modal' : true ,
371- ' ui-v1-modal_overlapped' : state .overlapped ,
372- ' ui-v1-modal-sidebar-overlay' : true ,
373- ' ui-v1-modal-sidebar-overlay_fixed' : props .fixed ,
374- [` ui-v1-modal-sidebar-overlay_${props .direction } ` ]: props .fixed ,
375- }],
376- onClick : (event : Event ) => {
377- if (event .target === event .currentTarget && ! state .overlapped ) {
378- close (CLOSE_METHOD .CLICK_OUTSIDE )
379- }
380- },
381- }, renderSidebar ()), [[vShow , state .shown ]]),
382- }))
371+ const onCloserClick = () => {
372+ if (! state .overlapped ) {
373+ close (CLOSE_METHOD .CLICK_CROSS )
374+ }
383375}
384376 </script >
385377
386- <template >
387- <EmbedModalSidebar />
388- </template >
389-
390378<style lang="less" src="./modal-sidebar.less " />
0 commit comments