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