[READ-ONLY] Mirror of https://github.com/flo-bit/room. tiny 3d rooms saved locally or in your bluesky account, svelte/threlte
flo-bit.dev/room/
2.4 kB
104 lines
1<script lang="ts">
2 import { cn } from '$lib/utils';
3 import { Portal } from 'bits-ui';
4 import { onMount, type Snippet } from 'svelte';
5
6 let {
7 class: className,
8 children,
9 container,
10 title,
11 titleClasses,
12 hideCursor = true
13 }: {
14 class?: string;
15 children?: Snippet;
16 container?: HTMLElement | string;
17 title?: string;
18 titleClasses?: string;
19 hideCursor?: boolean;
20 } = $props();
21
22 let x = $state(-1000);
23 let y = $state(-1000);
24 let isInside: boolean = $state(false);
25
26 onMount(() => {
27 if (typeof container === 'string') {
28 let containerElement = document.querySelector(container);
29
30 if (containerElement) {
31 container = containerElement as HTMLElement;
32 } else {
33 console.error(`Container ${container} not found, using body`);
34 }
35 }
36
37 if (!container) {
38 container = document.body;
39 }
40
41 if (container instanceof HTMLElement) {
42 container.addEventListener('mousemove', handleMouseMove);
43 container.addEventListener('mouseleave', handleMouseLeave);
44 container.addEventListener('mouseenter', handleMouseEnter);
45 window.addEventListener('resize', handleResize);
46 rect = container.getBoundingClientRect();
47
48 if (hideCursor) {
49 // important:
50 // container.style.cursor = 'none !important;';
51 }
52 }
53 });
54
55 let rect: DOMRect | null = null;
56
57 const handleResize = () => {
58 if (container instanceof HTMLElement) {
59 rect = container.getBoundingClientRect();
60 }
61 };
62
63 const handleMouseMove = (e: MouseEvent) => {
64 const scrollX = window.scrollX;
65 const scrollY = window.scrollY;
66
67 x = e.clientX - (rect?.left ?? 0) + scrollX;
68 y = e.clientY - (rect?.top ?? 0) + scrollY;
69 };
70 const handleMouseLeave = () => {
71 console.log('mouse leave');
72 isInside = false;
73 };
74
75 const handleMouseEnter = () => {
76 console.log('mouse enter');
77 isInside = true;
78 };
79</script>
80
81<Portal to={container}>
82 {#if isInside}
83 <div
84 class={cn(
85 'animate-in fade-in-0 pointer-events-none absolute z-50',
86 className
87 )}
88 style={`top: ${y}px; left: ${x}px`}
89 >
90 {#if title}
91 <div
92 class={cn(
93 'bg-accent-500/10 dark:bg-accent-500/10 text-accent-600 dark:text-accent-400 border-accent-500/20 dark:border-accent-500/10 m-3 w-fit rounded-2xl border px-2 py-1 text-sm backdrop-blur-sm',
94 titleClasses
95 )}
96 >
97 {title}
98 </div>
99 {/if}
100
101 {@render children?.()}
102 </div>
103 {/if}
104</Portal>