mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-06 00:32:01 +02:00
fix dialog z-indexing issue
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
<VRCXUpdateDialog></VRCXUpdateDialog>
|
||||
</div>
|
||||
<div id="x-dialog-portal" class="x-dialog-portal"></div>
|
||||
</el-config-provider>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup>
|
||||
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import AlertDialogStateProvider from './AlertDialogStateProvider.vue';
|
||||
|
||||
const props = defineProps({
|
||||
open: { type: Boolean, required: false },
|
||||
defaultOpen: { type: Boolean, required: false }
|
||||
@@ -12,6 +14,8 @@
|
||||
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<script setup>
|
||||
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 { reactiveOmit } from '@vueuse/core';
|
||||
|
||||
import { ALERT_DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
});
|
||||
@@ -26,10 +30,30 @@
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogPortal :to="portalTo">
|
||||
<AlertDialogOverlay
|
||||
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" />
|
||||
|
||||
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>
|
||||
import { DialogRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
import DialogStateProvider from './DialogStateProvider.vue';
|
||||
|
||||
const props = defineProps({
|
||||
open: { type: Boolean, required: false },
|
||||
defaultOpen: { type: Boolean, required: false },
|
||||
@@ -13,6 +15,8 @@
|
||||
|
||||
<template>
|
||||
<DialogRoot v-slot="slotProps" data-slot="dialog" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
<DialogStateProvider :open="slotProps.open">
|
||||
<slot v-bind="slotProps" />
|
||||
</DialogStateProvider>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script setup>
|
||||
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
||||
import { inject, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { acquireModalPortalLayer } from '@/lib/modalPortalLayers';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
|
||||
import { DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||
|
||||
import DialogOverlay from './DialogOverlay.vue';
|
||||
|
||||
defineOptions({
|
||||
@@ -30,10 +34,30 @@
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogPortal :to="portalTo">
|
||||
<DialogOverlay />
|
||||
<DialogContent
|
||||
data-slot="dialog-content"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script setup>
|
||||
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui';
|
||||
import { inject, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { acquireModalPortalLayer } from '@/lib/modalPortalLayers';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
|
||||
import { DIALOG_OPEN_INJECTION_KEY } from './context';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
});
|
||||
@@ -27,10 +31,30 @@
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogPortal :to="portalTo">
|
||||
<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">
|
||||
<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
|
||||
} = storeToRefs(modalStore);
|
||||
|
||||
const { handleSubmit, resetForm, setValues, values } = useForm({
|
||||
const { handleSubmit, resetForm, values } = useForm({
|
||||
initialValues: {
|
||||
value: ''
|
||||
}
|
||||
@@ -88,7 +88,11 @@
|
||||
[promptOpen, promptInputValue],
|
||||
([open, inputValue]) => {
|
||||
if (open) {
|
||||
setValues({ value: inputValue ?? '' });
|
||||
resetForm({
|
||||
values: {
|
||||
value: inputValue ?? ''
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,12 +114,19 @@
|
||||
@pointerDownOutside="onPointerDownOutside"
|
||||
@interactOutside="onInteractOutside">
|
||||
<form @submit="onSubmit">
|
||||
<DialogHeader>
|
||||
<DialogHeader class="mb-5">
|
||||
<DialogTitle>{{ promptTitle }}</DialogTitle>
|
||||
<DialogDescription>{{ promptDescription }}</DialogDescription>
|
||||
</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>
|
||||
<FormLabel class="sr-only">Input</FormLabel>
|
||||
<FormControl>
|
||||
@@ -125,7 +136,7 @@
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogFooter class="mt-5">
|
||||
<Button type="button" variant="outline" @click="handleCancel">
|
||||
{{ promptCancelText }}
|
||||
</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">
|
||||
<TabsList :class="listClass" :aria-label="ariaLabel || undefined">
|
||||
<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" />
|
||||
</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