@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
@@ -197,6 +197,8 @@ function createWindowManager(options = {}) {
197
197
  let focusedId = null;
198
198
  let viewport = options.viewport ?? { width: 0, height: 0 };
199
199
  let workspace = normalizeWorkspace(options.workspace, 0);
200
+ let activeTabs = {};
201
+ let groupCounter = 0;
200
202
  let seq = 0;
201
203
  let idCounter = 0;
202
204
  let cascadeIndex = 0;
@@ -209,12 +211,13 @@ function createWindowManager(options = {}) {
209
211
  let future = [];
210
212
  let interactionDepth = 0;
211
213
  let interactionRecorded = false;
214
+ let interactionMark = null;
212
215
  let skipNextHistory = false;
213
216
  let pendingHistory = null;
214
217
  const layouts = /* @__PURE__ */ new Map();
215
218
  function getState() {
216
219
  if (!snapshot) {
217
- snapshot = { windows, order, focusedId, viewport, workspace };
220
+ snapshot = { windows, order, focusedId, viewport, workspace, groups: buildGroups() };
218
221
  }
219
222
  return snapshot;
220
223
  }
@@ -266,11 +269,54 @@ function createWindowManager(options = {}) {
266
269
  function onActiveWorkspace(win) {
267
270
  return win.workspace === workspace;
268
271
  }
272
+ function membersOf(groupId) {
273
+ return order.filter((entry) => windows[entry].groupId === groupId);
274
+ }
275
+ function isVisibleTab(win) {
276
+ return win.groupId === null || activeTabs[win.groupId] === win.id;
277
+ }
278
+ function buildGroups() {
279
+ const result = {};
280
+ for (const [groupId, activeId] of Object.entries(activeTabs)) {
281
+ result[groupId] = { id: groupId, activeId, members: membersOf(groupId) };
282
+ }
283
+ return result;
284
+ }
285
+ function syncGroup(source) {
286
+ if (source.groupId === null) return;
287
+ for (const id of membersOf(source.groupId)) {
288
+ if (id === source.id) continue;
289
+ const member = windows[id];
290
+ if (boundsEqual(member.bounds, source.bounds) && member.stage === source.stage && member.snapZone === source.snapZone && member.layer === source.layer && member.workspace === source.workspace) {
291
+ continue;
292
+ }
293
+ setWindow({
294
+ ...member,
295
+ bounds: source.bounds,
296
+ stage: source.stage,
297
+ snapZone: source.snapZone,
298
+ restoreBounds: source.restoreBounds,
299
+ restoreStage: source.restoreStage,
300
+ layer: source.layer,
301
+ workspace: source.workspace
302
+ });
303
+ }
304
+ }
305
+ function dissolveIfOrphaned(groupId) {
306
+ const members = membersOf(groupId);
307
+ if (members.length >= 2) return;
308
+ for (const id of members) {
309
+ setWindow({ ...windows[id], groupId: null });
310
+ }
311
+ const { [groupId]: _dropped, ...rest } = activeTabs;
312
+ activeTabs = rest;
313
+ }
269
314
  function topModalId() {
270
315
  for (let i = order.length - 1; i >= 0; i -= 1) {
271
316
  const win = windows[order[i]];
272
- if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win))
317
+ if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win)) {
273
318
  return win.id;
319
+ }
274
320
  }
275
321
  return null;
276
322
  }
@@ -278,7 +324,7 @@ function createWindowManager(options = {}) {
278
324
  const modal = topModalId();
279
325
  return order.filter((id) => {
280
326
  const win = windows[id];
281
- if (win.stage === "minimized" || !onActiveWorkspace(win)) return false;
327
+ if (win.stage === "minimized" || !onActiveWorkspace(win) || !isVisibleTab(win)) return false;
282
328
  if (modal && win.layer !== "modal") return false;
283
329
  return true;
284
330
  });
@@ -319,7 +365,13 @@ function createWindowManager(options = {}) {
319
365
  width: init.width ?? defaultSize.width,
320
366
  height: init.height ?? defaultSize.height
321
367
  };
322
- const size = aspectRatio === null ? clampSize(requested, minSize, maxSize) : applyAspect(requested, aspectRatio, minSize, maxSize, "width");
368
+ const size = aspectRatio === null ? clampSize(requested, minSize, maxSize) : applyAspect(
369
+ requested,
370
+ aspectRatio,
371
+ minSize,
372
+ maxSize,
373
+ init.width === void 0 && init.height !== void 0 ? "height" : "width"
374
+ );
323
375
  const position = positionForOpen(init);
324
376
  let bounds = { ...position, ...size };
325
377
  if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible);
