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");