mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-13 20:03:51 +02:00
fix dialog z-indexing issue
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
<VRCXUpdateDialog></VRCXUpdateDialog>
|
<VRCXUpdateDialog></VRCXUpdateDialog>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="x-dialog-portal" class="x-dialog-portal"></div>
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui';
|
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui';
|
||||||
|
|
||||||
|
import AlertDialogStateProvider from './AlertDialogStateProvider.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
open: { type: Boolean, required: false },
|
open: { type: Boolean, required: false },
|
||||||
defaultOpen: { type: Boolean, required: false }
|
defaultOpen: { type: Boolean, required: false }
|
||||||
@@ -12,6 +14,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AlertDialogRoot v-slot="slotProps" data-slot="alert-dialog" v-bind="forwarded">
|
<AlertDialogRoot v-slot="slotProps" data-slot="alert-dialog" v-bind="forwarded">
|
||||||
<slot v-bind="slotProps" />
|
<AlertDialogStateProvider :open="slotProps.open">
|
||||||
|
<slot v-bind="slotProps" />
|
||||||
|
</AlertDialogStateProvider>
|
||||||
</AlertDialogRoot>
|
</AlertDialogRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { AlertDialogContent, AlertDialogOverlay, AlertDialogPortal, useForwardPropsEmits } from 'reka-ui';
|
import { AlertDialogContent, AlertDialogOverlay, AlertDialogPortal, useForwardPropsEmits } from 'reka-ui';
|
||||||
|
import { inject, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
|
import { acquireModalPortalLayer } from '@/lib/modalPortalLayers';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { reactiveOmit } from '@vueuse/core';
|
import { reactiveOmit } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { ALERT_DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false
|
inheritAttrs: false
|
||||||
});
|
});
|
||||||
@@ -26,10 +30,30 @@
|
|||||||
const delegatedProps = reactiveOmit(props, 'class');
|
const delegatedProps = reactiveOmit(props, 'class');
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
|
const injectedOpen = inject(ALERT_DIALOG_OPEN_INJECTION_KEY, null);
|
||||||
|
const open = injectedOpen ?? ref(true);
|
||||||
|
|
||||||
|
const portalLayer = acquireModalPortalLayer();
|
||||||
|
const portalTo = portalLayer.element;
|
||||||
|
|
||||||
|
watch(
|
||||||
|
open,
|
||||||
|
(isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
portalLayer.bringToFront();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
portalLayer.release();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal :to="portalTo">
|
||||||
<AlertDialogOverlay
|
<AlertDialogOverlay
|
||||||
data-slot="alert-dialog-overlay"
|
data-slot="alert-dialog-overlay"
|
||||||
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80" />
|
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80" />
|
||||||
|
|||||||
15
src/components/ui/alert-dialog/AlertDialogStateProvider.vue
Normal file
15
src/components/ui/alert-dialog/AlertDialogStateProvider.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup>
|
||||||
|
import { provide, toRef } from 'vue';
|
||||||
|
|
||||||
|
import { ALERT_DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
open: { type: Boolean, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
provide(ALERT_DIALOG_OPEN_INJECTION_KEY, toRef(props, 'open'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
1
src/components/ui/alert-dialog/context.js
Normal file
1
src/components/ui/alert-dialog/context.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const ALERT_DIALOG_OPEN_INJECTION_KEY = Symbol('vrcx-alert-dialog-open');
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { DialogRoot, useForwardPropsEmits } from 'reka-ui';
|
import { DialogRoot, useForwardPropsEmits } from 'reka-ui';
|
||||||
|
|
||||||
|
import DialogStateProvider from './DialogStateProvider.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
open: { type: Boolean, required: false },
|
open: { type: Boolean, required: false },
|
||||||
defaultOpen: { type: Boolean, required: false },
|
defaultOpen: { type: Boolean, required: false },
|
||||||
@@ -13,6 +15,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogRoot v-slot="slotProps" data-slot="dialog" v-bind="forwarded">
|
<DialogRoot v-slot="slotProps" data-slot="dialog" v-bind="forwarded">
|
||||||
<slot v-bind="slotProps" />
|
<DialogStateProvider :open="slotProps.open">
|
||||||
|
<slot v-bind="slotProps" />
|
||||||
|
</DialogStateProvider>
|
||||||
</DialogRoot>
|
</DialogRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
||||||
|
import { inject, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
import { X } from 'lucide-vue-next';
|
import { X } from 'lucide-vue-next';
|
||||||
|
import { acquireModalPortalLayer } from '@/lib/modalPortalLayers';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { reactiveOmit } from '@vueuse/core';
|
import { reactiveOmit } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||||
|
|
||||||
import DialogOverlay from './DialogOverlay.vue';
|
import DialogOverlay from './DialogOverlay.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -30,10 +34,30 @@
|
|||||||
const delegatedProps = reactiveOmit(props, 'class');
|
const delegatedProps = reactiveOmit(props, 'class');
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
|
const injectedOpen = inject(DIALOG_OPEN_INJECTION_KEY, null);
|
||||||
|
const open = injectedOpen ?? ref(true);
|
||||||
|
|
||||||
|
const portalLayer = acquireModalPortalLayer();
|
||||||
|
const portalTo = portalLayer.element;
|
||||||
|
|
||||||
|
watch(
|
||||||
|
open,
|
||||||
|
(isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
portalLayer.bringToFront();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
portalLayer.release();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal :to="portalTo">
|
||||||
<DialogOverlay />
|
<DialogOverlay />
|
||||||
<DialogContent
|
<DialogContent
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
||||||
|
import { inject, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
import { X } from 'lucide-vue-next';
|
import { X } from 'lucide-vue-next';
|
||||||
|
import { acquireModalPortalLayer } from '@/lib/modalPortalLayers';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { reactiveOmit } from '@vueuse/core';
|
import { reactiveOmit } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false
|
inheritAttrs: false
|
||||||
});
|
});
|
||||||
@@ -27,10 +31,30 @@
|
|||||||
const delegatedProps = reactiveOmit(props, 'class');
|
const delegatedProps = reactiveOmit(props, 'class');
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
|
const injectedOpen = inject(DIALOG_OPEN_INJECTION_KEY, null);
|
||||||
|
const open = injectedOpen ?? ref(true);
|
||||||
|
|
||||||
|
const portalLayer = acquireModalPortalLayer();
|
||||||
|
const portalTo = portalLayer.element;
|
||||||
|
|
||||||
|
watch(
|
||||||
|
open,
|
||||||
|
(isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
portalLayer.bringToFront();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
portalLayer.release();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal :to="portalTo">
|
||||||
<DialogOverlay
|
<DialogOverlay
|
||||||
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0">
|
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0">
|
||||||
<DialogContent
|
<DialogContent
|
||||||
|
|||||||
15
src/components/ui/dialog/DialogStateProvider.vue
Normal file
15
src/components/ui/dialog/DialogStateProvider.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup>
|
||||||
|
import { provide, toRef } from 'vue';
|
||||||
|
|
||||||
|
import { DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
open: { type: Boolean, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
provide(DIALOG_OPEN_INJECTION_KEY, toRef(props, 'open'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
promptErrorMessage
|
promptErrorMessage
|
||||||
} = storeToRefs(modalStore);
|
} = storeToRefs(modalStore);
|
||||||
|
|
||||||
const { handleSubmit, resetForm, setValues, values } = useForm({
|
const { handleSubmit, resetForm, values } = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
value: ''
|
value: ''
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,11 @@
|
|||||||
[promptOpen, promptInputValue],
|
[promptOpen, promptInputValue],
|
||||||
([open, inputValue]) => {
|
([open, inputValue]) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
setValues({ value: inputValue ?? '' });
|
resetForm({
|
||||||
|
values: {
|
||||||
|
value: inputValue ?? ''
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +114,19 @@
|
|||||||
@pointerDownOutside="onPointerDownOutside"
|
@pointerDownOutside="onPointerDownOutside"
|
||||||
@interactOutside="onInteractOutside">
|
@interactOutside="onInteractOutside">
|
||||||
<form @submit="onSubmit">
|
<form @submit="onSubmit">
|
||||||
<DialogHeader>
|
<DialogHeader class="mb-5">
|
||||||
<DialogTitle>{{ promptTitle }}</DialogTitle>
|
<DialogTitle>{{ promptTitle }}</DialogTitle>
|
||||||
<DialogDescription>{{ promptDescription }}</DialogDescription>
|
<DialogDescription>{{ promptDescription }}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<FormField name="value" :rules="validateValue" v-slot="{ componentField }">
|
<FormField
|
||||||
|
name="value"
|
||||||
|
:rules="validateValue"
|
||||||
|
:validate-on-blur="false"
|
||||||
|
:validate-on-change="false"
|
||||||
|
:validate-on-input="false"
|
||||||
|
:validate-on-model-update="false"
|
||||||
|
v-slot="{ componentField }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel class="sr-only">Input</FormLabel>
|
<FormLabel class="sr-only">Input</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -125,7 +136,7 @@
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter class="mt-5">
|
||||||
<Button type="button" variant="outline" @click="handleCancel">
|
<Button type="button" variant="outline" @click="handleCancel">
|
||||||
{{ promptCancelText }}
|
{{ promptCancelText }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
1
src/components/ui/dialog/context.js
Normal file
1
src/components/ui/dialog/context.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const DIALOG_OPEN_INJECTION_KEY = Symbol('vrcx-dialog-open');
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
@update:modelValue="onValueChange">
|
@update:modelValue="onValueChange">
|
||||||
<TabsList :class="listClass" :aria-label="ariaLabel || undefined">
|
<TabsList :class="listClass" :aria-label="ariaLabel || undefined">
|
||||||
<TabsIndicator
|
<TabsIndicator
|
||||||
class="pointer-events-none absolute left-0 bottom-0 z-20 h-0.5 w-(--reka-tabs-indicator-size) translate-x-(--reka-tabs-indicator-position) transition-[width,translate] duration-200 ease-out">
|
class="pointer-events-none absolute left-0 bottom-0 h-0.5 w-(--reka-tabs-indicator-size) translate-x-(--reka-tabs-indicator-position) transition-[width,translate] duration-200 ease-out">
|
||||||
<div class="h-full w-full rounded-full bg-primary" />
|
<div class="h-full w-full rounded-full bg-primary" />
|
||||||
</TabsIndicator>
|
</TabsIndicator>
|
||||||
|
|
||||||
|
|||||||
65
src/lib/modalPortalLayers.js
Normal file
65
src/lib/modalPortalLayers.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
const MODAL_PORTAL_ROOT_ID = 'vrcx-modal-portal-root';
|
||||||
|
const APP_PORTAL_ROOT_ID = 'x-dialog-portal';
|
||||||
|
|
||||||
|
const BASE_Z_INDEX = 50;
|
||||||
|
const Z_STEP = 10;
|
||||||
|
|
||||||
|
let nextLayerIndex = 0;
|
||||||
|
|
||||||
|
function ensureModalPortalRoot() {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let root = document.getElementById(APP_PORTAL_ROOT_ID);
|
||||||
|
if (root) {
|
||||||
|
root.style.position ||= 'relative';
|
||||||
|
root.style.isolation ||= 'isolate';
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = document.getElementById(MODAL_PORTAL_ROOT_ID);
|
||||||
|
if (root) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = document.createElement('div');
|
||||||
|
root.id = MODAL_PORTAL_ROOT_ID;
|
||||||
|
root.style.position = 'relative';
|
||||||
|
root.style.isolation = 'isolate';
|
||||||
|
document.body.appendChild(root);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function acquireModalPortalLayer() {
|
||||||
|
const root = ensureModalPortalRoot();
|
||||||
|
if (!root) {
|
||||||
|
return {
|
||||||
|
element: undefined,
|
||||||
|
bringToFront() {},
|
||||||
|
release() {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = document.createElement('div');
|
||||||
|
element.dataset.vrcxPortalLayer = 'modal';
|
||||||
|
element.style.position = 'relative';
|
||||||
|
element.style.isolation = 'isolate';
|
||||||
|
root.appendChild(element);
|
||||||
|
|
||||||
|
const bringToFront = () => {
|
||||||
|
nextLayerIndex += 1;
|
||||||
|
element.style.zIndex = String(BASE_Z_INDEX + nextLayerIndex * Z_STEP);
|
||||||
|
root.appendChild(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
const release = () => {
|
||||||
|
element.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
element,
|
||||||
|
bringToFront,
|
||||||
|
release
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user