@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
@@ -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;
@@ -215,7 +217,7 @@ function createWindowManager(options = {}) {
215
217
  const layouts = /* @__PURE__ */ new Map();
216
218
  function getState() {
217
219
  if (!snapshot) {
218
- snapshot = { windows, order, focusedId, viewport, workspace };
220
+ snapshot = { windows, order, focusedId, viewport, workspace, groups: buildGroups() };
219
221
  }
220
222
  return snapshot;
221
223
  }
@@ -250,16 +252,22 @@ function createWindowManager(options = {}) {
250
252
  function sortByLayer(ids) {
251
253
  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);
252
254
  }
255
+ function blockOf(id) {
256
+ const win = windows[id];
257
+ if (win.groupId === null) return [id];
258
+ return [...membersOf(win.groupId).filter((entry) => entry !== id), id];
259
+ }
253
260
  function raise(id) {
254
261
  const win = windows[id];
255
- const without = order.filter((entry) => entry !== id);
262
+ const block = blockOf(id);
263
+ const without = order.filter((entry) => !block.includes(entry));
256
264
  const rank = LAYER_RANK[win.layer];
257
265
  let insertAt = without.length;
258
266
  for (let i = without.length - 1; i >= 0; i -= 1) {
259
267
  if (layerRankOf(without[i]) > rank) insertAt = i;
260
268
  else break;
261
269
  }
262
- const next = [...without.slice(0, insertAt), id, ...without.slice(insertAt)];
270
+ const next = [...without.slice(0, insertAt), ...block, ...without.slice(insertAt)];
263
271
  const changed = next.length !== order.length || next.some((entry, i) => entry !== order[i]);
264
272
  order = next;
265
273
  return changed;
@@ -267,11 +275,54 @@ function createWindowManager(options = {}) {
267
275
  function onActiveWorkspace(win) {
268
276
  return win.workspace === workspace;
269
277
  }
278
+ function membersOf(groupId) {
279
+ return order.filter((entry) => windows[entry].groupId === groupId);
280
+ }
281
+ function isVisibleTab(win) {
282
+ return win.groupId === null || activeTabs[win.groupId] === win.id;
283
+ }
284
+ function buildGroups() {
285
+ const result = /* @__PURE__ */ Object.create(null);
286
+ for (const [groupId, activeId] of Object.entries(activeTabs)) {
287
+ result[groupId] = { id: groupId, activeId, members: membersOf(groupId) };
288
+ }
289
+ return result;
290
+ }
291
+ function syncGroup(source) {
292
+ if (source.groupId === null) return;
293
+ for (const id of membersOf(source.groupId)) {
294
+ if (id === source.id) continue;
295
+ const member = windows[id];
296
+ if (boundsEqual(member.bounds, source.bounds) && member.stage === source.stage && member.snapZone === source.snapZone && member.layer === source.layer && member.workspace === source.workspace) {
297
+ continue;
298
+ }
299
+ setWindow({
300
+ ...member,
301
+ bounds: source.bounds,
302
+ stage: source.stage,
303
+ snapZone: source.snapZone,
304
+ restoreBounds: source.restoreBounds,
305
+ restoreStage: source.restoreStage,
306
+ layer: source.layer,
307
+ workspace: source.workspace
308
+ });
309
+ }
310
+ }
311
+ function dissolveIfOrphaned(groupId) {
312
+ const members = membersOf(groupId);
313
+ if (members.length >= 2) return;
314
+ for (const id of members) {
315
+ setWindow({ ...windows[id], groupId: null });
316
+ }
317
+ const { [groupId]: _dropped, ...rest } = activeTabs;
318
+ activeTabs = rest;
319
+ }
270
320
  function topModalId() {
271
321
  for (let i = order.length - 1; i >= 0; i -= 1) {
272
322
  const win = windows[order[i]];
273
- if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win))
323
+ if (win.layer === "modal" && win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win)) {
274
324
  return win.id;
325
+ }
275
326
  }
276
327
  return null;
277
328
  }
