@flighthq/application 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application.d.ts +24 -0
- package/dist/application.d.ts.map +1 -0
- package/dist/application.js +371 -0
- package/dist/application.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/window.d.ts +63 -0
- package/dist/window.d.ts.map +1 -0
- package/dist/window.js +661 -0
- package/dist/window.js.map +1 -0
- package/package.json +38 -0
- package/src/application.test.ts +743 -0
- package/src/window.test.ts +1185 -0
package/dist/window.js
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import { connectSignal, createSignal, disconnectSignal, emitSignal } from '@flighthq/signals';
|
|
2
|
+
const kClose = Symbol();
|
|
3
|
+
const kDropFile = Symbol();
|
|
4
|
+
const kFocus = Symbol();
|
|
5
|
+
const kFullscreen = Symbol();
|
|
6
|
+
const kMove = Symbol();
|
|
7
|
+
const kOrientation = Symbol();
|
|
8
|
+
const kRenderContext = Symbol();
|
|
9
|
+
const kRenderState = Symbol();
|
|
10
|
+
const kResize = Symbol();
|
|
11
|
+
const kVisibility = Symbol();
|
|
12
|
+
// Wires the browser's beforeunload/pagehide to the window's close signals: beforeunload emits
|
|
13
|
+
// onCloseRequest and, if a listener vetoes (cancelSignal), prompts the user via the native unload
|
|
14
|
+
// dialog; pagehide emits onClose once the page is actually going away. Idempotent.
|
|
15
|
+
export function attachWindowClose(win) {
|
|
16
|
+
const observers = getApplicationWindowObservers(win);
|
|
17
|
+
observers.get(kClose)?.();
|
|
18
|
+
if (typeof window === 'undefined')
|
|
19
|
+
return;
|
|
20
|
+
const onBeforeUnload = (e) => {
|
|
21
|
+
emitSignal(win.onCloseRequest);
|
|
22
|
+
if (win.onCloseRequest.data?.cancelled === true) {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
e.returnValue = '';
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const onPageHide = () => emitSignal(win.onClose);
|
|
28
|
+
window.addEventListener('beforeunload', onBeforeUnload);
|
|
29
|
+
window.addEventListener('pagehide', onPageHide);
|
|
30
|
+
observers.set(kClose, () => {
|
|
31
|
+
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
32
|
+
window.removeEventListener('pagehide', onPageHide);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function attachWindowDropFile(win, element) {
|
|
36
|
+
const observers = getApplicationWindowObservers(win);
|
|
37
|
+
observers.get(kDropFile)?.();
|
|
38
|
+
const onDragOver = (e) => e.preventDefault();
|
|
39
|
+
const onDrop = (e) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
for (const file of Array.from(e.dataTransfer?.files ?? [])) {
|
|
42
|
+
emitSignal(win.onDropFile, file.name);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
element.addEventListener('dragover', onDragOver);
|
|
46
|
+
element.addEventListener('drop', onDrop);
|
|
47
|
+
observers.set(kDropFile, () => {
|
|
48
|
+
element.removeEventListener('dragover', onDragOver);
|
|
49
|
+
element.removeEventListener('drop', onDrop);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function attachWindowFocus(win, element) {
|
|
53
|
+
const observers = getApplicationWindowObservers(win);
|
|
54
|
+
observers.get(kFocus)?.();
|
|
55
|
+
const onFocus = () => emitSignal(win.onFocusIn);
|
|
56
|
+
const onBlur = () => emitSignal(win.onFocusOut);
|
|
57
|
+
element.addEventListener('focus', onFocus);
|
|
58
|
+
element.addEventListener('blur', onBlur);
|
|
59
|
+
observers.set(kFocus, () => {
|
|
60
|
+
element.removeEventListener('focus', onFocus);
|
|
61
|
+
element.removeEventListener('blur', onBlur);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function attachWindowFullscreen(win) {
|
|
65
|
+
const observers = getApplicationWindowObservers(win);
|
|
66
|
+
observers.get(kFullscreen)?.();
|
|
67
|
+
const handler = () => emitSignal(win.onFullscreenChanged);
|
|
68
|
+
document.addEventListener('fullscreenchange', handler);
|
|
69
|
+
observers.set(kFullscreen, () => document.removeEventListener('fullscreenchange', handler));
|
|
70
|
+
}
|
|
71
|
+
// Wires OS/screen-originated window-move events to win.onMove. On web this is best-effort via
|
|
72
|
+
// the window 'resize' event (which fires on page-move too in some browsers) — browsers do not
|
|
73
|
+
// expose a reliable 'move' event on the page window. No-op in non-browser environments.
|
|
74
|
+
export function attachWindowMove(win) {
|
|
75
|
+
const observers = getApplicationWindowObservers(win);
|
|
76
|
+
observers.get(kMove)?.();
|
|
77
|
+
if (typeof window === 'undefined')
|
|
78
|
+
return;
|
|
79
|
+
const handler = () => {
|
|
80
|
+
// Read back the real screen position from the browser and update the entity if it changed.
|
|
81
|
+
if (typeof window.screenX === 'number' && typeof window.screenY === 'number') {
|
|
82
|
+
const x = window.screenX;
|
|
83
|
+
const y = window.screenY;
|
|
84
|
+
if (win.x !== x || win.y !== y) {
|
|
85
|
+
win.x = x;
|
|
86
|
+
win.y = y;
|
|
87
|
+
emitSignal(win.onMove);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
// Both 'resize' and a polling listener on 'pointermove' are imperfect; 'resize' fires more
|
|
92
|
+
// reliably across browsers as a proxy for page layout change that includes moves.
|
|
93
|
+
window.addEventListener('resize', handler);
|
|
94
|
+
observers.set(kMove, () => window.removeEventListener('resize', handler));
|
|
95
|
+
}
|
|
96
|
+
export function attachWindowOrientation(win) {
|
|
97
|
+
const observers = getApplicationWindowObservers(win);
|
|
98
|
+
observers.get(kOrientation)?.();
|
|
99
|
+
if (!screen.orientation)
|
|
100
|
+
return;
|
|
101
|
+
const handler = () => emitSignal(win.onOrientationChanged);
|
|
102
|
+
screen.orientation.addEventListener('change', handler);
|
|
103
|
+
observers.set(kOrientation, () => screen.orientation.removeEventListener('change', handler));
|
|
104
|
+
}
|
|
105
|
+
export function attachWindowRenderContext(win, canvas) {
|
|
106
|
+
const observers = getApplicationWindowObservers(win);
|
|
107
|
+
observers.get(kRenderContext)?.();
|
|
108
|
+
const onContextLost = (e) => {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
emitSignal(win.onRenderContextLost);
|
|
111
|
+
};
|
|
112
|
+
const onContextRestored = () => emitSignal(win.onRenderContextRestored);
|
|
113
|
+
canvas.addEventListener('webglcontextlost', onContextLost);
|
|
114
|
+
canvas.addEventListener('webglcontextrestored', onContextRestored);
|
|
115
|
+
observers.set(kRenderContext, () => {
|
|
116
|
+
canvas.removeEventListener('webglcontextlost', onContextLost);
|
|
117
|
+
canvas.removeEventListener('webglcontextrestored', onContextRestored);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// Binds a canvas render state to the window's size and devicePixelRatio: sizes the canvas backing
|
|
121
|
+
// store and writes the device transform (renderTransform2D), then keeps both in sync on every
|
|
122
|
+
// onResize, so moving the window between displays or zooming is handled. Pair with attachWindowResize
|
|
123
|
+
// — it is the source of the size/DPI updates this reacts to. The render state must have an
|
|
124
|
+
// initialized renderTransform2D (every create*RenderState factory does). DOM render states need no
|
|
125
|
+
// device transform (the browser rasterizes DOM at device resolution), so this is for canvas/Gl.
|
|
126
|
+
export function attachWindowRenderState(win, state, canvas) {
|
|
127
|
+
const observers = getApplicationWindowObservers(win);
|
|
128
|
+
observers.get(kRenderState)?.();
|
|
129
|
+
const apply = () => {
|
|
130
|
+
canvas.width = Math.round(win.width * win.devicePixelRatio);
|
|
131
|
+
canvas.height = Math.round(win.height * win.devicePixelRatio);
|
|
132
|
+
if (state.renderTransform2D !== null)
|
|
133
|
+
computeWindowDeviceTransform(win, state.renderTransform2D);
|
|
134
|
+
};
|
|
135
|
+
apply();
|
|
136
|
+
connectSignal(win.onResize, apply);
|
|
137
|
+
observers.set(kRenderState, () => disconnectSignal(win.onResize, apply));
|
|
138
|
+
}
|
|
139
|
+
export function attachWindowResize(win, element) {
|
|
140
|
+
const observers = getApplicationWindowObservers(win);
|
|
141
|
+
observers.get(kResize)?.();
|
|
142
|
+
const observer = new ResizeObserver((entries) => {
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
win.width = Math.round(entry.contentRect.width);
|
|
145
|
+
win.height = Math.round(entry.contentRect.height);
|
|
146
|
+
win.devicePixelRatio = window.devicePixelRatio || 1;
|
|
147
|
+
emitSignal(win.onResize);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
observer.observe(element);
|
|
151
|
+
observers.set(kResize, () => observer.disconnect());
|
|
152
|
+
}
|
|
153
|
+
export function attachWindowVisibility(win) {
|
|
154
|
+
const observers = getApplicationWindowObservers(win);
|
|
155
|
+
observers.get(kVisibility)?.();
|
|
156
|
+
const handler = () => {
|
|
157
|
+
if (document.hidden) {
|
|
158
|
+
emitSignal(win.onDeactivate);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
emitSignal(win.onActivate);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
document.addEventListener('visibilitychange', handler);
|
|
165
|
+
observers.set(kVisibility, () => document.removeEventListener('visibilitychange', handler));
|
|
166
|
+
}
|
|
167
|
+
// Centers the window on its current display via the backend.
|
|
168
|
+
export function centerWindow(win) {
|
|
169
|
+
getWindowBackend().center(win);
|
|
170
|
+
}
|
|
171
|
+
// Closes the window. First emits onCloseRequest; if a listener vetoes (cancelSignal), the close is
|
|
172
|
+
// aborted and this returns false. Otherwise the backend closes the window, onClose fires, and it
|
|
173
|
+
// returns true.
|
|
174
|
+
export function closeWindow(win) {
|
|
175
|
+
if (!requestWindowClose(win))
|
|
176
|
+
return false;
|
|
177
|
+
getWindowBackend().close(win);
|
|
178
|
+
emitSignal(win.onClose);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
// Writes the window's device transform — a uniform scale by devicePixelRatio — into out and returns
|
|
182
|
+
// it. DPI is a device concern, so it belongs in a render state's device transform (renderTransform2D),
|
|
183
|
+
// leaving the scene authored in logical units. Reads win before writing out, so out may alias an input.
|
|
184
|
+
export function computeWindowDeviceTransform(win, out) {
|
|
185
|
+
const scale = win.devicePixelRatio;
|
|
186
|
+
out.a = scale;
|
|
187
|
+
out.b = 0;
|
|
188
|
+
out.c = 0;
|
|
189
|
+
out.d = scale;
|
|
190
|
+
out.tx = 0;
|
|
191
|
+
out.ty = 0;
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
export function createApplicationWindow() {
|
|
195
|
+
return {
|
|
196
|
+
alwaysOnTop: false,
|
|
197
|
+
devicePixelRatio: 1,
|
|
198
|
+
focused: false,
|
|
199
|
+
fullscreen: false,
|
|
200
|
+
height: 0,
|
|
201
|
+
icon: '',
|
|
202
|
+
maxHeight: -1,
|
|
203
|
+
maximized: false,
|
|
204
|
+
maxWidth: -1,
|
|
205
|
+
minHeight: 0,
|
|
206
|
+
minimized: false,
|
|
207
|
+
minWidth: 0,
|
|
208
|
+
opacity: 1,
|
|
209
|
+
resizable: true,
|
|
210
|
+
skipTaskbar: false,
|
|
211
|
+
title: '',
|
|
212
|
+
visible: true,
|
|
213
|
+
width: 0,
|
|
214
|
+
x: 0,
|
|
215
|
+
y: 0,
|
|
216
|
+
onActivate: createSignal(),
|
|
217
|
+
onClose: createSignal(),
|
|
218
|
+
onCloseRequest: createSignal(),
|
|
219
|
+
onDeactivate: createSignal(),
|
|
220
|
+
onDropFile: createSignal(),
|
|
221
|
+
onFocusIn: createSignal(),
|
|
222
|
+
onFocusOut: createSignal(),
|
|
223
|
+
onFullscreenChanged: createSignal(),
|
|
224
|
+
onMaximize: createSignal(),
|
|
225
|
+
onMinimize: createSignal(),
|
|
226
|
+
onMove: createSignal(),
|
|
227
|
+
onOrientationChanged: createSignal(),
|
|
228
|
+
onRenderContextLost: createSignal(),
|
|
229
|
+
onRenderContextRestored: createSignal(),
|
|
230
|
+
onResize: createSignal(),
|
|
231
|
+
onRestore: createSignal(),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// Builds the default web window backend over the browser page-window. Covers what a browser can do
|
|
235
|
+
// (title via document.title, fullscreen, focus, popup move/resize/close); minimize/maximize/restore,
|
|
236
|
+
// always-on-top, and size constraints have no browser equivalent and are no-ops — the window command
|
|
237
|
+
// functions still update the entity state and emit signals, and native hosts implement the rest.
|
|
238
|
+
export function createWebWindowBackend() {
|
|
239
|
+
return {
|
|
240
|
+
open() {
|
|
241
|
+
return typeof window !== 'undefined';
|
|
242
|
+
},
|
|
243
|
+
close() {
|
|
244
|
+
if (typeof window !== 'undefined' && typeof window.close === 'function') {
|
|
245
|
+
try {
|
|
246
|
+
window.close();
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
/* a non-script-opened window cannot be closed by script */
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
setTitle(_win, title) {
|
|
254
|
+
if (typeof document !== 'undefined')
|
|
255
|
+
document.title = title;
|
|
256
|
+
},
|
|
257
|
+
setPosition(_win, x, y) {
|
|
258
|
+
if (typeof window !== 'undefined' && typeof window.moveTo === 'function') {
|
|
259
|
+
try {
|
|
260
|
+
window.moveTo(x, y);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
/* blocked outside a script-opened window */
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
setSize(_win, width, height) {
|
|
268
|
+
if (typeof window !== 'undefined' && typeof window.resizeTo === 'function') {
|
|
269
|
+
try {
|
|
270
|
+
window.resizeTo(width, height);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
/* blocked outside a script-opened window */
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
getBounds(win, out) {
|
|
278
|
+
if (typeof window === 'undefined') {
|
|
279
|
+
out.x = win.x;
|
|
280
|
+
out.y = win.y;
|
|
281
|
+
out.width = win.width;
|
|
282
|
+
out.height = win.height;
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
out.x = window.screenX ?? win.x;
|
|
286
|
+
out.y = window.screenY ?? win.y;
|
|
287
|
+
out.width = window.innerWidth ?? win.width;
|
|
288
|
+
out.height = window.innerHeight ?? win.height;
|
|
289
|
+
return out;
|
|
290
|
+
},
|
|
291
|
+
minimize() { },
|
|
292
|
+
maximize() { },
|
|
293
|
+
restore() { },
|
|
294
|
+
focus() {
|
|
295
|
+
if (typeof window !== 'undefined' && typeof window.focus === 'function')
|
|
296
|
+
window.focus();
|
|
297
|
+
},
|
|
298
|
+
show() { },
|
|
299
|
+
hide() { },
|
|
300
|
+
center(win) {
|
|
301
|
+
if (typeof window === 'undefined' || typeof window.moveTo !== 'function' || typeof screen === 'undefined')
|
|
302
|
+
return;
|
|
303
|
+
try {
|
|
304
|
+
window.moveTo(Math.round((screen.availWidth - win.width) / 2), Math.round((screen.availHeight - win.height) / 2));
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
/* blocked outside a script-opened window */
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
setResizable() { },
|
|
311
|
+
setAlwaysOnTop() { },
|
|
312
|
+
setMinimumSize() { },
|
|
313
|
+
setMaximumSize() { },
|
|
314
|
+
setFullscreen(_win, fullscreen) {
|
|
315
|
+
if (typeof document === 'undefined')
|
|
316
|
+
return;
|
|
317
|
+
try {
|
|
318
|
+
if (fullscreen)
|
|
319
|
+
void document.documentElement.requestFullscreen?.();
|
|
320
|
+
else
|
|
321
|
+
void document.exitFullscreen?.();
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
/* fullscreen requires a user gesture; ignore rejection */
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
setIcon(_win, icon) {
|
|
328
|
+
// The browser equivalent of a window icon is the page favicon; native hosts set the real icon.
|
|
329
|
+
if (typeof document === 'undefined')
|
|
330
|
+
return;
|
|
331
|
+
let link = document.querySelector('link[rel="icon"]');
|
|
332
|
+
if (link === null) {
|
|
333
|
+
link = document.createElement('link');
|
|
334
|
+
link.rel = 'icon';
|
|
335
|
+
document.head.appendChild(link);
|
|
336
|
+
}
|
|
337
|
+
link.href = icon;
|
|
338
|
+
},
|
|
339
|
+
setOpacity() { },
|
|
340
|
+
setSkipTaskbar() { },
|
|
341
|
+
setMenuBarVisible() { },
|
|
342
|
+
setParent() { },
|
|
343
|
+
setProgress() { },
|
|
344
|
+
requestAttention() { },
|
|
345
|
+
setContentProtection() { },
|
|
346
|
+
flashWindowFrame() { },
|
|
347
|
+
setHasShadow() { },
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
export function detachWindowClose(win) {
|
|
351
|
+
const observers = getApplicationWindowObservers(win);
|
|
352
|
+
observers.get(kClose)?.();
|
|
353
|
+
observers.delete(kClose);
|
|
354
|
+
}
|
|
355
|
+
export function detachWindowDropFile(win) {
|
|
356
|
+
const observers = getApplicationWindowObservers(win);
|
|
357
|
+
observers.get(kDropFile)?.();
|
|
358
|
+
observers.delete(kDropFile);
|
|
359
|
+
}
|
|
360
|
+
export function detachWindowFocus(win) {
|
|
361
|
+
const observers = getApplicationWindowObservers(win);
|
|
362
|
+
observers.get(kFocus)?.();
|
|
363
|
+
observers.delete(kFocus);
|
|
364
|
+
}
|
|
365
|
+
export function detachWindowFullscreen(win) {
|
|
366
|
+
const observers = getApplicationWindowObservers(win);
|
|
367
|
+
observers.get(kFullscreen)?.();
|
|
368
|
+
observers.delete(kFullscreen);
|
|
369
|
+
}
|
|
370
|
+
export function detachWindowMove(win) {
|
|
371
|
+
const observers = getApplicationWindowObservers(win);
|
|
372
|
+
observers.get(kMove)?.();
|
|
373
|
+
observers.delete(kMove);
|
|
374
|
+
}
|
|
375
|
+
export function detachWindowOrientation(win) {
|
|
376
|
+
const observers = getApplicationWindowObservers(win);
|
|
377
|
+
observers.get(kOrientation)?.();
|
|
378
|
+
observers.delete(kOrientation);
|
|
379
|
+
}
|
|
380
|
+
export function detachWindowRenderContext(win) {
|
|
381
|
+
const observers = getApplicationWindowObservers(win);
|
|
382
|
+
observers.get(kRenderContext)?.();
|
|
383
|
+
observers.delete(kRenderContext);
|
|
384
|
+
}
|
|
385
|
+
export function detachWindowRenderState(win) {
|
|
386
|
+
const observers = getApplicationWindowObservers(win);
|
|
387
|
+
observers.get(kRenderState)?.();
|
|
388
|
+
observers.delete(kRenderState);
|
|
389
|
+
}
|
|
390
|
+
export function detachWindowResize(win) {
|
|
391
|
+
const observers = getApplicationWindowObservers(win);
|
|
392
|
+
observers.get(kResize)?.();
|
|
393
|
+
observers.delete(kResize);
|
|
394
|
+
}
|
|
395
|
+
export function detachWindowVisibility(win) {
|
|
396
|
+
const observers = getApplicationWindowObservers(win);
|
|
397
|
+
observers.get(kVisibility)?.();
|
|
398
|
+
observers.delete(kVisibility);
|
|
399
|
+
}
|
|
400
|
+
export function disposeApplicationWindow(win) {
|
|
401
|
+
const observers = getApplicationWindowObservers(win);
|
|
402
|
+
for (const cleanup of observers.values())
|
|
403
|
+
cleanup();
|
|
404
|
+
observers.clear();
|
|
405
|
+
}
|
|
406
|
+
export function exitApplicationFullscreen() {
|
|
407
|
+
return document.exitFullscreen();
|
|
408
|
+
}
|
|
409
|
+
// Releases the Pointer Lock on the document, restoring cursor movement.
|
|
410
|
+
export function exitApplicationPointerLock() {
|
|
411
|
+
if (typeof document === 'undefined' || typeof document.exitPointerLock !== 'function') {
|
|
412
|
+
return Promise.resolve();
|
|
413
|
+
}
|
|
414
|
+
document.exitPointerLock();
|
|
415
|
+
return Promise.resolve();
|
|
416
|
+
}
|
|
417
|
+
// Briefly flashes the window frame to attract attention. No-op on web; native hosts implement via
|
|
418
|
+
// the WindowBackend (e.g. Electron window.flashFrame(true)).
|
|
419
|
+
export function flashWindowFrame(win) {
|
|
420
|
+
getWindowBackend().flashWindowFrame(win);
|
|
421
|
+
}
|
|
422
|
+
// Brings the window to the foreground and marks it focused.
|
|
423
|
+
export function focusWindow(win) {
|
|
424
|
+
win.focused = true;
|
|
425
|
+
getWindowBackend().focus(win);
|
|
426
|
+
}
|
|
427
|
+
// The active window backend, or a lazily-created web default. There is always a backend.
|
|
428
|
+
export function getWindowBackend() {
|
|
429
|
+
if (_windowBackend === null)
|
|
430
|
+
_windowBackend = createWebWindowBackend();
|
|
431
|
+
return _windowBackend;
|
|
432
|
+
}
|
|
433
|
+
// Fills `out` with the window's current screen bounds and returns it.
|
|
434
|
+
export function getWindowBounds(win, out) {
|
|
435
|
+
return getWindowBackend().getBounds(win, out);
|
|
436
|
+
}
|
|
437
|
+
// Returns the index of the display (screen) the window is currently on, or -1 if unknown.
|
|
438
|
+
// This is a seam: on web it always returns -1 (no multi-monitor API); native backends
|
|
439
|
+
// (host-electron, host-winit) resolve the display via @flighthq/screen and return the index.
|
|
440
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
441
|
+
export function getWindowDisplay(win) {
|
|
442
|
+
return -1;
|
|
443
|
+
}
|
|
444
|
+
// Hides the window without closing it.
|
|
445
|
+
export function hideWindow(win) {
|
|
446
|
+
if (!win.visible)
|
|
447
|
+
return;
|
|
448
|
+
win.visible = false;
|
|
449
|
+
getWindowBackend().hide(win);
|
|
450
|
+
}
|
|
451
|
+
// Requests Pointer Lock on an element, hiding and confining the cursor so raw mouse deltas are
|
|
452
|
+
// delivered via pointermove events. Returns a promise that resolves on success or rejects if the
|
|
453
|
+
// browser denies (requires a prior user gesture). Use exitApplicationPointerLock to release.
|
|
454
|
+
export function lockApplicationPointer(element) {
|
|
455
|
+
if (typeof element.requestPointerLock !== 'function')
|
|
456
|
+
return Promise.resolve();
|
|
457
|
+
const result = element.requestPointerLock();
|
|
458
|
+
// requestPointerLock returns a Promise in newer browsers and undefined in older ones.
|
|
459
|
+
return (result instanceof Promise ? result : Promise.resolve());
|
|
460
|
+
}
|
|
461
|
+
// Maximizes the window. Updates state and emits onMaximize when the state changes.
|
|
462
|
+
export function maximizeWindow(win) {
|
|
463
|
+
if (win.maximized)
|
|
464
|
+
return;
|
|
465
|
+
win.maximized = true;
|
|
466
|
+
getWindowBackend().maximize(win);
|
|
467
|
+
emitSignal(win.onMaximize);
|
|
468
|
+
}
|
|
469
|
+
// Minimizes the window. Updates state and emits onMinimize when the state changes.
|
|
470
|
+
export function minimizeWindow(win) {
|
|
471
|
+
if (win.minimized)
|
|
472
|
+
return;
|
|
473
|
+
win.minimized = true;
|
|
474
|
+
getWindowBackend().minimize(win);
|
|
475
|
+
emitSignal(win.onMinimize);
|
|
476
|
+
}
|
|
477
|
+
// Opens (or configures) the window from options, applying each provided field to the entity and
|
|
478
|
+
// delegating to the backend. Returns whether the host opened a window. On web this configures the
|
|
479
|
+
// existing page-window; native hosts create a real OS window.
|
|
480
|
+
export function openWindow(win, options = {}) {
|
|
481
|
+
if (options.title !== undefined)
|
|
482
|
+
win.title = options.title;
|
|
483
|
+
if (options.x !== undefined)
|
|
484
|
+
win.x = options.x;
|
|
485
|
+
if (options.y !== undefined)
|
|
486
|
+
win.y = options.y;
|
|
487
|
+
if (options.width !== undefined)
|
|
488
|
+
win.width = options.width;
|
|
489
|
+
if (options.height !== undefined)
|
|
490
|
+
win.height = options.height;
|
|
491
|
+
if (options.resizable !== undefined)
|
|
492
|
+
win.resizable = options.resizable;
|
|
493
|
+
if (options.alwaysOnTop !== undefined)
|
|
494
|
+
win.alwaysOnTop = options.alwaysOnTop;
|
|
495
|
+
if (options.fullscreen !== undefined)
|
|
496
|
+
win.fullscreen = options.fullscreen;
|
|
497
|
+
if (options.minimized !== undefined)
|
|
498
|
+
win.minimized = options.minimized;
|
|
499
|
+
if (options.maximized !== undefined)
|
|
500
|
+
win.maximized = options.maximized;
|
|
501
|
+
if (options.visible !== undefined)
|
|
502
|
+
win.visible = options.visible;
|
|
503
|
+
if (options.minWidth !== undefined)
|
|
504
|
+
win.minWidth = options.minWidth;
|
|
505
|
+
if (options.minHeight !== undefined)
|
|
506
|
+
win.minHeight = options.minHeight;
|
|
507
|
+
if (options.maxWidth !== undefined)
|
|
508
|
+
win.maxWidth = options.maxWidth;
|
|
509
|
+
if (options.maxHeight !== undefined)
|
|
510
|
+
win.maxHeight = options.maxHeight;
|
|
511
|
+
const result = getWindowBackend().open(win, options);
|
|
512
|
+
// Apply center after open so the backend has registered the OS window before moving it.
|
|
513
|
+
if (options.center === true)
|
|
514
|
+
centerWindow(win);
|
|
515
|
+
return result;
|
|
516
|
+
}
|
|
517
|
+
// Prepares an element for direct input by setting CSS properties that suppress default browser
|
|
518
|
+
// touch/selection/tap-highlight behavior: touch-action:none, user-select:none,
|
|
519
|
+
// webkit-tap-highlight-color:transparent. For canvas elements, adds translateZ(0) to promote to
|
|
520
|
+
// a GPU compositing layer, reducing canvas flicker on touch. Call once; no teardown needed.
|
|
521
|
+
export function prepareElementForInput(element) {
|
|
522
|
+
element.style.touchAction = 'none';
|
|
523
|
+
element.style.userSelect = 'none';
|
|
524
|
+
element.style.webkitUserSelect = 'none';
|
|
525
|
+
element.style.webkitTapHighlightColor = 'transparent';
|
|
526
|
+
if (element instanceof HTMLCanvasElement) {
|
|
527
|
+
element.style.transform = 'translateZ(0)';
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
export function requestApplicationFullscreen(element) {
|
|
531
|
+
return element.requestFullscreen();
|
|
532
|
+
}
|
|
533
|
+
// Requests user attention on the window (taskbar flash / dock bounce); pass false to stop.
|
|
534
|
+
export function requestWindowAttention(win, attention) {
|
|
535
|
+
getWindowBackend().requestAttention(win, attention);
|
|
536
|
+
}
|
|
537
|
+
// Emits onCloseRequest and returns whether the close may proceed (false when a listener vetoed by
|
|
538
|
+
// calling cancelSignal(win.onCloseRequest)). Use to gate an app-driven close without closing.
|
|
539
|
+
export function requestWindowClose(win) {
|
|
540
|
+
emitSignal(win.onCloseRequest);
|
|
541
|
+
return win.onCloseRequest.data?.cancelled !== true;
|
|
542
|
+
}
|
|
543
|
+
// Restores the window from a minimized/maximized state. Emits onRestore when state changed.
|
|
544
|
+
export function restoreWindow(win) {
|
|
545
|
+
if (!win.minimized && !win.maximized)
|
|
546
|
+
return;
|
|
547
|
+
win.minimized = false;
|
|
548
|
+
win.maximized = false;
|
|
549
|
+
getWindowBackend().restore(win);
|
|
550
|
+
emitSignal(win.onRestore);
|
|
551
|
+
}
|
|
552
|
+
// Sets whether the window floats above others.
|
|
553
|
+
export function setWindowAlwaysOnTop(win, alwaysOnTop) {
|
|
554
|
+
win.alwaysOnTop = alwaysOnTop;
|
|
555
|
+
getWindowBackend().setAlwaysOnTop(win, alwaysOnTop);
|
|
556
|
+
}
|
|
557
|
+
// Installs a native host window backend; pass null to fall back to the web default.
|
|
558
|
+
export function setWindowBackend(backend) {
|
|
559
|
+
_windowBackend = backend;
|
|
560
|
+
}
|
|
561
|
+
// Prevents (or allows) the window contents from being captured in screenshots or screen sharing.
|
|
562
|
+
// Web no-op; native hosts implement via the WindowBackend (e.g. Electron window.setContentProtection).
|
|
563
|
+
export function setWindowContentProtection(win, enabled) {
|
|
564
|
+
getWindowBackend().setContentProtection(win, enabled);
|
|
565
|
+
}
|
|
566
|
+
// Sets fullscreen state. Updates state and emits onFullscreenChanged when the state changes.
|
|
567
|
+
export function setWindowFullscreen(win, fullscreen) {
|
|
568
|
+
if (win.fullscreen === fullscreen)
|
|
569
|
+
return;
|
|
570
|
+
win.fullscreen = fullscreen;
|
|
571
|
+
getWindowBackend().setFullscreen(win, fullscreen);
|
|
572
|
+
emitSignal(win.onFullscreenChanged);
|
|
573
|
+
}
|
|
574
|
+
// Shows or hides the native drop shadow around the window. macOS / native only; web no-op.
|
|
575
|
+
export function setWindowHasShadow(win, hasShadow) {
|
|
576
|
+
getWindowBackend().setHasShadow(win, hasShadow);
|
|
577
|
+
}
|
|
578
|
+
// Sets the window icon (path/URL). On web this updates the page favicon.
|
|
579
|
+
export function setWindowIcon(win, icon) {
|
|
580
|
+
win.icon = icon;
|
|
581
|
+
getWindowBackend().setIcon(win, icon);
|
|
582
|
+
}
|
|
583
|
+
// Sets the maximum window size in logical pixels (-1 for unbounded).
|
|
584
|
+
export function setWindowMaximumSize(win, width, height) {
|
|
585
|
+
win.maxWidth = width;
|
|
586
|
+
win.maxHeight = height;
|
|
587
|
+
getWindowBackend().setMaximumSize(win, width, height);
|
|
588
|
+
}
|
|
589
|
+
// Shows or hides the window's menu bar (native hosts; no-op on web).
|
|
590
|
+
export function setWindowMenuBarVisible(win, visible) {
|
|
591
|
+
getWindowBackend().setMenuBarVisible(win, visible);
|
|
592
|
+
}
|
|
593
|
+
// Sets the minimum window size in logical pixels.
|
|
594
|
+
export function setWindowMinimumSize(win, width, height) {
|
|
595
|
+
win.minWidth = width;
|
|
596
|
+
win.minHeight = height;
|
|
597
|
+
getWindowBackend().setMinimumSize(win, width, height);
|
|
598
|
+
}
|
|
599
|
+
// Sets the window opacity in [0, 1].
|
|
600
|
+
export function setWindowOpacity(win, opacity) {
|
|
601
|
+
win.opacity = opacity;
|
|
602
|
+
getWindowBackend().setOpacity(win, opacity);
|
|
603
|
+
}
|
|
604
|
+
// Sets the window's parent (for modal/child relationships); pass null to detach. Native hosts only.
|
|
605
|
+
export function setWindowParent(win, parent) {
|
|
606
|
+
getWindowBackend().setParent(win, parent);
|
|
607
|
+
}
|
|
608
|
+
// Moves the window's top-left to (x, y) in screen coordinates. Updates state and emits onMove.
|
|
609
|
+
export function setWindowPosition(win, x, y) {
|
|
610
|
+
win.x = x;
|
|
611
|
+
win.y = y;
|
|
612
|
+
getWindowBackend().setPosition(win, x, y);
|
|
613
|
+
emitSignal(win.onMove);
|
|
614
|
+
}
|
|
615
|
+
// Sets the taskbar/dock progress indicator in [0, 1]; a negative value clears it.
|
|
616
|
+
export function setWindowProgress(win, progress) {
|
|
617
|
+
getWindowBackend().setProgress(win, progress);
|
|
618
|
+
}
|
|
619
|
+
// Sets whether the user can resize the window.
|
|
620
|
+
export function setWindowResizable(win, resizable) {
|
|
621
|
+
win.resizable = resizable;
|
|
622
|
+
getWindowBackend().setResizable(win, resizable);
|
|
623
|
+
}
|
|
624
|
+
// Resizes the window to width x height (logical pixels). Updates state and emits onResize.
|
|
625
|
+
export function setWindowSize(win, width, height) {
|
|
626
|
+
win.width = width;
|
|
627
|
+
win.height = height;
|
|
628
|
+
getWindowBackend().setSize(win, width, height);
|
|
629
|
+
emitSignal(win.onResize);
|
|
630
|
+
}
|
|
631
|
+
// Sets whether the window is hidden from the taskbar/dock switcher.
|
|
632
|
+
export function setWindowSkipTaskbar(win, skip) {
|
|
633
|
+
win.skipTaskbar = skip;
|
|
634
|
+
getWindowBackend().setSkipTaskbar(win, skip);
|
|
635
|
+
}
|
|
636
|
+
// Sets the window title text.
|
|
637
|
+
export function setWindowTitle(win, title) {
|
|
638
|
+
win.title = title;
|
|
639
|
+
getWindowBackend().setTitle(win, title);
|
|
640
|
+
}
|
|
641
|
+
// Shows a hidden window.
|
|
642
|
+
export function showWindow(win) {
|
|
643
|
+
if (win.visible)
|
|
644
|
+
return;
|
|
645
|
+
win.visible = true;
|
|
646
|
+
getWindowBackend().show(win);
|
|
647
|
+
}
|
|
648
|
+
// Internal teardown registry, kept off the public ApplicationWindow entity (a side table like
|
|
649
|
+
// input's binding map). attach/detach/dispose track cleanup closures internally so callers hold
|
|
650
|
+
// nothing.
|
|
651
|
+
const _applicationWindowObservers = new WeakMap();
|
|
652
|
+
let _windowBackend = null;
|
|
653
|
+
function getApplicationWindowObservers(win) {
|
|
654
|
+
let observers = _applicationWindowObservers.get(win);
|
|
655
|
+
if (observers === undefined) {
|
|
656
|
+
observers = new Map();
|
|
657
|
+
_applicationWindowObservers.set(win, observers);
|
|
658
|
+
}
|
|
659
|
+
return observers;
|
|
660
|
+
}
|
|
661
|
+
//# sourceMappingURL=window.js.map
|