@surdeddd/wmkit 0.4.1 → 0.5.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.
Files changed (41) hide show
  1. package/README.md +2 -1
  2. package/README.ru.md +2 -1
  3. package/dist/angular.cjs +3 -3
  4. package/dist/angular.d.cts +2 -2
  5. package/dist/angular.d.ts +2 -2
  6. package/dist/angular.js +1 -1
  7. package/dist/{binder-B2A2IZGh.d.ts → binder-Ca3GOfZX.d.ts} +5 -1
  8. package/dist/{binder-Dl07SXIM.d.cts → binder-EBroZTRw.d.cts} +5 -1
  9. package/dist/{chunk-OOEDFA7K.js → chunk-L43XBN44.js} +290 -28
  10. package/dist/chunk-L43XBN44.js.map +1 -0
  11. package/dist/{chunk-TJ6QOOJ2.cjs → chunk-RXAMUBUJ.cjs} +290 -28
  12. package/dist/chunk-RXAMUBUJ.cjs.map +1 -0
  13. package/dist/index.cjs +18 -18
  14. package/dist/index.d.cts +4 -4
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +1 -1
  17. package/dist/persist.d.cts +1 -1
  18. package/dist/persist.d.ts +1 -1
  19. package/dist/popout.d.cts +1 -1
  20. package/dist/popout.d.ts +1 -1
  21. package/dist/react.cjs +3 -3
  22. package/dist/react.d.cts +2 -2
  23. package/dist/react.d.ts +2 -2
  24. package/dist/react.js +1 -1
  25. package/dist/solid.cjs +3 -3
  26. package/dist/solid.d.cts +2 -2
  27. package/dist/solid.d.ts +2 -2
  28. package/dist/solid.js +1 -1
  29. package/dist/svelte.cjs +3 -3
  30. package/dist/svelte.d.cts +2 -2
  31. package/dist/svelte.d.ts +2 -2
  32. package/dist/svelte.js +1 -1
  33. package/dist/{types-t6pyVYKN.d.cts → types-LP7Inhxx.d.cts} +18 -0
  34. package/dist/{types-t6pyVYKN.d.ts → types-LP7Inhxx.d.ts} +18 -0
  35. package/dist/vue.cjs +3 -3
  36. package/dist/vue.d.cts +2 -2
  37. package/dist/vue.d.ts +2 -2
  38. package/dist/vue.js +1 -1
  39. package/package.json +7 -7
  40. package/dist/chunk-OOEDFA7K.js.map +0 -1
  41. package/dist/chunk-TJ6QOOJ2.cjs.map +0 -1
@@ -199,6 +199,8 @@ function createWindowManager(options = {}) {
199
199
  let focusedId = null;
200
200
  let viewport = options.viewport ?? { width: 0, height: 0 };
201
201
  let workspace = normalizeWorkspace(options.workspace, 0);
202
+ let activeTabs = {};
203
+ let groupCounter = 0;
202
204
  let seq = 0;
203
205
  let idCounter = 0;
204
206
  let cascadeIndex = 0;
@@ -211,12 +213,13 @@ function createWindowManager(options = {}) {
211
213
  let future = [];
212
214
  let interactionDepth = 0;
213
215
  let interactionRecorded = false;
216
+ let interactionMark = null;
214
217
  let skipNextHistory = false;
215
218
  let pendingHistory = null;
216
219
  const layouts = /* @__PURE__ */ new Map();
217
220
  function getState() {
218
221
  if (!snapshot) {
219
- snapshot = { windows, order, focusedId, viewport, workspace };
222
+ snapshot = { windows, order, focusedId, viewport, workspace, groups: buildGroups() };
220
223
  }
221
224
  return snapshot;
222
225
  }
@@ -268,11 +271,54 @@ function createWindowManager(options = {}) {
268
271
  function onActiveWorkspace(win) {
269
272
  return win.workspace === workspace;
270
273
  }
274
+ function membersOf(groupId) {
275
+ return order.filter((entry) => windows[entry].groupId === groupId);
276
+ }
277
+ function isVisibleTab(win) {
278
+ return win.groupId === null || activeTabs[win.groupId] === win.id;
279
+ }
280
+ function buildGroups() {
281
+ const result = {};
282
+ for (const [groupId, activeId] of Object.entries(activeTabs)) {
283
+ result[groupId] = { id: groupId, activeId, members: membersOf(groupId) };
284
+ }
285
+ return result;
286
+ }
287
+ function syncGroup(source) {
288
+ if (source.groupId === null) return;
289
+ for (const id of membersOf(source.groupId)) {
290
+ if (id === source.id) continue;
291
+ const member = windows[id];
292
+ if (boundsEqual(member.bounds, source.bounds) && member.stage === source.stage && member.snapZone === source.snapZone && member.layer === source.layer && member.workspace === source.workspace) {
293
+ continue;
294
+ }
295
+ setWindow({
296
+ ...member,
297
+ bounds: source.bounds,
298
+ stage: source.stage,
299
+ snapZone: source.snapZone,
300
+ restoreBounds: source.restoreBounds,
301
+ restoreStage: source.restoreStage,
302
+ layer: source.layer,
303
+ workspace: source.workspace
304
+ });
305
+ }
306
+ }
307
+ function dissolveIfOrphaned(groupId) {
308
+ const members = membersOf(groupId);
309
+ if (members.length >= 2) return;
310
+ for (const id of members) {
311
+ setWindow({ ...windows[id], groupId: null });
312
+ }
313
+ const { [groupId]: _dropped, ...rest } = activeTabs;
314
+ activeTabs = rest;
315
+ }
271
316
  function topModalId() {
272
317
  for (let i = order.length - 1; i >= 0; i -= 1) {
273
318
  const win = windows[order[i]];
274
- if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win))
319
+ if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win)) {
275
320
  return win.id;
321
+ }
276
322
  }