@@ -279,7 +330,7 @@ function createWindowManager(options = {}) {
279
330
  const modal = topModalId();
280
331
  return order.filter((id) => {
281
332
  const win = windows[id];
282
- if (win.stage === "minimized" || !onActiveWorkspace(win)) return false;
333
+ if (win.stage === "minimized" || !onActiveWorkspace(win) || !isVisibleTab(win)) return false;
283
334
  if (modal && win.layer !== "modal") return false;
284
335
  return true;
285
336
  });
@@ -288,6 +339,14 @@ function createWindowManager(options = {}) {
288
339
  const targets = focusTargets();
289
340
  focusedId = targets.length > 0 ? targets[targets.length - 1] : null;
290
341
  }
342
+ function reconcileFocus() {
343
+ if (focusedId === null) return;
344
+ const win = windows[focusedId];
345
+ if (win && win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win)) return;
346
+ const previous = focusedId;
347
+ focusTop();
348
+ if (focusedId) emitFocus(windows[focusedId], previous);
349
+ }
291
350
  function emitFocus(win, previous) {
292
351
  queueEvent(() => emitter.emit("focus", { window: win, previous }));
293
352
  }
@@ -342,6 +401,7 @@ function createWindowManager(options = {}) {
342
401
  snapZone: null,
343
402
  layer: init.layer ?? "normal",
344
403
  workspace: normalizeWorkspace(init.workspace, workspace),
404
+ groupId: null,
345
405
  minSize,
346
406
  maxSize,
347
407
  aspectRatio,
@@ -385,6 +445,13 @@ function createWindowManager(options = {}) {
385
445
  const win = windows[id];
386
446
  if (!win) return false;
387
447
  removeWindow(id);
448
+ if (win.groupId !== null) {
449
+ const remaining = membersOf(win.groupId);
450
+ if (activeTabs[win.groupId] === id && remaining[0]) {
451
+ activeTabs = { ...activeTabs, [win.groupId]: remaining[0] };
452
+ }
453
+ dissolveIfOrphaned(win.groupId);
454
+ }
388
455
  queueEvent(() => emitter.emit("close", { window: win }));
389
456
  if (focusedId === id) {
390
457
  focusTop();
@@ -408,6 +475,7 @@ function createWindowManager(options = {}) {
408
475
  commitQuiet();
409
476
  return false;
410
477
  }
478
+ if (!isVisibleTab(win)) activateTab(id);
411
479
  if (win.stage === "minimized") {
412
480
  restore(id);
413
481
  return focusedId === id;
@@ -460,7 +528,9 @@ function createWindowManager(options = {}) {
460
528
  const next = build(win);
461
529
  if (!next) return false;
462
530
  setWindow(next);
531
+ syncGroup(next);
463
532
  queueEvent(() => emitter.emit("stage", { window: next, previous: win.stage }));
533
+ if (next.groupId !== null) reconcileFocus();
464
534
  commit();
465
535
  return true;
466
536
  }
@@ -569,6 +639,7 @@ function createWindowManager(options = {}) {
569
639
  if (boundsEqual(bounds, win.bounds)) return true;
570
640
  const next = { ...win, bounds };
571
641
  setWindow(next);
642
+ syncGroup(next);
572
643
  queueEvent(() => emitter.emit("move", { window: next }));
573
644
  commit();
574
645
  return true;
@@ -590,6 +661,7 @@ function createWindowManager(options = {}) {
590
661
  if (boundsEqual(bounds, win.bounds) && !becameNormal) return true;
591
662
  const next = becameNormal ? { ...win, stage: "normal", snapZone: null, restoreBounds: null, bounds } : { ...win, bounds };
592
663
  setWindow(next);
664
+ syncGroup(next);
593
665
  if (becameNormal) queueEvent(() => emitter.emit("stage", { window: next, previous: "snapped" }));
594
666
  queueEvent(() => emitter.emit("resize", { window: next }));
595
667
  commit();
@@ -617,6 +689,7 @@ function createWindowManager(options = {}) {
617
689
  const resized = size.width !== next.bounds.width || size.height !== next.bounds.height;
618
690
  const finalWin = resized ? { ...next, bounds: { ...next.bounds, ...size } } : next;
619
691
  setWindow(finalWin);
692
+ syncGroup(finalWin);
620
693
  if (patch.layer && patch.layer !== win.layer) {
621
694
  order = sortByLayer(order);
622
695
  queueEvent(() => emitter.emit("order", { order }));
@@ -658,9 +731,12 @@ function createWindowManager(options = {}) {
658
731
  if (target === win.workspace) return false;
659
732
  const updated = { ...win, workspace: target };
660
733
  setWindow(updated);
734
+ syncGroup(updated);
661
735
  if (!onActiveWorkspace(updated) && focusedId === id) {
662
736
  focusTop();
663
737
  if (focusedId) emitFocus(windows[focusedId], id);
738
+ } else if (updated.groupId !== null && !onActiveWorkspace(updated)) {
739
+ reconcileFocus();
664
740
  } else if (onActiveWorkspace(updated) && topModalId() === id && focusedId !== id) {
665
741
  const previous = focusedId;
666
742
  focusedId = id;
@@ -670,17 +746,104 @@ function createWindowManager(options = {}) {
670
746
  commit();
671
747
  return true;
672
748
  }
749
+ function emitGroup(groupId, previous) {
750
+ const members = membersOf(groupId);
751
+ const activeId = activeTabs[groupId];
752
+ queueEvent(() => emitter.emit("group", { groupId, members, activeId, previous }));
753
+ }
754
+ function group(ids) {
755
+ const seeds = ids.filter((id, index) => windows[id] && ids.indexOf(id) === index);
756
+ const host = seeds[0] ? windows[seeds[0]] : null;
757
+ if (!host) return null;
758
+ const expanded = [];
759
+ for (const id of seeds) {
760
+ const win = windows[id];
761
+ const siblings = win.groupId === null ? [id] : membersOf(win.groupId);
762
+ for (const entry of siblings) if (!expanded.includes(entry)) expanded.push(entry);
763
+ }
764
+ if (expanded.length < 2) return null;
765
+ let groupId = `${idPrefix}-group-${++groupCounter}`;
766
+ while (activeTabs[groupId]) groupId = `${idPrefix}-group-${++groupCounter}`;
767
+ const shared = expanded.reduce(
768
+ (size, id) => {
769
+ const fit = normalizeSize(size, windows[id]);
770
+ return { width: Math.max(size.width, fit.width), height: Math.max(size.height, fit.height) };
771
+ },
772
+ { width: host.bounds.width, height: host.bounds.height }
773
+ );
774
+ const bounds = { x: host.bounds.x, y: host.bounds.y, ...shared };
775
+ const retired = /* @__PURE__ */ new Set();
776
+ for (const id of expanded) {
777
+ const win = windows[id];
778
+ if (win.groupId !== null) retired.add(win.groupId);
779
+ setWindow({
780
+ ...win,
781
+ groupId,
782
+ bounds,
783
+ stage: host.stage,
784
+ snapZone: host.snapZone,
785
+ restoreBounds: host.restoreBounds,
786
+ restoreStage: host.restoreStage,
787
+ layer: host.layer,
788
+ workspace: host.workspace
789
+ });
790
+ }
791
+ const next = { ...activeTabs, [groupId]: host.id };
792
+ for (const old of retired) delete next[old];
793
+ activeTabs = next;
794
+ raise(host.id);
795
+ emitGroup(groupId, null);
796
+ queueEvent(() => emitter.emit("order", { order }));
797
+ const hostVisible = onActiveWorkspace(host) && host.stage !== "minimized";
798
+ if (!hostVisible) reconcileFocus();
799
+ commit();
800
+ if (hostVisible) focus(host.id);
801
+ return groupId;
802
+ }
803
+ function ungroup(id) {
804
+ const win = windows[id];
805
+ if (!win?.groupId) return false;
806
+ const groupId = win.groupId;
807
+ const wasActive = activeTabs[groupId] === id;
808
+ setWindow({ ...win, groupId: null });
809
+ const remaining = membersOf(groupId);
810
+ if (wasActive) activeTabs = { ...activeTabs, [groupId]: remaining[0] };
811
+ dissolveIfOrphaned(groupId);
812
+ if (membersOf(groupId).length >= 2) emitGroup(groupId, wasActive ? id : null);
813
+ commit();
814
+ if (onActiveWorkspace(win) && win.stage !== "minimized") focus(id);
815
+ return true;
816
+ }
817
+ function activateTab(id) {
818
+ const win = windows[id];
819
+ if (!win?.groupId) return false;
820
+ const groupId = win.groupId;
821
+ const previous = activeTabs[groupId];
822
+ if (previous === id) return false;
823
+ activeTabs = { ...activeTabs, [groupId]: id };
824
+ if (focusedId === previous) {
825
+ focusedId = id;
826
+ emitFocus(win, previous);
827
+ }
828
+ emitGroup(groupId, previous);
829
+ commit();
830
+ return true;
831
+ }
832
+ function groupMembers(groupId) {
833
+ return membersOf(groupId).map((id) => windows[id]);
834
+ }
673
835
  function sendToBack(id) {
674
836
  const win = windows[id];
675
837
  if (!win) return false;
676
- const without = order.filter((entry) => entry !== id);
838
+ const block = blockOf(id);
839
+ const without = order.filter((entry) => !block.includes(entry));
677
840
  const rank = LAYER_RANK[win.layer];
678
841
  let insertAt = 0;
679
842
  for (const entry of without) {
680
843
  if (layerRankOf(entry) < rank) insertAt += 1;
681
844
  else break;
682
845
  }
683
- const next = [...without.slice(0, insertAt), id, ...without.slice(insertAt)];
846
+ const next = [...without.slice(0, insertAt), ...block, ...without.slice(insertAt)];
684
847
  if (next.every((entry, i) => entry === order[i])) return false;
685
848
  order = next;
686
849
  queueEvent(() => emitter.emit("order", { order }));
@@ -701,10 +864,10 @@ function createWindowManager(options = {}) {
701
864
  );
702
865
  }
703
866
  function minimized() {
704
- return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win)).sort((a, b) => a.openedSeq - b.openedSeq);
867
+ return Object.values(windows).filter((win) => win.stage === "minimized" && onActiveWorkspace(win) && isVisibleTab(win)).sort((a, b) => a.openedSeq - b.openedSeq);
705
868
  }
706
869
  function captureEntry() {
707
- return { windows, order, focusedId, workspace };
870
+ return { windows, order, focusedId, workspace, activeTabs };
708
871
  }
709
872
  function recordHistory() {
710
873
  if (!historyDirty) return;
@@ -744,19 +907,23 @@ function createWindowManager(options = {}) {
744
907
  function reflowViewport() {
745
908
  for (const id of order) {
746
909
  const win = windows[id];
910
+ if (!isVisibleTab(win)) continue;
747
911
  if (win.stage === "maximized") {
748
912
  const updated = { ...win, bounds: fullBounds() };
749
913
  setWindow(updated);
914
+ syncGroup(updated);
750
915
  queueEvent(() => emitter.emit("resize", { window: updated }));
751
916
  } else if (win.stage === "snapped" && win.snapZone) {
752
917
  const updated = { ...win, bounds: snapBounds(win, win.snapZone) };
753
918
  setWindow(updated);
919
+ syncGroup(updated);
754
920
  queueEvent(() => emitter.emit("resize", { window: updated }));
755
921
  } else if (win.stage === "normal" && keepInViewport) {
756
922
  const bounds = clampToViewport(win.bounds, viewport, minVisible);
757
923
  if (!boundsEqual(bounds, win.bounds)) {
758
924
  const updated = { ...win, bounds };
759
925
  setWindow(updated);
926
+ syncGroup(updated);
760
927
  queueEvent(() => emitter.emit("move", { window: updated }));
761
928
  }
762
929
  }
@@ -766,6 +933,7 @@ function createWindowManager(options = {}) {
766
933
  windows = entry.windows;
767
934
  order = [...entry.order];
768
935
  focusedId = entry.focusedId;
936
+ activeTabs = entry.activeTabs;
769
937
  const previousWorkspace = workspace;
770
938
  workspace = entry.workspace;
771
939
  if (workspace !== previousWorkspace) {
@@ -842,7 +1010,7 @@ function createWindowManager(options = {}) {
842
1010
  function arrange(mode) {
843
1011
  const ids = order.filter((id) => {
844
1012
  const win = windows[id];
845
- return win.stage !== "minimized" && onActiveWorkspace(win);
1013
+ return win.stage !== "minimized" && onActiveWorkspace(win) && isVisibleTab(win);
846
1014
  });
847
1015
  if (ids.length === 0) return;
848
1016
  if (mode === "cascade") {
@@ -874,7 +1042,8 @@ function createWindowManager(options = {}) {
874
1042
  }
875
1043
  function minimizeAll() {
876
1044
  for (const id of [...order]) {
877
- if (onActiveWorkspace(windows[id])) minimize(id);
1045
+ const win = windows[id];
1046
+ if (onActiveWorkspace(win) && isVisibleTab(win)) minimize(id);
878
1047
  }
879
1048
  }
880
1049
  function restoreAll() {
@@ -896,7 +1065,8 @@ function createWindowManager(options = {}) {
896
1065
  })),
897
1066
  order: [...order],
898
1067
  focusedId,
899
- workspace
1068
+ workspace,
1069
+ activeTabs: { ...activeTabs }
900
1070
  };
901
1071
  }
902
1072
  function isBounds(value) {
@@ -951,6 +1121,7 @@ function createWindowManager(options = {}) {
951
1121
  snapZone: ZONES.includes(raw.snapZone) ? raw.snapZone : null,
952
1122
  layer: raw.layer,
953
1123
  workspace: normalizeWorkspace(raw.workspace, 0),
1124
+ groupId: typeof raw.groupId === "string" && raw.groupId.length > 0 ? raw.groupId : null,
954
1125
  minSize: isSize(raw.minSize) ? { ...raw.minSize } : { width: 160, height: 100 },
955
1126
  maxSize: readMaxSize(raw.maxSize),
956
1127
  aspectRatio: typeof raw.aspectRatio === "number" && Number.isFinite(raw.aspectRatio) && raw.aspectRatio > 0 ? raw.aspectRatio : null,
@@ -992,7 +1163,18 @@ function createWindowManager(options = {}) {
992
1163
  if (focusedCandidate && (focusedCandidate.stage === "minimized" || !onActiveWorkspace(focusedCandidate))) {
993
1164
  focusedId = null;
994
1165
  }
995
- if (!focusedId) focusTop();
1166
+ const restoredTabs = /* @__PURE__ */ Object.create(null);
1167
+ const rawTabs = data.activeTabs;
1168
+ for (const id of order) {
1169
+ const groupId = windows[id].groupId;
1170
+ if (groupId === null || restoredTabs[groupId]) continue;
1171
+ const wanted = typeof rawTabs === "object" && rawTabs !== null ? rawTabs[groupId] : void 0;
1172
+ const members = membersOf(groupId);
1173
+ restoredTabs[groupId] = typeof wanted === "string" && members.includes(wanted) ? wanted : members[0];
1174
+ }
1175
+ activeTabs = restoredTabs;
1176
+ for (const groupId of Object.keys(restoredTabs)) dissolveIfOrphaned(groupId);
1177
+ if (!focusedId || !isVisibleTab(windows[focusedId])) focusTop();
996
1178
  const modal = topModalId();
997
1179
  if (modal) {
998
1180
  const focusedWin = focusedId ? windows[focusedId] : null;
@@ -1046,6 +1228,10 @@ function createWindowManager(options = {}) {
1046
1228
  workspace: () => workspace,
1047
1229
  setWorkspace: (next) => transact(() => setWorkspace(next)),
1048
1230
  moveToWorkspace: (id, next) => transact(() => moveToWorkspace(id, next)),
1231
+ group: (ids) => transact(() => group(ids)),
1232
+ ungroup: (id) => transact(() => ungroup(id)),
1233
+ activateTab: (id) => transact(() => activateTab(id)),
1234
+ groupMembers,
1049
1235
  batch: (run) => transact(run),
1050
1236
  serialize,
1051
1237
  hydrate: (data) => transact(() => hydrate(data)),
@@ -1235,6 +1421,11 @@ function createDragStarter(ctx) {
1235
1421
  restored: win.stage === "normal",
1236
1422
  moved: false,
1237
1423
  zone: null,
1424
+ groupTarget: null,
1425
+ hoverId: null,
1426
+ hoverTimer: 0,
1427
+ clientX: event.clientX,
1428
+ clientY: event.clientY,
1238
1429
  raf: 0,
1239
1430
  pendingX: 0,
1240
1431
  pendingY: 0,
@@ -1243,6 +1434,20 @@ function createDragStarter(ctx) {
1243
1434
  }
1244
1435
  };
1245
1436
  const el = ctx.windowElement(id);
1437
+ function armGroup(target2) {
1438
+ session.groupTarget = target2;
1439
+ session.zone = null;
1440
+ ctx.hidePreview();
1441
+ ctx.markGroupTarget(target2);
1442
+ }
1443
+ function clearHover() {
1444
+ if (session.hoverTimer !== 0) {
1445
+ view.clearTimeout(session.hoverTimer);
1446
+ session.hoverTimer = 0;
1447
+ }
1448
+ session.groupTarget = null;
1449
+ ctx.markGroupTarget(null);
1450
+ }
1246
1451
  function flush() {
1247
1452
  if (!session.hasPending || ctx.currentDrag() !== session) return;
1248
1453
  session.hasPending = false;
@@ -1277,9 +1482,9 @@ function createDragStarter(ctx) {
1277
1482
  for (const otherId of state.order) {
1278
1483
  if (otherId === id) continue;
1279
1484
  const other = state.windows[otherId];
1280
- if (other && other.stage !== "minimized" && other.workspace === state.workspace) {
1281
- targets.push(other.bounds);
1282
- }
1485
+ if (!other || other.stage === "minimized" || other.workspace !== state.workspace) continue;
1486
+ if (other.groupId !== null && state.groups[other.groupId]?.activeId !== otherId) continue;
1487
+ targets.push(other.bounds);
1283
1488
  }
1284
1489
  const magnet = magnetize(
1285
1490
  { x: nextX, y: nextY, width: current.bounds.width, height: current.bounds.height },
@@ -1290,6 +1495,20 @@ function createDragStarter(ctx) {
1290
1495
  nextY = magnet.y;
1291
1496
  }
1292
1497
  wm.move(id, nextX, nextY);
1498
+ const candidate = ctx.groupTarget(session.clientX, session.clientY, id);
1499
+ if (candidate !== session.hoverId) {
1500
+ session.hoverId = candidate;
1501
+ clearHover();
1502
+ if (candidate) {
1503
+ if (ctx.groupDwell <= 0) armGroup(candidate);
1504
+ else session.hoverTimer = view.setTimeout(() => armGroup(candidate), ctx.groupDwell);
1505
+ }
1506
+ }
1507
+ if (session.groupTarget) {
1508
+ session.zone = null;
1509
+ ctx.hidePreview();
1510
+ return;
1511
+ }
1293
1512
  if (ctx.snapEnabled && current.snappable) {
1294
1513
  const viewport = wm.getState().viewport;
1295
1514
  const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect);
@@ -1322,6 +1541,8 @@ function createDragStarter(ctx) {
1322
1541
  }
1323
1542
  session.pendingX = movePoint.x;
1324
1543
  session.pendingY = movePoint.y;
1544
+ session.clientX = moveEvent.clientX;
1545
+ session.clientY = moveEvent.clientY;
1325
1546
  session.hasPending = true;
1326
1547
  if (session.raf === 0) session.raf = view.requestAnimationFrame(flush);
1327
1548
  }
@@ -1356,6 +1577,8 @@ function createDragStarter(ctx) {
1356
1577
  wm.move(id, session.startBounds.x, session.startBounds.y);
1357
1578
  }
1358
1579
  }
1580
+ } else if (session.moved && session.groupTarget) {
1581
+ if (wm.group([session.groupTarget, id])) wm.activateTab(id);
1359
1582
  } else if (session.moved && session.zone) {
1360
1583
  if (session.zone === "maximize") wm.maximize(id);
1361
1584
  else wm.snap(id, session.zone);
@@ -1375,6 +1598,8 @@ function createDragStarter(ctx) {
1375
1598
  el.style.willChange = "";
1376
1599
  }
1377
1600
  ctx.hidePreview();
1601
+ if (session.hoverTimer !== 0) view.clearTimeout(session.hoverTimer);
1602
+ ctx.markGroupTarget(null);
1378
1603
  if (cancelled) wm.abortInteraction();
1379
1604
  else wm.endInteraction();
1380
1605
  }
@@ -1544,6 +1769,8 @@ function attachDesktop(wm, element, options = {}) {
1544
1769
  const doc = element.ownerDocument;
1545
1770
  const view = windowOf(element);
1546
1771
  const coarsePointer = typeof view.matchMedia === "function" && view.matchMedia("(pointer: coarse)").matches;
1772
+ const groupingEnabled = options.grouping !== false;
1773
+ const groupDwell = (typeof options.grouping === "object" ? options.grouping.dwell : void 0) ?? 420;
1547
1774
  const snapEnabled = options.snap !== false;
1548
1775
  const snapOptions = typeof options.snap === "object" ? options.snap : {};
1549
1776
  const snapPreviewEnabled = snapOptions.preview !== false;
@@ -1567,6 +1794,8 @@ function attachDesktop(wm, element, options = {}) {
1567
1794
  let lastOrder = null;
1568
1795
  let lastFocused = null;
1569
1796
  let drag = null;
1797
+ let groupTargetId = null;
1798
+ let groupTargetEl = null;
1570
1799
  let cachedRect = null;
1571
1800
  let rectUsers = 0;
1572
1801
  let announcer = null;
@@ -1638,6 +1867,24 @@ function attachDesktop(wm, element, options = {}) {
1638
1867
  hitEdge,
1639
1868
  hitCorner,
1640
1869
  magnetThreshold,
1870
+ groupDwell,
1871
+ groupTarget(clientX, clientY, selfId) {
1872
+ if (!groupingEnabled || typeof doc.elementsFromPoint !== "function") return null;
1873
+ for (const node of doc.elementsFromPoint(clientX, clientY)) {
1874
+ const handle = node.closest?.("[data-wm-drag]");
1875
+ const host = handle?.closest("[data-wm-window]");
1876
+ const id = host?.dataset.wmWindow;
1877
+ if (id && id !== selfId && registry.get(id)?.element === host) return id;
1878
+ }
1879
+ return null;
1880
+ },
1881
+ markGroupTarget(id) {
1882
+ if (groupTargetId === id) return;
1883
+ if (groupTargetEl) delete groupTargetEl.dataset.wmTabTarget;
1884
+ groupTargetId = id;
1885
+ groupTargetEl = id ? registry.get(id)?.element ?? null : null;
1886
+ if (groupTargetEl) groupTargetEl.dataset.wmTabTarget = "";
1887
+ },
1641
1888
  currentDrag: () => drag,
1642
1889
  claimDrag(session) {
1643
1890
  drag = session;
@@ -1655,18 +1902,25 @@ function attachDesktop(wm, element, options = {}) {
1655
1902
  observer.observe(element);
1656
1903
  cleanup.push(() => observer.disconnect());
1657
1904
  }
1658
- function syncWindow(attached, win, zIndex, activeWorkspace) {
1905
+ function syncWindow(attached, win, zIndex, activeWorkspace, activeTab) {
1659
1906
  const el = attached.element;
1660
1907
  const firstSync = attached.lastState === null;
1661
1908
  if (firstSync) el.style.transition = "none";
1662
- if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace) {
1909
+ if (attached.lastState !== win || attached.lastWorkspace !== activeWorkspace || attached.lastTab !== activeTab) {
1663
1910
  el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
1664
1911
  el.style.width = `${win.bounds.width}px`;
1665
1912
  el.style.height = `${win.bounds.height}px`;
1666
1913
  el.dataset.wmStage = win.stage;
1667
1914
  el.dataset.wmLayer = win.layer;
1668
1915
  el.dataset.wmWorkspace = String(win.workspace);
1669
- const hide = win.stage === "minimized" || win.workspace !== activeWorkspace;
1916
+ if (win.groupId === null) {
1917
+ delete el.dataset.wmGroup;
1918
+ delete el.dataset.wmTab;
1919
+ } else {
1920
+ el.dataset.wmGroup = win.groupId;
1921
+ el.dataset.wmTab = activeTab ? "active" : "inactive";
1922
+ }
1923
+ const hide = win.stage === "minimized" || win.workspace !== activeWorkspace || !activeTab;
1670
1924
  if (hide && !el.hidden && el.contains(doc.activeElement)) {
1671
1925
  element.focus({ preventScroll: true });
1672
1926
  }
@@ -1684,6 +1938,7 @@ function attachDesktop(wm, element, options = {}) {
1684
1938
  }
1685
1939
  attached.lastState = win;
1686
1940
  attached.lastWorkspace = activeWorkspace;
1941
+ attached.lastTab = activeTab;
1687
1942
  }
1688
1943
  if (attached.lastZ !== zIndex) {
1689
1944
  el.style.zIndex = String(zIndex + 1);
@@ -1701,8 +1956,9 @@ function attachDesktop(wm, element, options = {}) {
1701
1956
  const attached = registry.get(id);
1702
1957
  const win = state.windows[id];
1703
1958
  if (!attached || !win) return;
1704
- if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace) {
1705
- syncWindow(attached, win, index, state.workspace);
1959
+ const activeTab = win.groupId === null || state.groups[win.groupId]?.activeId === win.id;
1960
+ if (orderChanged || attached.lastState !== win || attached.lastWorkspace !== state.workspace || attached.lastTab !== activeTab) {
1961
+ syncWindow(attached, win, index, state.workspace, activeTab);
1706
1962
  }
1707
1963
  });
1708
1964
  if (state.focusedId !== lastFocused) {
@@ -1805,6 +2061,7 @@ function attachDesktop(wm, element, options = {}) {
1805
2061
  lastState: null,
1806
2062
  lastZ: -1,
1807
2063
  lastWorkspace: -1,
2064
+ lastTab: true,
1808
2065
  cleanup: []
1809
2066
  };
1810
2067
  windowElement.dataset.wmWindow = id;
@@ -2028,5 +2285,5 @@ function createDesktopBinder(wm, options = {}) {
2028
2285
  }
2029
2286
 
2030
2287
  export { applyAspect, attachDesktop, boundsEqual, clamp, clampSize, clampToViewport, createAnnouncer, createDesktopBinder, createEmitter, createWindowManager, defaultMessages, detectSnapZone, flipFromTarget, flipToTarget, magnetize, prefersReducedMotion, zoneBounds };
2031
- //# sourceMappingURL=chunk-GGDF2EWV.js.map
2032
- //# sourceMappingURL=chunk-GGDF2EWV.js.map
2288
+ //# sourceMappingURL=chunk-2WQHHYYR.js.map
2289
+ //# sourceMappingURL=chunk-2WQHHYYR.js.map