vue-portal.ts
edited
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});