277
323
  return null;
278
324
  }
@@ -280,7 +326,7 @@ function createWindowManager(options = {}) {
280
326
  const modal = topModalId();
281
327
  return order.filter((id) => {
282
328
  const win = windows[id];
283
- if (win.stage === "minimized" || !onActiveWorkspace(win)) return false;
329
+ if (win.stage === "minimized" || !onActiveWorkspace(win) || !isVisibleTab(win)) return false;
284
330
  if (modal && win.layer !== "modal") return false;
285
331
  return true;
286
332
  });
@@ -321,7 +367,13 @@ function createWindowManager(options = {}) {
321
367
  width: init.width ?? defaultSize.width,
322
368
  height: init.height ?? defaultSize.height
323
369
  };
324
- const size = aspectRatio === null ? clampSize(requested, minSize, maxSize) : applyAspect(requested, aspectRatio, minSize, maxSize, "width");
370
+ const size = aspectRatio === null ? clampSize(requested, minSize, maxSize) : applyAspect(
371
+ requested,
372
+ aspectRatio,
373
+ minSize,
374
+ maxSize,
375
+ init.width === void 0 && init.height !== void 0 ? "height" : "width"
376
+ );
325
377
  const position = positionForOpen(init);
326
378
  let bounds = { ...position, ...size };
327
379
  if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible);
