@surdeddd/wmkit 0.4.2 → 0.5.1

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 +1 -0
  2. package/README.ru.md +1 -0
  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-GGDF2EWV.js → chunk-2WQHHYYR.js} +280 -23
  10. package/dist/chunk-2WQHHYYR.js.map +1 -0
  11. package/dist/{chunk-GLBVT4AQ.cjs → chunk-C2UHNBSA.cjs} +280 -23
  12. package/dist/chunk-C2UHNBSA.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-GGDF2EWV.js.map +0 -1
  41. package/dist/chunk-GLBVT4AQ.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;
@@ -217,7 +219,7 @@ function createWindowManager(options = {}) {
217
219
  const layouts = /* @__PURE__ */ new Map();
218
220
  function getState() {
219
221
  if (!snapshot) {
220
- snapshot = { windows, order, focusedId, viewport, workspace };
222
+ snapshot = { windows, order, focusedId, viewport, workspace, groups: buildGroups() };
221
223
  }
222
224
  return snapshot;
223
225
  }
@@ -252,16 +254,22 @@ function createWindowManager(options = {}) {
252
254
  function sortByLayer(ids) {
253
255
  return ids.map((id, index) => ({ id, index, rank: layerRankOf(id) })).sort((a, b) => a.rank !== b.rank ? a.rank - b.rank : a.index - b.index).map((entry) => entry.id);
254
256
  }
257
+ function blockOf(id) {
258
+ const win = windows[id];
259
+ if (win.groupId === null) return [id];
260
+ return [...membersOf(win.groupId).filter((entry) => entry !== id), id];
261
+ }
255
262
  function raise(id) {
256
263
  const win = windows[id];
257
- const without = order.filter((entry) => entry !== id);
264
+ const block = blockOf(id);
265
+ const without = order.filter((entry) => !block.includes(entry));
258
266
  const rank = LAYER_RANK[win.layer];
259
267
  let insertAt = without.length;
260
268
  for (let i = without.length - 1; i >= 0; i -= 1) {
261
269
  if (layerRankOf(without[i]) > rank) insertAt = i;
262
270
  else break;
263
271
  }
264
- const next = [...without.slice(0, insertAt), id, ...without.slice(insertAt)];
272
+ const next = [...without.slice(0, insertAt), ...block, ...without.slice(insertAt)];
265
273
  const changed = next.length !== order.length || next.some((entry, i) => entry !== order[i]);
266
274
  order = next;
267
275
  return changed;
@@ -269,11 +277,54 @@ function createWindowManager(options = {}) {
269
277
  function onActiveWorkspace(win) {
270
278
  return win.workspace === workspace;
271
279
  }
280
+ function membersOf(groupId) {
281
+ return order.filter((entry) => windows[entry].groupId === groupId);
282
+ }
283
+ function isVisibleTab(win) {
284
+ return win.groupId === null || activeTabs[win.groupId] === win.id;
285
+ }
286
+ function buildGroups() {
287
+ const result = /* @__PURE__ */ Object.create(null);
288
+ for (const [groupId, activeId] of Object.entries(activeTabs)) {
289
+ result[groupId] = { id: groupId, activeId, members: membersOf(groupId) };
290
+ }
291
+ return result;
292
+ }
293
+ function syncGroup(source) {
294
+ if (source.groupId === null) return;
295
+ for (const id of membersOf(source.groupId)) {
296
+ if (id === source.id) continue;
297
+ const member = windows[id];
298
+ if (boundsEqual(member.bounds, source.bounds) && member.stage === source.stage && member.snapZone === source.snapZone && member.layer === source.layer && member.workspace === source.workspace) {
299
+ continue;
300
+ }
301
+ setWindow({
302
+ ...member,
303
+ bounds: source.bounds,
304
+ stage: source.stage,
305
+ snapZone: source.snapZone,
306
+ restoreBounds: source.restoreBounds,
307
+ restoreStage: source.restoreStage,
308
+ layer: source.layer,
309
+ workspace: source.workspace
310
+ });
311
+ }
312
+ }
313
+ function dissolveIfOrphaned(groupId) {
314
+ const members = membersOf(groupId);
315
+ if (members.length >= 2) return;
316
+ for (const id of members) {
317
+ setWindow({ ...windows[id], groupId: null });
318
+ }
319
+ const { [groupId]: _dropped, ...rest } = activeTabs;
320
+ activeTabs = rest;
321
+ }
272
322
  function topModalId() {
273
323
  for (let i = order.length - 1; i >= 0; i -= 1) {
274
324
  const win = windows[order[i]];
275
- if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win))
325
+ if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win)) {
276
326
  return win.id;
327
+ }
277
328
  }
278
329
  return null;
279
330
  }
