Align Time Travel modal with settings shell

Remove Time Travel's floating modal behavior so it uses the standard centered modal shell and shared backdrop like Settings. Keep the modal-only sizing classes on the component document and stop exempting Time Travel from backdrop rendering.

Alessandro committed May 5, 2026 at 17:33 UTC 278a696b8ec6534f5c10e227d0ee011b1a051c2a
4 files changed +5 -115
plugins/_time_travel/webui/main.html
+2 -2
@@ -1,8 +1,8 @@
1 -<html>
1 +<html class="time-travel-modal">
2 <head>
3 <title>Time Travel</title>
4 </head>
5 -<body>
5 +<body class="time-travel-modal-body">
6 <x-component path="/plugins/_time_travel/webui/time-travel-panel.html" mode="modal"></x-component>
7 </body>
8 </html>
plugins/_time_travel/webui/time-travel-panel.html
+2 -13
@@ -242,26 +242,15 @@
242 min-height: min(520px, calc(100vh - 16px));
243 max-width: calc(100vw - 16px);
244 max-height: calc(100vh - 16px);
245 - resize: both;
245 border: 1px solid color-mix(in srgb, var(--color-border) 75%, transparent);
246 border-radius: 7px;
247 box-shadow: 0 18px 48px rgba(0, 0, 0, 0.32);
248 background: color-mix(in srgb, var(--color-background) 94%, #000 6%);
249 }
250
252 - .modal.modal-floating {
253 - pointer-events: none;
254 - }
255 -
256 - .modal.modal-floating .modal-inner {
257 - pointer-events: auto;
258 - }
259 -
251 .modal-inner.time-travel-modal .modal-header {
261 - min-height: 34px;
262 - padding: 0.35rem 0.75rem 0.35rem 1rem;
263 - cursor: move;
264 - user-select: none;
252 + min-height: 42px;
253 + padding: 0.45rem 0.75rem 0.45rem 1rem;
254 background: color-mix(in srgb, var(--color-background) 92%, #000 8%);
255 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
256 }
plugins/_time_travel/webui/time-travel-store.js
+1 -98
@@ -49,7 +49,6 @@ const model = {
49 _mode: "canvas",
50 _refreshTimer: null,
51 _filterTimer: null,
52 - _floatingCleanup: null,
52 _requestSeq: 0,
53 _diffSeq: 0,
54
@@ -60,9 +59,7 @@ const model = {
59 async onMount(element = null, options = {}) {
60 if (element) this._root = element;
61 this._mode = options?.mode === "modal" ? "modal" : "canvas";
63 - if (this._mode === "modal") {
64 - this.setupFloatingModal(element);
65 - } else {
62 + if (this._mode !== "modal") {
63 this.setupCanvasSurface(element);
64 }
65 this.contextId = this.resolveContextId();
@@ -85,103 +82,9 @@ const model = {
82 clearTimeout(this._filterTimer);
83 this._filterTimer = null;
84 }
88 - this._floatingCleanup?.();
89 - this._floatingCleanup = null;
90 - },
91 -
92 - setupFloatingModal(element = null) {
93 - this._floatingCleanup?.();
94 - const root = element || globalThis.document?.querySelector(".time-travel-panel");
95 - const modal = root?.closest?.(".modal");
96 - const inner = modal?.querySelector?.(".modal-inner");
97 - const body = modal?.querySelector?.(".modal-bd");
98 - const header = modal?.querySelector?.(".modal-header");
99 - if (!modal || !inner || !header) return;
100 - modal.classList.add("modal-floating");
101 - inner.classList.add("time-travel-modal", "modal-no-backdrop");
102 - body?.classList?.add("time-travel-modal-body");
103 -
104 - const rect = inner.getBoundingClientRect();
105 - inner.style.left = `${Math.max(8, rect.left)}px`;
106 - inner.style.top = `${Math.max(8, rect.top)}px`;
107 - inner.style.transform = "none";
108 -
109 - let drag = null;
110 - let resizeObserver = null;
111 - const viewportGap = 8;
112 - const clampPosition = (left, top) => {
113 - const bounds = inner.getBoundingClientRect();
114 - const maxLeft = Math.max(viewportGap, globalThis.innerWidth - bounds.width - viewportGap);
115 - const maxTop = Math.max(viewportGap, globalThis.innerHeight - bounds.height - viewportGap);
116 - return {
117 - left: Math.min(Math.max(viewportGap, left), maxLeft),
118 - top: Math.min(Math.max(viewportGap, top), maxTop),
119 - };
120 - };
121 - const clampGeometry = () => {
122 - const bounds = inner.getBoundingClientRect();
123 - const maxWidth = Math.max(360, globalThis.innerWidth - viewportGap * 2);
124 - const maxHeight = Math.max(420, globalThis.innerHeight - viewportGap * 2);
125 - if (bounds.width > maxWidth) inner.style.width = `${maxWidth}px`;
126 - if (bounds.height > maxHeight) inner.style.height = `${maxHeight}px`;
127 - const next = clampPosition(bounds.left, bounds.top);
128 - inner.style.left = `${next.left}px`;
129 - inner.style.top = `${next.top}px`;
130 - inner.style.maxWidth = `${Math.max(360, globalThis.innerWidth - next.left - viewportGap)}px`;
131 - inner.style.maxHeight = `${Math.max(420, globalThis.innerHeight - next.top - viewportGap)}px`;
132 - };
133 - clampGeometry();
134 - globalThis.addEventListener("resize", clampGeometry);
135 - if (globalThis.ResizeObserver) {
136 - resizeObserver = new ResizeObserver(clampGeometry);
137 - resizeObserver.observe(inner);
138 - }
139 -
140 - const onPointerMove = (event) => {
141 - if (!drag) return;
142 - const next = clampPosition(drag.left + event.clientX - drag.x, drag.top + event.clientY - drag.y);
143 - inner.style.left = `${next.left}px`;
144 - inner.style.top = `${next.top}px`;
145 - clampGeometry();
146 - };
147 - const onPointerUp = () => {
148 - drag = null;
149 - globalThis.removeEventListener("pointermove", onPointerMove);
150 - globalThis.removeEventListener("pointerup", onPointerUp);
151 - try {
152 - header.releasePointerCapture?.(header.__timeTravelPanelPointerId || 0);
153 - } catch {}
154 - };
155 - const onPointerDown = (event) => {
156 - if (event.button !== 0) return;
157 - if (event.target?.closest?.("button, input, select, textarea, a")) return;
158 - const current = inner.getBoundingClientRect();
159 - drag = {
160 - x: event.clientX,
161 - y: event.clientY,
162 - left: current.left,
163 - top: current.top,
164 - };
165 - header.__timeTravelPanelPointerId = event.pointerId;
166 - header.setPointerCapture?.(event.pointerId);
167 - globalThis.addEventListener("pointermove", onPointerMove);
168 - globalThis.addEventListener("pointerup", onPointerUp);
169 - event.preventDefault();
170 - };
171 - header.addEventListener("pointerdown", onPointerDown);
172 -
173 - this._floatingCleanup = () => {
174 - header.removeEventListener("pointerdown", onPointerDown);
175 - globalThis.removeEventListener("pointermove", onPointerMove);
176 - globalThis.removeEventListener("pointerup", onPointerUp);
177 - globalThis.removeEventListener("resize", clampGeometry);
178 - resizeObserver?.disconnect?.();
179 - };
85 },
86
87 setupCanvasSurface(element = null) {
183 - this._floatingCleanup?.();
184 - this._floatingCleanup = null;
88 if (element) this._root = element;
89 },
90
webui/js/modals.js
-2
@@ -139,8 +139,6 @@ function modalSuppressesBackdrop(modal) {
139 || path === "plugins/_browser/webui/main.html"
140 || path === "/plugins/_office/webui/main.html"
141 || path === "plugins/_office/webui/main.html"
142 - || path === "/plugins/_time_travel/webui/main.html"
143 - || path === "plugins/_time_travel/webui/main.html"
142 || modal?.element?.classList?.contains("modal-floating")
143 || modal?.element?.classList?.contains("modal-no-backdrop")
144 || modal?.inner?.classList?.contains("modal-no-backdrop");