@@ -337,6 +389,7 @@ function createWindowManager(options = {}) {
337
389
  snapZone: null,
338
390
  layer: init.layer ?? "normal",
339
391
  workspace: normalizeWorkspace(init.workspace, workspace),
392
+ groupId: null,
340
393
  minSize,
341
394
  maxSize,
342
395
  aspectRatio,
@@ -380,6 +433,13 @@ function createWindowManager(options = {}) {
380
433
  const win = windows[id];
381
434
  if (!win) return false;
382
435
  removeWindow(id);
436
+ if (win.groupId !== null) {
437
+ const remaining = membersOf(win.groupId);
438
+ if (activeTabs[win.groupId] === id && remaining[0]) {
439
+ activeTabs = { ...activeTabs, [win.groupId]: remaining[0] };
440
+ }
441
+ dissolveIfOrphaned(win.groupId);
442
+ }
383
443
  queueEvent(() => emitter.emit("close", { window: win }));
384
444
  if (focusedId === id) {
385
445
  focusTop();
@@ -396,6 +456,7 @@ function createWindowManager(options = {}) {
396
456
  const win = windows[id];
397
457
  if (!win) return false;
398
458
  if (!onActiveWorkspace(win)) setWorkspace(win.workspace);
459
+ if (!isVisibleTab(win)) activateTab(id);
399
460
  const modal = topModalId();
400
461
  if (modal && modal !== id && win.layer !== "modal") {
401
462
  const modalWin = windows[modal];
@@ -437,7 +498,13 @@ function createWindowManager(options = {}) {
437
498
  }
438
499
  function snapBounds(win, zone) {
439
500
  const rect = zoneBounds(zone, viewport);
440
- return { x: rect.x, y: rect.y, ...normalizeSize(rect, win) };
501
+ const byWidth = normalizeSize(rect, win);
502
+ const size = byWidth.height > rect.height ? normalizeSize(rect, win, "height") : byWidth;
503
+ return {
504
+ x: Math.max(0, Math.min(rect.x, viewport.width - size.width)),
505
+ y: Math.max(0, Math.min(rect.y, viewport.height - size.height)),
506
+ ...size
507
+ };
441
508
  }
442
509
  function focusUnlessBlocked(id) {
443
510
  const modal = topModalId();
@@ -449,6 +516,7 @@ function createWindowManager(options = {}) {
449
516
  const next = build(win);
450
517
  if (!next) return false;
451
518
  setWindow(next);
519
+ syncGroup(next);
452
520
  queueEvent(() => emitter.emit("stage", { window: next, previous: win.stage }));
453
521
  commit();
454
522
  return true;
@@ -558,6 +626,7 @@ function createWindowManager(options = {}) {
558
626
  if (boundsEqual(bounds, win.bounds)) return true;
559
627
  const next = { ...win, bounds };
560
628
  setWindow(next);
629
+ syncGroup(next);
561
630
  queueEvent(() => emitter.emit("move", { window: next }));
562
631
  commit();
563
632
  return true;
@@ -579,6 +648,7 @@ function createWindowManager(options = {}) {
579
648
  if (boundsEqual(bounds, win.bounds) && !becameNormal) return true;
580
649
  const next = becameNormal ? { ...win, stage: "normal", snapZone: null, restoreBounds: null, bounds } : { ...win, bounds };
581
650
  setWindow(next);
651
+ syncGroup(next);
582
652
  if (becameNormal) queueEvent(() => emitter.emit("stage", { window: next, previous: "snapped" }));
583
653
  queueEvent(() => emitter.emit("resize", { window: next }));
584
654
  commit();
@@ -602,10 +672,11 @@ function createWindowManager(options = {}) {
602
672
  snappable: patch.snappable ?? win.snappable,
603
673
  meta: patch.meta ? { ...win.meta, ...patch.meta } : win.meta
604
674
  };
605
- const size = normalizeSize(next.bounds, next);
675
+ const size = next.stage === "maximized" ? next.bounds : normalizeSize(next.bounds, next);
606
676
  const resized = size.width !== next.bounds.width || size.height !== next.bounds.height;
607
677
  const finalWin = resized ? { ...next, bounds: { ...next.bounds, ...size } } : next;
608
678
  setWindow(finalWin);
679
+ syncGroup(finalWin);
609
680
  if (patch.layer && patch.layer !== win.layer) {
610
681
  order = sortByLayer(order);
611
682
  queueEvent(() => emitter.emit("order", { order }));
@@ -647,14 +718,90 @@ function createWindowManager(options = {}) {
647
718
  if (target === win.workspace) return false;
648
719
  const updated = { ...win, workspace: target };
649
720
  setWindow(updated);
721
+ syncGroup(updated);
650
722
  if (!onActiveWorkspace(updated) && focusedId === id) {
651
723
  focusTop();
652
724
  if (focusedId) emitFocus(windows[focusedId], id);
725
+ } else if (onActiveWorkspace(updated) && topModalId() === id && focusedId !== id) {
726
+ const previous = focusedId;
727
+ focusedId = id;
728
+ emitFocus(updated, previous);
653
729
  }
654
730
  queueEvent(() => emitter.emit("update", { window: updated }));
655
731
  commit();
656
732
  return true;
657
733
  }
734
+ function emitGroup(groupId, previous) {
735
+ const members = membersOf(groupId);
736
+ const activeId = activeTabs[groupId];
737
+ queueEvent(() => emitter.emit("group", { groupId, members, activeId, previous }));
738
+ }
739
+ function group(ids) {
740
+ const seeds = ids.filter((id, index) => windows[id] && ids.indexOf(id) === index);
741
+ const host = seeds[0] ? windows[seeds[0]] : null;
742
+ if (!host) return null;
743
+ const expanded = [];
744
+ for (const id of seeds) {
745
+ const win = windows[id];
746
+ const siblings = win.groupId === null ? [id] : membersOf(win.groupId);
747
+ for (const entry of siblings) if (!expanded.includes(entry)) expanded.push(entry);
748
+ }
749
+ if (expanded.length < 2) return null;
750
+ const groupId = `${idPrefix}-group-${++groupCounter}`;
751
+ const retired = /* @__PURE__ */ new Set();
752
+ for (const id of expanded) {
753
+ const win = windows[id];
754
+ if (win.groupId !== null) retired.add(win.groupId);
755
+ setWindow({
756
+ ...win,
757
+ groupId,
758
+ bounds: host.bounds,
759
+ stage: host.stage,
760
+ snapZone: host.snapZone,
761
+ restoreBounds: host.restoreBounds,
762
+ restoreStage: host.restoreStage,
763
+ layer: host.layer,
764
+ workspace: host.workspace
765
+ });
766
+ }
767
+ const next = { ...activeTabs, [groupId]: host.id };
768
+ for (const old of retired) delete next[old];
769
+ activeTabs = next;
770
+ raise(host.id);
771
+ emitGroup(groupId, null);
772
+ queueEvent(() => emitter.emit("order", { order }));
773
+ commit();
774
+ if (onActiveWorkspace(host) && host.stage !== "minimized") focus(host.id);
775
+ return groupId;
776
+ }
777
+ function ungroup(id) {
778
+ const win = windows[id];
779
+ if (!win?.groupId) return false;
780
+ const groupId = win.groupId;
781
+ const wasActive = activeTabs[groupId] === id;
782
+ setWindow({ ...win, groupId: null });
783
+ const remaining = membersOf(groupId);
784
+ if (wasActive) activeTabs = { ...activeTabs, [groupId]: remaining[0] };
785
+ dissolveIfOrphaned(groupId);
786
+ if (membersOf(groupId).length >= 2) emitGroup(groupId, wasActive ? id : null);
787
+ commit();
788
+ focus(id);
789
+ return true;
790
+ }
791
+ function activateTab(id) {
792
+ const win = windows[id];
793
+ if (!win?.groupId) return false;
794
+ const groupId = win.groupId;
795
+ const previous = activeTabs[groupId];
796
+ if (previous === id) return false;
797
+ activeTabs = { ...activeTabs, [groupId]: id };
798
+ emitGroup(groupId, previous);
799
+ commit();
800
+ return true;
801
+ }
802
+ function groupMembers(groupId) {
803
+ return membersOf(groupId).map((id) => windows[id]);
804
+ }
658
805
  function sendToBack(id) {
659
806
  const win = windows[id];
660
807
  if (!win) return false;
@@ -686,10 +833,10 @@ function createWindowManager(options = {}) {
686
833
  );
687
834
  }
688
835
  function minimized() {
689
- return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win)).sort((a, b) => a.openedSeq - b.openedSeq);
836
+ return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win) && isVisibleTab(win)).sort((a, b) => a.openedSeq - b.openedSeq);
690
837
  }
691
838
  function captureEntry() {
692
- return { windows, order, focusedId, workspace };
839
+ return { windows, order, focusedId, workspace, activeTabs };
693
840
  }
694
841
  function recordHistory() {
695
842
  if (!historyDirty) return;
@@ -751,6 +898,7 @@ function createWindowManager(options = {}) {
751
898
  windows = entry.windows;
752
899
  order = [...entry.order];
753
900
  focusedId = entry.focusedId;
901
+ activeTabs = entry.activeTabs;
754
902
  const previousWorkspace = workspace;
755
903
  workspace = entry.workspace;
756
904
  if (workspace !== previousWorkspace) {
@@ -777,14 +925,20 @@ function createWindowManager(options = {}) {
777
925
  }
778
926
  function beginInteraction() {
779
927
  interactionDepth += 1;
780
- if (interactionDepth === 1) interactionRecorded = false;
928
+ if (interactionDepth === 1) {
929
+ interactionRecorded = false;
930
+ interactionMark = { past: [...past], future };
931
+ }
781
932
  }
782
933
  function endInteraction() {
783
934
  if (interactionDepth > 0) interactionDepth -= 1;
935
+ if (interactionDepth === 0) interactionMark = null;
784
936
  }
785
937
  function abortInteraction() {
786
- if (interactionDepth > 0 && interactionRecorded) {
787
- past.pop();
938
+ if (interactionDepth > 0 && interactionMark) {
939
+ past.length = 0;
940
+ past.push(...interactionMark.past);
941
+ future = interactionMark.future;
788
942
  interactionRecorded = false;
789
943
  }
790
944
  endInteraction();
@@ -875,7 +1029,8 @@ function createWindowManager(options = {}) {
875
1029
  })),
876
1030
  order: [...order],
877
1031
  focusedId,
878
- workspace
1032
+ workspace,
1033
+ activeTabs: { ...activeTabs }
879
1034
  };
880
1035
  }
881
1036
  function isBounds(value) {
@@ -930,6 +1085,7 @@ function createWindowManager(options = {}) {
930
1085
  snapZone: ZONES.includes(raw.snapZone) ? raw.snapZone : null,
931
1086
  layer: raw.layer,
932
1087
  workspace: normalizeWorkspace(raw.workspace, 0),
1088
+ groupId: typeof raw.groupId === "string" && raw.groupId.length > 0 ? raw.groupId : null,
933
1089
  minSize: isSize(raw.minSize) ? { ...raw.minSize } : { width: 160, height: 100 },
934
1090
  maxSize: readMaxSize(raw.maxSize),
935
1091
  aspectRatio: typeof raw.aspectRatio === "number" && Number.isFinite(raw.aspectRatio) && raw.aspectRatio > 0 ? raw.aspectRatio : null,
@@ -971,7 +1127,18 @@ function createWindowManager(options = {}) {
971
1127
  if (focusedCandidate && (focusedCandidate.stage === "minimized" || !onActiveWorkspace(focusedCandidate))) {
972
1128
  focusedId = null;
973
1129
  }
974
- if (!focusedId) focusTop();
1130
+ const restoredTabs = {};
1131
+ const rawTabs = data.activeTabs;
1132
+ for (const id of order) {
1133
+ const groupId = windows[id].groupId;
1134
+ if (groupId === null || restoredTabs[groupId]) continue;
1135
+ const wanted = typeof rawTabs === "object" && rawTabs !== null ? rawTabs[groupId] : void 0;
1136
+ const members = membersOf(groupId);
1137
+ restoredTabs[groupId] = typeof wanted === "string" && members.includes(wanted) ? wanted : members[0];
1138
+ }
1139
+ activeTabs = restoredTabs;
1140
+ for (const groupId of Object.keys(restoredTabs)) dissolveIfOrphaned(groupId);
1141
+ if (!focusedId || !isVisibleTab(windows[focusedId])) focusTop();
975
1142
  const modal = topModalId();
976
1143
  if (modal) {
977
1144
  const focusedWin = focusedId ? windows[focusedId] : null;
@@ -1025,6 +1192,10 @@ function createWindowManager(options = {}) {
1025
1192
  workspace: () => workspace,
1026
1193
  setWorkspace: (next) => transact(() => setWorkspace(next)),
1027
1194
  moveToWorkspace: (id, next) => transact(() => moveToWorkspace(id, next)),
1195
+ group: (ids) => transact(() => group(ids)),
1196
+ ungroup: (id) => transact(() => ungroup(id)),
1197
+ activateTab: (id) => transact(() => activateTab(id)),
1198
+ groupMembers,
1028
1199
  batch: (run) => transact(run),
1029
1200
  serialize,
1030
1201
  hydrate: (data) => transact(() => hydrate(data)),
@@ -1132,10 +1303,8 @@ function createAnnouncer(wm, container, messages = {}) {
1132
1303
  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";
1133
1304
  container.append(element);
1134
1305
  let clearTimer;
1135
- let lastMessage = "";
1136
1306
  let detailed = false;
1137
1307
  function announce(message) {
1138
- lastMessage = message;
1139
1308
  element.textContent = message;
1140
1309
  if (clearTimer !== void 0) clearTimeout(clearTimer);
1141
1310
  clearTimer = setTimeout(() => {
@@ -1143,8 +1312,14 @@ function createAnnouncer(wm, container, messages = {}) {
1143
1312
  }, 2e3);
1144
1313
  }
1145
1314
  const unsubscribers = [
1146
- wm.on("open", ({ window: win }) => announce(dict.opened(win.title))),
1147
- wm.on("close", ({ window: win }) => announce(dict.closed(win.title))),
1315
+ wm.on("open", ({ window: win }) => {
1316
+ announce(dict.opened(win.title));
1317
+ detailed = true;
1318
+ }),
1319
+ wm.on("close", ({ window: win }) => {
1320
+ announce(dict.closed(win.title));
1321
+ detailed = true;
1322
+ }),
1148
1323
  wm.on("stage", ({ window: win, previous }) => {
1149
1324
  if (win.stage === "minimized") announce(dict.minimized(win.title));
1150
1325
  else if (win.stage === "maximized") announce(dict.maximized(win.title));
@@ -1155,7 +1330,7 @@ function createAnnouncer(wm, container, messages = {}) {
1155
1330
  detailed = true;
1156
1331
  }),
1157
1332
  wm.on("focus", ({ window: win, previous }) => {
1158
- if (detailed || previous === null || lastMessage === dict.opened(win.title)) return;
1333
+ if (detailed || previous === null) return;
1159
1334
  announce(dict.focused(win.title));
1160
1335
  }),
1161
1336
  wm.on("workspace", ({ workspace }) => {
@@ -1210,6 +1385,11 @@ function createDragStarter(ctx) {
1210
1385
  restored: win.stage === "normal",
1211
1386
  moved: false,
1212
1387
  zone: null,
1388
+ groupTarget: null,
1389
+ hoverId: null,
1390
+ hoverTimer: 0,
1391
+ clientX: event.clientX,
1392
+ clientY: event.clientY,
1213
1393
  raf: 0,
1214
1394
  pendingX: 0,
1215
1395
  pendingY: 0,
@@ -1218,6 +1398,18 @@ function createDragStarter(ctx) {
1218
1398
  }
1219
1399
  };
1220
1400
  const el = ctx.windowElement(id);
1401
+ function armGroup(target2) {
1402
+ session.groupTarget = target2;
1403
+ ctx.markGroupTarget(target2);
1404
+ }
1405
+ function clearHover() {
1406
+ if (session.hoverTimer !== 0) {
1407
+ view.clearTimeout(session.hoverTimer);
1408
+ session.hoverTimer = 0;
1409
+ }
1410
+ session.groupTarget = null;
1411
+ ctx.markGroupTarget(null);
1412
+ }
1221
1413
  function flush() {
1222
1414
  if (!session.hasPending || ctx.currentDrag() !== session) return;
1223
1415
  session.hasPending = false;
@@ -1265,6 +1457,20 @@ function createDragStarter(ctx) {
1265
1457
  nextY = magnet.y;
1266
1458
  }
1267
1459
  wm.move(id, nextX, nextY);
1460
+ const candidate = ctx.groupTarget(session.clientX, session.clientY, id);
1461
+ if (candidate !== session.hoverId) {
1462
+ session.hoverId = candidate;
1463
+ clearHover();
1464
+ if (candidate) {
1465
+ if (ctx.groupDwell <= 0) armGroup(candidate);
1466
+ else session.hoverTimer = view.setTimeout(() => armGroup(candidate), ctx.groupDwell);
1467
+ }
1468
+ }
1469
+ if (session.groupTarget) {
1470
+ session.zone = null;
1471
+ ctx.hidePreview();
1472
+ return;
1473
+ }
1268
1474
  if (ctx.snapEnabled && current.snappable) {
1269
1475
  const viewport = wm.getState().viewport;
1270
1476
  const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect);
@@ -1297,6 +1503,8 @@ function createDragStarter(ctx) {
1297
1503
  }
1298
1504
  session.pendingX = movePoint.x;
1299
1505
  session.pendingY = movePoint.y;
1506
+ session.clientX = moveEvent.clientX;
1507
+ session.clientY = moveEvent.clientY;
1300
1508
  session.hasPending = true;
1301
1509
  if (session.raf === 0) session.raf = view.requestAnimationFrame(flush);
1302
1510
  }
@@ -1331,6 +1539,8 @@ function createDragStarter(ctx) {
1331
1539
  wm.move(id, session.startBounds.x, session.startBounds.y);
1332
1540
  }
1333
1541
  }
1542
+ } else if (session.moved && session.groupTarget) {
1543
+ if (wm.group([session.groupTarget, id])) wm.activateTab(id);
1334
1544
  } else if (session.moved && session.zone) {
1335
1545
  if (session.zone === "maximize") wm.maximize(id);
1336
1546
  else wm.snap(id, session.zone);
@@ -1350,6 +1560,8 @@ function createDragStarter(ctx) {
1350
1560
  el.style.willChange = "";
1351
1561
  }
1352
1562
  ctx.hidePreview();
1563
+ if (session.hoverTimer !== 0) view.clearTimeout(session.hoverTimer);
1564
+ ctx.markGroupTarget(null);
1353
1565
  if (cancelled) wm.abortInteraction();
1354
1566
  else wm.endInteraction();
1355
1567
  }
@@ -1402,6 +1614,7 @@ function createResizeStarter(ctx) {
1402
1614
  let pendingX = 0;
1403
1615
  let pendingY = 0;
1404
1616
  let hasPending = false;
1617
+ let applied = false;
1405
1618
  const el = ctx.windowElement(id);
1406
1619
  if (el) el.dataset.wmResizing = direction;
1407
1620
  function flush() {
@@ -1410,18 +1623,24 @@ function createResizeStarter(ctx) {
1410
1623
  raf = 0;
1411
1624
  const dx = pendingX - startPoint.x;
1412
1625
  const dy = pendingY - startPoint.y;
1413
- const top = direction.includes("n") ? Math.max(0, start.y + dy) : start.y;
1626
+ const floor = Math.min(0, start.y);
1627
+ const top = direction.includes("n") ? Math.max(floor, start.y + dy) : start.y;
1414
1628
  const raw = {
1415
1629
  width: direction.includes("e") ? start.width + dx : direction.includes("w") ? start.width - dx : start.width,
1416
1630
  height: direction.includes("s") ? start.height + dy : direction.includes("n") ? start.y + start.height - top : start.height
1417
1631
  };
1418
- const size = aspect === null ? clampSize(raw, minSize, maxSize) : applyAspect(raw, aspect, minSize, maxSize, drive);
1632
+ let size = aspect === null ? clampSize(raw, minSize, maxSize) : applyAspect(raw, aspect, minSize, maxSize, drive);
1633
+ const available = start.y + start.height - floor;
1634
+ if (direction.includes("n") && size.height > available) {
1635
+ size = applyAspect({ ...size, height: available }, aspect ?? 0, minSize, maxSize, "height");
1636
+ }
1419
1637
  const next = {
1420
1638
  x: direction.includes("w") ? start.x + start.width - size.width : start.x,
1421
1639
  y: direction.includes("n") ? start.y + start.height - size.height : start.y,
1422
1640
  ...size
1423
1641
  };
1424
1642
  wm.resize(id, next);
1643
+ applied = true;
1425
1644
  }
1426
1645
  function onMove(moveEvent) {
1427
1646
  if (moveEvent.pointerId !== event.pointerId) return;
@@ -1436,7 +1655,7 @@ function createResizeStarter(ctx) {
1436
1655
  try {
1437
1656
  if (raf !== 0) view.cancelAnimationFrame(raf);
1438
1657
  if (hasPending && !cancelled) flush();
1439
- if (cancelled) {
1658
+ if (cancelled && applied) {
1440
1659
  if (startStage === "snapped" && startZone) {
1441
1660
  wm.restoreTo(id, startRestore ?? start);
1442
1661
  wm.snap(id, startZone);
@@ -1512,6 +1731,8 @@ function attachDesktop(wm, element, options = {}) {
1512
1731
  const doc = element.ownerDocument;
1513
1732
  const view = windowOf(element);
1514
1733
  const coarsePointer = typeof view.matchMedia === "function" && view.matchMedia("(pointer: coarse)").matches;
1734
+ const groupingEnabled = options.grouping !== false;
1735
+ const groupDwell = (typeof options.grouping === "object" ? options.grouping.dwell : void 0) ?? 420;
1515
1736
  const snapEnabled = options.snap !== false;
1516
1737
  const snapOptions = typeof options.snap === "object" ? options.snap : {};
1517
1738
  const snapPreviewEnabled = snapOptions.preview !== false;
@@ -1535,6 +1756,7 @@ function attachDesktop(wm, element, options = {}) {
1535
1756
  let lastOrder = null;
1536
1757
  let lastFocused = null;
1537
1758
  let drag = null;
1759
+ let groupTargetId = null;
1538
1760
  let cachedRect = null;
1539
1761
  let rectUsers = 0;
1540
1762
  let announcer = null;
@@ -1606,6 +1828,29 @@ function attachDesktop(wm, element, options = {}) {
1606
1828
  hitEdge,
1607
1829
  hitCorner,
1608
1830
  magnetThreshold,
1831
+ groupDwell,
1832
+ groupTarget(clientX, clientY, selfId) {
1833
+ if (!groupingEnabled || typeof doc.elementsFromPoint !== "function") return null;
1834
+ for (const node of doc.elementsFromPoint(clientX, clientY)) {
1835
+ const handle = node.closest?.("[data-wm-drag]");
1836
+ const host = handle?.closest("[data-wm-window]");
1837
+ const id = host?.dataset.wmWindow;
1838
+ if (id && id !== selfId && registry.has(id)) return id;
1839
+ }
1840
+ return null;
1841
+ },
1842
+ markGroupTarget(id) {
1843
+ if (groupTargetId === id) return;
1844
+ if (groupTargetId) {
1845
+ const previous = registry.get(groupTargetId);
1846
+ if (previous) delete previous.element.dataset.wmTabTarget;
1847
+ }
1848
+ groupTargetId = id;
1849
+ if (id) {
1850
+ const next = registry.get(id);
1851
+ if (next) next.element.dataset.wmTabTarget = "";
1852
+ }
1853
+ },
1609
1854
  currentDrag: () => drag,
1610
1855
  claimDrag(session) {
1611
1856
  drag = session;
@@ -1623,21 +1868,33 @@ function attachDesktop(wm, element, options = {}) {
1623
1868
  observer.observe(element);
1624
1869
  cleanup.push(() => observer.disconnect());
1625
1870
  }
1626
- function syncWindow(attached, win, zIndex, activeWorkspace) {
1871
+ function syncWindow(attached, win, zIndex, activeWorkspace, activeTab) {
1627
1872
  const el = attached.element;
1628
1873
  const firstSync = attached.lastState === null;
1629
1874
  if (firstSync) el.style.transition = "none";
1630
- if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace) {
1875
+ if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace || attached.lastTab !== activeTab) {
1631
1876
  el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
1632
1877
  el.style.width = `${win.bounds.width}px`;
1633
1878
  el.style.height = `${win.bounds.height}px`;
1634
1879
  el.dataset.wmStage = win.stage;
1635
1880
  el.dataset.wmLayer = win.layer;
1636
1881
  el.dataset.wmWorkspace = String(win.workspace);
1637
- const hide = win.stage === "minimized" || win.workspace !== activeWorkspace;
1882
+ if (win.groupId === null) {
1883
+ delete el.dataset.wmGroup;
1884
+ delete el.dataset.wmTab;
1885
+ } else {
1886
+ el.dataset.wmGroup = win.groupId;
1887
+ el.dataset.wmTab = activeTab ? "active" : "inactive";
1888
+ }
1889
+ const hide = win.stage === "minimized" || win.workspace !== activeWorkspace || !activeTab;
1638
1890
  if (hide && !el.hidden && el.contains(doc.activeElement)) {
1639
1891
  element.focus({ preventScroll: true });
1640
1892
  }
1893
+ if (!hide && el.hidden && wm.getState().focusedId === win.id) {
1894
+ queueMicrotask(() => {
1895
+ if (!el.hidden && !el.contains(doc.activeElement)) el.focus({ preventScroll: true });
1896
+ });
1897
+ }
1641
1898
  el.hidden = hide;
1642
1899
  el.setAttribute("aria-label", win.title);
1643
1900
  if (win.layer === "modal") el.setAttribute("aria-modal", "true");
@@ -1647,6 +1904,7 @@ function attachDesktop(wm, element, options = {}) {
1647
1904
  }
1648
1905
  attached.lastState = win;
1649
1906
  attached.lastWorkspace = activeWorkspace;
1907
+ attached.lastTab = activeTab;
1650
1908
  }
1651
1909
  if (attached.lastZ !== zIndex) {
1652
1910
  el.style.zIndex = String(zIndex + 1);
@@ -1664,8 +1922,9 @@ function attachDesktop(wm, element, options = {}) {
1664
1922
  const attached = registry.get(id);
1665
1923
  const win = state.windows[id];
1666
1924
  if (!attached || !win) return;
1667
- if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace) {
1668
- syncWindow(attached, win, index, state.workspace);
1925
+ const activeTab = win.groupId === null || state.groups[win.groupId]?.activeId === win.id;
1926
+ if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace || attached.lastTab !== activeTab) {
1927
+ syncWindow(attached, win, index, state.workspace, activeTab);
1669
1928
  }
1670
1929
  });
1671
1930
  if (state.focusedId !== lastFocused) {
@@ -1686,6 +1945,7 @@ function attachDesktop(wm, element, options = {}) {
1686
1945
  wm.on("focus", ({ window: win }) => {
1687
1946
  const attached = registry.get(win.id);
1688
1947
  if (!attached) return;
1948
+ if (attached.element.hidden) syncAll();
1689
1949
  if (!attached.element.contains(doc.activeElement)) {
1690
1950
  attached.element.focus({ preventScroll: true });
1691
1951
  }
@@ -1721,6 +1981,7 @@ function attachDesktop(wm, element, options = {}) {
1721
1981
  return;
1722
1982
  }
1723
1983
  if (!event.ctrlKey && !event.metaKey) return;
1984
+ if (drag) return;
1724
1985
  const target = event.target;
1725
1986
  if (target?.closest(INTERACTIVE_SELECTOR)) return;
1726
1987
  if (historyShortcuts && (event.key === "z" || event.key === "Z")) {
@@ -1766,6 +2027,7 @@ function attachDesktop(wm, element, options = {}) {
1766
2027
  lastState: null,
1767
2028
  lastZ: -1,
1768
2029
  lastWorkspace: -1,
2030
+ lastTab: true,
1769
2031
  cleanup: []
1770
2032
  };
1771
2033
  windowElement.dataset.wmWindow = id;
@@ -2005,5 +2267,5 @@ exports.flipToTarget = flipToTarget;
2005
2267
  exports.magnetize = magnetize;
2006
2268
  exports.prefersReducedMotion = prefersReducedMotion;
2007
2269
  exports.zoneBounds = zoneBounds;
2008
- //# sourceMappingURL=chunk-TJ6QOOJ2.cjs.map
2009
- //# sourceMappingURL=chunk-TJ6QOOJ2.cjs.map
2270
+ //# sourceMappingURL=chunk-RXAMUBUJ.cjs.map
2271
+ //# sourceMappingURL=chunk-RXAMUBUJ.cjs.map