@surdeddd/wmkit 0.2.0 → 0.4.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/README.md +80 -16
- package/README.ru.md +33 -9
- package/dist/angular.cjs +53 -0
- package/dist/angular.cjs.map +1 -0
- package/dist/angular.d.cts +15 -0
- package/dist/angular.d.ts +15 -0
- package/dist/angular.js +48 -0
- package/dist/angular.js.map +1 -0
- package/dist/{binder-BY-saDUB.d.cts → binder-B2A2IZGh.d.ts} +19 -2
- package/dist/{binder-D9UEE1SM.d.ts → binder-Dl07SXIM.d.cts} +19 -2
- package/dist/{chunk-KQCVZ2W7.cjs → chunk-3KZZOLHW.cjs} +691 -156
- package/dist/chunk-3KZZOLHW.cjs.map +1 -0
- package/dist/{chunk-2TVOEDRG.js → chunk-FPZDAKNP.js} +689 -157
- package/dist/chunk-FPZDAKNP.js.map +1 -0
- package/dist/index.cjs +27 -15
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1 -1
- package/dist/persist.cjs +22 -3
- package/dist/persist.cjs.map +1 -1
- package/dist/persist.d.cts +3 -1
- package/dist/persist.d.ts +3 -1
- package/dist/persist.js +22 -3
- package/dist/persist.js.map +1 -1
- package/dist/popout.cjs +3 -4
- package/dist/popout.cjs.map +1 -1
- package/dist/popout.d.cts +1 -1
- package/dist/popout.d.ts +1 -1
- package/dist/popout.js +3 -4
- package/dist/popout.js.map +1 -1
- package/dist/react.cjs +4 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/solid.cjs +4 -3
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.d.cts +2 -2
- package/dist/solid.d.ts +2 -2
- package/dist/solid.js +2 -1
- package/dist/solid.js.map +1 -1
- package/dist/svelte.cjs +8 -5
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.d.cts +2 -2
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +6 -3
- package/dist/svelte.js.map +1 -1
- package/dist/themes/light.css +202 -0
- package/dist/themes/retro.css +154 -0
- package/dist/{types-Bkrq2djr.d.cts → types-t6pyVYKN.d.cts} +38 -2
- package/dist/{types-Bkrq2djr.d.ts → types-t6pyVYKN.d.ts} +38 -2
- package/dist/vue.cjs +4 -3
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.d.cts +2 -2
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +2 -1
- package/dist/vue.js.map +1 -1
- package/package.json +34 -7
- package/dist/chunk-2TVOEDRG.js.map +0 -1
- package/dist/chunk-KQCVZ2W7.cjs.map +0 -1
|
@@ -38,19 +38,48 @@ function clampSize(size, min, max) {
|
|
|
38
38
|
}
|
|
39
39
|
function clampToViewport(bounds, viewport, minVisible) {
|
|
40
40
|
if (viewport.width <= 0 || viewport.height <= 0) return bounds;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
41
|
+
const visibleX = Math.min(minVisible, bounds.width);
|
|
42
|
+
const visibleY = Math.min(minVisible, bounds.height);
|
|
43
|
+
const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX));
|
|
44
|
+
const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY));
|
|
43
45
|
return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y };
|
|
44
46
|
}
|
|
45
47
|
function boundsEqual(a, b) {
|
|
46
48
|
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
47
49
|
}
|
|
50
|
+
function applyAspect(size, ratio, min, max, drive) {
|
|
51
|
+
if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max);
|
|
52
|
+
const maxWidth = max ? max.width : Number.POSITIVE_INFINITY;
|
|
53
|
+
const maxHeight = max ? max.height : Number.POSITIVE_INFINITY;
|
|
54
|
+
if (drive === "height") {
|
|
55
|
+
const height = clamp(
|
|
56
|
+
size.height,
|
|
57
|
+
Math.max(min.height, min.width / ratio),
|
|
58
|
+
Math.min(maxHeight, maxWidth / ratio)
|
|
59
|
+
);
|
|
60
|
+
return { width: height * ratio, height };
|
|
61
|
+
}
|
|
62
|
+
const width = clamp(
|
|
63
|
+
size.width,
|
|
64
|
+
Math.max(min.width, min.height * ratio),
|
|
65
|
+
Math.min(maxWidth, maxHeight * ratio)
|
|
66
|
+
);
|
|
67
|
+
return { width, height: width / ratio };
|
|
68
|
+
}
|
|
48
69
|
function zoneBounds(zone, viewport) {
|
|
49
70
|
const w = viewport.width;
|
|
50
71
|
const h = viewport.height;
|
|
51
72
|
const halfW = Math.round(w / 2);
|
|
52
73
|
const halfH = Math.round(h / 2);
|
|
74
|
+
const oneThird = Math.round(w / 3);
|
|
75
|
+
const twoThirds = Math.round(w * 2 / 3);
|
|
53
76
|
switch (zone) {
|
|
77
|
+
case "left-third":
|
|
78
|
+
return { x: 0, y: 0, width: oneThird, height: h };
|
|
79
|
+
case "center-third":
|
|
80
|
+
return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h };
|
|
81
|
+
case "right-third":
|
|
82
|
+
return { x: twoThirds, y: 0, width: w - twoThirds, height: h };
|
|
54
83
|
case "left":
|
|
55
84
|
return { x: 0, y: 0, width: halfW, height: h };
|
|
56
85
|
case "right":
|
|
@@ -97,6 +126,42 @@ function detectSnapZone(x, y, viewport, options = {}) {
|
|
|
97
126
|
}
|
|
98
127
|
return null;
|
|
99
128
|
}
|
|
129
|
+
function nearestEdge(low, high, targetLow, targetHigh) {
|
|
130
|
+
return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high];
|
|
131
|
+
}
|
|
132
|
+
function magnetize(bounds, targets, threshold) {
|
|
133
|
+
const result = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false };
|
|
134
|
+
if (threshold <= 0 || targets.length === 0) return result;
|
|
135
|
+
let bestX = threshold + 1;
|
|
136
|
+
let bestY = threshold + 1;
|
|
137
|
+
for (const target of targets) {
|
|
138
|
+
for (const delta of nearestEdge(
|
|
139
|
+
bounds.x,
|
|
140
|
+
bounds.x + bounds.width,
|
|
141
|
+
target.x,
|
|
142
|
+
target.x + target.width
|
|
143
|
+
)) {
|
|
144
|
+
if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta;
|
|
145
|
+
}
|
|
146
|
+
for (const delta of nearestEdge(
|
|
147
|
+
bounds.y,
|
|
148
|
+
bounds.y + bounds.height,
|
|
149
|
+
target.y,
|
|
150
|
+
target.y + target.height
|
|
151
|
+
)) {
|
|
152
|
+
if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (Math.abs(bestX) <= threshold) {
|
|
156
|
+
result.x = bounds.x + bestX;
|
|
157
|
+
result.snappedX = true;
|
|
158
|
+
}
|
|
159
|
+
if (Math.abs(bestY) <= threshold) {
|
|
160
|
+
result.y = bounds.y + bestY;
|
|
161
|
+
result.snappedY = true;
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
100
165
|
|
|
101
166
|
// src/core/manager.ts
|
|
102
167
|
var LAYER_RANK = { normal: 0, floating: 1, modal: 2 };
|
|
@@ -110,8 +175,14 @@ var ZONES = [
|
|
|
110
175
|
"top-left",
|
|
111
176
|
"top-right",
|
|
112
177
|
"bottom-left",
|
|
113
|
-
"bottom-right"
|
|
178
|
+
"bottom-right",
|
|
179
|
+
"left-third",
|
|
180
|
+
"center-third",
|
|
181
|
+
"right-third"
|
|
114
182
|
];
|
|
183
|
+
function normalizeWorkspace(value, fallback) {
|
|
184
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
185
|
+
}
|
|
115
186
|
function createWindowManager(options = {}) {
|
|
116
187
|
const emitter = createEmitter();
|
|
117
188
|
const keepInViewport = options.keepInViewport ?? true;
|
|
@@ -120,26 +191,41 @@ function createWindowManager(options = {}) {
|
|
|
120
191
|
const cascadeOffset = options.cascadeOffset ?? 32;
|
|
121
192
|
const cascadeOrigin = options.cascadeOrigin ?? { x: 32, y: 32 };
|
|
122
193
|
const idPrefix = options.idPrefix ?? "wm";
|
|
194
|
+
const historyLimit = options.historyLimit ?? 50;
|
|
123
195
|
let windows = {};
|
|
124
196
|
let order = [];
|
|
125
197
|
let focusedId = null;
|
|
126
198
|
let viewport = options.viewport ?? { width: 0, height: 0 };
|
|
199
|
+
let workspace = normalizeWorkspace(options.workspace, 0);
|
|
127
200
|
let seq = 0;
|
|
128
201
|
let idCounter = 0;
|
|
129
202
|
let cascadeIndex = 0;
|
|
130
203
|
let snapshot = null;
|
|
131
204
|
let batchDepth = 0;
|
|
132
205
|
let batchDirty = false;
|
|
206
|
+
let historyDirty = false;
|
|
133
207
|
const pendingEvents = [];
|
|
208
|
+
const past = [];
|
|
209
|
+
let future = [];
|
|
210
|
+
let interactionDepth = 0;
|
|
211
|
+
let interactionRecorded = false;
|
|
212
|
+
let skipNextHistory = false;
|
|
213
|
+
let pendingHistory = null;
|
|
214
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
134
215
|
function getState() {
|
|
135
216
|
if (!snapshot) {
|
|
136
|
-
snapshot = { windows, order, focusedId, viewport };
|
|
217
|
+
snapshot = { windows, order, focusedId, viewport, workspace };
|
|
137
218
|
}
|
|
138
219
|
return snapshot;
|
|
139
220
|
}
|
|
140
221
|
function commit() {
|
|
141
222
|
snapshot = null;
|
|
142
223
|
batchDirty = true;
|
|
224
|
+
historyDirty = true;
|
|
225
|
+
}
|
|
226
|
+
function commitQuiet() {
|
|
227
|
+
snapshot = null;
|
|
228
|
+
batchDirty = true;
|
|
143
229
|
}
|
|
144
230
|
function flushEvents() {
|
|
145
231
|
while (pendingEvents.length > 0) {
|
|
@@ -177,10 +263,14 @@ function createWindowManager(options = {}) {
|
|
|
177
263
|
order = next;
|
|
178
264
|
return changed;
|
|
179
265
|
}
|
|
266
|
+
function onActiveWorkspace(win) {
|
|
267
|
+
return win.workspace === workspace;
|
|
268
|
+
}
|
|
180
269
|
function topModalId() {
|
|
181
270
|
for (let i = order.length - 1; i >= 0; i -= 1) {
|
|
182
271
|
const win = windows[order[i]];
|
|
183
|
-
if (win.layer === "modal" && win.stage !== "minimized"
|
|
272
|
+
if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win))
|
|
273
|
+
return win.id;
|
|
184
274
|
}
|
|
185
275
|
return null;
|
|
186
276
|
}
|
|
@@ -188,7 +278,7 @@ function createWindowManager(options = {}) {
|
|
|
188
278
|
const modal = topModalId();
|
|
189
279
|
return order.filter((id) => {
|
|
190
280
|
const win = windows[id];
|
|
191
|
-
if (win.stage === "minimized") return false;
|
|
281
|
+
if (win.stage === "minimized" || !onActiveWorkspace(win)) return false;
|
|
192
282
|
if (modal && win.layer !== "modal") return false;
|
|
193
283
|
return true;
|
|
194
284
|
});
|
|
@@ -200,8 +290,9 @@ function createWindowManager(options = {}) {
|
|
|
200
290
|
function emitFocus(win, previous) {
|
|
201
291
|
queueEvent(() => emitter.emit("focus", { window: win, previous }));
|
|
202
292
|
}
|
|
203
|
-
function normalizeSize(size, win) {
|
|
204
|
-
return clampSize(size, win.minSize, win.maxSize);
|
|
293
|
+
function normalizeSize(size, win, drive = "width") {
|
|
294
|
+
if (win.aspectRatio === null) return clampSize(size, win.minSize, win.maxSize);
|
|
295
|
+
return applyAspect(size, win.aspectRatio, win.minSize, win.maxSize, drive);
|
|
205
296
|
}
|
|
206
297
|
function positionForOpen(init) {
|
|
207
298
|
if (init.x !== void 0 && init.y !== void 0) return { x: init.x, y: init.y };
|
|
@@ -223,11 +314,12 @@ function createWindowManager(options = {}) {
|
|
|
223
314
|
width: init.maxWidth ?? Number.POSITIVE_INFINITY,
|
|
224
315
|
height: init.maxHeight ?? Number.POSITIVE_INFINITY
|
|
225
316
|
} : null;
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
317
|
+
const aspectRatio = init.aspectRatio !== void 0 && Number.isFinite(init.aspectRatio) && init.aspectRatio > 0 ? init.aspectRatio : null;
|
|
318
|
+
const requested = {
|
|
319
|
+
width: init.width ?? defaultSize.width,
|
|
320
|
+
height: init.height ?? defaultSize.height
|
|
321
|
+
};
|
|
322
|
+
const size = aspectRatio === null ? clampSize(requested, minSize, maxSize) : applyAspect(requested, aspectRatio, minSize, maxSize, "width");
|
|
231
323
|
const position = positionForOpen(init);
|
|
232
324
|
let bounds = { ...position, ...size };
|
|
233
325
|
if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible);
|
|
@@ -242,8 +334,10 @@ function createWindowManager(options = {}) {
|
|
|
242
334
|
stage: "normal",
|
|
243
335
|
snapZone: null,
|
|
244
336
|
layer: init.layer ?? "normal",
|
|
337
|
+
workspace: normalizeWorkspace(init.workspace, workspace),
|
|
245
338
|
minSize,
|
|
246
339
|
maxSize,
|
|
340
|
+
aspectRatio,
|
|
247
341
|
openedSeq: ++seq,
|
|
248
342
|
draggable: init.draggable ?? true,
|
|
249
343
|
resizable: init.resizable ?? true,
|
|
@@ -261,7 +355,7 @@ function createWindowManager(options = {}) {
|
|
|
261
355
|
setWindow(win);
|
|
262
356
|
raise(id);
|
|
263
357
|
let focusPayload = null;
|
|
264
|
-
if (stage !== "minimized") {
|
|
358
|
+
if (stage !== "minimized" && onActiveWorkspace(win)) {
|
|
265
359
|
const previous = focusedId;
|
|
266
360
|
const modal = topModalId();
|
|
267
361
|
if (!modal || win.layer === "modal") {
|
|
@@ -299,11 +393,12 @@ function createWindowManager(options = {}) {
|
|
|
299
393
|
function focus(id) {
|
|
300
394
|
const win = windows[id];
|
|
301
395
|
if (!win) return false;
|
|
396
|
+
if (!onActiveWorkspace(win)) setWorkspace(win.workspace);
|
|
302
397
|
const modal = topModalId();
|
|
303
398
|
if (modal && modal !== id && win.layer !== "modal") {
|
|
304
399
|
const modalWin = windows[modal];
|
|
305
400
|
if (modalWin) queueEvent(() => emitter.emit("modalblocked", { window: modalWin }));
|
|
306
|
-
|
|
401
|
+
commitQuiet();
|
|
307
402
|
return false;
|
|
308
403
|
}
|
|
309
404
|
if (win.stage === "minimized") {
|
|
@@ -338,6 +433,14 @@ function createWindowManager(options = {}) {
|
|
|
338
433
|
function fullBounds() {
|
|
339
434
|
return { x: 0, y: 0, width: viewport.width, height: viewport.height };
|
|
340
435
|
}
|
|
436
|
+
function snapBounds(win, zone) {
|
|
437
|
+
const rect = zoneBounds(zone, viewport);
|
|
438
|
+
return { x: rect.x, y: rect.y, ...normalizeSize(rect, win) };
|
|
439
|
+
}
|
|
440
|
+
function focusUnlessBlocked(id) {
|
|
441
|
+
const modal = topModalId();
|
|
442
|
+
if (modal === null || modal === id) focus(id);
|
|
443
|
+
}
|
|
341
444
|
function applyStage(id, build) {
|
|
342
445
|
const win = windows[id];
|
|
343
446
|
if (!win) return false;
|
|
@@ -364,7 +467,7 @@ function createWindowManager(options = {}) {
|
|
|
364
467
|
function maximize(id) {
|
|
365
468
|
const result = applyStage(id, (win) => {
|
|
366
469
|
if (win.stage === "maximized") return null;
|
|
367
|
-
const restoreBounds = win.stage === "normal" ? win.bounds : win.restoreBounds;
|
|
470
|
+
const restoreBounds = win.stage === "normal" ? win.bounds : win.restoreBounds ?? win.bounds;
|
|
368
471
|
return {
|
|
369
472
|
...win,
|
|
370
473
|
stage: "maximized",
|
|
@@ -385,22 +488,23 @@ function createWindowManager(options = {}) {
|
|
|
385
488
|
function restore(id) {
|
|
386
489
|
const result = applyStage(id, (win) => {
|
|
387
490
|
if (win.stage === "minimized") {
|
|
388
|
-
const
|
|
389
|
-
if (
|
|
491
|
+
const target2 = win.restoreStage ?? "normal";
|
|
492
|
+
if (target2 === "maximized") {
|
|
390
493
|
return { ...win, stage: "maximized", restoreStage: null, bounds: fullBounds() };
|
|
391
494
|
}
|
|
392
|
-
if (
|
|
495
|
+
if (target2 === "snapped" && win.snapZone) {
|
|
393
496
|
return {
|
|
394
497
|
...win,
|
|
395
498
|
stage: "snapped",
|
|
396
499
|
restoreStage: null,
|
|
397
|
-
bounds:
|
|
500
|
+
bounds: snapBounds(win, win.snapZone)
|
|
398
501
|
};
|
|
399
502
|
}
|
|
400
503
|
return { ...win, stage: "normal", restoreStage: null };
|
|
401
504
|
}
|
|
402
505
|
if (win.stage === "normal") return null;
|
|
403
|
-
const
|
|
506
|
+
const target = win.restoreBounds ?? win.bounds;
|
|
507
|
+
const bounds = { x: target.x, y: target.y, ...normalizeSize(target, win) };
|
|
404
508
|
return {
|
|
405
509
|
...win,
|
|
406
510
|
stage: "normal",
|
|
@@ -410,7 +514,7 @@ function createWindowManager(options = {}) {
|
|
|
410
514
|
bounds: keepInViewport ? clampToViewport(bounds, viewport, minVisible) : bounds
|
|
411
515
|
};
|
|
412
516
|
});
|
|
413
|
-
if (result)
|
|
517
|
+
if (result) focusUnlessBlocked(id);
|
|
414
518
|
return result;
|
|
415
519
|
}
|
|
416
520
|
function restoreTo(id, bounds) {
|
|
@@ -426,19 +530,19 @@ function createWindowManager(options = {}) {
|
|
|
426
530
|
bounds: keepInViewport ? clampToViewport(next, viewport, minVisible) : next
|
|
427
531
|
};
|
|
428
532
|
});
|
|
429
|
-
if (result)
|
|
533
|
+
if (result) focusUnlessBlocked(id);
|
|
430
534
|
return result;
|
|
431
535
|
}
|
|
432
536
|
function snap(id, zone) {
|
|
433
537
|
const result = applyStage(id, (win) => {
|
|
434
|
-
const restoreBounds = win.stage === "normal" ? win.bounds : win.restoreBounds;
|
|
538
|
+
const restoreBounds = win.stage === "normal" ? win.bounds : win.restoreBounds ?? win.bounds;
|
|
435
539
|
return {
|
|
436
540
|
...win,
|
|
437
541
|
stage: "snapped",
|
|
438
542
|
snapZone: zone,
|
|
439
543
|
restoreBounds,
|
|
440
544
|
restoreStage: null,
|
|
441
|
-
bounds:
|
|
545
|
+
bounds: snapBounds(win, zone)
|
|
442
546
|
};
|
|
443
547
|
});
|
|
444
548
|
if (result) focus(id);
|
|
@@ -465,7 +569,8 @@ function createWindowManager(options = {}) {
|
|
|
465
569
|
const win = windows[id];
|
|
466
570
|
if (!win || win.stage === "maximized" || win.stage === "minimized") return false;
|
|
467
571
|
const merged = { ...win.bounds, ...patch };
|
|
468
|
-
const
|
|
572
|
+
const drive = Math.abs(merged.height - win.bounds.height) > Math.abs(merged.width - win.bounds.width) ? "height" : "width";
|
|
573
|
+
const size = normalizeSize(merged, win, drive);
|
|
469
574
|
let bounds = { x: merged.x, y: merged.y, ...size };
|
|
470
575
|
if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible);
|
|
471
576
|
const becameNormal = win.stage === "snapped";
|
|
@@ -486,6 +591,7 @@ function createWindowManager(options = {}) {
|
|
|
486
591
|
layer: patch.layer ?? win.layer,
|
|
487
592
|
minSize: patch.minSize ?? win.minSize,
|
|
488
593
|
maxSize: patch.maxSize === void 0 ? win.maxSize : patch.maxSize,
|
|
594
|
+
aspectRatio: patch.aspectRatio === void 0 ? win.aspectRatio : patch.aspectRatio !== null && Number.isFinite(patch.aspectRatio) && patch.aspectRatio > 0 ? patch.aspectRatio : null,
|
|
489
595
|
draggable: patch.draggable ?? win.draggable,
|
|
490
596
|
resizable: patch.resizable ?? win.resizable,
|
|
491
597
|
closable: patch.closable ?? win.closable,
|
|
@@ -515,47 +621,242 @@ function createWindowManager(options = {}) {
|
|
|
515
621
|
function setViewport(next) {
|
|
516
622
|
if (next.width === viewport.width && next.height === viewport.height) return;
|
|
517
623
|
viewport = { ...next };
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
624
|
+
reflowViewport();
|
|
625
|
+
commitQuiet();
|
|
626
|
+
}
|
|
627
|
+
function setWorkspace(next) {
|
|
628
|
+
const target = normalizeWorkspace(next, workspace);
|
|
629
|
+
if (target === workspace) return false;
|
|
630
|
+
const previous = workspace;
|
|
631
|
+
workspace = target;
|
|
632
|
+
const previousFocus = focusedId;
|
|
633
|
+
focusTop();
|
|
634
|
+
queueEvent(() => emitter.emit("workspace", { workspace: target, previous }));
|
|
635
|
+
if (focusedId && focusedId !== previousFocus) {
|
|
636
|
+
emitFocus(windows[focusedId], previousFocus);
|
|
637
|
+
}
|
|
638
|
+
commit();
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
function moveToWorkspace(id, next) {
|
|
642
|
+
const win = windows[id];
|
|
643
|
+
if (!win) return false;
|
|
644
|
+
const target = normalizeWorkspace(next, win.workspace);
|
|
645
|
+
if (target === win.workspace) return false;
|
|
646
|
+
const updated = { ...win, workspace: target };
|
|
647
|
+
setWindow(updated);
|
|
648
|
+
if (!onActiveWorkspace(updated) && focusedId === id) {
|
|
649
|
+
focusTop();
|
|
650
|
+
if (focusedId) emitFocus(windows[focusedId], id);
|
|
651
|
+
}
|
|
652
|
+
queueEvent(() => emitter.emit("update", { window: updated }));
|
|
653
|
+
commit();
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
function sendToBack(id) {
|
|
657
|
+
const win = windows[id];
|
|
658
|
+
if (!win) return false;
|
|
659
|
+
const without = order.filter((entry) => entry !== id);
|
|
660
|
+
const rank = LAYER_RANK[win.layer];
|
|
661
|
+
let insertAt = 0;
|
|
662
|
+
for (const entry of without) {
|
|
663
|
+
if (layerRankOf(entry) < rank) insertAt += 1;
|
|
664
|
+
else break;
|
|
665
|
+
}
|
|
666
|
+
const next = [...without.slice(0, insertAt), id, ...without.slice(insertAt)];
|
|
667
|
+
if (next.every((entry, i) => entry === order[i])) return false;
|
|
668
|
+
order = next;
|
|
669
|
+
queueEvent(() => emitter.emit("order", { order }));
|
|
670
|
+
if (focusedId === id) {
|
|
671
|
+
focusTop();
|
|
672
|
+
if (focusedId) emitFocus(windows[focusedId], id);
|
|
673
|
+
}
|
|
674
|
+
commit();
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
function center(id) {
|
|
678
|
+
const win = windows[id];
|
|
679
|
+
if (win?.stage !== "normal") return false;
|
|
680
|
+
return move(
|
|
681
|
+
id,
|
|
682
|
+
Math.round((viewport.width - win.bounds.width) / 2),
|
|
683
|
+
Math.round((viewport.height - win.bounds.height) / 2)
|
|
684
|
+
);
|
|
541
685
|
}
|
|
542
686
|
function minimized() {
|
|
543
|
-
return Object.values(windows).filter((win) => win.stage === "minimized").sort((a, b) => a.openedSeq - b.openedSeq);
|
|
687
|
+
return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win)).sort((a, b) => a.openedSeq - b.openedSeq);
|
|
688
|
+
}
|
|
689
|
+
function captureEntry() {
|
|
690
|
+
return { windows, order, focusedId, workspace };
|
|
691
|
+
}
|
|
692
|
+
function recordHistory() {
|
|
693
|
+
if (!historyDirty) return;
|
|
694
|
+
if (skipNextHistory) {
|
|
695
|
+
skipNextHistory = false;
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (historyLimit <= 0) return;
|
|
699
|
+
if (interactionDepth > 0) {
|
|
700
|
+
if (interactionRecorded) return;
|
|
701
|
+
interactionRecorded = true;
|
|
702
|
+
}
|
|
703
|
+
past.push(pendingHistory);
|
|
704
|
+
if (past.length > historyLimit) past.shift();
|
|
705
|
+
future = [];
|
|
544
706
|
}
|
|
545
707
|
function transact(run) {
|
|
708
|
+
if (batchDepth === 0) pendingHistory = captureEntry();
|
|
546
709
|
batchDepth += 1;
|
|
547
710
|
try {
|
|
548
711
|
return run();
|
|
549
712
|
} finally {
|
|
550
713
|
batchDepth -= 1;
|
|
551
|
-
if (batchDepth === 0
|
|
552
|
-
batchDirty
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
714
|
+
if (batchDepth === 0) {
|
|
715
|
+
if (batchDirty) {
|
|
716
|
+
batchDirty = false;
|
|
717
|
+
recordHistory();
|
|
718
|
+
historyDirty = false;
|
|
719
|
+
snapshot = null;
|
|
720
|
+
flushEvents();
|
|
721
|
+
emitter.emit("change", { state: getState() });
|
|
722
|
+
}
|
|
723
|
+
pendingHistory = null;
|
|
556
724
|
}
|
|
557
725
|
}
|
|
558
726
|
}
|
|
727
|
+
function reflowViewport() {
|
|
728
|
+
for (const id of order) {
|
|
729
|
+
const win = windows[id];
|
|
730
|
+
if (win.stage === "maximized") {
|
|
731
|
+
const updated = { ...win, bounds: fullBounds() };
|
|
732
|
+
setWindow(updated);
|
|
733
|
+
queueEvent(() => emitter.emit("resize", { window: updated }));
|
|
734
|
+
} else if (win.stage === "snapped" && win.snapZone) {
|
|
735
|
+
const updated = { ...win, bounds: snapBounds(win, win.snapZone) };
|
|
736
|
+
setWindow(updated);
|
|
737
|
+
queueEvent(() => emitter.emit("resize", { window: updated }));
|
|
738
|
+
} else if (win.stage === "normal" && keepInViewport) {
|
|
739
|
+
const bounds = clampToViewport(win.bounds, viewport, minVisible);
|
|
740
|
+
if (!boundsEqual(bounds, win.bounds)) {
|
|
741
|
+
const updated = { ...win, bounds };
|
|
742
|
+
setWindow(updated);
|
|
743
|
+
queueEvent(() => emitter.emit("move", { window: updated }));
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function applyEntry(entry) {
|
|
749
|
+
windows = entry.windows;
|
|
750
|
+
order = [...entry.order];
|
|
751
|
+
focusedId = entry.focusedId;
|
|
752
|
+
const previousWorkspace = workspace;
|
|
753
|
+
workspace = entry.workspace;
|
|
754
|
+
if (workspace !== previousWorkspace) {
|
|
755
|
+
queueEvent(() => emitter.emit("workspace", { workspace, previous: previousWorkspace }));
|
|
756
|
+
}
|
|
757
|
+
reflowViewport();
|
|
758
|
+
commit();
|
|
759
|
+
}
|
|
760
|
+
function undo() {
|
|
761
|
+
const entry = past.pop();
|
|
762
|
+
if (!entry) return false;
|
|
763
|
+
future.push(captureEntry());
|
|
764
|
+
skipNextHistory = true;
|
|
765
|
+
applyEntry(entry);
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
function redo() {
|
|
769
|
+
const entry = future.pop();
|
|
770
|
+
if (!entry) return false;
|
|
771
|
+
past.push(captureEntry());
|
|
772
|
+
skipNextHistory = true;
|
|
773
|
+
applyEntry(entry);
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
function beginInteraction() {
|
|
777
|
+
interactionDepth += 1;
|
|
778
|
+
if (interactionDepth === 1) interactionRecorded = false;
|
|
779
|
+
}
|
|
780
|
+
function endInteraction() {
|
|
781
|
+
if (interactionDepth > 0) interactionDepth -= 1;
|
|
782
|
+
}
|
|
783
|
+
function abortInteraction() {
|
|
784
|
+
if (interactionDepth > 0 && interactionRecorded) {
|
|
785
|
+
past.pop();
|
|
786
|
+
interactionRecorded = false;
|
|
787
|
+
}
|
|
788
|
+
endInteraction();
|
|
789
|
+
}
|
|
790
|
+
function clearHistory() {
|
|
791
|
+
past.length = 0;
|
|
792
|
+
future = [];
|
|
793
|
+
}
|
|
794
|
+
function saveLayout(name) {
|
|
795
|
+
const data = serialize();
|
|
796
|
+
layouts.set(name, structuredClone(data));
|
|
797
|
+
return data;
|
|
798
|
+
}
|
|
799
|
+
function loadLayout(name) {
|
|
800
|
+
const data = layouts.get(name);
|
|
801
|
+
if (!data) return false;
|
|
802
|
+
return hydrate(structuredClone(data));
|
|
803
|
+
}
|
|
804
|
+
function getLayout(name) {
|
|
805
|
+
const data = layouts.get(name);
|
|
806
|
+
return data ? structuredClone(data) : void 0;
|
|
807
|
+
}
|
|
808
|
+
function setLayout(name, data) {
|
|
809
|
+
if (!isValidSerialized(data)) return false;
|
|
810
|
+
layouts.set(name, structuredClone(data));
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
function deleteLayout(name) {
|
|
814
|
+
return layouts.delete(name);
|
|
815
|
+
}
|
|
816
|
+
function layoutNames() {
|
|
817
|
+
return [...layouts.keys()];
|
|
818
|
+
}
|
|
819
|
+
function arrange(mode) {
|
|
820
|
+
const ids = order.filter((id) => {
|
|
821
|
+
const win = windows[id];
|
|
822
|
+
return win.stage !== "minimized" && onActiveWorkspace(win);
|
|
823
|
+
});
|
|
824
|
+
if (ids.length === 0) return;
|
|
825
|
+
if (mode === "cascade") {
|
|
826
|
+
ids.forEach((id, index) => {
|
|
827
|
+
const win = windows[id];
|
|
828
|
+
const size = win.restoreBounds ?? win.bounds;
|
|
829
|
+
restoreTo(id, {
|
|
830
|
+
x: cascadeOrigin.x + index % 10 * cascadeOffset,
|
|
831
|
+
y: cascadeOrigin.y + index % 10 * cascadeOffset,
|
|
832
|
+
width: size.width,
|
|
833
|
+
height: size.height
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (viewport.width <= 0 || viewport.height <= 0) return;
|
|
839
|
+
const cols = Math.ceil(Math.sqrt(ids.length));
|
|
840
|
+
const rows = Math.ceil(ids.length / cols);
|
|
841
|
+
const cellWidth = Math.floor(viewport.width / cols);
|
|
842
|
+
const cellHeight = Math.floor(viewport.height / rows);
|
|
843
|
+
ids.forEach((id, index) => {
|
|
844
|
+
restoreTo(id, {
|
|
845
|
+
x: index % cols * cellWidth,
|
|
846
|
+
y: Math.floor(index / cols) * cellHeight,
|
|
847
|
+
width: cellWidth,
|
|
848
|
+
height: cellHeight
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
function minimizeAll() {
|
|
853
|
+
for (const id of [...order]) {
|
|
854
|
+
if (onActiveWorkspace(windows[id])) minimize(id);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function restoreAll() {
|
|
858
|
+
for (const win of minimized()) restore(win.id);
|
|
859
|
+
}
|
|
559
860
|
function serialize() {
|
|
560
861
|
return {
|
|
561
862
|
version: 1,
|
|
@@ -571,7 +872,8 @@ function createWindowManager(options = {}) {
|
|
|
571
872
|
meta: { ...win.meta }
|
|
572
873
|
})),
|
|
573
874
|
order: [...order],
|
|
574
|
-
focusedId
|
|
875
|
+
focusedId,
|
|
876
|
+
workspace
|
|
575
877
|
};
|
|
576
878
|
}
|
|
577
879
|
function isBounds(value) {
|
|
@@ -602,10 +904,16 @@ function createWindowManager(options = {}) {
|
|
|
602
904
|
const win = value;
|
|
603
905
|
return typeof win.id === "string" && win.id.length > 0 && isBounds(win.bounds) && STAGES.includes(win.stage) && LAYERS.includes(win.layer);
|
|
604
906
|
}
|
|
605
|
-
function
|
|
907
|
+
function isValidSerialized(data) {
|
|
606
908
|
if (typeof data !== "object" || data === null) return false;
|
|
607
909
|
if (data.version !== 1 || !Array.isArray(data.windows)) return false;
|
|
608
910
|
if (!data.windows.every(isSerializedWindow)) return false;
|
|
911
|
+
const ids = new Set(data.windows.map((win) => win.id));
|
|
912
|
+
return ids.size === data.windows.length;
|
|
913
|
+
}
|
|
914
|
+
function hydrate(data) {
|
|
915
|
+
if (!isValidSerialized(data)) return false;
|
|
916
|
+
const previousWindows = windows;
|
|
609
917
|
const nextWindows = {};
|
|
610
918
|
let maxSeq = 0;
|
|
611
919
|
for (const raw of data.windows) {
|
|
@@ -619,8 +927,10 @@ function createWindowManager(options = {}) {
|
|
|
619
927
|
stage: raw.stage,
|
|
620
928
|
snapZone: ZONES.includes(raw.snapZone) ? raw.snapZone : null,
|
|
621
929
|
layer: raw.layer,
|
|
930
|
+
workspace: normalizeWorkspace(raw.workspace, 0),
|
|
622
931
|
minSize: isSize(raw.minSize) ? { ...raw.minSize } : { width: 160, height: 100 },
|
|
623
932
|
maxSize: readMaxSize(raw.maxSize),
|
|
933
|
+
aspectRatio: typeof raw.aspectRatio === "number" && Number.isFinite(raw.aspectRatio) && raw.aspectRatio > 0 ? raw.aspectRatio : null,
|
|
624
934
|
openedSeq: typeof raw.openedSeq === "number" ? raw.openedSeq : ++maxSeq,
|
|
625
935
|
draggable: raw.draggable !== false,
|
|
626
936
|
resizable: raw.resizable !== false,
|
|
@@ -649,14 +959,38 @@ function createWindowManager(options = {}) {
|
|
|
649
959
|
order = nextOrder;
|
|
650
960
|
order = sortByLayer(order);
|
|
651
961
|
seq = maxSeq;
|
|
962
|
+
const previousWorkspace = workspace;
|
|
963
|
+
workspace = normalizeWorkspace(data.workspace, 0);
|
|
964
|
+
if (workspace !== previousWorkspace) {
|
|
965
|
+
queueEvent(() => emitter.emit("workspace", { workspace, previous: previousWorkspace }));
|
|
966
|
+
}
|
|
652
967
|
focusedId = data.focusedId && windows[data.focusedId] ? data.focusedId : null;
|
|
653
|
-
|
|
968
|
+
const focusedCandidate = focusedId ? windows[focusedId] : void 0;
|
|
969
|
+
if (focusedCandidate && (focusedCandidate.stage === "minimized" || !onActiveWorkspace(focusedCandidate))) {
|
|
970
|
+
focusedId = null;
|
|
971
|
+
}
|
|
654
972
|
if (!focusedId) focusTop();
|
|
655
973
|
const modal = topModalId();
|
|
656
974
|
if (modal) {
|
|
657
975
|
const focusedWin = focusedId ? windows[focusedId] : null;
|
|
658
976
|
if (focusedWin?.layer !== "modal") focusedId = modal;
|
|
659
977
|
}
|
|
978
|
+
for (const id of Object.keys(previousWindows)) {
|
|
979
|
+
if (!windows[id]) {
|
|
980
|
+
const closed = previousWindows[id];
|
|
981
|
+
queueEvent(() => emitter.emit("close", { window: closed }));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
for (const id of order) {
|
|
985
|
+
if (!previousWindows[id]) {
|
|
986
|
+
const opened = windows[id];
|
|
987
|
+
queueEvent(() => emitter.emit("open", { window: opened }));
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (viewport.width > 0 && viewport.height > 0) reflowViewport();
|
|
991
|
+
queueEvent(() => emitter.emit("order", { order }));
|
|
992
|
+
clearHistory();
|
|
993
|
+
skipNextHistory = true;
|
|
660
994
|
commit();
|
|
661
995
|
return true;
|
|
662
996
|
}
|
|
@@ -678,15 +1012,37 @@ function createWindowManager(options = {}) {
|
|
|
678
1012
|
snap: (id, zone) => transact(() => snap(id, zone)),
|
|
679
1013
|
move: (id, x, y) => transact(() => move(id, x, y)),
|
|
680
1014
|
moveBy: (id, dx, dy) => transact(() => moveBy(id, dx, dy)),
|
|
1015
|
+
center: (id) => transact(() => center(id)),
|
|
1016
|
+
sendToBack: (id) => transact(() => sendToBack(id)),
|
|
681
1017
|
resize: (id, patch) => transact(() => resize(id, patch)),
|
|
682
1018
|
update: (id, patch) => transact(() => update(id, patch)),
|
|
683
1019
|
get: (id) => windows[id],
|
|
684
1020
|
getState,
|
|
685
1021
|
minimized,
|
|
686
1022
|
setViewport: (next) => transact(() => setViewport(next)),
|
|
1023
|
+
workspace: () => workspace,
|
|
1024
|
+
setWorkspace: (next) => transact(() => setWorkspace(next)),
|
|
1025
|
+
moveToWorkspace: (id, next) => transact(() => moveToWorkspace(id, next)),
|
|
687
1026
|
batch: (run) => transact(run),
|
|
688
1027
|
serialize,
|
|
689
1028
|
hydrate: (data) => transact(() => hydrate(data)),
|
|
1029
|
+
undo: () => transact(undo),
|
|
1030
|
+
redo: () => transact(redo),
|
|
1031
|
+
canUndo: () => past.length > 0,
|
|
1032
|
+
canRedo: () => future.length > 0,
|
|
1033
|
+
beginInteraction,
|
|
1034
|
+
endInteraction,
|
|
1035
|
+
abortInteraction,
|
|
1036
|
+
clearHistory,
|
|
1037
|
+
saveLayout,
|
|
1038
|
+
loadLayout: (name) => transact(() => loadLayout(name)),
|
|
1039
|
+
getLayout,
|
|
1040
|
+
setLayout,
|
|
1041
|
+
deleteLayout,
|
|
1042
|
+
layoutNames,
|
|
1043
|
+
arrange: (mode) => transact(() => arrange(mode)),
|
|
1044
|
+
minimizeAll: () => transact(minimizeAll),
|
|
1045
|
+
restoreAll: () => transact(restoreAll),
|
|
690
1046
|
subscribe: (listener) => emitter.on("change", ({ state }) => listener(state)),
|
|
691
1047
|
on: (event, listener) => emitter.on(event, listener),
|
|
692
1048
|
destroy
|
|
@@ -697,6 +1053,34 @@ function createWindowManager(options = {}) {
|
|
|
697
1053
|
function prefersReducedMotion(win) {
|
|
698
1054
|
return win.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
699
1055
|
}
|
|
1056
|
+
function flipFromTarget(source, target, options = {}) {
|
|
1057
|
+
const view = source.ownerDocument.defaultView;
|
|
1058
|
+
if (!view || prefersReducedMotion(view)) return;
|
|
1059
|
+
if (typeof source.animate !== "function") return;
|
|
1060
|
+
const from = target.getBoundingClientRect();
|
|
1061
|
+
const to = source.getBoundingClientRect();
|
|
1062
|
+
if (from.width === 0 || to.width === 0) return;
|
|
1063
|
+
const ghost = source.ownerDocument.createElement("div");
|
|
1064
|
+
const style = view.getComputedStyle(source);
|
|
1065
|
+
ghost.style.cssText = `position:fixed;left:${to.left}px;top:${to.top}px;width:${to.width}px;height:${to.height}px;margin:0;pointer-events:none;z-index:2147483647;border-radius:${style.borderRadius};background:${style.backgroundColor};box-shadow:${style.boxShadow};transform-origin:top left;will-change:transform,opacity`;
|
|
1066
|
+
source.ownerDocument.body.append(ghost);
|
|
1067
|
+
const dx = from.left + from.width / 2 - (to.left + to.width / 2);
|
|
1068
|
+
const dy = from.top + from.height / 2 - (to.top + to.height / 2);
|
|
1069
|
+
const scaleX = Math.max(from.width / to.width, 0.05);
|
|
1070
|
+
const scaleY = Math.max(from.height / to.height, 0.05);
|
|
1071
|
+
const animation = ghost.animate(
|
|
1072
|
+
[
|
|
1073
|
+
{ transform: `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`, opacity: 0.2 },
|
|
1074
|
+
{ transform: "translate(0, 0) scale(1, 1)", opacity: 0.9 }
|
|
1075
|
+
],
|
|
1076
|
+
{
|
|
1077
|
+
duration: options.duration ?? 260,
|
|
1078
|
+
easing: options.easing ?? "cubic-bezier(0.32, 0.72, 0, 1)"
|
|
1079
|
+
}
|
|
1080
|
+
);
|
|
1081
|
+
animation.onfinish = () => ghost.remove();
|
|
1082
|
+
animation.oncancel = () => ghost.remove();
|
|
1083
|
+
}
|
|
700
1084
|
function flipToTarget(source, target, options = {}) {
|
|
701
1085
|
const view = source.ownerDocument.defaultView;
|
|
702
1086
|
if (!view || prefersReducedMotion(view)) return;
|
|
@@ -734,7 +1118,8 @@ var defaultMessages = {
|
|
|
734
1118
|
restored: (title) => `${title} restored`,
|
|
735
1119
|
maximized: (title) => `${title} maximized`,
|
|
736
1120
|
snapped: (title, zone) => `${title} snapped to ${zone.replace("-", " ")}`,
|
|
737
|
-
focused: (title) => `${title} focused
|
|
1121
|
+
focused: (title) => `${title} focused`,
|
|
1122
|
+
workspace: (index) => `workspace ${index + 1}`
|
|
738
1123
|
};
|
|
739
1124
|
function createAnnouncer(wm, container, messages = {}) {
|
|
740
1125
|
const dict = { ...defaultMessages, ...messages };
|
|
@@ -745,7 +1130,10 @@ function createAnnouncer(wm, container, messages = {}) {
|
|
|
745
1130
|
element.style.cssText = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0";
|
|
746
1131
|
container.append(element);
|
|
747
1132
|
let clearTimer;
|
|
1133
|
+
let lastMessage = "";
|
|
1134
|
+
let detailed = false;
|
|
748
1135
|
function announce(message) {
|
|
1136
|
+
lastMessage = message;
|
|
749
1137
|
element.textContent = message;
|
|
750
1138
|
if (clearTimer !== void 0) clearTimeout(clearTimer);
|
|
751
1139
|
clearTimer = setTimeout(() => {
|
|
@@ -761,6 +1149,19 @@ function createAnnouncer(wm, container, messages = {}) {
|
|
|
761
1149
|
else if (win.stage === "snapped" && win.snapZone)
|
|
762
1150
|
announce(dict.snapped(win.title, win.snapZone));
|
|
763
1151
|
else if (win.stage === "normal" && previous !== "normal") announce(dict.restored(win.title));
|
|
1152
|
+
else return;
|
|
1153
|
+
detailed = true;
|
|
1154
|
+
}),
|
|
1155
|
+
wm.on("focus", ({ window: win, previous }) => {
|
|
1156
|
+
if (detailed || previous === null || lastMessage === dict.opened(win.title)) return;
|
|
1157
|
+
announce(dict.focused(win.title));
|
|
1158
|
+
}),
|
|
1159
|
+
wm.on("workspace", ({ workspace }) => {
|
|
1160
|
+
announce(dict.workspace(workspace));
|
|
1161
|
+
detailed = true;
|
|
1162
|
+
}),
|
|
1163
|
+
wm.subscribe(() => {
|
|
1164
|
+
detailed = false;
|
|
764
1165
|
})
|
|
765
1166
|
];
|
|
766
1167
|
return {
|
|
@@ -839,7 +1240,29 @@ function createDragStarter(ctx) {
|
|
|
839
1240
|
}
|
|
840
1241
|
return;
|
|
841
1242
|
}
|
|
842
|
-
|
|
1243
|
+
let nextX = session.pendingX - session.grabDX;
|
|
1244
|
+
let nextY = session.pendingY - session.grabDY;
|
|
1245
|
+
if (ctx.magnetThreshold > 0) {
|
|
1246
|
+
const state = wm.getState();
|
|
1247
|
+
const targets = [
|
|
1248
|
+
{ x: 0, y: 0, width: state.viewport.width, height: state.viewport.height }
|
|
1249
|
+
];
|
|
1250
|
+
for (const otherId of state.order) {
|
|
1251
|
+
if (otherId === id) continue;
|
|
1252
|
+
const other = state.windows[otherId];
|
|
1253
|
+
if (other && other.stage !== "minimized" && other.workspace === state.workspace) {
|
|
1254
|
+
targets.push(other.bounds);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
const magnet = magnetize(
|
|
1258
|
+
{ x: nextX, y: nextY, width: current.bounds.width, height: current.bounds.height },
|
|
1259
|
+
targets,
|
|
1260
|
+
ctx.magnetThreshold
|
|
1261
|
+
);
|
|
1262
|
+
nextX = magnet.x;
|
|
1263
|
+
nextY = magnet.y;
|
|
1264
|
+
}
|
|
1265
|
+
wm.move(id, nextX, nextY);
|
|
843
1266
|
if (ctx.snapEnabled && current.snappable) {
|
|
844
1267
|
const viewport = wm.getState().viewport;
|
|
845
1268
|
const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect);
|
|
@@ -863,7 +1286,7 @@ function createDragStarter(ctx) {
|
|
|
863
1286
|
const movePoint = ctx.toLocal(moveEvent);
|
|
864
1287
|
if (!session.moved) {
|
|
865
1288
|
const travelled = Math.abs(movePoint.x - (session.startBounds.x + session.grabDX)) + Math.abs(movePoint.y - (session.startBounds.y + session.grabDY));
|
|
866
|
-
if (travelled < 3
|
|
1289
|
+
if (travelled < 3) return;
|
|
867
1290
|
session.moved = true;
|
|
868
1291
|
if (el) {
|
|
869
1292
|
el.dataset.wmDragging = "";
|
|
@@ -891,42 +1314,46 @@ function createDragStarter(ctx) {
|
|
|
891
1314
|
}
|
|
892
1315
|
function finish(cancelled) {
|
|
893
1316
|
if (ctx.currentDrag() !== session) return;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}
|
|
909
|
-
ctx.hidePreview();
|
|
910
|
-
if (cancelled) {
|
|
911
|
-
if (session.moved && session.restored) {
|
|
912
|
-
if (session.startStage === "maximized") {
|
|
913
|
-
wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds);
|
|
914
|
-
wm.maximize(id);
|
|
915
|
-
} else if (session.startStage === "snapped" && session.startZone) {
|
|
916
|
-
wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds);
|
|
917
|
-
wm.snap(id, session.startZone);
|
|
918
|
-
} else {
|
|
919
|
-
wm.move(id, session.startBounds.x, session.startBounds.y);
|
|
1317
|
+
try {
|
|
1318
|
+
if (session.raf !== 0) view.cancelAnimationFrame(session.raf);
|
|
1319
|
+
if (session.hasPending && !cancelled) flush();
|
|
1320
|
+
if (cancelled) {
|
|
1321
|
+
if (session.moved && session.restored) {
|
|
1322
|
+
if (session.startStage === "maximized") {
|
|
1323
|
+
wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds);
|
|
1324
|
+
wm.maximize(id);
|
|
1325
|
+
} else if (session.startStage === "snapped" && session.startZone) {
|
|
1326
|
+
wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds);
|
|
1327
|
+
wm.snap(id, session.startZone);
|
|
1328
|
+
} else {
|
|
1329
|
+
wm.move(id, session.startBounds.x, session.startBounds.y);
|
|
1330
|
+
}
|
|
920
1331
|
}
|
|
1332
|
+
} else if (session.moved && session.zone) {
|
|
1333
|
+
if (session.zone === "maximize") wm.maximize(id);
|
|
1334
|
+
else wm.snap(id, session.zone);
|
|
921
1335
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1336
|
+
} finally {
|
|
1337
|
+
ctx.releaseDrag(session);
|
|
1338
|
+
releaseRect();
|
|
1339
|
+
handle.removeEventListener("pointermove", onMove);
|
|
1340
|
+
handle.removeEventListener("pointerup", onUp);
|
|
1341
|
+
handle.removeEventListener("pointercancel", onCancel);
|
|
1342
|
+
doc.removeEventListener("keydown", onKeydown, true);
|
|
1343
|
+
if (handle.hasPointerCapture(session.pointerId)) {
|
|
1344
|
+
handle.releasePointerCapture(session.pointerId);
|
|
1345
|
+
}
|
|
1346
|
+
if (el) {
|
|
1347
|
+
delete el.dataset.wmDragging;
|
|
1348
|
+
el.style.willChange = "";
|
|
1349
|
+
}
|
|
1350
|
+
ctx.hidePreview();
|
|
1351
|
+
if (cancelled) wm.abortInteraction();
|
|
1352
|
+
else wm.endInteraction();
|
|
927
1353
|
}
|
|
928
1354
|
}
|
|
929
1355
|
session.finish = finish;
|
|
1356
|
+
wm.beginInteraction();
|
|
930
1357
|
ctx.claimDrag(session);
|
|
931
1358
|
handle.setPointerCapture(event.pointerId);
|
|
932
1359
|
handle.addEventListener("pointermove", onMove);
|
|
@@ -956,40 +1383,42 @@ function createResizeStarter(ctx) {
|
|
|
956
1383
|
event.preventDefault();
|
|
957
1384
|
event.stopPropagation();
|
|
958
1385
|
const handleEl = event.currentTarget;
|
|
1386
|
+
const session = { id, finish: (cancelled) => finish(cancelled) };
|
|
1387
|
+
ctx.claimDrag(session);
|
|
1388
|
+
wm.beginInteraction();
|
|
959
1389
|
const releaseRect = ctx.trackRect();
|
|
960
1390
|
const startPoint = ctx.toLocal(event);
|
|
961
1391
|
const start = win.bounds;
|
|
1392
|
+
const startStage = win.stage;
|
|
1393
|
+
const startZone = win.snapZone;
|
|
1394
|
+
const startRestore = win.restoreBounds;
|
|
962
1395
|
const minSize = win.minSize;
|
|
963
1396
|
const maxSize = win.maxSize;
|
|
1397
|
+
const aspect = win.aspectRatio;
|
|
1398
|
+
const drive = direction === "n" || direction === "s" ? "height" : "width";
|
|
964
1399
|
let raf = 0;
|
|
965
1400
|
let pendingX = 0;
|
|
966
1401
|
let pendingY = 0;
|
|
967
1402
|
let hasPending = false;
|
|
968
1403
|
const el = ctx.windowElement(id);
|
|
969
1404
|
if (el) el.dataset.wmResizing = direction;
|
|
970
|
-
function clampWidth(width) {
|
|
971
|
-
return clamp(width, minSize.width, maxSize ? maxSize.width : Number.POSITIVE_INFINITY);
|
|
972
|
-
}
|
|
973
|
-
function clampHeight(height) {
|
|
974
|
-
return clamp(height, minSize.height, maxSize ? maxSize.height : Number.POSITIVE_INFINITY);
|
|
975
|
-
}
|
|
976
1405
|
function flush() {
|
|
977
1406
|
if (!hasPending) return;
|
|
978
1407
|
hasPending = false;
|
|
979
1408
|
raf = 0;
|
|
980
1409
|
const dx = pendingX - startPoint.x;
|
|
981
1410
|
const dy = pendingY - startPoint.y;
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
1411
|
+
const top = direction.includes("n") ? Math.max(0, start.y + dy) : start.y;
|
|
1412
|
+
const raw = {
|
|
1413
|
+
width: direction.includes("e") ? start.width + dx : direction.includes("w") ? start.width - dx : start.width,
|
|
1414
|
+
height: direction.includes("s") ? start.height + dy : direction.includes("n") ? start.y + start.height - top : start.height
|
|
1415
|
+
};
|
|
1416
|
+
const size = aspect === null ? clampSize(raw, minSize, maxSize) : applyAspect(raw, aspect, minSize, maxSize, drive);
|
|
1417
|
+
const next = {
|
|
1418
|
+
x: direction.includes("w") ? start.x + start.width - size.width : start.x,
|
|
1419
|
+
y: direction.includes("n") ? start.y + start.height - size.height : start.y,
|
|
1420
|
+
...size
|
|
1421
|
+
};
|
|
993
1422
|
wm.resize(id, next);
|
|
994
1423
|
}
|
|
995
1424
|
function onMove(moveEvent) {
|
|
@@ -1001,18 +1430,32 @@ function createResizeStarter(ctx) {
|
|
|
1001
1430
|
if (raf === 0) raf = view.requestAnimationFrame(flush);
|
|
1002
1431
|
}
|
|
1003
1432
|
function finish(cancelled) {
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1433
|
+
if (ctx.currentDrag() !== session) return;
|
|
1434
|
+
try {
|
|
1435
|
+
if (raf !== 0) view.cancelAnimationFrame(raf);
|
|
1436
|
+
if (hasPending && !cancelled) flush();
|
|
1437
|
+
if (cancelled) {
|
|
1438
|
+
if (startStage === "snapped" && startZone) {
|
|
1439
|
+
wm.restoreTo(id, startRestore ?? start);
|
|
1440
|
+
wm.snap(id, startZone);
|
|
1441
|
+
} else {
|
|
1442
|
+
wm.resize(id, start);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
} finally {
|
|
1446
|
+
ctx.releaseDrag(session);
|
|
1447
|
+
releaseRect();
|
|
1448
|
+
handleEl.removeEventListener("pointermove", onMove);
|
|
1449
|
+
handleEl.removeEventListener("pointerup", onUp);
|
|
1450
|
+
handleEl.removeEventListener("pointercancel", onCancelPointer);
|
|
1451
|
+
doc.removeEventListener("keydown", onKeydown, true);
|
|
1452
|
+
if (handleEl.hasPointerCapture(event.pointerId)) {
|
|
1453
|
+
handleEl.releasePointerCapture(event.pointerId);
|
|
1454
|
+
}
|
|
1455
|
+
if (el) delete el.dataset.wmResizing;
|
|
1456
|
+
if (cancelled) wm.abortInteraction();
|
|
1457
|
+
else wm.endInteraction();
|
|
1013
1458
|
}
|
|
1014
|
-
if (el) delete el.dataset.wmResizing;
|
|
1015
|
-
if (cancelled) wm.resize(id, start);
|
|
1016
1459
|
}
|
|
1017
1460
|
function onUp(upEvent) {
|
|
1018
1461
|
if (upEvent.pointerId !== event.pointerId) return;
|
|
@@ -1057,6 +1500,11 @@ function createResizeHandles(doc, edge, corner) {
|
|
|
1057
1500
|
}
|
|
1058
1501
|
|
|
1059
1502
|
// src/dom/controller.ts
|
|
1503
|
+
var SNAP_SHORTCUTS = {
|
|
1504
|
+
ArrowLeft: "left",
|
|
1505
|
+
ArrowRight: "right"
|
|
1506
|
+
};
|
|
1507
|
+
var FOCUSABLE_SELECTOR = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1060
1508
|
function attachDesktop(wm, element, options = {}) {
|
|
1061
1509
|
const doc = element.ownerDocument;
|
|
1062
1510
|
const view = windowOf(element);
|
|
@@ -1069,9 +1517,13 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1069
1517
|
const keyboardOptions = typeof options.keyboard === "object" ? options.keyboard : {};
|
|
1070
1518
|
const moveStep = keyboardOptions.moveStep ?? 16;
|
|
1071
1519
|
const cycleEnabled = keyboardOptions.cycle !== false;
|
|
1520
|
+
const snapShortcuts = keyboardOptions.snapShortcuts !== false;
|
|
1521
|
+
const historyShortcuts = keyboardOptions.historyShortcuts !== false;
|
|
1072
1522
|
const hitEdge = options.hitAreas?.edge ?? (coarsePointer ? 16 : 8);
|
|
1073
1523
|
const hitCorner = options.hitAreas?.corner ?? (coarsePointer ? 24 : 12);
|
|
1524
|
+
const magnetThreshold = options.magnetism === false ? 0 : (typeof options.magnetism === "object" ? options.magnetism.threshold : void 0) ?? (coarsePointer ? 12 : 8);
|
|
1074
1525
|
element.dataset.wmDesktop = "";
|
|
1526
|
+
element.tabIndex = -1;
|
|
1075
1527
|
if (view.getComputedStyle(element).position === "static") {
|
|
1076
1528
|
element.style.position = "relative";
|
|
1077
1529
|
}
|
|
@@ -1108,6 +1560,10 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1108
1560
|
function hidePreview() {
|
|
1109
1561
|
if (preview) preview.style.display = "none";
|
|
1110
1562
|
}
|
|
1563
|
+
function refreshRect() {
|
|
1564
|
+
const rect = element.getBoundingClientRect();
|
|
1565
|
+
cachedRect = { left: rect.left, top: rect.top };
|
|
1566
|
+
}
|
|
1111
1567
|
const ctx = {
|
|
1112
1568
|
wm,
|
|
1113
1569
|
doc,
|
|
@@ -1118,8 +1574,9 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1118
1574
|
},
|
|
1119
1575
|
trackRect() {
|
|
1120
1576
|
if (rectUsers === 0) {
|
|
1121
|
-
|
|
1122
|
-
|
|
1577
|
+
refreshRect();
|
|
1578
|
+
view.addEventListener("scroll", refreshRect, true);
|
|
1579
|
+
view.addEventListener("resize", refreshRect);
|
|
1123
1580
|
}
|
|
1124
1581
|
rectUsers += 1;
|
|
1125
1582
|
let released = false;
|
|
@@ -1127,7 +1584,11 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1127
1584
|
if (released) return;
|
|
1128
1585
|
released = true;
|
|
1129
1586
|
rectUsers -= 1;
|
|
1130
|
-
if (rectUsers === 0)
|
|
1587
|
+
if (rectUsers === 0) {
|
|
1588
|
+
view.removeEventListener("scroll", refreshRect, true);
|
|
1589
|
+
view.removeEventListener("resize", refreshRect);
|
|
1590
|
+
cachedRect = null;
|
|
1591
|
+
}
|
|
1131
1592
|
};
|
|
1132
1593
|
},
|
|
1133
1594
|
windowElement: (id) => registry.get(id)?.element,
|
|
@@ -1141,6 +1602,7 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1141
1602
|
topEdge,
|
|
1142
1603
|
hitEdge,
|
|
1143
1604
|
hitCorner,
|
|
1605
|
+
magnetThreshold,
|
|
1144
1606
|
currentDrag: () => drag,
|
|
1145
1607
|
claimDrag(session) {
|
|
1146
1608
|
drag = session;
|
|
@@ -1154,28 +1616,34 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1154
1616
|
if (options.autoViewport !== false) {
|
|
1155
1617
|
const applyViewport = () => wm.setViewport({ width: element.clientWidth, height: element.clientHeight });
|
|
1156
1618
|
applyViewport();
|
|
1157
|
-
const observer = new ResizeObserver(applyViewport);
|
|
1619
|
+
const observer = new view.ResizeObserver(applyViewport);
|
|
1158
1620
|
observer.observe(element);
|
|
1159
1621
|
cleanup.push(() => observer.disconnect());
|
|
1160
1622
|
}
|
|
1161
|
-
function syncWindow(attached, win, zIndex) {
|
|
1623
|
+
function syncWindow(attached, win, zIndex, activeWorkspace) {
|
|
1162
1624
|
const el = attached.element;
|
|
1163
1625
|
const firstSync = attached.lastState === null;
|
|
1164
1626
|
if (firstSync) el.style.transition = "none";
|
|
1165
|
-
if (attached.lastState !== win) {
|
|
1627
|
+
if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace) {
|
|
1166
1628
|
el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
|
|
1167
1629
|
el.style.width = `${win.bounds.width}px`;
|
|
1168
1630
|
el.style.height = `${win.bounds.height}px`;
|
|
1169
1631
|
el.dataset.wmStage = win.stage;
|
|
1170
1632
|
el.dataset.wmLayer = win.layer;
|
|
1171
|
-
el.
|
|
1633
|
+
el.dataset.wmWorkspace = String(win.workspace);
|
|
1634
|
+
const hide = win.stage === "minimized" || win.workspace !== activeWorkspace;
|
|
1635
|
+
if (hide && !el.hidden && el.contains(doc.activeElement)) {
|
|
1636
|
+
element.focus({ preventScroll: true });
|
|
1637
|
+
}
|
|
1638
|
+
el.hidden = hide;
|
|
1172
1639
|
el.setAttribute("aria-label", win.title);
|
|
1173
1640
|
if (win.layer === "modal") el.setAttribute("aria-modal", "true");
|
|
1174
1641
|
else el.removeAttribute("aria-modal");
|
|
1175
1642
|
for (const handle of attached.handles) {
|
|
1176
|
-
handle.style.display = win.resizable && win.stage === "normal" ? "" : "none";
|
|
1643
|
+
handle.style.display = win.resizable && (win.stage === "normal" || win.stage === "snapped") ? "" : "none";
|
|
1177
1644
|
}
|
|
1178
1645
|
attached.lastState = win;
|
|
1646
|
+
attached.lastWorkspace = activeWorkspace;
|
|
1179
1647
|
}
|
|
1180
1648
|
if (attached.lastZ !== zIndex) {
|
|
1181
1649
|
el.style.zIndex = String(zIndex + 1);
|
|
@@ -1193,7 +1661,9 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1193
1661
|
const attached = registry.get(id);
|
|
1194
1662
|
const win = state.windows[id];
|
|
1195
1663
|
if (!attached || !win) return;
|
|
1196
|
-
if (orderChanged || attached.lastState !== win
|
|
1664
|
+
if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace) {
|
|
1665
|
+
syncWindow(attached, win, index, state.workspace);
|
|
1666
|
+
}
|
|
1197
1667
|
});
|
|
1198
1668
|
if (state.focusedId !== lastFocused) {
|
|
1199
1669
|
if (lastFocused) {
|
|
@@ -1229,17 +1699,51 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1229
1699
|
);
|
|
1230
1700
|
cleanup.push(
|
|
1231
1701
|
wm.on("stage", ({ window: win, previous }) => {
|
|
1232
|
-
if (win.stage !== "minimized" || previous === "minimized") return;
|
|
1233
1702
|
const attached = registry.get(win.id);
|
|
1234
|
-
|
|
1235
|
-
if (
|
|
1703
|
+
if (!attached) return;
|
|
1704
|
+
if (win.stage === "minimized" && previous !== "minimized") {
|
|
1705
|
+
const target = options.minimizeTarget?.(win);
|
|
1706
|
+
if (target) flipToTarget(attached.element, target);
|
|
1707
|
+
} else if (previous === "minimized" && win.stage !== "minimized") {
|
|
1708
|
+
const target = options.minimizeTarget?.(win);
|
|
1709
|
+
if (target) view.requestAnimationFrame(() => flipFromTarget(attached.element, target));
|
|
1710
|
+
}
|
|
1236
1711
|
})
|
|
1237
1712
|
);
|
|
1238
|
-
if (keyboardEnabled
|
|
1713
|
+
if (keyboardEnabled) {
|
|
1239
1714
|
const onDesktopKeydown = (event) => {
|
|
1240
|
-
if (event.key === "F6") {
|
|
1715
|
+
if (cycleEnabled && event.key === "F6") {
|
|
1241
1716
|
event.preventDefault();
|
|
1242
1717
|
wm.cycleFocus(event.shiftKey ? -1 : 1);
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
if (!event.ctrlKey && !event.metaKey) return;
|
|
1721
|
+
const target = event.target;
|
|
1722
|
+
if (target?.closest(INTERACTIVE_SELECTOR)) return;
|
|
1723
|
+
if (historyShortcuts && (event.key === "z" || event.key === "Z")) {
|
|
1724
|
+
event.preventDefault();
|
|
1725
|
+
if (event.shiftKey) wm.redo();
|
|
1726
|
+
else wm.undo();
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (!snapShortcuts || !event.altKey) return;
|
|
1730
|
+
const focused = wm.getState().focusedId;
|
|
1731
|
+
const win = focused ? wm.get(focused) : void 0;
|
|
1732
|
+
if (!win) return;
|
|
1733
|
+
const zone = SNAP_SHORTCUTS[event.key];
|
|
1734
|
+
if (zone) {
|
|
1735
|
+
event.preventDefault();
|
|
1736
|
+
if (win.snappable) wm.snap(win.id, zone);
|
|
1737
|
+
} else if (event.key === "ArrowUp") {
|
|
1738
|
+
event.preventDefault();
|
|
1739
|
+
if (win.maximizable) wm.maximize(win.id);
|
|
1740
|
+
} else if (event.key === "ArrowDown") {
|
|
1741
|
+
event.preventDefault();
|
|
1742
|
+
if (win.stage === "normal") {
|
|
1743
|
+
if (win.minimizable) wm.minimize(win.id);
|
|
1744
|
+
} else {
|
|
1745
|
+
wm.restore(win.id);
|
|
1746
|
+
}
|
|
1243
1747
|
}
|
|
1244
1748
|
};
|
|
1245
1749
|
element.addEventListener("keydown", onDesktopKeydown);
|
|
@@ -1258,6 +1762,7 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1258
1762
|
handles: [],
|
|
1259
1763
|
lastState: null,
|
|
1260
1764
|
lastZ: -1,
|
|
1765
|
+
lastWorkspace: -1,
|
|
1261
1766
|
cleanup: []
|
|
1262
1767
|
};
|
|
1263
1768
|
windowElement.dataset.wmWindow = id;
|
|
@@ -1308,6 +1813,16 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1308
1813
|
};
|
|
1309
1814
|
handle.addEventListener("dblclick", onDoubleClick);
|
|
1310
1815
|
attached.cleanup.push(() => handle.removeEventListener("dblclick", onDoubleClick));
|
|
1816
|
+
if (options.onTitlebarContextMenu) {
|
|
1817
|
+
const onContextMenu = (event) => {
|
|
1818
|
+
const current = wm.get(id);
|
|
1819
|
+
if (!current) return;
|
|
1820
|
+
event.preventDefault();
|
|
1821
|
+
options.onTitlebarContextMenu?.(current, event);
|
|
1822
|
+
};
|
|
1823
|
+
handle.addEventListener("contextmenu", onContextMenu);
|
|
1824
|
+
attached.cleanup.push(() => handle.removeEventListener("contextmenu", onContextMenu));
|
|
1825
|
+
}
|
|
1311
1826
|
}
|
|
1312
1827
|
if (windowOptions.resizeHandles !== false) {
|
|
1313
1828
|
for (const { element: resizeHandle, direction } of createResizeHandles(
|
|
@@ -1333,19 +1848,20 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1333
1848
|
ArrowUp: [0, -1],
|
|
1334
1849
|
ArrowDown: [0, 1]
|
|
1335
1850
|
};
|
|
1851
|
+
if (event.ctrlKey || event.metaKey) return;
|
|
1336
1852
|
const vector = arrows[event.key];
|
|
1337
1853
|
if (!vector || !keyboardEnabled) return;
|
|
1338
|
-
event.preventDefault();
|
|
1339
1854
|
const step = event.altKey ? 1 : moveStep;
|
|
1340
1855
|
const [dx, dy] = vector;
|
|
1341
1856
|
if (event.shiftKey) {
|
|
1342
|
-
if (current.resizable)
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
}
|
|
1857
|
+
if (!current.resizable) return;
|
|
1858
|
+
event.preventDefault();
|
|
1859
|
+
wm.resize(id, {
|
|
1860
|
+
width: current.bounds.width + dx * step,
|
|
1861
|
+
height: current.bounds.height + dy * step
|
|
1862
|
+
});
|
|
1348
1863
|
} else if (current.draggable && current.stage === "normal") {
|
|
1864
|
+
event.preventDefault();
|
|
1349
1865
|
wm.moveBy(id, dx * step, dy * step);
|
|
1350
1866
|
}
|
|
1351
1867
|
};
|
|
@@ -1355,13 +1871,18 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1355
1871
|
if (event.key !== "Tab") return;
|
|
1356
1872
|
const current = wm.get(id);
|
|
1357
1873
|
if (current?.layer !== "modal") return;
|
|
1358
|
-
const focusables =
|
|
1359
|
-
|
|
1360
|
-
);
|
|
1874
|
+
const focusables = [
|
|
1875
|
+
...windowElement.querySelectorAll(FOCUSABLE_SELECTOR)
|
|
1876
|
+
].filter((node) => node.offsetParent !== null || node === doc.activeElement);
|
|
1361
1877
|
if (focusables.length === 0) return;
|
|
1362
1878
|
const first = focusables[0];
|
|
1363
1879
|
const last = focusables[focusables.length - 1];
|
|
1364
1880
|
if (!first || !last) return;
|
|
1881
|
+
if (!windowElement.contains(doc.activeElement)) {
|
|
1882
|
+
event.preventDefault();
|
|
1883
|
+
(event.shiftKey ? last : first).focus();
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1365
1886
|
if (event.shiftKey && doc.activeElement === first) {
|
|
1366
1887
|
event.preventDefault();
|
|
1367
1888
|
last.focus();
|
|
@@ -1390,6 +1911,9 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1390
1911
|
lastOrder = null;
|
|
1391
1912
|
lastFocused = null;
|
|
1392
1913
|
syncAll();
|
|
1914
|
+
if (wm.getState().focusedId === id && !windowElement.contains(doc.activeElement)) {
|
|
1915
|
+
windowElement.focus({ preventScroll: true });
|
|
1916
|
+
}
|
|
1393
1917
|
return detach;
|
|
1394
1918
|
}
|
|
1395
1919
|
return {
|
|
@@ -1413,29 +1937,33 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1413
1937
|
// src/dom/binder.ts
|
|
1414
1938
|
function createDesktopBinder(wm, options = {}) {
|
|
1415
1939
|
let controller = null;
|
|
1940
|
+
let stopOpen = null;
|
|
1416
1941
|
const entries = /* @__PURE__ */ new Set();
|
|
1417
1942
|
function attachEntry(entry) {
|
|
1418
1943
|
if (controller && !entry.detach && wm.get(entry.id)) {
|
|
1419
1944
|
entry.detach = controller.attachWindow(entry.id, entry.element, entry.options);
|
|
1420
1945
|
}
|
|
1421
1946
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1947
|
+
function unbindDesktop() {
|
|
1948
|
+
stopOpen?.();
|
|
1949
|
+
stopOpen = null;
|
|
1950
|
+
for (const entry of entries) entry.detach = null;
|
|
1951
|
+
controller?.destroy();
|
|
1952
|
+
controller = null;
|
|
1953
|
+
}
|
|
1427
1954
|
return {
|
|
1428
1955
|
wm,
|
|
1429
1956
|
controller: () => controller,
|
|
1430
1957
|
bindDesktop(element) {
|
|
1431
1958
|
if (controller) throw new Error("wmkit: desktop is already bound");
|
|
1432
1959
|
controller = attachDesktop(wm, element, options);
|
|
1960
|
+
stopOpen = wm.on("open", ({ window: win }) => {
|
|
1961
|
+
for (const entry of entries) {
|
|
1962
|
+
if (entry.id === win.id) attachEntry(entry);
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1433
1965
|
for (const entry of entries) attachEntry(entry);
|
|
1434
|
-
return
|
|
1435
|
-
for (const entry of entries) entry.detach = null;
|
|
1436
|
-
controller?.destroy();
|
|
1437
|
-
controller = null;
|
|
1438
|
-
};
|
|
1966
|
+
return unbindDesktop;
|
|
1439
1967
|
},
|
|
1440
1968
|
bindWindow(id, element, windowOptions) {
|
|
1441
1969
|
const entry = { id, element, options: windowOptions, detach: null };
|
|
@@ -1446,10 +1974,14 @@ function createDesktopBinder(wm, options = {}) {
|
|
|
1446
1974
|
entry.detach = null;
|
|
1447
1975
|
entries.delete(entry);
|
|
1448
1976
|
};
|
|
1977
|
+
},
|
|
1978
|
+
destroy() {
|
|
1979
|
+
unbindDesktop();
|
|
1980
|
+
entries.clear();
|
|
1449
1981
|
}
|
|
1450
1982
|
};
|
|
1451
1983
|
}
|
|
1452
1984
|
|
|
1453
|
-
export { attachDesktop, boundsEqual, clamp, clampSize, clampToViewport, createAnnouncer, createDesktopBinder, createEmitter, createWindowManager, defaultMessages, detectSnapZone, flipToTarget, prefersReducedMotion, zoneBounds };
|
|
1454
|
-
//# sourceMappingURL=chunk-
|
|
1455
|
-
//# sourceMappingURL=chunk-
|
|
1985
|
+
export { applyAspect, attachDesktop, boundsEqual, clamp, clampSize, clampToViewport, createAnnouncer, createDesktopBinder, createEmitter, createWindowManager, defaultMessages, detectSnapZone, flipFromTarget, flipToTarget, magnetize, prefersReducedMotion, zoneBounds };
|
|
1986
|
+
//# sourceMappingURL=chunk-FPZDAKNP.js.map
|
|
1987
|
+
//# sourceMappingURL=chunk-FPZDAKNP.js.map
|