@tutti-os/workbench-surface 0.0.26 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -43,6 +43,9 @@ function createWorkbenchCommands(store) {
43
43
  applyLayoutPreset(nodeIDs, preset) {
44
44
  store.dispatch({ type: "applyLayoutPreset", nodeIDs, preset });
45
45
  },
46
+ applyVisibleLayoutPreset(preset) {
47
+ store.dispatch({ type: "applyVisibleLayoutPreset", preset });
48
+ },
46
49
  applyActiveSnapTarget(nodeID) {
47
50
  store.dispatch({ type: "applyActiveSnapTarget", nodeID });
48
51
  },
@@ -104,6 +107,7 @@ var defaultWorkbenchSurfaceSize = {
104
107
  // src/core/geometry.ts
105
108
  var WORKBENCH_MIN_VISIBLE_PX = 40;
106
109
  var WORKBENCH_LAYOUT_PRESET_GAP_PX = 12;
110
+ var WORKBENCH_EDGE_SNAP_THRESHOLD_PX = 24;
107
111
  function normalizeWorkbenchLayoutConstraints(constraints = {}) {
108
112
  return {
109
113
  minWidth: constraints.minWidth ?? defaultWorkbenchLayoutConstraints.minWidth,
@@ -241,18 +245,53 @@ function getWorkbenchSnapRect(snapTarget, surfaceSize, constraints = defaultWork
241
245
  sizeConstraints
242
246
  );
243
247
  const full = getWorkbenchSafeLayoutRect(surfaceSize, normalized);
248
+ const halfWidth = Math.round(full.width / 2);
249
+ const halfHeight = Math.round(full.height / 2);
244
250
  const clampSnapRect = (rect) => clampWorkbenchRect(rect, surfaceSize, normalized);
245
- if (snapTarget === "left") {
246
- return clampSnapRect({ ...full, width: Math.round(full.width / 2) });
247
- }
248
- if (snapTarget === "right") {
249
- const width = Math.round(full.width / 2);
250
- return clampSnapRect({ ...full, x: full.x + full.width - width, width });
251
- }
252
- if (snapTarget === "top") {
253
- return clampSnapRect(full);
251
+ switch (snapTarget) {
252
+ case "left":
253
+ return clampSnapRect({ ...full, width: halfWidth });
254
+ case "right":
255
+ return clampSnapRect({
256
+ ...full,
257
+ x: full.x + full.width - halfWidth,
258
+ width: halfWidth
259
+ });
260
+ case "top":
261
+ return clampSnapRect(full);
262
+ case "bottom":
263
+ return clampSnapRect({
264
+ ...full,
265
+ y: full.y + full.height - halfHeight,
266
+ height: halfHeight
267
+ });
268
+ case "top-left":
269
+ return clampSnapRect({ ...full, width: halfWidth, height: halfHeight });
270
+ case "top-right":
271
+ return clampSnapRect({
272
+ ...full,
273
+ x: full.x + full.width - halfWidth,
274
+ width: halfWidth,
275
+ height: halfHeight
276
+ });
277
+ case "bottom-left":
278
+ return clampSnapRect({
279
+ ...full,
280
+ y: full.y + full.height - halfHeight,
281
+ width: halfWidth,
282
+ height: halfHeight
283
+ });
284
+ case "bottom-right":
285
+ return clampSnapRect({
286
+ ...full,
287
+ x: full.x + full.width - halfWidth,
288
+ y: full.y + full.height - halfHeight,
289
+ width: halfWidth,
290
+ height: halfHeight
291
+ });
292
+ case null:
293
+ return null;
254
294
  }
255
- return null;
256
295
  }
257
296
  function getWorkbenchQuickLayoutRect(target, surfaceSize, constraints = defaultWorkbenchLayoutConstraints, sizeConstraints) {
258
297
  const normalized = resolveWorkbenchRectConstraints(
@@ -260,6 +299,8 @@ function getWorkbenchQuickLayoutRect(target, surfaceSize, constraints = defaultW
260
299
  sizeConstraints
261
300
  );
262
301
  const full = getWorkbenchSafeLayoutRect(surfaceSize, normalized);
302
+ const halfWidth = Math.round(full.width / 2);
303
+ const halfHeight = Math.round(full.height / 2);
263
304
  const clampQuickLayoutRect = (rect) => clampWorkbenchRect(rect, surfaceSize, normalized);
264
305
  switch (target) {
265
306
  case "left":
@@ -281,13 +322,50 @@ function getWorkbenchQuickLayoutRect(target, surfaceSize, constraints = defaultW
281
322
  height: Math.round(full.height / 2)
282
323
  });
283
324
  case "bottom": {
284
- const height = Math.round(full.height / 2);
285
325
  return clampQuickLayoutRect({
286
326
  ...full,
287
- y: full.y + full.height - height,
327
+ y: full.y + full.height - halfHeight,
328
+ height: halfHeight
329
+ });
330
+ }
331
+ case "center": {
332
+ const width = Math.round(full.width * 0.72);
333
+ const height = Math.round(full.height * 0.72);
334
+ return clampQuickLayoutRect({
335
+ x: full.x + Math.round((full.width - width) / 2),
336
+ y: full.y + Math.round((full.height - height) / 2),
337
+ width,
288
338
  height
289
339
  });
290
340
  }
341
+ case "top-left":
342
+ return clampQuickLayoutRect({
343
+ ...full,
344
+ width: halfWidth,
345
+ height: halfHeight
346
+ });
347
+ case "top-right":
348
+ return clampQuickLayoutRect({
349
+ ...full,
350
+ x: full.x + full.width - halfWidth,
351
+ width: halfWidth,
352
+ height: halfHeight
353
+ });
354
+ case "bottom-left":
355
+ return clampQuickLayoutRect({
356
+ ...full,
357
+ y: full.y + full.height - halfHeight,
358
+ width: halfWidth,
359
+ height: halfHeight
360
+ });
361
+ case "bottom-right":
362
+ return clampQuickLayoutRect({
363
+ ...full,
364
+ x: full.x + full.width - halfWidth,
365
+ y: full.y + full.height - halfHeight,
366
+ width: halfWidth,
367
+ height: halfHeight
368
+ });
291
369
  }
292
370
  }
293
371
  function resolveWorkbenchRectConstraints(constraints = defaultWorkbenchLayoutConstraints, sizeConstraints) {
@@ -340,10 +418,34 @@ function getWorkbenchSafeLayoutRect(surfaceSize, constraints = defaultWorkbenchL
340
418
  }
341
419
  function inferWorkbenchSnapTarget(dragPoint, surfaceSize, threshold = 0, constraints = defaultWorkbenchLayoutConstraints) {
342
420
  const frame = getWorkbenchLayoutFrame(surfaceSize, constraints);
343
- const topSnapBoundary = frame.y - threshold;
344
- if (dragPoint.y < topSnapBoundary || topSnapBoundary === 0 && dragPoint.y <= 0) {
421
+ const crossesLeft = threshold > 0 ? dragPoint.x <= frame.x + threshold : dragPoint.x < frame.x || frame.x === 0 && dragPoint.x <= 0;
422
+ const crossesRight = threshold > 0 ? dragPoint.x >= frame.x + frame.width - threshold : dragPoint.x > frame.x + frame.width || frame.x + frame.width === surfaceSize.width && dragPoint.x >= surfaceSize.width;
423
+ const crossesTop = threshold > 0 ? dragPoint.y <= frame.y + threshold : dragPoint.y < frame.y || frame.y === 0 && dragPoint.y <= 0;
424
+ const crossesBottom = threshold > 0 ? dragPoint.y >= frame.y + frame.height - threshold : dragPoint.y > frame.y + frame.height || frame.y + frame.height === surfaceSize.height && dragPoint.y >= surfaceSize.height;
425
+ if (crossesTop && crossesLeft) {
426
+ return "top-left";
427
+ }
428
+ if (crossesTop && crossesRight) {
429
+ return "top-right";
430
+ }
431
+ if (crossesBottom && crossesLeft) {
432
+ return "bottom-left";
433
+ }
434
+ if (crossesBottom && crossesRight) {
435
+ return "bottom-right";
436
+ }
437
+ if (crossesTop) {
345
438
  return "top";
346
439
  }
440
+ if (crossesBottom) {
441
+ return "bottom";
442
+ }
443
+ if (crossesLeft) {
444
+ return "left";
445
+ }
446
+ if (crossesRight) {
447
+ return "right";
448
+ }
347
449
  return null;
348
450
  }
349
451
  function rectsEqual(left, right) {
@@ -618,49 +720,14 @@ function reduceWorkbenchState(state, action) {
618
720
  minimizedAtUnixMs: null
619
721
  };
620
722
  });
621
- case "applyLayoutPreset": {
622
- const nodeIDs = uniqueKnownNodeIDs(state.nodes, action.nodeIDs);
623
- if (nodeIDs.length === 0) {
624
- return state;
625
- }
626
- const frames = getWorkbenchLayoutPresetFrames(
627
- nodeIDs.length,
628
- action.preset,
629
- state.surfaceSize,
630
- state.layoutConstraints
631
- );
632
- if (!frames) {
633
- return state;
634
- }
635
- const frameByNodeID = new Map(
636
- nodeIDs.map((nodeID, index) => [nodeID, frames[index]])
723
+ case "applyLayoutPreset":
724
+ return applyLayoutPresetToNodes(state, action.nodeIDs, action.preset);
725
+ case "applyVisibleLayoutPreset":
726
+ return applyLayoutPresetToNodes(
727
+ state,
728
+ state.nodes.filter((node) => !node.isMinimized).map((node) => node.id),
729
+ action.preset
637
730
  );
638
- const nodes = state.nodes.map((node) => {
639
- const frame = frameByNodeID.get(node.id);
640
- if (!frame) {
641
- return node;
642
- }
643
- const nextFrame = clampWorkbenchRect(
644
- frame,
645
- state.surfaceSize,
646
- state.layoutConstraints,
647
- node.sizeConstraints
648
- );
649
- return {
650
- ...node,
651
- frame: nextFrame,
652
- displayMode: "floating",
653
- restoreFrame: null,
654
- isMinimized: false,
655
- minimizedAtUnixMs: null
656
- };
657
- });
658
- let nodeStack = state.nodeStack;
659
- for (const nodeID of nodeIDs) {
660
- nodeStack = focusWorkbenchStack(nodeStack, nodeID);
661
- }
662
- return { ...state, nodes, nodeStack };
663
- }
664
731
  case "applyActiveSnapTarget":
665
732
  case "applySnapTarget":
666
733
  return updateNode(state, action.nodeID, (node) => {
@@ -864,6 +931,49 @@ function updateNode(state, nodeID, update) {
864
931
  nodeStack: focusWorkbenchStack(state.nodeStack, nodeID)
865
932
  };
866
933
  }
934
+ function applyLayoutPresetToNodes(state, inputNodeIDs, preset) {
935
+ const nodeIDs = uniqueKnownNodeIDs(state.nodes, inputNodeIDs);
936
+ if (nodeIDs.length === 0) {
937
+ return state;
938
+ }
939
+ const frames = getWorkbenchLayoutPresetFrames(
940
+ nodeIDs.length,
941
+ preset,
942
+ state.surfaceSize,
943
+ state.layoutConstraints
944
+ );
945
+ if (!frames) {
946
+ return state;
947
+ }
948
+ const frameByNodeID = new Map(
949
+ nodeIDs.map((nodeID, index) => [nodeID, frames[index]])
950
+ );
951
+ const nodes = state.nodes.map((node) => {
952
+ const frame = frameByNodeID.get(node.id);
953
+ if (!frame) {
954
+ return node;
955
+ }
956
+ const nextFrame = clampWorkbenchRect(
957
+ frame,
958
+ state.surfaceSize,
959
+ state.layoutConstraints,
960
+ node.sizeConstraints
961
+ );
962
+ return {
963
+ ...node,
964
+ frame: nextFrame,
965
+ displayMode: "floating",
966
+ restoreFrame: null,
967
+ isMinimized: false,
968
+ minimizedAtUnixMs: null
969
+ };
970
+ });
971
+ let nodeStack = state.nodeStack;
972
+ for (const nodeID of nodeIDs) {
973
+ nodeStack = focusWorkbenchStack(nodeStack, nodeID);
974
+ }
975
+ return { ...state, nodes, nodeStack };
976
+ }
867
977
  function uniqueKnownNodeIDs(nodes, nodeIDs) {
868
978
  const knownNodeIDs = new Set(nodes.map((node) => node.id));
869
979
  const orderedNodeIDs = [];
@@ -1015,6 +1125,11 @@ function summarizeWorkbenchAction(action) {
1015
1125
  nodeIDs: action.nodeIDs,
1016
1126
  preset: action.preset
1017
1127
  };
1128
+ case "applyVisibleLayoutPreset":
1129
+ return {
1130
+ type: action.type,
1131
+ preset: action.preset
1132
+ };
1018
1133
  case "applySnapTarget":
1019
1134
  return {
1020
1135
  type: action.type,
@@ -1629,6 +1744,16 @@ function selectFocusedWorkbenchNode(state) {
1629
1744
  const focusedID = state.nodeStack.at(-1);
1630
1745
  return state.nodes.find((node) => node.id === focusedID) ?? null;
1631
1746
  }
1747
+ function selectFocusedVisibleWorkbenchNode(state) {
1748
+ const nodeByID = new Map(state.nodes.map((node) => [node.id, node]));
1749
+ for (let index = state.nodeStack.length - 1; index >= 0; index -= 1) {
1750
+ const node = nodeByID.get(state.nodeStack[index] ?? "");
1751
+ if (node && !node.isMinimized) {
1752
+ return node;
1753
+ }
1754
+ }
1755
+ return state.nodes.find((node) => !node.isMinimized) ?? null;
1756
+ }
1632
1757
  function selectFullscreenNodeToExitBeforeDockLaunch(state, targetNodeID) {
1633
1758
  const nodeByID = new Map(state.nodes.map((node) => [node.id, node]));
1634
1759
  const isVisibleFullscreenNode = (node) => {
@@ -1854,27 +1979,46 @@ import { useCallback as useCallback4 } from "react";
1854
1979
 
1855
1980
  // src/react/hooks/useWorkbenchSnap.ts
1856
1981
  import { useCallback as useCallback3 } from "react";
1982
+
1983
+ // src/react/hooks/workbenchSnapTarget.ts
1984
+ function resolveWorkbenchActiveSnapTarget(target, options = {}) {
1985
+ if (options.edgeSnapEnabled) {
1986
+ return target;
1987
+ }
1988
+ switch (target) {
1989
+ case "top":
1990
+ case "top-left":
1991
+ case "top-right":
1992
+ return "top";
1993
+ default:
1994
+ return null;
1995
+ }
1996
+ }
1997
+
1998
+ // src/react/hooks/useWorkbenchSnap.ts
1857
1999
  function useWorkbenchSnap() {
1858
2000
  const controller = useWorkbenchController();
1859
2001
  return useCallback3(
1860
- (pointerClientY) => {
2002
+ (pointerClientPoint, options = {}) => {
1861
2003
  const target = inferWorkbenchSnapTarget(
1862
- { y: pointerClientY },
2004
+ pointerClientPoint,
1863
2005
  controller.getSnapshot().surfaceSize,
1864
- void 0,
2006
+ options.edgeSnapEnabled ? WORKBENCH_EDGE_SNAP_THRESHOLD_PX : 0,
1865
2007
  controller.getSnapshot().layoutConstraints
1866
2008
  );
1867
- controller.commands.setActiveSnapTarget(target);
1868
- return target;
2009
+ const activeTarget = resolveWorkbenchActiveSnapTarget(target, options);
2010
+ controller.commands.setActiveSnapTarget(activeTarget);
2011
+ return activeTarget;
1869
2012
  },
1870
2013
  [controller]
1871
2014
  );
1872
2015
  }
1873
2016
 
1874
2017
  // src/react/hooks/useWorkbenchDrag.ts
1875
- function useWorkbenchDrag(node) {
2018
+ function useWorkbenchDrag(node, options = {}) {
1876
2019
  const controller = useWorkbenchController();
1877
2020
  const updateSnap = useWorkbenchSnap();
2021
+ const edgeSnapEnabled = options.edgeSnapEnabled === true;
1878
2022
  return useCallback4(
1879
2023
  (event) => {
1880
2024
  if (event.button !== 0) {
@@ -1892,7 +2036,10 @@ function useWorkbenchDrag(node) {
1892
2036
  x: initialFrame.x + moveEvent.clientX - origin.x,
1893
2037
  y: initialFrame.y + moveEvent.clientY - origin.y
1894
2038
  };
1895
- updateSnap(moveEvent.clientY);
2039
+ updateSnap(
2040
+ { x: moveEvent.clientX, y: moveEvent.clientY },
2041
+ { edgeSnapEnabled }
2042
+ );
1896
2043
  controller.commands.dragNode(node.id, nextFrame);
1897
2044
  };
1898
2045
  const clearListeners = () => {
@@ -1903,7 +2050,10 @@ function useWorkbenchDrag(node) {
1903
2050
  window.removeEventListener("pointercancel", cancelDrag);
1904
2051
  };
1905
2052
  const finishDrag = (upEvent) => {
1906
- if (updateSnap(upEvent.clientY) !== null) {
2053
+ if (updateSnap(
2054
+ { x: upEvent.clientX, y: upEvent.clientY },
2055
+ { edgeSnapEnabled }
2056
+ ) !== null) {
1907
2057
  controller.commands.applyActiveSnapTarget(node.id);
1908
2058
  }
1909
2059
  clearListeners();
@@ -1915,7 +2065,7 @@ function useWorkbenchDrag(node) {
1915
2065
  window.addEventListener("pointerup", finishDrag);
1916
2066
  window.addEventListener("pointercancel", cancelDrag);
1917
2067
  },
1918
- [controller, node.frame, node.id, updateSnap]
2068
+ [controller, edgeSnapEnabled, node.frame, node.id, updateSnap]
1919
2069
  );
1920
2070
  }
1921
2071
 
@@ -2047,6 +2197,7 @@ function resolveWorkbenchNodeTypeId(data) {
2047
2197
  }
2048
2198
  function WorkbenchWindowFrame({
2049
2199
  children,
2200
+ edgeSnapEnabled = false,
2050
2201
  fullscreenHeaderMode,
2051
2202
  genie,
2052
2203
  hiddenMounted = false,
@@ -2071,7 +2222,7 @@ function WorkbenchWindowFrame({
2071
2222
  const isResizing = useWorkbenchSelector(
2072
2223
  (state) => state.activeResizeNodeId === node.id
2073
2224
  );
2074
- const onDragStart = useWorkbenchDrag(node);
2225
+ const onDragStart = useWorkbenchDrag(node, { edgeSnapEnabled });
2075
2226
  const onHeaderDoubleClick = () => {
2076
2227
  if (!interactive) {
2077
2228
  return;
@@ -2272,6 +2423,7 @@ function stringArraysEqual(left, right) {
2272
2423
  import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
2273
2424
  function WorkbenchNodeLayer({
2274
2425
  genie,
2426
+ edgeSnapEnabled = false,
2275
2427
  interactive = true,
2276
2428
  presentation,
2277
2429
  renderNode,
@@ -2319,6 +2471,7 @@ function WorkbenchNodeLayer({
2319
2471
  {
2320
2472
  fullscreenHeaderMode: resolveFullscreenHeaderMode,
2321
2473
  genie,
2474
+ edgeSnapEnabled,
2322
2475
  interactive,
2323
2476
  nodeID,
2324
2477
  presentation,
@@ -2337,6 +2490,7 @@ function WorkbenchNodeLayer({
2337
2490
  function WorkbenchNodeLayerItem({
2338
2491
  fullscreenHeaderMode,
2339
2492
  genie,
2493
+ edgeSnapEnabled,
2340
2494
  interactive,
2341
2495
  nodeID,
2342
2496
  presentation,
@@ -2362,6 +2516,7 @@ function WorkbenchNodeLayerItem({
2362
2516
  return /* @__PURE__ */ jsx6(
2363
2517
  WorkbenchWindowFrame,
2364
2518
  {
2519
+ edgeSnapEnabled,
2365
2520
  hiddenMounted: node.isMinimized,
2366
2521
  interactive,
2367
2522
  presentation,
@@ -2398,26 +2553,111 @@ var MemoizedWorkbenchNodeLayerItem = memo(
2398
2553
 
2399
2554
  // src/react/hooks/useWorkbenchShortcuts.ts
2400
2555
  import { useEffect as useEffect2 } from "react";
2401
- function useWorkbenchShortcuts(enabled = true) {
2556
+
2557
+ // src/react/hooks/workbenchShortcutIntent.ts
2558
+ function resolveWorkbenchShortcutIntent(event, options = {}) {
2559
+ if (event.defaultPrevented || isEditableShortcutTarget(event.target ?? null)) {
2560
+ return null;
2561
+ }
2562
+ if (event.key === "Escape" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) {
2563
+ return { type: "exitFullscreen" };
2564
+ }
2565
+ if (!matchesWindowManagementShortcutPreset(
2566
+ event,
2567
+ options.windowManagementShortcutPreset ?? null
2568
+ )) {
2569
+ return null;
2570
+ }
2571
+ switch (event.key) {
2572
+ case "ArrowLeft":
2573
+ return { type: "applyFocusedSnapTarget", snapTarget: "left" };
2574
+ case "ArrowRight":
2575
+ return { type: "applyFocusedSnapTarget", snapTarget: "right" };
2576
+ case "ArrowUp":
2577
+ return { type: "applyFocusedQuickLayout", target: "top" };
2578
+ case "ArrowDown":
2579
+ return { type: "applyFocusedQuickLayout", target: "bottom" };
2580
+ case "0":
2581
+ return {
2582
+ type: "applyVisibleLayoutPreset",
2583
+ preset: { kind: "balanced" }
2584
+ };
2585
+ default:
2586
+ return null;
2587
+ }
2588
+ }
2589
+ function matchesWindowManagementShortcutPreset(event, preset) {
2590
+ if (preset === null) {
2591
+ return false;
2592
+ }
2593
+ if (!event.metaKey || event.ctrlKey || event.altKey) {
2594
+ return false;
2595
+ }
2596
+ switch (preset) {
2597
+ case "commandArrows":
2598
+ return !event.shiftKey;
2599
+ case "commandShiftArrows":
2600
+ return event.shiftKey === true;
2601
+ }
2602
+ }
2603
+ function isEditableShortcutTarget(target) {
2604
+ const candidate = target;
2605
+ const tagName = candidate?.tagName?.toUpperCase();
2606
+ return candidate?.isContentEditable === true || tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT";
2607
+ }
2608
+
2609
+ // src/react/hooks/useWorkbenchShortcuts.ts
2610
+ function useWorkbenchShortcuts(options = {}) {
2402
2611
  const controller = useWorkbenchController();
2612
+ const enabled = options.enabled ?? true;
2613
+ const windowManagementShortcutPreset = options.windowManagementShortcutPreset ?? null;
2403
2614
  useEffect2(() => {
2404
2615
  if (!enabled) {
2405
2616
  return void 0;
2406
2617
  }
2407
2618
  const onKeyDown = (event) => {
2408
- if (event.key !== "Escape") {
2619
+ const intent = resolveWorkbenchShortcutIntent(event, {
2620
+ windowManagementShortcutPreset
2621
+ });
2622
+ if (!intent) {
2409
2623
  return;
2410
2624
  }
2411
- const focusedNode = selectFocusedWorkbenchNode(controller.getSnapshot());
2412
- if (focusedNode?.displayMode === "fullscreen") {
2413
- controller.commands.exitFullscreen(focusedNode.id);
2625
+ let handled = false;
2626
+ const focusedNode = selectFocusedVisibleWorkbenchNode(
2627
+ controller.getSnapshot()
2628
+ );
2629
+ if (intent.type === "exitFullscreen") {
2630
+ if (focusedNode?.displayMode === "fullscreen") {
2631
+ controller.commands.exitFullscreen(focusedNode.id);
2632
+ handled = true;
2633
+ }
2634
+ } else if (intent.type === "applyFocusedSnapTarget") {
2635
+ if (focusedNode) {
2636
+ controller.commands.applySnapTarget(
2637
+ focusedNode.id,
2638
+ intent.snapTarget
2639
+ );
2640
+ handled = true;
2641
+ }
2642
+ } else if (intent.type === "applyFocusedQuickLayout") {
2643
+ if (focusedNode) {
2644
+ controller.commands.applyQuickLayout(focusedNode.id, intent.target);
2645
+ handled = true;
2646
+ }
2647
+ } else {
2648
+ controller.commands.applyVisibleLayoutPreset(intent.preset);
2649
+ handled = true;
2650
+ }
2651
+ if (handled) {
2652
+ event.preventDefault();
2653
+ event.stopPropagation();
2414
2654
  }
2415
2655
  };
2416
2656
  window.addEventListener("keydown", onKeyDown);
2417
2657
  return () => {
2418
2658
  window.removeEventListener("keydown", onKeyDown);
2419
2659
  };
2420
- }, [controller, enabled]);
2660
+ }, [controller, enabled, windowManagementShortcutPreset]);
2421
2661
  }
2422
2662
 
2423
2663
  // src/react/hooks/useWorkbenchSurfaceSize.ts
@@ -4346,6 +4586,7 @@ function WorkbenchSurface({
4346
4586
  shortcutsEnabled,
4347
4587
  shouldCaptureNodePreviewImage,
4348
4588
  wallpaper,
4589
+ windowManagement,
4349
4590
  windowChromeMode,
4350
4591
  windowChromeI18n
4351
4592
  }) {
@@ -4378,6 +4619,7 @@ function WorkbenchSurface({
4378
4619
  shortcutsEnabled,
4379
4620
  shouldCaptureNodePreviewImage,
4380
4621
  wallpaper,
4622
+ windowManagement,
4381
4623
  windowChromeMode,
4382
4624
  windowChromeI18n
4383
4625
  }
@@ -4410,6 +4652,7 @@ function WorkbenchSurfaceInner({
4410
4652
  shortcutsEnabled,
4411
4653
  shouldCaptureNodePreviewImage,
4412
4654
  wallpaper,
4655
+ windowManagement,
4413
4656
  windowChromeMode,
4414
4657
  windowChromeI18n
4415
4658
  }) {
@@ -4432,7 +4675,10 @@ function WorkbenchSurfaceInner({
4432
4675
  resolveDockPreviewCacheKey: resolveDockPreviewCacheKey2,
4433
4676
  shouldCaptureNodePreviewImage
4434
4677
  });
4435
- useWorkbenchShortcuts((shortcutsEnabled ?? true) && interactive);
4678
+ useWorkbenchShortcuts({
4679
+ enabled: (shortcutsEnabled ?? true) && interactive,
4680
+ windowManagementShortcutPreset: windowManagement?.shortcutPreset ?? null
4681
+ });
4436
4682
  useEffect4(() => {
4437
4683
  if (!layoutConstraints) {
4438
4684
  return;
@@ -4472,6 +4718,7 @@ function WorkbenchSurfaceInner({
4472
4718
  interactive,
4473
4719
  presentation,
4474
4720
  renderNode,
4721
+ edgeSnapEnabled: windowManagement?.edgeSnapEnabled === true,
4475
4722
  renderWindowActions,
4476
4723
  renderWindowHeader,
4477
4724
  shouldKeepMinimizedNodeMounted,
@@ -12185,6 +12432,7 @@ function WorkbenchHost({
12185
12432
  snapshotRepository,
12186
12433
  shortcutsEnabled,
12187
12434
  wallpaper,
12435
+ windowManagement,
12188
12436
  workspaceId
12189
12437
  }) {
12190
12438
  const hostRuntimeConfig = useMemo7(
@@ -12309,6 +12557,7 @@ function WorkbenchHost({
12309
12557
  shortcutsEnabled,
12310
12558
  shouldCaptureNodePreviewImage: surfaceRenderers.shouldCaptureNodePreviewImage,
12311
12559
  wallpaper,
12560
+ windowManagement,
12312
12561
  windowChromeI18n,
12313
12562
  windowChromeMode: surfaceRenderers.windowChromeMode
12314
12563
  }