better Vue portals
vue-portal.ts edited
80 lines 1.6 kB view raw
1import { createContext } from 'radix-vue'; 2import { 3 type Ref, 4 type Slot, 5 type SlotsType, 6 computed, 7 defineComponent, 8 onUnmounted, 9 shallowRef, 10 toRef, 11} from 'vue'; 12 13const [inject, provide] = createContext<{ 14 held: Ref<Ref<Slot | undefined>[]>; 15}>('DrawerPortal'); 16 17export const Root = defineComponent({ 18 inheritAttrs: false, 19 slots: Object as SlotsType<{ 20 default(props: { open: boolean }): unknown; 21 }>, 22 setup(_props, { slots }) { 23 const { held } = provide({ 24 held: shallowRef([]), 25 }); 26 27 const isOpen = computed(() => held.value.length > 0); 28 29 return () => { 30 return slots.default({ open: isOpen.value }); 31 }; 32 }, 33}); 34 35export const useIsPortalOpen = (): Ref<boolean> => { 36 const context = inject(null); 37 if (!context) { 38 return toRef(() => false); 39 } 40 41 const { held } = context; 42 const isOpen = computed(() => held.value.length > 0); 43 44 return isOpen; 45}; 46 47export const Outlet = defineComponent({ 48 inheritAttrs: false, 49 setup(_props) { 50 const { held } = inject(); 51 const top = computed(() => held.value.at(0)); 52 53 return () => { 54 const container = top.value; 55 if (container !== undefined) { 56 const slot = container.value; 57 return slot?.(); 58 } 59 }; 60 }, 61}); 62 63export const Content = defineComponent({ 64 inheritAttrs: false, 65 setup(_props, { slots }) { 66 const { held } = inject(); 67 68 const container = shallowRef<Slot>(); 69 70 held.value = [...held.value, container]; 71 onUnmounted(() => { 72 held.value = held.value.filter((c) => c !== container); 73 }); 74 75 return () => { 76 container.value = slots.default; 77 return undefined; 78 }; 79 }, 80});