https://domlink.deployments.hotsocket.fyi/
1import { string } from "../out/main.js";
2import { Button, Container, Label, Node } from "./domlink.ts";
3
4
5class TitleBar extends Container {
6 label: Label;
7 closeButton: Button;
8 constructor(title: string, closeCallback: EventListener) {
9 super();
10 this.class("LWindowHandle");
11 this.label = new Label(title);
12 this.closeButton = new Button("x", closeCallback);
13 this.with(this.label,this.closeButton);
14 }
15 get closable(): boolean {
16 return this.closeButton.wraps.style.display != "none";
17 }
18 set closable(x: boolean) {
19 this.closeButton.wraps.style.display = x ? "inline-block" : "none";
20 }
21}
22export class Window extends Container {
23 private static currentlyDragged: Window | null = null;
24 private static mouseRelX = 0;
25 private static mouseRelY = 0;
26 private static zIndexCounter = 100;
27 titleBar: TitleBar;
28 content = new Container().class("LWindowContent");
29
30 constructor(title: string = "New Window", height: number = 300, width: number = 400) {
31 super();
32 this.class("LWindow");
33 this.titleBar = new TitleBar(title, ()=>{this.wraps.remove();});
34 this.add(this.titleBar);
35 this.titleBar.wraps.addEventListener("mousedown", this.titleGrabHandler.bind(this));
36 this.wraps.addEventListener("mousedown", ()=>{this.wraps.style.zIndex = `${Window.zIndexCounter++}`;});
37 this.add(this.content);
38 this.style((s)=>{
39 s.width = `${width}px`;
40 s.height = `${height}px`;
41 s.top = "0px";
42 s.left = "0px";
43 });
44 }
45 override with(...nodes: (Node | string)[]): this {
46 const w = this.wraps.style.width;
47 const h = this.wraps.style.height;
48 this.content.with(...nodes);
49 this.wraps.style.width = w;
50 this.wraps.style.height = h;
51 return this;
52 }
53
54 get title() {
55 return (this.titleBar.children[0] as Label).text;
56 }
57 set title(newTitle: string) {
58 (this.titleBar.children[0] as Label).text = newTitle;
59 }
60 public closable(can: boolean = true) {
61 this.titleBar.closable = can;
62 return this;
63 }
64
65 private titleGrabHandler(ev: MouseEvent) {
66 if (ev.button !== 0) return;
67
68 Window.currentlyDragged = this;
69 this.titleBar.wraps.style.cursor = 'grabbing';
70
71 const rect = this.wraps.getBoundingClientRect();
72 Window.mouseRelX = ev.clientX - rect.left;
73 Window.mouseRelY = ev.clientY - rect.top;
74
75 ev.preventDefault();
76 }
77
78 private static onMouseMove(ev: MouseEvent) {
79 const draggedWindow = Window.currentlyDragged;
80 if (draggedWindow) {
81 const viewportWidth = document.documentElement.clientWidth;
82 const viewportHeight = document.documentElement.clientHeight;
83
84 const newLeft = ev.clientX - Window.mouseRelX;
85 const newTop = ev.clientY - Window.mouseRelY;
86
87 const clampedLeft = Math.min(Math.max(0, newLeft), viewportWidth - draggedWindow.wraps.offsetWidth);
88 const clampedTop = Math.min(Math.max(0, newTop), viewportHeight - draggedWindow.wraps.offsetHeight);
89
90 draggedWindow.wraps.style.left = `${clampedLeft}px`;
91 draggedWindow.wraps.style.top = `${clampedTop}px`;
92 }
93 }
94
95 private static onMouseUp(_ev: MouseEvent) {
96 if (Window.currentlyDragged) {
97 Window.currentlyDragged.titleBar.wraps.style.cursor = 'grab';
98 Window.currentlyDragged = null;
99 }
100 }
101
102 // A single place to initialize global listeners
103 private static _initialized = false;
104 private static initializeGlobalListeners() {
105 if (this._initialized) return;
106 globalThis.addEventListener("mousemove", this.onMouseMove);
107 globalThis.addEventListener("mouseup", this.onMouseUp);
108 this._initialized = true;
109 }
110
111 // Static initializer block to set up listeners once
112 static {
113 this.initializeGlobalListeners();
114 }
115}
116