@@ -281,7 +332,7 @@ function createWindowManager(options = {}) {
281
332
  const modal = topModalId();
282
333
  return order.filter((id) => {
283
334
  const win = windows[id];
284
- if (win.stage === "minimized" || !onActiveWorkspace(win)) return false;
335
+ if (win.stage === "minimized" || !onActiveWorkspace(win) || !isVisibleTab(win)) return false;
285
336
  if (modal && win.layer !== "modal") return false;
286
337
  return true;
287
338
  });
@@ -290,6 +341,14 @@ function createWindowManager(options = {}) {
290
341
  const targets = focusTargets();
291
342
  focusedId = targets.length > 0 ? targets[targets.length - 1] : null;
292
343
  }
344
+ function reconcileFocus() {
345
+ if (focusedId === null) return;
346
+ const win = windows[focusedId];
347
+ if (win && win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win)) return;
348
+ const previous = focusedId;
349
+ focusTop();
350
+ if (focusedId) emitFocus(windows[focusedId], previous);
351
+ }
293
352
  function emitFocus(win, previous) {
294
353
  queueEvent(() => emitter.emit("focus", { window: win, previous }));
295
354
  }
@@ -344,6 +403,7 @@ function createWindowManager(options = {}) {
344
403
  snapZone: null,
345
404
  layer: init.layer ?? "normal",
346
405
  workspace: normalizeWorkspace(init.workspace, workspace),
406
+ groupId: null,
347
407
  minSize,
348
408
  maxSize,
349
409
  aspectRatio,
@@ -387,6 +447,13 @@ function createWindowManager(options = {}) {
387
447
  const win = windows[id];
388
448
  if (!win) return false;
389
449
  removeWindow(id);
450
+ if (win.groupId !== null) {
451
+ const remaining = membersOf(win.groupId);
452
+ if (activeTabs[win.groupId] === id && remaining[0]) {
453
+ activeTabs = { ...activeTabs, [win.groupId]: remaining[0] };
454
+ }
455
+ dissolveIfOrphaned(win.groupId);
456
+ }
390
457
  queueEvent(() => emitter.emit("close", { window: win }));
391
458
  if (focusedId === id) {
392
459
  focusTop();
@@ -410,6 +477,7 @@ function createWindowManager(options = {}) {
410
477
  commitQuiet();
411
478
  return false;
412
479
  }
480
+ if (!isVisibleTab(win)) activateTab(id);
413
481
  if (win.stage === "minimized") {
414
482
  restore(id);
415
483
  return focusedId === id;
@@ -462,7 +530,9 @@ function createWindowManager(options = {}) {
462
530
  const next = build(win);
463
531
  if (!next) return false;
464
532
  setWindow(next);
533
+ syncGroup(next);
465
534
  queueEvent(() => emitter.emit("stage", { window: next, previous: win.stage }));
535
+ if (next.groupId !== null) reconcileFocus();
466
536
  commit();
467
537
  return true;
468
538
  }
@@ -571,6 +641,7 @@ function createWindowManager(options = {}) {
571
641
  if (boundsEqual(bounds, win.bounds)) return true;
572
642
  const next = { ...win, bounds };
573
643
  setWindow(next);
644
+ syncGroup(next);
574
645
  queueEvent(() => emitter.emit("move", { window: next }));
575
646
  commit();
576
647
  return true;
@@ -592,6 +663,7 @@ function createWindowManager(options = {}) {
592
663
  if (boundsEqual(bounds, win.bounds) && !becameNormal) return true;
593
664
  const next = becameNormal ? { ...win, stage: "normal", snapZone: null, restoreBounds: null, bounds } : { ...win, bounds };
594
665
  setWindow(next);
666
+ syncGroup(next);
595
667
  if (becameNormal) queueEvent(() => emitter.emit("stage", { window: next, previous: "snapped" }));
596
668
  queueEvent(() => emitter.emit("resize", { window: next }));
597
669
  commit();
@@ -619,6 +691,7 @@ function createWindowManager(options = {}) {
619
691
  const resized = size.width !== next.bounds.width || size.height !== next.bounds.height;
620
692
  const finalWin = resized ? { ...next, bounds: { ...next.bounds, ...size } } : next;
621
693
  setWindow(finalWin);
694
+ syncGroup(finalWin);
622
695
  if (patch.layer && patch.layer !== win.layer) {
623
696
  order = sortByLayer(order);
624
697
  queueEvent(() => emitter.emit("order", { order }));
@@ -660,9 +733,12 @@ function createWindowManager(options = {}) {
660
733
  if (target === win.workspace) return false;
661
734
  const updated = { ...win, workspace: target };
662
735
  setWindow(updated);
736
+ syncGroup(updated);
663
737
  if (!onActiveWorkspace(updated) && focusedId === id) {
664
738
  focusTop();
665
739
  if (focusedId) emitFocus(windows[focusedId], id);
740
+ } else if (updated.groupId !== null && !onActiveWorkspace(updated)) {
741
+ reconcileFocus();
666
742
  } else if (onActiveWorkspace(updated) && topModalId() === id && focusedId !== id) {
667
743
  const previous = focusedId;
668
744
  focusedId = id;
@@ -672,17 +748,104 @@ function createWindowManager(options = {}) {
672
748
  commit();
673
749
  return true;
674
750
  }
751
+ function emitGroup(groupId, previous) {
752
+ const members = membersOf(groupId);
753
+ const activeId = activeTabs[groupId];
754
+ queueEvent(() => emitter.emit("group", { groupId, members, activeId, previous }));
755
+ }
756
+ function group(ids) {
757
+ const seeds = ids.filter((id, index) => windows[id] && ids.indexOf(id) === index);
758
+ const host = seeds[0] ? windows[seeds[0]] : null;
759
+ if (!host) return null;
760
+ const expanded = [];
761
+ for (const id of seeds) {
762
+ const win = windows[id];
763
+ const siblings = win.groupId === null ? [id] : membersOf(win.groupId);
764
+ for (const entry of siblings) if (!expanded.includes(entry)) expanded.push(entry);
765
+ }
766
+ if (expanded.length < 2) return null;
767
+ let groupId = `${idPrefix}-group-${++groupCounter}`;
768
+ while (activeTabs[groupId]) groupId = `${idPrefix}-group-${++groupCounter}`;
769
+ const shared = expanded.reduce(
770
+ (size, id) => {
771
+ const fit = normalizeSize(size, windows[id]);
772
+ return { width: Math.max(size.width, fit.width), height: Math.max(size.height, fit.height) };
773
+ },
774
+ { width: host.bounds.width, height: host.bounds.height }
775
+ );
776
+ const bounds = { x: host.bounds.x, y: host.bounds.y, ...shared };
777
+ const retired = /* @__PURE__ */ new Set();
778
+ for (const id of expanded) {
779
+ const win = windows[id];
780
+ if (win.groupId !== null) retired.add(win.groupId);
781
+ setWindow({
782
+ ...win,
783
+ groupId,
784
+ bounds,
785
+ stage: host.stage,
786
+ snapZone: host.snapZone,
787
+ restoreBounds: host.restoreBounds,
788
+ restoreStage: host.restoreStage,
789
+ layer: host.layer,
790
+ workspace: host.workspace
791
+ });
792
+ }
793
+ const next = { ...activeTabs, [groupId]: host.id };
794
+ for (const old of retired) delete next[old];
795
+ activeTabs = next;
796
+ raise(host.id);
797
+ emitGroup(groupId, null);
798
+ queueEvent(() => emitter.emit("order", { order }));
799
+ const hostVisible = onActiveWorkspace(host) && host.stage !== "minimized";
800
+ if (!hostVisible) reconcileFocus();
801
+ commit();
802
+ if (hostVisible) focus(host.id);
803
+ return groupId;
804
+ }
805
+ function ungroup(id) {
806
+ const win = windows[id];
807
+ if (!win?.groupId) return false;
808
+ const groupId = win.groupId;
809
+ const wasActive = activeTabs[groupId] === id;
810
+ setWindow({ ...win, groupId: null });
811
+ const remaining = membersOf(groupId);
812
+ if (wasActive) activeTabs = { ...activeTabs, [groupId]: remaining[0] };
813
+ dissolveIfOrphaned(groupId);
814
+ if (membersOf(groupId).length >= 2) emitGroup(groupId, wasActive ? id : null);
815
+ commit();
816
+ if (onActiveWorkspace(win) && win.stage !== "minimized") focus(id);
817
+ return true;
818
+ }
819
+ function activateTab(id) {
820
+ const win = windows[id];
821
+ if (!win?.groupId) return false;
822
+ const groupId = win.groupId;
823
+ const previous = activeTabs[groupId];
824
+ if (previous === id) return false;
825
+ activeTabs = { ...activeTabs, [groupId]: id };
826
+ if (focusedId === previous) {
827
+ focusedId = id;
828
+ emitFocus(win, previous);
829
+ }
830
+ emitGroup(groupId, previous);
831
+ commit();
832
+ return true;
833
+ }
834
+ function groupMembers(groupId) {
835
+ return membersOf(groupId).map((id) => windows[id]);
836
+ }
675
837
  function sendToBack(id) {
676
838
  const win = windows[id];
677
839
  if (!win) return false;
678
- const without = order.filter((entry) => entry !== id);
840
+ const block = blockOf(id);
841
+ const without = order.filter((entry) => !block.includes(entry));
679
842
  const rank = LAYER_RANK[win.layer];
680
843
  let insertAt = 0;
681
844
  for (const entry of without) {
682
845
  if (layerRankOf(entry) < rank) insertAt += 1;
683
846
  else break;
684
847
  }
685
- const next = [...without.slice(0, insertAt), id, ...without.slice(insertAt)];
848
+ const next = [...without.slice(0, insertAt), ...block, ...without.slice(insertAt)];
686
849
  if (next.every((entry, i) => entry === order[i])) return false;
687
850
  order = next;
688
851
  queueEvent(() => emitter.emit("order", { order }));
@@ -703,10 +866,10 @@ function createWindowManager(options = {}) {
703
866
  );
704
867
  }
705
868
  function minimized() {
706
- return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win)).sort((a, b) => a.openedSeq - b.openedSeq);
869
+ return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win) && isVisibleTab(win)).sort((a, b) => a.openedSeq - b.openedSeq);
707
870
  }
708
871
  function captureEntry() {
709
- return { windows, order, focusedId, workspace };
872
+ return { windows, order, focusedId, workspace, activeTabs };
710
873
  }
711
874
  function recordHistory() {
712
875
  if (!historyDirty) return;
@@ -746,19 +909,23 @@ function createWindowManager(options = {}) {
746
909
  function reflowViewport() {
747
910
  for (const id of order) {
748
911
  const win = windows[id];
912
+ if (!isVisibleTab(win)) continue;
749
913
  if (win.stage === "maximized") {
750
914
  const updated = { ...win, bounds: fullBounds() };
751
915
  setWindow(updated);
916
+ syncGroup(updated);
752
917
  queueEvent(() => emitter.emit("resize", { window: updated }));
753
918
  } else if (win.stage === "snapped" && win.snapZone) {
754
919
  const updated = { ...win, bounds: snapBounds(win, win.snapZone) };
755
920
  setWindow(updated);
921
+ syncGroup(updated);
756
922
  queueEvent(() => emitter.emit("resize", { window: updated }));
757
923
  } else if (win.stage === "normal" && keepInViewport) {
758
924
  const bounds = clampToViewport(win.bounds, viewport, minVisible);
759
925
  if (!boundsEqual(bounds, win.bounds)) {
760
926
  const updated = { ...win, bounds };
761
927
  setWindow(updated);
928
+ syncGroup(updated);
762
929
  queueEvent(() => emitter.emit("move", { window: updated }));
763
930
  }
764
931
  }
@@ -768,6 +935,7 @@ function createWindowManager(options = {}) {
768
935
  windows = entry.windows;
769
936
  order = [...entry.order];
770
937
  focusedId = entry.focusedId;
938
+ activeTabs = entry.activeTabs;
771
939
  const previousWorkspace = workspace;
772
940
  workspace = entry.workspace;
773
941
  if (workspace !== previousWorkspace) {
@@ -844,7 +1012,7 @@ function createWindowManager(options = {}) {
844
1012
  function arrange(mode) {
845
1013
  const ids = order.filter((id) => {
846
1014
  const win = windows[id];
847
- return win.stage !== "minimized" && onActiveWorkspace(win);
1015
+ return win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win);
848
1016
  });
849
1017
  if (ids.length === 0) return;
850
1018
  if (mode === "cascade") {
@@ -876,7 +1044,8 @@ function createWindowManager(options = {}) {
876
1044
  }
877
1045
  function minimizeAll() {
878
1046
  for (const id of [...order]) {
879
- if (onActiveWorkspace(windows[id])) minimize(id);
1047
+ const win = windows[id];
1048
+ if (onActiveWorkspace(win) && isVisibleTab(win)) minimize(id);
880
1049
  }
881
1050
  }
882
1051
  function restoreAll() {
@@ -898,7 +1067,8 @@ function createWindowManager(options = {}) {
898
1067
  })),
899
1068
  order: [...order],
900
1069
  focusedId,
901
- workspace
1070
+ workspace,
1071
+ activeTabs: { ...activeTabs }
902
1072
  };
903
1073
  }
904
1074
  function isBounds(value) {
@@ -953,6 +1123,7 @@ function createWindowManager(options = {}) {
953
1123
  snapZone: ZONES.includes(raw.snapZone) ? raw.snapZone : null,
954
1124
  layer: raw.layer,
955
1125
  workspace: normalizeWorkspace(raw.workspace, 0),
1126
+ groupId: typeof raw.groupId === "string" && raw.groupId.length > 0 ? raw.groupId : null,
956
1127
  minSize: isSize(raw.minSize) ? { ...raw.minSize } : { width: 160, height: 100 },
957
1128
  maxSize: readMaxSize(raw.maxSize),
958
1129
  aspectRatio: typeof raw.aspectRatio === "number" && Number.isFinite(raw.aspectRatio) && raw.aspectRatio > 0 ? raw.aspectRatio : null,
@@ -994,7 +1165,18 @@ function createWindowManager(options = {}) {
994
1165
  if (focusedCandidate && (focusedCandidate.stage === "minimized" || !onActiveWorkspace(focusedCandidate))) {
995
1166
  focusedId = null;
996
1167
  }
997
- if (!focusedId) focusTop();
1168
+ const restoredTabs = /* @__PURE__ */ Object.create(null);
1169
+ const rawTabs = data.activeTabs;
1170
+ for (const id of order) {
1171
+ const groupId = windows[id].groupId;
1172
+ if (groupId === null || restoredTabs[groupId]) continue;
1173
+ const wanted = typeof rawTabs === "object" && rawTabs !== null ? rawTabs[groupId] : void 0;
1174
+ const members = membersOf(groupId);
1175
+ restoredTabs[groupId] = typeof wanted === "string" && members.includes(wanted) ? wanted : members[0];
1176
+ }
1177
+ activeTabs = restoredTabs;
1178
+ for (const groupId of Object.keys(restoredTabs)) dissolveIfOrphaned(groupId);
1179
+ if (!focusedId || !isVisibleTab(windows[focusedId])) focusTop();
998
1180
  const modal = topModalId();
999
1181
  if (modal) {
1000
1182
  const focusedWin = focusedId ? windows[focusedId] : null;
@@ -1048,6 +1230,10 @@ function createWindowManager(options = {}) {
1048
1230
  workspace: () => workspace,
1049
1231
  setWorkspace: (next) => transact(() => setWorkspace(next)),
1050
1232
  moveToWorkspace: (id, next) => transact(() => moveToWorkspace(id, next)),
1233
+ group: (ids) => transact(() => group(ids)),
1234
+ ungroup: (id) => transact(() => ungroup(id)),
1235
+ activateTab: (id) => transact(() => activateTab(id)),
1236
+ groupMembers,
1051
1237
  batch: (run) => transact(run),
1052
1238
  serialize,
1053
1239
  hydrate: (data) => transact(() => hydrate(data)),
@@ -1237,6 +1423,11 @@ function createDragStarter(ctx) {
1237
1423
  restored: win.stage === "normal",
1238
1424
  moved: false,
1239
1425
  zone: null,
1426
+ groupTarget: null,
1427
+ hoverId: null,
1428
+ hoverTimer: 0,
1429
+ clientX: event.clientX,
1430
+ clientY: event.clientY,
1240
1431
  raf: 0,
1241
1432
  pendingX: 0,
1242
1433
  pendingY: 0,
@@ -1245,6 +1436,20 @@ function createDragStarter(ctx) {
1245
1436
  }
1246
1437
  };
1247
1438
  const el = ctx.windowElement(id);
1439
+ function armGroup(target2) {
1440
+ session.groupTarget = target2;
1441
+ session.zone = null;
1442
+ ctx.hidePreview();
1443
+ ctx.markGroupTarget(target2);
1444
+ }
1445
+ function clearHover() {
1446
+ if (session.hoverTimer !== 0) {
1447
+ view.clearTimeout(session.hoverTimer);
1448
+ session.hoverTimer = 0;
1449
+ }
1450
+ session.groupTarget = null;
1451
+ ctx.markGroupTarget(null);
1452
+ }
1248
1453
  function flush() {
1249
1454
  if (!session.hasPending || ctx.currentDrag() !== session) return;
1250
1455
  session.hasPending = false;
@@ -1279,9 +1484,9 @@ function createDragStarter(ctx) {
1279
1484
  for (const otherId of state.order) {
1280
1485
  if (otherId === id) continue;
1281
1486
  const other = state.windows[otherId];
1282
- if (other && other.stage !== "minimized" && other.workspace === state.workspace) {
1283
- targets.push(other.bounds);
1284
- }
1487
+ if (!other || other.stage === "minimized" || other.workspace !== state.workspace) continue;
1488
+ if (other.groupId !== null && state.groups[other.groupId]?.activeId !== otherId) continue;
1489
+ targets.push(other.bounds);
1285
1490
  }
1286
1491
  const magnet = magnetize(
1287
1492
  { x: nextX, y: nextY, width: current.bounds.width, height: current.bounds.height },
@@ -1292,6 +1497,20 @@ function createDragStarter(ctx) {
1292
1497
  nextY = magnet.y;
1293
1498
  }
1294
1499
  wm.move(id, nextX, nextY);
1500
+ const candidate = ctx.groupTarget(session.clientX, session.clientY, id);
1501
+ if (candidate !== session.hoverId) {
1502
+ session.hoverId = candidate;
1503
+ clearHover();
1504
+ if (candidate) {
1505
+ if (ctx.groupDwell <= 0) armGroup(candidate);
1506
+ else session.hoverTimer = view.setTimeout(() => armGroup(candidate), ctx.groupDwell);
1507
+ }
1508
+ }
1509
+ if (session.groupTarget) {
1510
+ session.zone = null;
1511
+ ctx.hidePreview();
1512
+ return;
1513
+ }
1295
1514
  if (ctx.snapEnabled && current.snappable) {
1296
1515
  const viewport = wm.getState().viewport;
1297
1516
  const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect);
@@ -1324,6 +1543,8 @@ function createDragStarter(ctx) {
1324
1543
  }
1325
1544
  session.pendingX = movePoint.x;
1326
1545
  session.pendingY = movePoint.y;
1546
+ session.clientX = moveEvent.clientX;
1547
+ session.clientY = moveEvent.clientY;
1327
1548
  session.hasPending = true;
1328
1549
  if (session.raf === 0) session.raf = view.requestAnimationFrame(flush);
1329
1550
  }
@@ -1358,6 +1579,8 @@ function createDragStarter(ctx) {
1358
1579
  wm.move(id, session.startBounds.x, session.startBounds.y);
1359
1580
  }
1360
1581
  }
1582
+ } else if (session.moved && session.groupTarget) {
1583
+ if (wm.group([session.groupTarget, id])) wm.activateTab(id);
1361
1584
  } else if (session.moved && session.zone) {
1362
1585
  if (session.zone === "maximize") wm.maximize(id);
1363
1586
  else wm.snap(id, session.zone);
@@ -1377,6 +1600,8 @@ function createDragStarter(ctx) {
1377
1600
  el.style.willChange = "";
1378
1601
  }
1379
1602
  ctx.hidePreview();
1603
+ if (session.hoverTimer !== 0) view.clearTimeout(session.hoverTimer);
1604
+ ctx.markGroupTarget(null);
1380
1605
  if (cancelled) wm.abortInteraction();
1381
1606
  else wm.endInteraction();
1382
1607
  }
@@ -1546,6 +1771,8 @@ function attachDesktop(wm, element, options = {}) {
1546
1771
  const doc = element.ownerDocument;
1547
1772
  const view = windowOf(element);
1548
1773
  const coarsePointer = typeof view.matchMedia === "function" && view.matchMedia("(pointer: coarse)").matches;
1774
+ const groupingEnabled = options.grouping !== false;
1775
+ const groupDwell = (typeof options.grouping === "object" ? options.grouping.dwell : void 0) ?? 420;
1549
1776
  const snapEnabled = options.snap !== false;
1550
1777
  const snapOptions = typeof options.snap === "object" ? options.snap : {};
1551
1778
  const snapPreviewEnabled = snapOptions.preview !== false;
@@ -1569,6 +1796,8 @@ function attachDesktop(wm, element, options = {}) {
1569
1796
  let lastOrder = null;
1570
1797
  let lastFocused = null;
1571
1798
  let drag = null;
1799
+ let groupTargetId = null;
1800
+ let groupTargetEl = null;
1572
1801
  let cachedRect = null;
1573
1802
  let rectUsers = 0;
1574
1803
  let announcer = null;
@@ -1640,6 +1869,24 @@ function attachDesktop(wm, element, options = {}) {
1640
1869
  hitEdge,
1641
1870
  hitCorner,
1642
1871
  magnetThreshold,
1872
+ groupDwell,
1873
+ groupTarget(clientX, clientY, selfId) {
1874
+ if (!groupingEnabled || typeof doc.elementsFromPoint !== "function") return null;
1875
+ for (const node of doc.elementsFromPoint(clientX, clientY)) {
1876
+ const handle = node.closest?.("[data-wm-drag]");
1877
+ const host = handle?.closest("[data-wm-window]");
1878
+ const id = host?.dataset.wmWindow;
1879
+ if (id && id !== selfId && registry.get(id)?.element === host) return id;
1880
+ }
1881
+ return null;
1882
+ },
1883
+ markGroupTarget(id) {
1884
+ if (groupTargetId === id) return;
1885
+ if (groupTargetEl) delete groupTargetEl.dataset.wmTabTarget;
1886
+ groupTargetId = id;
1887
+ groupTargetEl = id ? registry.get(id)?.element ?? null : null;
1888
+ if (groupTargetEl) groupTargetEl.dataset.wmTabTarget = "";
1889
+ },
1643
1890
  currentDrag: () => drag,
1644
1891
  claimDrag(session) {
1645
1892
  drag = session;
@@ -1657,18 +1904,25 @@ function attachDesktop(wm, element, options = {}) {
1657
1904
  observer.observe(element);
1658
1905
  cleanup.push(() => observer.disconnect());
1659
1906
  }
1660
- function syncWindow(attached, win, zIndex, activeWorkspace) {
1907
+ function syncWindow(attached, win, zIndex, activeWorkspace, activeTab) {
1661
1908
  const el = attached.element;
1662
1909
  const firstSync = attached.lastState === null;
1663
1910
  if (firstSync) el.style.transition = "none";
1664
- if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace) {
1911
+ if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace || attached.lastTab !== activeTab) {
1665
1912
  el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
1666
1913
  el.style.width = `${win.bounds.width}px`;
1667
1914
  el.style.height = `${win.bounds.height}px`;
1668
1915
  el.dataset.wmStage = win.stage;
1669
1916
  el.dataset.wmLayer = win.layer;
1670
1917
  el.dataset.wmWorkspace = String(win.workspace);
1671
- const hide = win.stage === "minimized" || win.workspace !== activeWorkspace;
1918
+ if (win.groupId === null) {
1919
+ delete el.dataset.wmGroup;
1920
+ delete el.dataset.wmTab;
1921
+ } else {
1922
+ el.dataset.wmGroup = win.groupId;
1923
+ el.dataset.wmTab = activeTab ? "active" : "inactive";
1924
+ }
1925
+ const hide = win.stage === "minimized" || win.workspace !== activeWorkspace || !activeTab;
1672
1926
  if (hide && !el.hidden && el.contains(doc.activeElement)) {
1673
1927
  element.focus({ preventScroll: true });
1674
1928
  }
@@ -1686,6 +1940,7 @@ function attachDesktop(wm, element, options = {}) {
1686
1940
  }
1687
1941
  attached.lastState = win;
1688
1942
  attached.lastWorkspace = activeWorkspace;
1943
+ attached.lastTab = activeTab;
1689
1944
  }
1690
1945
  if (attached.lastZ !== zIndex) {
1691
1946
  el.style.zIndex = String(zIndex + 1);
@@ -1703,8 +1958,9 @@ function attachDesktop(wm, element, options = {}) {
1703
1958
  const attached = registry.get(id);
1704
1959
  const win = state.windows[id];
1705
1960
  if (!attached || !win) return;
1706
- if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace) {
1707
- syncWindow(attached, win, index, state.workspace);
1961
+ const activeTab = win.groupId === null || state.groups[win.groupId]?.activeId === win.id;
1962
+ if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace || attached.lastTab !== activeTab) {
1963
+ syncWindow(attached, win, index, state.workspace, activeTab);
1708
1964
  }
1709
1965
  });
1710
1966
  if (state.focusedId !== lastFocused) {
@@ -1807,6 +2063,7 @@ function attachDesktop(wm, element, options = {}) {
1807
2063
  lastState: null,
1808
2064
  lastZ: -1,
1809
2065
  lastWorkspace: -1,
2066
+ lastTab: true,
1810
2067
  cleanup: []
1811
2068
  };
1812
2069
  windowElement.dataset.wmWindow = id;
@@ -2046,5 +2303,5 @@ exports.flipToTarget = flipToTarget;
2046
2303
  exports.magnetize = magnetize;
2047
2304
  exports.prefersReducedMotion = prefersReducedMotion;
2048
2305
  exports.zoneBounds = zoneBounds;
2049
- //# sourceMappingURL=chunk-GLBVT4AQ.cjs.map
2050
- //# sourceMappingURL=chunk-GLBVT4AQ.cjs.map
2306
+ //# sourceMappingURL=chunk-C2UHNBSA.cjs.map
2307
+ //# sourceMappingURL=chunk-C2UHNBSA.cjs.map