@@ -335,6 +387,7 @@ function createWindowManager(options = {}) {
335
387
  snapZone: null,
336
388
  layer: init.layer ?? "normal",
337
389
  workspace: normalizeWorkspace(init.workspace, workspace),
390
+ groupId: null,
338
391
  minSize,
339
392
  maxSize,
340
393
  aspectRatio,
@@ -378,6 +431,13 @@ function createWindowManager(options = {}) {
378
431
  const win = windows[id];
379
432
  if (!win) return false;
380
433
  removeWindow(id);
434
+ if (win.groupId !== null) {
435
+ const remaining = membersOf(win.groupId);
436
+ if (activeTabs[win.groupId] === id && remaining[0]) {
437
+ activeTabs = { ...activeTabs, [win.groupId]: remaining[0] };
438
+ }
439
+ dissolveIfOrphaned(win.groupId);
440
+ }
381
441
  queueEvent(() => emitter.emit("close", { window: win }));
382
442
  if (focusedId === id) {
383
443
  focusTop();
@@ -394,6 +454,7 @@ function createWindowManager(options = {}) {
394
454
  const win = windows[id];
395
455
  if (!win) return false;
396
456
  if (!onActiveWorkspace(win)) setWorkspace(win.workspace);
457
+ if (!isVisibleTab(win)) activateTab(id);
397
458
  const modal = topModalId();
398
459
  if (modal && modal !== id && win.layer !== "modal") {
399
460
  const modalWin = windows[modal];
@@ -435,7 +496,13 @@ function createWindowManager(options = {}) {
435
496
  }
436
497
  function snapBounds(win, zone) {
437
498
  const rect = zoneBounds(zone, viewport);
438
- return { x: rect.x, y: rect.y, ...normalizeSize(rect, win) };
499
+ const byWidth = normalizeSize(rect, win);
500
+ const size = byWidth.height > rect.height ? normalizeSize(rect, win, "height") : byWidth;
501
+ return {
502
+ x: Math.max(0, Math.min(rect.x, viewport.width - size.width)),
503
+ y: Math.max(0, Math.min(rect.y, viewport.height - size.height)),
504
+ ...size
505
+ };
439
506
  }
440
507
  function focusUnlessBlocked(id) {
441
508
  const modal = topModalId();
@@ -447,6 +514,7 @@ function createWindowManager(options = {}) {
447
514
  const next = build(win);
448
515
  if (!next) return false;
449
516
  setWindow(next);
517
+ syncGroup(next);
450
518
  queueEvent(() => emitter.emit("stage", { window: next, previous: win.stage }));
451
519
  commit();
452
520
  return true;
@@ -556,6 +624,7 @@ function createWindowManager(options = {}) {
556
624
  if (boundsEqual(bounds, win.bounds)) return true;
557
625
  const next = { ...win, bounds };
558
626
  setWindow(next);
627
+ syncGroup(next);
559
628
  queueEvent(() => emitter.emit("move", { window: next }));
560
629
  commit();
561
630
  return true;
@@ -577,6 +646,7 @@ function createWindowManager(options = {}) {
577
646
  if (boundsEqual(bounds, win.bounds) && !becameNormal) return true;
578
647
  const next = becameNormal ? { ...win, stage: "normal", snapZone: null, restoreBounds: null, bounds } : { ...win, bounds };
579
648
  setWindow(next);
649
+ syncGroup(next);
580
650
  if (becameNormal) queueEvent(() => emitter.emit("stage", { window: next, previous: "snapped" }));
581
651
  queueEvent(() => emitter.emit("resize", { window: next }));
582
652
  commit();
@@ -600,10 +670,11 @@ function createWindowManager(options = {}) {
600
670
  snappable: patch.snappable ?? win.snappable,
601
671
  meta: patch.meta ? { ...win.meta, ...patch.meta } : win.meta
602
672
  };
603
- const size = normalizeSize(next.bounds, next);
673
+ const size = next.stage === "maximized" ? next.bounds : normalizeSize(next.bounds, next);
604
674
  const resized = size.width !== next.bounds.width || size.height !== next.bounds.height;
605
675
  const finalWin = resized ? { ...next, bounds: { ...next.bounds, ...size } } : next;
606
676
  setWindow(finalWin);
677
+ syncGroup(finalWin);
607
678
  if (patch.layer && patch.layer !== win.layer) {
608
679
  order = sortByLayer(order);
609
680
  queueEvent(() => emitter.emit("order", { order }));
@@ -645,14 +716,90 @@ function createWindowManager(options = {}) {
645
716
  if (target === win.workspace) return false;
646
717
  const updated = { ...win, workspace: target };
647
718
  setWindow(updated);
719
+ syncGroup(updated);
648
720
  if (!onActiveWorkspace(updated) && focusedId === id) {
649
721
  focusTop();
650
722
  if (focusedId) emitFocus(windows[focusedId], id);
723
+ } else if (onActiveWorkspace(updated) && topModalId() === id && focusedId !== id) {
724
+ const previous = focusedId;
725
+ focusedId = id;
726
+ emitFocus(updated, previous);
651
727
  }
652
728
  queueEvent(() => emitter.emit("update", { window: updated }));
653
729
  commit();
654
730
  return true;
655
731
  }
732
+ function emitGroup(groupId, previous) {
733
+ const members = membersOf(groupId);
734
+ const activeId = activeTabs[groupId];
735
+ queueEvent(() => emitter.emit("group", { groupId, members, activeId, previous }));
736
+ }
737
+ function group(ids) {
738
+ const seeds = ids.filter((id, index) => windows[id] && ids.indexOf(id) === index);
739
+ const host = seeds[0] ? windows[seeds[0]] : null;
740
+ if (!host) return null;
741
+ const expanded = [];
742
+ for (const id of seeds) {
743
+ const win = windows[id];
744
+ const siblings = win.groupId === null ? [id] : membersOf(win.groupId);
745
+ for (const entry of siblings) if (!expanded.includes(entry)) expanded.push(entry);
746
+ }
747
+ if (expanded.length < 2) return null;
748
+ const groupId = `${idPrefix}-group-${++groupCounter}`;
749
+ const retired = /* @__PURE__ */ new Set();
750
+ for (const id of expanded) {
751
+ const win = windows[id];
752
+ if (win.groupId !== null) retired.add(win.groupId);
753
+ setWindow({
754
+ ...win,
755
+ groupId,
756
+ bounds: host.bounds,
757
+ stage: host.stage,
758
+ snapZone: host.snapZone,
759
+ restoreBounds: host.restoreBounds,
760
+ restoreStage: host.restoreStage,
761
+ layer: host.layer,
762
+ workspace: host.workspace
763
+ });
764
+ }
765
+ const next = { ...activeTabs, [groupId]: host.id };
766
+ for (const old of retired) delete next[old];
767
+ activeTabs = next;
768
+ raise(host.id);
769
+ emitGroup(groupId, null);
770
+ queueEvent(() => emitter.emit("order", { order }));
771
+ commit();
772
+ if (onActiveWorkspace(host) && host.stage !== "minimized") focus(host.id);
773
+ return groupId;
774
+ }
775
+ function ungroup(id) {
776
+ const win = windows[id];
777
+ if (!win?.groupId) return false;
778
+ const groupId = win.groupId;
779
+ const wasActive = activeTabs[groupId] === id;
780
+ setWindow({ ...win, groupId: null });
781
+ const remaining = membersOf(groupId);
782
+ if (wasActive) activeTabs = { ...activeTabs, [groupId]: remaining[0] };
783
+ dissolveIfOrphaned(groupId);
784
+ if (membersOf(groupId).length >= 2) emitGroup(groupId, wasActive ? id : null);
785
+ commit();
786
+ focus(id);
787
+ return true;
788
+ }
789
+ function activateTab(id) {
790
+ const win = windows[id];
791
+ if (!win?.groupId) return false;
792
+ const groupId = win.groupId;
793
+ const previous = activeTabs[groupId];
794
+ if (previous === id) return false;
795
+ activeTabs = { ...activeTabs, [groupId]: id };
796
+ emitGroup(groupId, previous);
797
+ commit();
798
+ return true;
799
+ }
800
+ function groupMembers(groupId) {
801
+ return membersOf(groupId).map((id) => windows[id]);
802
+ }
656
803
  function sendToBack(id) {
657
804
  const win = windows[id];
658
805
  if (!win) return false;
@@ -684,10 +831,10 @@ function createWindowManager(options = {}) {
684
831
  );
685
832
  }
686
833
  function minimized() {
687
- return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win)).sort((a, b) => a.openedSeq - b.openedSeq);
834
+ return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win) && isVisibleTab(win)).sort((a, b) => a.openedSeq - b.openedSeq);
688
835
  }
689
836
  function captureEntry() {
690
- return { windows, order, focusedId, workspace };
837
+ return { windows, order, focusedId, workspace, activeTabs };
691
838
  }
692
839
  function recordHistory() {
693
840
  if (!historyDirty) return;
@@ -749,6 +896,7 @@ function createWindowManager(options = {}) {
749
896
  windows = entry.windows;
750
897
  order = [...entry.order];
751
898
  focusedId = entry.focusedId;
899
+ activeTabs = entry.activeTabs;
752
900
  const previousWorkspace = workspace;
753
901
  workspace = entry.workspace;
754
902
  if (workspace !== previousWorkspace) {
@@ -775,14 +923,20 @@ function createWindowManager(options = {}) {
775
923
  }
776
924
  function beginInteraction() {
777
925
  interactionDepth += 1;
778
- if (interactionDepth === 1) interactionRecorded = false;
926
+ if (interactionDepth === 1) {
927
+ interactionRecorded = false;
928
+ interactionMark = { past: [...past], future };
929
+ }
779
930
  }
780
931
  function endInteraction() {
781
932
  if (interactionDepth > 0) interactionDepth -= 1;
933
+ if (interactionDepth === 0) interactionMark = null;
782
934
  }
783
935
  function abortInteraction() {
784
- if (interactionDepth > 0 && interactionRecorded) {
785
- past.pop();
936
+ if (interactionDepth > 0 && interactionMark) {
937
+ past.length = 0;
938
+ past.push(...interactionMark.past);
939
+ future = interactionMark.future;
786
940
  interactionRecorded = false;
787
941
  }
788
942
  endInteraction();
@@ -873,7 +1027,8 @@ function createWindowManager(options = {}) {
873
1027
  })),
874
1028
  order: [...order],
875
1029
  focusedId,
876
- workspace
1030
+ workspace,
1031
+ activeTabs: { ...activeTabs }
877
1032
  };
878
1033
  }
879
1034
  function isBounds(value) {
@@ -928,6 +1083,7 @@ function createWindowManager(options = {}) {
928
1083
  snapZone: ZONES.includes(raw.snapZone) ? raw.snapZone : null,
929
1084
  layer: raw.layer,
930
1085
  workspace: normalizeWorkspace(raw.workspace, 0),
1086
+ groupId: typeof raw.groupId === "string" && raw.groupId.length > 0 ? raw.groupId : null,
931
1087
  minSize: isSize(raw.minSize) ? { ...raw.minSize } : { width: 160, height: 100 },
932
1088
  maxSize: readMaxSize(raw.maxSize),
933
1089
  aspectRatio: typeof raw.aspectRatio === "number" && Number.isFinite(raw.aspectRatio) && raw.aspectRatio > 0 ? raw.aspectRatio : null,
@@ -969,7 +1125,18 @@ function createWindowManager(options = {}) {
969
1125
  if (focusedCandidate && (focusedCandidate.stage === "minimized" || !onActiveWorkspace(focusedCandidate))) {
970
1126
  focusedId = null;
971
1127
  }
972
- if (!focusedId) focusTop();
1128
+ const restoredTabs = {};
1129
+ const rawTabs = data.activeTabs;
1130
+ for (const id of order) {
1131
+ const groupId = windows[id].groupId;
1132
+ if (groupId === null || restoredTabs[groupId]) continue;
1133
+ const wanted = typeof rawTabs === "object" && rawTabs !== null ? rawTabs[groupId] : void 0;
1134
+ const members = membersOf(groupId);
1135
+ restoredTabs[groupId] = typeof wanted === "string" && members.includes(wanted) ? wanted : members[0];
1136
+ }
1137
+ activeTabs = restoredTabs;
1138
+ for (const groupId of Object.keys(restoredTabs)) dissolveIfOrphaned(groupId);
1139
+ if (!focusedId || !isVisibleTab(windows[focusedId])) focusTop();
973
1140
  const modal = topModalId();
974
1141
  if (modal) {
975
1142
  const focusedWin = focusedId ? windows[focusedId] : null;
@@ -1023,6 +1190,10 @@ function createWindowManager(options = {}) {
1023
1190
  workspace: () => workspace,
1024
1191
  setWorkspace: (next) => transact(() => setWorkspace(next)),
1025
1192
  moveToWorkspace: (id, next) => transact(() => moveToWorkspace(id, next)),
1193
+ group: (ids) => transact(() => group(ids)),
1194
+ ungroup: (id) => transact(() => ungroup(id)),
1195
+ activateTab: (id) => transact(() => activateTab(id)),
1196
+ groupMembers,
1026
1197
  batch: (run) => transact(run),
1027
1198
  serialize,
1028
1199
  hydrate: (data) => transact(() => hydrate(data)),
@@ -1130,10 +1301,8 @@ function createAnnouncer(wm, container, messages = {}) {
1130
1301
  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";
1131
1302
  container.append(element);
1132
1303
  let clearTimer;
1133
- let lastMessage = "";
1134
1304
  let detailed = false;
1135
1305
  function announce(message) {
1136
- lastMessage = message;
1137
1306
  element.textContent = message;
1138
1307
  if (clearTimer !== void 0) clearTimeout(clearTimer);
1139
1308
  clearTimer = setTimeout(() => {
@@ -1141,8 +1310,14 @@ function createAnnouncer(wm, container, messages = {}) {
1141
1310
  }, 2e3);
1142
1311
  }
1143
1312
  const unsubscribers = [
1144
- wm.on("open", ({ window: win }) => announce(dict.opened(win.title))),
1145
- wm.on("close", ({ window: win }) => announce(dict.closed(win.title))),
1313
+ wm.on("open", ({ window: win }) => {
1314
+ announce(dict.opened(win.title));
1315
+ detailed = true;
1316
+ }),
1317
+ wm.on("close", ({ window: win }) => {
1318
+ announce(dict.closed(win.title));
1319
+ detailed = true;
1320
+ }),
1146
1321
  wm.on("stage", ({ window: win, previous }) => {
1147
1322
  if (win.stage === "minimized") announce(dict.minimized(win.title));
1148
1323
  else if (win.stage === "maximized") announce(dict.maximized(win.title));
@@ -1153,7 +1328,7 @@ function createAnnouncer(wm, container, messages = {}) {
1153
1328
  detailed = true;
1154
1329
  }),
1155
1330
  wm.on("focus", ({ window: win, previous }) => {
1156
- if (detailed || previous === null || lastMessage === dict.opened(win.title)) return;
1331
+ if (detailed || previous === null) return;
1157
1332
  announce(dict.focused(win.title));
1158
1333
  }),
1159
1334
  wm.on("workspace", ({ workspace }) => {
@@ -1208,6 +1383,11 @@ function createDragStarter(ctx) {
1208
1383
  restored: win.stage === "normal",
1209
1384
  moved: false,
1210
1385
  zone: null,
1386
+ groupTarget: null,
1387
+ hoverId: null,
1388
+ hoverTimer: 0,
1389
+ clientX: event.clientX,
1390
+ clientY: event.clientY,
1211
1391
  raf: 0,
1212
1392
  pendingX: 0,
1213
1393
  pendingY: 0,
@@ -1216,6 +1396,18 @@ function createDragStarter(ctx) {
1216
1396
  }
1217
1397
  };
1218
1398
  const el = ctx.windowElement(id);
1399
+ function armGroup(target2) {
1400
+ session.groupTarget = target2;
1401
+ ctx.markGroupTarget(target2);
1402
+ }
1403
+ function clearHover() {
1404
+ if (session.hoverTimer !== 0) {
1405
+ view.clearTimeout(session.hoverTimer);
1406
+ session.hoverTimer = 0;
1407
+ }
1408
+ session.groupTarget = null;
1409
+ ctx.markGroupTarget(null);
1410
+ }
1219
1411
  function flush() {
1220
1412
  if (!session.hasPending || ctx.currentDrag() !== session) return;
1221
1413
  session.hasPending = false;
@@ -1263,6 +1455,20 @@ function createDragStarter(ctx) {
1263
1455
  nextY = magnet.y;
1264
1456
  }
1265
1457
  wm.move(id, nextX, nextY);
1458
+ const candidate = ctx.groupTarget(session.clientX, session.clientY, id);
1459
+ if (candidate !== session.hoverId) {
1460
+ session.hoverId = candidate;
1461
+ clearHover();
1462
+ if (candidate) {
1463
+ if (ctx.groupDwell <= 0) armGroup(candidate);
1464
+ else session.hoverTimer = view.setTimeout(() => armGroup(candidate), ctx.groupDwell);
1465
+ }
1466
+ }
1467
+ if (session.groupTarget) {
1468
+ session.zone = null;
1469
+ ctx.hidePreview();
1470
+ return;
1471
+ }
1266
1472
  if (ctx.snapEnabled && current.snappable) {
1267
1473
  const viewport = wm.getState().viewport;
1268
1474
  const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect);
@@ -1295,6 +1501,8 @@ function createDragStarter(ctx) {
1295
1501
  }
1296
1502
  session.pendingX = movePoint.x;
1297
1503
  session.pendingY = movePoint.y;
1504
+ session.clientX = moveEvent.clientX;
1505
+ session.clientY = moveEvent.clientY;
1298
1506
  session.hasPending = true;
1299
1507
  if (session.raf === 0) session.raf = view.requestAnimationFrame(flush);
1300
1508
  }
@@ -1329,6 +1537,8 @@ function createDragStarter(ctx) {
1329
1537
  wm.move(id, session.startBounds.x, session.startBounds.y);
1330
1538
  }
1331
1539
  }
1540
+ } else if (session.moved && session.groupTarget) {
1541
+ if (wm.group([session.groupTarget, id])) wm.activateTab(id);
1332
1542
  } else if (session.moved && session.zone) {
1333
1543
  if (session.zone === "maximize") wm.maximize(id);
1334
1544
  else wm.snap(id, session.zone);
@@ -1348,6 +1558,8 @@ function createDragStarter(ctx) {
1348
1558
  el.style.willChange = "";
1349
1559
  }
1350
1560
  ctx.hidePreview();
1561
+ if (session.hoverTimer !== 0) view.clearTimeout(session.hoverTimer);
1562
+ ctx.markGroupTarget(null);
1351
1563
  if (cancelled) wm.abortInteraction();
1352
1564
  else wm.endInteraction();
1353
1565
  }
@@ -1400,6 +1612,7 @@ function createResizeStarter(ctx) {
1400
1612
  let pendingX = 0;
1401
1613
  let pendingY = 0;
1402
1614
  let hasPending = false;
1615
+ let applied = false;
1403
1616
  const el = ctx.windowElement(id);
1404
1617
  if (el) el.dataset.wmResizing = direction;
1405
1618
  function flush() {
@@ -1408,18 +1621,24 @@ function createResizeStarter(ctx) {
1408
1621
  raf = 0;
1409
1622
  const dx = pendingX - startPoint.x;
1410
1623
  const dy = pendingY - startPoint.y;
1411
- const top = direction.includes("n") ? Math.max(0, start.y + dy) : start.y;
1624
+ const floor = Math.min(0, start.y);
1625
+ const top = direction.includes("n") ? Math.max(floor, start.y + dy) : start.y;
1412
1626
  const raw = {
1413
1627
  width: direction.includes("e") ? start.width + dx : direction.includes("w") ? start.width - dx : start.width,
1414
1628
  height: direction.includes("s") ? start.height + dy : direction.includes("n") ? start.y + start.height - top : start.height
1415
1629
  };
1416
- const size = aspect === null ? clampSize(raw, minSize, maxSize) : applyAspect(raw, aspect, minSize, maxSize, drive);
1630
+ let size = aspect === null ? clampSize(raw, minSize, maxSize) : applyAspect(raw, aspect, minSize, maxSize, drive);
1631
+ const available = start.y + start.height - floor;
1632
+ if (direction.includes("n") && size.height > available) {
1633
+ size = applyAspect({ ...size, height: available }, aspect ?? 0, minSize, maxSize, "height");
1634
+ }
1417
1635
  const next = {
1418
1636
  x: direction.includes("w") ? start.x + start.width - size.width : start.x,
1419
1637
  y: direction.includes("n") ? start.y + start.height - size.height : start.y,
1420
1638
  ...size
1421
1639
  };
1422
1640
  wm.resize(id, next);
1641
+ applied = true;
1423
1642
  }
1424
1643
  function onMove(moveEvent) {
1425
1644
  if (moveEvent.pointerId !== event.pointerId) return;
@@ -1434,7 +1653,7 @@ function createResizeStarter(ctx) {
1434
1653
  try {
1435
1654
  if (raf !== 0) view.cancelAnimationFrame(raf);
1436
1655
  if (hasPending && !cancelled) flush();
1437
- if (cancelled) {
1656
+ if (cancelled && applied) {
1438
1657
  if (startStage === "snapped" && startZone) {
1439
1658
  wm.restoreTo(id, startRestore ?? start);
1440
1659
  wm.snap(id, startZone);
@@ -1510,6 +1729,8 @@ function attachDesktop(wm, element, options = {}) {
1510
1729
  const doc = element.ownerDocument;
1511
1730
  const view = windowOf(element);
1512
1731
  const coarsePointer = typeof view.matchMedia === "function" && view.matchMedia("(pointer: coarse)").matches;
1732
+ const groupingEnabled = options.grouping !== false;
1733
+ const groupDwell = (typeof options.grouping === "object" ? options.grouping.dwell : void 0) ?? 420;
1513
1734
  const snapEnabled = options.snap !== false;
1514
1735
  const snapOptions = typeof options.snap === "object" ? options.snap : {};
1515
1736
  const snapPreviewEnabled = snapOptions.preview !== false;
@@ -1533,6 +1754,7 @@ function attachDesktop(wm, element, options = {}) {
1533
1754
  let lastOrder = null;
1534
1755
  let lastFocused = null;
1535
1756
  let drag = null;
1757
+ let groupTargetId = null;
1536
1758
  let cachedRect = null;
1537
1759
  let rectUsers = 0;
1538
1760
  let announcer = null;
@@ -1604,6 +1826,29 @@ function attachDesktop(wm, element, options = {}) {
1604
1826
  hitEdge,
1605
1827
  hitCorner,
1606
1828
  magnetThreshold,
1829
+ groupDwell,
1830
+ groupTarget(clientX, clientY, selfId) {
1831
+ if (!groupingEnabled || typeof doc.elementsFromPoint !== "function") return null;
1832
+ for (const node of doc.elementsFromPoint(clientX, clientY)) {
1833
+ const handle = node.closest?.("[data-wm-drag]");
1834
+ const host = handle?.closest("[data-wm-window]");
1835
+ const id = host?.dataset.wmWindow;
1836
+ if (id && id !== selfId && registry.has(id)) return id;
1837
+ }
1838
+ return null;
1839
+ },
1840
+ markGroupTarget(id) {
1841
+ if (groupTargetId === id) return;
1842
+ if (groupTargetId) {
1843
+ const previous = registry.get(groupTargetId);
1844
+ if (previous) delete previous.element.dataset.wmTabTarget;
1845
+ }
1846
+ groupTargetId = id;
1847
+ if (id) {
1848
+ const next = registry.get(id);
1849
+ if (next) next.element.dataset.wmTabTarget = "";
1850
+ }
1851
+ },
1607
1852
  currentDrag: () => drag,
1608
1853
  claimDrag(session) {
1609
1854
  drag = session;
@@ -1621,21 +1866,33 @@ function attachDesktop(wm, element, options = {}) {
1621
1866
  observer.observe(element);
1622
1867
  cleanup.push(() => observer.disconnect());
1623
1868
  }
1624
- function syncWindow(attached, win, zIndex, activeWorkspace) {
1869
+ function syncWindow(attached, win, zIndex, activeWorkspace, activeTab) {
1625
1870
  const el = attached.element;
1626
1871
  const firstSync = attached.lastState === null;
1627
1872
  if (firstSync) el.style.transition = "none";
1628
- if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace) {
1873
+ if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace || attached.lastTab !== activeTab) {
1629
1874
  el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
1630
1875
  el.style.width = `${win.bounds.width}px`;
1631
1876
  el.style.height = `${win.bounds.height}px`;
1632
1877
  el.dataset.wmStage = win.stage;
1633
1878
  el.dataset.wmLayer = win.layer;
1634
1879
  el.dataset.wmWorkspace = String(win.workspace);
1635
- const hide = win.stage === "minimized" || win.workspace !== activeWorkspace;
1880
+ if (win.groupId === null) {
1881
+ delete el.dataset.wmGroup;
1882
+ delete el.dataset.wmTab;
1883
+ } else {
1884
+ el.dataset.wmGroup = win.groupId;
1885
+ el.dataset.wmTab = activeTab ? "active" : "inactive";
1886
+ }
1887
+ const hide = win.stage === "minimized" || win.workspace !== activeWorkspace || !activeTab;
1636
1888
  if (hide && !el.hidden && el.contains(doc.activeElement)) {
1637
1889
  element.focus({ preventScroll: true });
1638
1890
  }
1891
+ if (!hide && el.hidden && wm.getState().focusedId === win.id) {
1892
+ queueMicrotask(() => {
1893
+ if (!el.hidden && !el.contains(doc.activeElement)) el.focus({ preventScroll: true });
1894
+ });
1895
+ }
1639
1896
  el.hidden = hide;
1640
1897
  el.setAttribute("aria-label", win.title);
1641
1898
  if (win.layer === "modal") el.setAttribute("aria-modal", "true");
@@ -1645,6 +1902,7 @@ function attachDesktop(wm, element, options = {}) {
1645
1902
  }
1646
1903
  attached.lastState = win;
1647
1904
  attached.lastWorkspace = activeWorkspace;
1905
+ attached.lastTab = activeTab;
1648
1906
  }
1649
1907
  if (attached.lastZ !== zIndex) {
1650
1908
  el.style.zIndex = String(zIndex + 1);
@@ -1662,8 +1920,9 @@ function attachDesktop(wm, element, options = {}) {
1662
1920
  const attached = registry.get(id);
1663
1921
  const win = state.windows[id];
1664
1922
  if (!attached || !win) return;
1665
- if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace) {
1666
- syncWindow(attached, win, index, state.workspace);
1923
+ const activeTab = win.groupId === null || state.groups[win.groupId]?.activeId === win.id;
1924
+ if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace || attached.lastTab !== activeTab) {
1925
+ syncWindow(attached, win, index, state.workspace, activeTab);
1667
1926
  }
1668
1927
  });
1669
1928
  if (state.focusedId !== lastFocused) {
@@ -1684,6 +1943,7 @@ function attachDesktop(wm, element, options = {}) {
1684
1943
  wm.on("focus", ({ window: win }) => {
1685
1944
  const attached = registry.get(win.id);
1686
1945
  if (!attached) return;
1946
+ if (attached.element.hidden) syncAll();
1687
1947
  if (!attached.element.contains(doc.activeElement)) {
1688
1948
  attached.element.focus({ preventScroll: true });
1689
1949
  }
@@ -1719,6 +1979,7 @@ function attachDesktop(wm, element, options = {}) {
1719
1979
  return;
1720
1980
  }
1721
1981
  if (!event.ctrlKey && !event.metaKey) return;
1982
+ if (drag) return;
1722
1983
  const target = event.target;
1723
1984
  if (target?.closest(INTERACTIVE_SELECTOR)) return;
1724
1985
  if (historyShortcuts && (event.key === "z" || event.key === "Z")) {
@@ -1764,6 +2025,7 @@ function attachDesktop(wm, element, options = {}) {
1764
2025
  lastState: null,
1765
2026
  lastZ: -1,
1766
2027
  lastWorkspace: -1,
2028
+ lastTab: true,
1767
2029
  cleanup: []
1768
2030
  };
1769
2031
  windowElement.dataset.wmWindow = id;
@@ -1987,5 +2249,5 @@ function createDesktopBinder(wm, options = {}) {
1987
2249
  }
1988
2250
 
1989
2251
  export { applyAspect, attachDesktop, boundsEqual, clamp, clampSize, clampToViewport, createAnnouncer, createDesktopBinder, createEmitter, createWindowManager, defaultMessages, detectSnapZone, flipFromTarget, flipToTarget, magnetize, prefersReducedMotion, zoneBounds };
1990
- //# sourceMappingURL=chunk-OOEDFA7K.js.map
1991
- //# sourceMappingURL=chunk-OOEDFA7K.js.map
2252
+ //# sourceMappingURL=chunk-L43XBN44.js.map
2253
+ //# sourceMappingURL=chunk-L43XBN44.js.map