@surdeddd/wmkit 0.1.0 → 0.2.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 (54) hide show
  1. package/README.md +14 -2
  2. package/README.ru.md +8 -2
  3. package/dist/{controller-D5zIriLg.d.ts → binder-BY-saDUB.d.cts} +9 -3
  4. package/dist/{controller-B0bogK9N.d.cts → binder-D9UEE1SM.d.ts} +9 -3
  5. package/dist/{chunk-7HHDQEBI.js → chunk-2TVOEDRG.js} +368 -247
  6. package/dist/chunk-2TVOEDRG.js.map +1 -0
  7. package/dist/{chunk-KWLI7JIY.cjs → chunk-KQCVZ2W7.cjs} +368 -247
  8. package/dist/chunk-KQCVZ2W7.cjs.map +1 -0
  9. package/dist/index.cjs +15 -15
  10. package/dist/index.d.cts +7 -4
  11. package/dist/index.d.ts +7 -4
  12. package/dist/index.js +1 -1
  13. package/dist/persist.cjs +1 -1
  14. package/dist/persist.cjs.map +1 -1
  15. package/dist/persist.d.cts +1 -1
  16. package/dist/persist.d.ts +1 -1
  17. package/dist/persist.js +1 -1
  18. package/dist/persist.js.map +1 -1
  19. package/dist/popout.cjs +1 -1
  20. package/dist/popout.cjs.map +1 -1
  21. package/dist/popout.d.cts +1 -1
  22. package/dist/popout.d.ts +1 -1
  23. package/dist/popout.js +1 -1
  24. package/dist/popout.js.map +1 -1
  25. package/dist/react.cjs +3 -3
  26. package/dist/react.cjs.map +1 -1
  27. package/dist/react.d.cts +2 -2
  28. package/dist/react.d.ts +2 -2
  29. package/dist/react.js +1 -1
  30. package/dist/react.js.map +1 -1
  31. package/dist/solid.cjs +3 -3
  32. package/dist/solid.cjs.map +1 -1
  33. package/dist/solid.d.cts +2 -2
  34. package/dist/solid.d.ts +2 -2
  35. package/dist/solid.js +1 -1
  36. package/dist/solid.js.map +1 -1
  37. package/dist/svelte.cjs +4 -4
  38. package/dist/svelte.cjs.map +1 -1
  39. package/dist/svelte.d.cts +2 -2
  40. package/dist/svelte.d.ts +2 -2
  41. package/dist/svelte.js +2 -2
  42. package/dist/svelte.js.map +1 -1
  43. package/dist/themes/glass.css +0 -1
  44. package/dist/{types-CtFzL_oA.d.cts → types-Bkrq2djr.d.cts} +9 -2
  45. package/dist/{types-CtFzL_oA.d.ts → types-Bkrq2djr.d.ts} +9 -2
  46. package/dist/vue.cjs +3 -3
  47. package/dist/vue.cjs.map +1 -1
  48. package/dist/vue.d.cts +2 -2
  49. package/dist/vue.d.ts +2 -2
  50. package/dist/vue.js +1 -1
  51. package/dist/vue.js.map +1 -1
  52. package/package.json +5 -5
  53. package/dist/chunk-7HHDQEBI.js.map +0 -1
  54. package/dist/chunk-KWLI7JIY.cjs.map +0 -1
@@ -141,12 +141,7 @@ function createWindowManager(options = {}) {
141
141
  }
142
142
  function commit() {
143
143
  snapshot = null;
144
- if (batchDepth > 0) {
145
- batchDirty = true;
146
- return;
147
- }
148
- flushEvents();
149
- emitter.emit("change", { state: getState() });
144
+ batchDirty = true;
150
145
  }
151
146
  function flushEvents() {
152
147
  while (pendingEvents.length > 0) {
@@ -155,7 +150,6 @@ function createWindowManager(options = {}) {
155
150
  }
156
151
  function queueEvent(emit) {
157
152
  pendingEvents.push(emit);
158
- if (batchDepth === 0) flushEvents();
159
153
  }
160
154
  function setWindow(next) {
161
155
  windows = { ...windows, [next.id]: next };
@@ -219,7 +213,12 @@ function createWindowManager(options = {}) {
219
213
  return { x: init.x ?? base.x, y: init.y ?? base.y };
220
214
  }
221
215
  function open(init = {}) {
222
- const id = init.id ?? `${idPrefix}-${++idCounter}`;
216
+ let id = init.id;
217
+ if (id === void 0) {
218
+ do {
219
+ id = `${idPrefix}-${++idCounter}`;
220
+ } while (windows[id]);
221
+ }
223
222
  if (windows[id]) throw new Error(`wmkit: window id "${id}" already exists`);
224
223
  const minSize = { width: init.minWidth ?? 160, height: init.minHeight ?? 100 };
225
224
  const maxSize = init.maxWidth !== void 0 || init.maxHeight !== void 0 ? {
@@ -297,9 +296,7 @@ function createWindowManager(options = {}) {
297
296
  return true;
298
297
  }
299
298
  function closeAll() {
300
- batch(() => {
301
- for (const id of [...order]) close(id);
302
- });
299
+ for (const id of [...order]) close(id);
303
300
  }
304
301
  function focus(id) {
305
302
  const win = windows[id];
@@ -506,6 +503,11 @@ function createWindowManager(options = {}) {
506
503
  if (patch.layer && patch.layer !== win.layer) {
507
504
  order = sortByLayer(order);
508
505
  queueEvent(() => emitter.emit("order", { order }));
506
+ if (patch.layer === "modal" && topModalId() === id && focusedId !== id) {
507
+ const previous = focusedId;
508
+ focusedId = id;
509
+ emitFocus(finalWin, previous);
510
+ }
509
511
  }
510
512
  queueEvent(() => emitter.emit("update", { window: finalWin }));
511
513
  if (resized) queueEvent(() => emitter.emit("resize", { window: finalWin }));
@@ -515,7 +517,7 @@ function createWindowManager(options = {}) {
515
517
  function setViewport(next) {
516
518
  if (next.width === viewport.width && next.height === viewport.height) return;
517
519
  viewport = { ...next };
518
- batch(() => {
520
+ transact(() => {
519
521
  for (const id of order) {
520
522
  const win = windows[id];
521
523
  if (win.stage === "maximized") {
@@ -542,10 +544,10 @@ function createWindowManager(options = {}) {
542
544
  function minimized() {
543
545
  return Object.values(windows).filter((win) => win.stage === "minimized").sort((a, b) => a.openedSeq - b.openedSeq);
544
546
  }
545
- function batch(run) {
547
+ function transact(run) {
546
548
  batchDepth += 1;
547
549
  try {
548
- run();
550
+ return run();
549
551
  } finally {
550
552
  batchDepth -= 1;
551
553
  if (batchDepth === 0 && batchDirty) {
@@ -564,7 +566,10 @@ function createWindowManager(options = {}) {
564
566
  bounds: { ...win.bounds },
565
567
  restoreBounds: win.restoreBounds ? { ...win.restoreBounds } : null,
566
568
  minSize: { ...win.minSize },
567
- maxSize: win.maxSize ? { ...win.maxSize } : null,
569
+ maxSize: win.maxSize ? {
570
+ width: Number.isFinite(win.maxSize.width) ? win.maxSize.width : null,
571
+ height: Number.isFinite(win.maxSize.height) ? win.maxSize.height : null
572
+ } : null,
568
573
  meta: { ...win.meta }
569
574
  })),
570
575
  order: [...order],
@@ -581,6 +586,19 @@ function createWindowManager(options = {}) {
581
586
  const size = value;
582
587
  return typeof size.width === "number" && typeof size.height === "number";
583
588
  }
589
+ function isSizeAxis(value) {
590
+ return value === null || typeof value === "number";
591
+ }
592
+ function readMaxSize(value) {
593
+ if (typeof value !== "object" || value === null) return null;
594
+ const size = value;
595
+ if (!isSizeAxis(size.width) || !isSizeAxis(size.height)) return null;
596
+ if (size.width === null && size.height === null) return null;
597
+ return {
598
+ width: size.width ?? Number.POSITIVE_INFINITY,
599
+ height: size.height ?? Number.POSITIVE_INFINITY
600
+ };
601
+ }
584
602
  function isSerializedWindow(value) {
585
603
  if (typeof value !== "object" || value === null) return false;
586
604
  const win = value;
@@ -604,7 +622,7 @@ function createWindowManager(options = {}) {
604
622
  snapZone: ZONES.includes(raw.snapZone) ? raw.snapZone : null,
605
623
  layer: raw.layer,
606
624
  minSize: isSize(raw.minSize) ? { ...raw.minSize } : { width: 160, height: 100 },
607
- maxSize: isSize(raw.maxSize) ? { ...raw.maxSize } : null,
625
+ maxSize: readMaxSize(raw.maxSize),
608
626
  openedSeq: typeof raw.openedSeq === "number" ? raw.openedSeq : ++maxSeq,
609
627
  draggable: raw.draggable !== false,
610
628
  resizable: raw.resizable !== false,
@@ -636,6 +654,11 @@ function createWindowManager(options = {}) {
636
654
  focusedId = data.focusedId && windows[data.focusedId] ? data.focusedId : null;
637
655
  if (focusedId && windows[focusedId]?.stage === "minimized") focusedId = null;
638
656
  if (!focusedId) focusTop();
657
+ const modal = topModalId();
658
+ if (modal) {
659
+ const focusedWin = focusedId ? windows[focusedId] : null;
660
+ if (focusedWin?.layer !== "modal") focusedId = modal;
661
+ }
639
662
  commit();
640
663
  return true;
641
664
  }
@@ -643,29 +666,29 @@ function createWindowManager(options = {}) {
643
666
  emitter.clear();
644
667
  }
645
668
  return {
646
- open,
647
- close,
648
- closeAll,
649
- focus,
650
- blur,
651
- cycleFocus,
652
- minimize,
653
- maximize,
654
- toggleMaximize,
655
- restore,
656
- restoreTo,
657
- snap,
658
- move,
659
- moveBy,
660
- resize,
661
- update,
669
+ open: (init) => transact(() => open(init)),
670
+ close: (id) => transact(() => close(id)),
671
+ closeAll: () => transact(closeAll),
672
+ focus: (id) => transact(() => focus(id)),
673
+ blur: () => transact(blur),
674
+ cycleFocus: (direction) => transact(() => cycleFocus(direction)),
675
+ minimize: (id) => transact(() => minimize(id)),
676
+ maximize: (id) => transact(() => maximize(id)),
677
+ toggleMaximize: (id) => transact(() => toggleMaximize(id)),
678
+ restore: (id) => transact(() => restore(id)),
679
+ restoreTo: (id, bounds) => transact(() => restoreTo(id, bounds)),
680
+ snap: (id, zone) => transact(() => snap(id, zone)),
681
+ move: (id, x, y) => transact(() => move(id, x, y)),
682
+ moveBy: (id, dx, dy) => transact(() => moveBy(id, dx, dy)),
683
+ resize: (id, patch) => transact(() => resize(id, patch)),
684
+ update: (id, patch) => transact(() => update(id, patch)),
662
685
  get: (id) => windows[id],
663
686
  getState,
664
687
  minimized,
665
- setViewport,
666
- batch,
688
+ setViewport: (next) => transact(() => setViewport(next)),
689
+ batch: (run) => transact(run),
667
690
  serialize,
668
- hydrate,
691
+ hydrate: (data) => transact(() => hydrate(data)),
669
692
  subscribe: (listener) => emitter.on("change", ({ state }) => listener(state)),
670
693
  on: (event, listener) => emitter.on(event, listener),
671
694
  destroy
@@ -753,175 +776,25 @@ function createAnnouncer(wm, container, messages = {}) {
753
776
  };
754
777
  }
755
778
 
756
- // src/dom/controller.ts
757
- var RESIZE_DIRECTIONS = ["n", "e", "s", "w", "ne", "nw", "se", "sw"];
758
- var RESIZE_CURSORS = {
759
- n: "ns-resize",
760
- s: "ns-resize",
761
- e: "ew-resize",
762
- w: "ew-resize",
763
- ne: "nesw-resize",
764
- sw: "nesw-resize",
765
- nw: "nwse-resize",
766
- se: "nwse-resize"
767
- };
779
+ // src/dom/shared.ts
768
780
  var INTERACTIVE_SELECTOR = "button, input, select, textarea, a[href], [contenteditable], [data-wm-close], [data-wm-minimize], [data-wm-maximize]";
769
781
  function windowOf(element) {
770
782
  const view = element.ownerDocument.defaultView;
771
783
  if (!view) throw new Error("wmkit: desktop element is not attached to a document");
772
784
  return view;
773
785
  }
774
- function attachDesktop(wm, element, options = {}) {
775
- const doc = element.ownerDocument;
776
- const view = windowOf(element);
777
- const snapEnabled = options.snap !== false;
778
- const snapOptions = typeof options.snap === "object" ? options.snap : {};
779
- const snapPreviewEnabled = snapOptions.preview !== false;
780
- const topEdge = snapOptions.topEdge ?? "maximize";
781
- const keyboardEnabled = options.keyboard !== false;
782
- const keyboardOptions = typeof options.keyboard === "object" ? options.keyboard : {};
783
- const moveStep = keyboardOptions.moveStep ?? 16;
784
- const cycleEnabled = keyboardOptions.cycle !== false;
785
- element.dataset.wmDesktop = "";
786
- if (view.getComputedStyle(element).position === "static") {
787
- element.style.position = "relative";
788
- }
789
- const registry = /* @__PURE__ */ new Map();
790
- const cleanup = [];
791
- let lastOrder = null;
792
- let lastFocused = null;
793
- let drag = null;
794
- let announcer = null;
795
- if (options.announce !== false) {
796
- announcer = createAnnouncer(
797
- wm,
798
- element,
799
- typeof options.announce === "object" ? options.announce : {}
800
- );
801
- cleanup.push(() => announcer?.destroy());
802
- }
803
- let preview = null;
804
- function showPreview(bounds) {
805
- if (!snapPreviewEnabled) return;
806
- if (!preview) {
807
- preview = doc.createElement("div");
808
- preview.dataset.wmSnapPreview = "";
809
- preview.style.cssText = "position:absolute;left:0;top:0;pointer-events:none;display:none;z-index:2147483646";
810
- element.append(preview);
811
- }
812
- preview.style.display = "block";
813
- preview.style.transform = `translate3d(${bounds.x}px, ${bounds.y}px, 0)`;
814
- preview.style.width = `${bounds.width}px`;
815
- preview.style.height = `${bounds.height}px`;
816
- }
817
- function hidePreview() {
818
- if (preview) preview.style.display = "none";
819
- }
820
- if (options.autoViewport !== false) {
821
- const applyViewport = () => wm.setViewport({ width: element.clientWidth, height: element.clientHeight });
822
- applyViewport();
823
- const observer = new ResizeObserver(applyViewport);
824
- observer.observe(element);
825
- cleanup.push(() => observer.disconnect());
826
- }
827
- function syncWindow(attached, win, zIndex) {
828
- const el = attached.element;
829
- const firstSync = attached.lastState === null;
830
- if (firstSync) el.style.transition = "none";
831
- if (attached.lastState !== win) {
832
- el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
833
- el.style.width = `${win.bounds.width}px`;
834
- el.style.height = `${win.bounds.height}px`;
835
- el.dataset.wmStage = win.stage;
836
- el.dataset.wmLayer = win.layer;
837
- el.hidden = win.stage === "minimized";
838
- el.setAttribute("aria-label", win.title);
839
- if (win.layer === "modal") el.setAttribute("aria-modal", "true");
840
- else el.removeAttribute("aria-modal");
841
- for (const handle of attached.handles) {
842
- handle.style.display = win.resizable && win.stage === "normal" ? "" : "none";
843
- }
844
- attached.lastState = win;
845
- }
846
- el.style.zIndex = String(zIndex + 1);
847
- if (firstSync) {
848
- void el.offsetWidth;
849
- el.style.transition = "";
850
- }
851
- }
852
- function syncAll() {
853
- const state = wm.getState();
854
- const orderChanged = state.order !== lastOrder;
855
- state.order.forEach((id, index) => {
856
- const attached = registry.get(id);
857
- const win = state.windows[id];
858
- if (!attached || !win) return;
859
- if (orderChanged || attached.lastState !== win) syncWindow(attached, win, index);
860
- });
861
- if (state.focusedId !== lastFocused) {
862
- if (lastFocused) {
863
- const prev = registry.get(lastFocused);
864
- if (prev) delete prev.element.dataset.wmFocused;
865
- }
866
- if (state.focusedId) {
867
- const next = registry.get(state.focusedId);
868
- if (next) next.element.dataset.wmFocused = "";
869
- }
870
- lastFocused = state.focusedId;
871
- }
872
- lastOrder = state.order;
873
- }
874
- cleanup.push(wm.subscribe(syncAll));
875
- cleanup.push(
876
- wm.on("focus", ({ window: win }) => {
877
- const attached = registry.get(win.id);
878
- if (!attached) return;
879
- if (!attached.element.contains(doc.activeElement)) {
880
- attached.element.focus({ preventScroll: true });
881
- }
882
- })
883
- );
884
- cleanup.push(
885
- wm.on("modalblocked", ({ window: win }) => {
886
- const attached = registry.get(win.id);
887
- if (!attached) return;
888
- delete attached.element.dataset.wmFlash;
889
- void attached.element.offsetWidth;
890
- attached.element.dataset.wmFlash = "";
891
- })
892
- );
893
- cleanup.push(
894
- wm.on("stage", ({ window: win, previous }) => {
895
- if (win.stage !== "minimized" || previous === "minimized") return;
896
- const attached = registry.get(win.id);
897
- const target = options.minimizeTarget?.(win);
898
- if (attached && target) flipToTarget(attached.element, target);
899
- })
900
- );
901
- if (keyboardEnabled && cycleEnabled) {
902
- const onDesktopKeydown = (event) => {
903
- if (event.key === "F6") {
904
- event.preventDefault();
905
- wm.cycleFocus(event.shiftKey ? -1 : 1);
906
- }
907
- };
908
- element.addEventListener("keydown", onDesktopKeydown);
909
- cleanup.push(() => element.removeEventListener("keydown", onDesktopKeydown));
910
- }
911
- function desktopPoint(event) {
912
- const rect = element.getBoundingClientRect();
913
- return { x: event.clientX - rect.left, y: event.clientY - rect.top };
914
- }
915
- function endDrag(cancelled) {
916
- if (drag) drag.finish(cancelled);
917
- }
918
- function startDrag(id, handle, event) {
786
+
787
+ // src/dom/drag.ts
788
+ function createDragStarter(ctx) {
789
+ const { wm, doc, view } = ctx;
790
+ return function startDrag(id, handle, event) {
919
791
  const win = wm.get(id);
920
- if (!win?.draggable || event.button !== 0 || drag) return;
792
+ if (!win?.draggable || event.button !== 0 || ctx.currentDrag()) return;
921
793
  const target = event.target;
922
794
  if (target?.closest(INTERACTIVE_SELECTOR)) return;
923
795
  event.preventDefault();
924
- const point = desktopPoint(event);
796
+ const releaseRect = ctx.trackRect();
797
+ const point = ctx.toLocal(event);
925
798
  const startBounds = win.bounds;
926
799
  const session = {
927
800
  id,
@@ -930,6 +803,9 @@ function attachDesktop(wm, element, options = {}) {
930
803
  grabDY: point.y - startBounds.y,
931
804
  grabRatio: clamp((point.x - startBounds.x) / startBounds.width, 0.05, 0.95),
932
805
  startBounds,
806
+ startStage: win.stage,
807
+ startZone: win.snapZone,
808
+ startRestoreBounds: win.restoreBounds,
933
809
  restored: win.stage === "normal",
934
810
  moved: false,
935
811
  zone: null,
@@ -940,10 +816,9 @@ function attachDesktop(wm, element, options = {}) {
940
816
  finish: () => {
941
817
  }
942
818
  };
943
- const attached = registry.get(id);
944
- const el = attached?.element;
819
+ const el = ctx.windowElement(id);
945
820
  function flush() {
946
- if (!session.hasPending || drag !== session) return;
821
+ if (!session.hasPending || ctx.currentDrag() !== session) return;
947
822
  session.hasPending = false;
948
823
  session.raf = 0;
949
824
  const current = wm.get(id);
@@ -967,32 +842,35 @@ function attachDesktop(wm, element, options = {}) {
967
842
  return;
968
843
  }
969
844
  wm.move(id, session.pendingX - session.grabDX, session.pendingY - session.grabDY);
970
- if (snapEnabled && current.snappable) {
845
+ if (ctx.snapEnabled && current.snappable) {
971
846
  const viewport = wm.getState().viewport;
972
- const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, snapOptions);
847
+ const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect);
973
848
  let zone = rawZone;
974
849
  if (rawZone === "top") {
975
- if (topEdge === "maximize") zone = current.maximizable ? "maximize" : null;
976
- else if (topEdge === "none") zone = null;
850
+ if (ctx.topEdge === "maximize") zone = current.maximizable ? "maximize" : null;
851
+ else if (ctx.topEdge === "none") zone = null;
977
852
  }
978
853
  session.zone = zone;
979
854
  if (zone) {
980
- showPreview(
855
+ ctx.showPreview(
981
856
  zone === "maximize" ? { x: 0, y: 0, width: viewport.width, height: viewport.height } : zoneBounds(zone, viewport)
982
857
  );
983
858
  } else {
984
- hidePreview();
859
+ ctx.hidePreview();
985
860
  }
986
861
  }
987
862
  }
988
863
  function onMove(moveEvent) {
989
864
  if (moveEvent.pointerId !== session.pointerId) return;
990
- const movePoint = desktopPoint(moveEvent);
865
+ const movePoint = ctx.toLocal(moveEvent);
991
866
  if (!session.moved) {
992
867
  const travelled = Math.abs(movePoint.x - (session.startBounds.x + session.grabDX)) + Math.abs(movePoint.y - (session.startBounds.y + session.grabDY));
993
868
  if (travelled < 3 && session.restored) return;
994
869
  session.moved = true;
995
- if (el) el.dataset.wmDragging = "";
870
+ if (el) {
871
+ el.dataset.wmDragging = "";
872
+ el.style.willChange = "transform";
873
+ }
996
874
  }
997
875
  session.pendingX = movePoint.x;
998
876
  session.pendingY = movePoint.y;
@@ -1014,10 +892,11 @@ function attachDesktop(wm, element, options = {}) {
1014
892
  }
1015
893
  }
1016
894
  function finish(cancelled) {
1017
- if (drag !== session) return;
1018
- if (session.raf !== 0) view?.cancelAnimationFrame(session.raf);
895
+ if (ctx.currentDrag() !== session) return;
896
+ if (session.raf !== 0) view.cancelAnimationFrame(session.raf);
1019
897
  if (session.hasPending && !cancelled) flush();
1020
- drag = null;
898
+ ctx.releaseDrag(session);
899
+ releaseRect();
1021
900
  handle.removeEventListener("pointermove", onMove);
1022
901
  handle.removeEventListener("pointerup", onUp);
1023
902
  handle.removeEventListener("pointercancel", onCancel);
@@ -1025,11 +904,22 @@ function attachDesktop(wm, element, options = {}) {
1025
904
  if (handle.hasPointerCapture(session.pointerId)) {
1026
905
  handle.releasePointerCapture(session.pointerId);
1027
906
  }
1028
- if (el) delete el.dataset.wmDragging;
1029
- hidePreview();
907
+ if (el) {
908
+ delete el.dataset.wmDragging;
909
+ el.style.willChange = "";
910
+ }
911
+ ctx.hidePreview();
1030
912
  if (cancelled) {
1031
913
  if (session.moved && session.restored) {
1032
- wm.move(id, session.startBounds.x, session.startBounds.y);
914
+ if (session.startStage === "maximized") {
915
+ wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds);
916
+ wm.maximize(id);
917
+ } else if (session.startStage === "snapped" && session.startZone) {
918
+ wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds);
919
+ wm.snap(id, session.startZone);
920
+ } else {
921
+ wm.move(id, session.startBounds.x, session.startBounds.y);
922
+ }
1033
923
  }
1034
924
  return;
1035
925
  }
@@ -1039,20 +929,37 @@ function attachDesktop(wm, element, options = {}) {
1039
929
  }
1040
930
  }
1041
931
  session.finish = finish;
1042
- drag = session;
932
+ ctx.claimDrag(session);
1043
933
  handle.setPointerCapture(event.pointerId);
1044
934
  handle.addEventListener("pointermove", onMove);
1045
935
  handle.addEventListener("pointerup", onUp);
1046
936
  handle.addEventListener("pointercancel", onCancel);
1047
937
  doc.addEventListener("keydown", onKeydown, true);
1048
- }
1049
- function startResize(id, direction, event) {
938
+ };
939
+ }
940
+
941
+ // src/dom/resize.ts
942
+ var RESIZE_DIRECTIONS = ["n", "e", "s", "w", "ne", "nw", "se", "sw"];
943
+ var RESIZE_CURSORS = {
944
+ n: "ns-resize",
945
+ s: "ns-resize",
946
+ e: "ew-resize",
947
+ w: "ew-resize",
948
+ ne: "nesw-resize",
949
+ sw: "nesw-resize",
950
+ nw: "nwse-resize",
951
+ se: "nwse-resize"
952
+ };
953
+ function createResizeStarter(ctx) {
954
+ const { wm, doc, view } = ctx;
955
+ return function startResize(id, direction, event) {
1050
956
  const win = wm.get(id);
1051
- if (!win?.resizable || event.button !== 0 || drag) return;
957
+ if (!win?.resizable || event.button !== 0 || ctx.currentDrag()) return;
1052
958
  event.preventDefault();
1053
959
  event.stopPropagation();
1054
960
  const handleEl = event.currentTarget;
1055
- const startPoint = desktopPoint(event);
961
+ const releaseRect = ctx.trackRect();
962
+ const startPoint = ctx.toLocal(event);
1056
963
  const start = win.bounds;
1057
964
  const minSize = win.minSize;
1058
965
  const maxSize = win.maxSize;
@@ -1060,8 +967,8 @@ function attachDesktop(wm, element, options = {}) {
1060
967
  let pendingX = 0;
1061
968
  let pendingY = 0;
1062
969
  let hasPending = false;
1063
- const attached = registry.get(id);
1064
- if (attached) attached.element.dataset.wmResizing = direction;
970
+ const el = ctx.windowElement(id);
971
+ if (el) el.dataset.wmResizing = direction;
1065
972
  function clampWidth(width) {
1066
973
  return clamp(width, minSize.width, maxSize ? maxSize.width : Number.POSITIVE_INFINITY);
1067
974
  }
@@ -1089,15 +996,16 @@ function attachDesktop(wm, element, options = {}) {
1089
996
  }
1090
997
  function onMove(moveEvent) {
1091
998
  if (moveEvent.pointerId !== event.pointerId) return;
1092
- const movePoint = desktopPoint(moveEvent);
999
+ const movePoint = ctx.toLocal(moveEvent);
1093
1000
  pendingX = movePoint.x;
1094
1001
  pendingY = movePoint.y;
1095
1002
  hasPending = true;
1096
- if (raf === 0) raf = view?.requestAnimationFrame(flush) ?? 0;
1003
+ if (raf === 0) raf = view.requestAnimationFrame(flush);
1097
1004
  }
1098
1005
  function finish(cancelled) {
1099
- if (raf !== 0) view?.cancelAnimationFrame(raf);
1006
+ if (raf !== 0) view.cancelAnimationFrame(raf);
1100
1007
  if (hasPending && !cancelled) flush();
1008
+ releaseRect();
1101
1009
  handleEl.removeEventListener("pointermove", onMove);
1102
1010
  handleEl.removeEventListener("pointerup", onUp);
1103
1011
  handleEl.removeEventListener("pointercancel", onCancelPointer);
@@ -1105,7 +1013,7 @@ function attachDesktop(wm, element, options = {}) {
1105
1013
  if (handleEl.hasPointerCapture(event.pointerId)) {
1106
1014
  handleEl.releasePointerCapture(event.pointerId);
1107
1015
  }
1108
- if (attached) delete attached.element.dataset.wmResizing;
1016
+ if (el) delete el.dataset.wmResizing;
1109
1017
  if (cancelled) wm.resize(id, start);
1110
1018
  }
1111
1019
  function onUp(upEvent) {
@@ -1127,6 +1035,220 @@ function attachDesktop(wm, element, options = {}) {
1127
1035
  handleEl.addEventListener("pointerup", onUp);
1128
1036
  handleEl.addEventListener("pointercancel", onCancelPointer);
1129
1037
  doc.addEventListener("keydown", onKeydown, true);
1038
+ };
1039
+ }
1040
+ function createResizeHandles(doc, edge, corner) {
1041
+ const base = "position:absolute;touch-action:none;user-select:none;-webkit-user-select:none;";
1042
+ const styles = {
1043
+ n: `top:${-edge / 2}px;left:${corner}px;right:${corner}px;height:${edge}px`,
1044
+ s: `bottom:${-edge / 2}px;left:${corner}px;right:${corner}px;height:${edge}px`,
1045
+ e: `right:${-edge / 2}px;top:${corner}px;bottom:${corner}px;width:${edge}px`,
1046
+ w: `left:${-edge / 2}px;top:${corner}px;bottom:${corner}px;width:${edge}px`,
1047
+ ne: `top:${-edge / 2}px;right:${-edge / 2}px;width:${corner}px;height:${corner}px`,
1048
+ nw: `top:${-edge / 2}px;left:${-edge / 2}px;width:${corner}px;height:${corner}px`,
1049
+ se: `bottom:${-edge / 2}px;right:${-edge / 2}px;width:${corner}px;height:${corner}px`,
1050
+ sw: `bottom:${-edge / 2}px;left:${-edge / 2}px;width:${corner}px;height:${corner}px`
1051
+ };
1052
+ return RESIZE_DIRECTIONS.map((direction) => {
1053
+ const element = doc.createElement("div");
1054
+ element.dataset.wmResize = direction;
1055
+ element.setAttribute("aria-hidden", "true");
1056
+ element.style.cssText = `${base}${styles[direction]};cursor:${RESIZE_CURSORS[direction]}`;
1057
+ return { element, direction };
1058
+ });
1059
+ }
1060
+
1061
+ // src/dom/controller.ts
1062
+ function attachDesktop(wm, element, options = {}) {
1063
+ const doc = element.ownerDocument;
1064
+ const view = windowOf(element);
1065
+ const coarsePointer = typeof view.matchMedia === "function" && view.matchMedia("(pointer: coarse)").matches;
1066
+ const snapEnabled = options.snap !== false;
1067
+ const snapOptions = typeof options.snap === "object" ? options.snap : {};
1068
+ const snapPreviewEnabled = snapOptions.preview !== false;
1069
+ const topEdge = snapOptions.topEdge ?? "maximize";
1070
+ const keyboardEnabled = options.keyboard !== false;
1071
+ const keyboardOptions = typeof options.keyboard === "object" ? options.keyboard : {};
1072
+ const moveStep = keyboardOptions.moveStep ?? 16;
1073
+ const cycleEnabled = keyboardOptions.cycle !== false;
1074
+ const hitEdge = options.hitAreas?.edge ?? (coarsePointer ? 16 : 8);
1075
+ const hitCorner = options.hitAreas?.corner ?? (coarsePointer ? 24 : 12);
1076
+ element.dataset.wmDesktop = "";
1077
+ if (view.getComputedStyle(element).position === "static") {
1078
+ element.style.position = "relative";
1079
+ }
1080
+ const registry = /* @__PURE__ */ new Map();
1081
+ const cleanup = [];
1082
+ let lastOrder = null;
1083
+ let lastFocused = null;
1084
+ let drag = null;
1085
+ let cachedRect = null;
1086
+ let rectUsers = 0;
1087
+ let announcer = null;
1088
+ if (options.announce !== false) {
1089
+ announcer = createAnnouncer(
1090
+ wm,
1091
+ element,
1092
+ typeof options.announce === "object" ? options.announce : {}
1093
+ );
1094
+ cleanup.push(() => announcer?.destroy());
1095
+ }
1096
+ let preview = null;
1097
+ function showPreview(bounds) {
1098
+ if (!snapPreviewEnabled) return;
1099
+ if (!preview) {
1100
+ preview = doc.createElement("div");
1101
+ preview.dataset.wmSnapPreview = "";
1102
+ preview.style.cssText = "position:absolute;left:0;top:0;pointer-events:none;display:none;z-index:2147483646";
1103
+ element.append(preview);
1104
+ }
1105
+ preview.style.display = "block";
1106
+ preview.style.transform = `translate3d(${bounds.x}px, ${bounds.y}px, 0)`;
1107
+ preview.style.width = `${bounds.width}px`;
1108
+ preview.style.height = `${bounds.height}px`;
1109
+ }
1110
+ function hidePreview() {
1111
+ if (preview) preview.style.display = "none";
1112
+ }
1113
+ const ctx = {
1114
+ wm,
1115
+ doc,
1116
+ view,
1117
+ toLocal(event) {
1118
+ const rect = cachedRect ?? element.getBoundingClientRect();
1119
+ return { x: event.clientX - rect.left, y: event.clientY - rect.top };
1120
+ },
1121
+ trackRect() {
1122
+ if (rectUsers === 0) {
1123
+ const rect = element.getBoundingClientRect();
1124
+ cachedRect = { left: rect.left, top: rect.top };
1125
+ }
1126
+ rectUsers += 1;
1127
+ let released = false;
1128
+ return () => {
1129
+ if (released) return;
1130
+ released = true;
1131
+ rectUsers -= 1;
1132
+ if (rectUsers === 0) cachedRect = null;
1133
+ };
1134
+ },
1135
+ windowElement: (id) => registry.get(id)?.element,
1136
+ showPreview,
1137
+ hidePreview,
1138
+ snapEnabled,
1139
+ snapDetect: {
1140
+ threshold: snapOptions.threshold ?? (coarsePointer ? 20 : 12),
1141
+ cornerSize: snapOptions.cornerSize ?? (coarsePointer ? 96 : 64)
1142
+ },
1143
+ topEdge,
1144
+ hitEdge,
1145
+ hitCorner,
1146
+ currentDrag: () => drag,
1147
+ claimDrag(session) {
1148
+ drag = session;
1149
+ },
1150
+ releaseDrag(session) {
1151
+ if (drag === session) drag = null;
1152
+ }
1153
+ };
1154
+ const startDrag = createDragStarter(ctx);
1155
+ const startResize = createResizeStarter(ctx);
1156
+ if (options.autoViewport !== false) {
1157
+ const applyViewport = () => wm.setViewport({ width: element.clientWidth, height: element.clientHeight });
1158
+ applyViewport();
1159
+ const observer = new ResizeObserver(applyViewport);
1160
+ observer.observe(element);
1161
+ cleanup.push(() => observer.disconnect());
1162
+ }
1163
+ function syncWindow(attached, win, zIndex) {
1164
+ const el = attached.element;
1165
+ const firstSync = attached.lastState === null;
1166
+ if (firstSync) el.style.transition = "none";
1167
+ if (attached.lastState !== win) {
1168
+ el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
1169
+ el.style.width = `${win.bounds.width}px`;
1170
+ el.style.height = `${win.bounds.height}px`;
1171
+ el.dataset.wmStage = win.stage;
1172
+ el.dataset.wmLayer = win.layer;
1173
+ el.hidden = win.stage === "minimized";
1174
+ el.setAttribute("aria-label", win.title);
1175
+ if (win.layer === "modal") el.setAttribute("aria-modal", "true");
1176
+ else el.removeAttribute("aria-modal");
1177
+ for (const handle of attached.handles) {
1178
+ handle.style.display = win.resizable && win.stage === "normal" ? "" : "none";
1179
+ }
1180
+ attached.lastState = win;
1181
+ }
1182
+ if (attached.lastZ !== zIndex) {
1183
+ el.style.zIndex = String(zIndex + 1);
1184
+ attached.lastZ = zIndex;
1185
+ }
1186
+ if (firstSync) {
1187
+ void el.offsetWidth;
1188
+ el.style.transition = "";
1189
+ }
1190
+ }
1191
+ function syncAll() {
1192
+ const state = wm.getState();
1193
+ const orderChanged = state.order !== lastOrder;
1194
+ state.order.forEach((id, index) => {
1195
+ const attached = registry.get(id);
1196
+ const win = state.windows[id];
1197
+ if (!attached || !win) return;
1198
+ if (orderChanged || attached.lastState !== win) syncWindow(attached, win, index);
1199
+ });
1200
+ if (state.focusedId !== lastFocused) {
1201
+ if (lastFocused) {
1202
+ const prev = registry.get(lastFocused);
1203
+ if (prev) delete prev.element.dataset.wmFocused;
1204
+ }
1205
+ if (state.focusedId) {
1206
+ const next = registry.get(state.focusedId);
1207
+ if (next) next.element.dataset.wmFocused = "";
1208
+ }
1209
+ lastFocused = state.focusedId;
1210
+ }
1211
+ lastOrder = state.order;
1212
+ }
1213
+ cleanup.push(wm.subscribe(syncAll));
1214
+ cleanup.push(
1215
+ wm.on("focus", ({ window: win }) => {
1216
+ const attached = registry.get(win.id);
1217
+ if (!attached) return;
1218
+ if (!attached.element.contains(doc.activeElement)) {
1219
+ attached.element.focus({ preventScroll: true });
1220
+ }
1221
+ })
1222
+ );
1223
+ cleanup.push(
1224
+ wm.on("modalblocked", ({ window: win }) => {
1225
+ const attached = registry.get(win.id);
1226
+ if (!attached) return;
1227
+ delete attached.element.dataset.wmFlash;
1228
+ void attached.element.offsetWidth;
1229
+ attached.element.dataset.wmFlash = "";
1230
+ })
1231
+ );
1232
+ cleanup.push(
1233
+ wm.on("stage", ({ window: win, previous }) => {
1234
+ if (win.stage !== "minimized" || previous === "minimized") return;
1235
+ const attached = registry.get(win.id);
1236
+ const target = options.minimizeTarget?.(win);
1237
+ if (attached && target) flipToTarget(attached.element, target);
1238
+ })
1239
+ );
1240
+ if (keyboardEnabled && cycleEnabled) {
1241
+ const onDesktopKeydown = (event) => {
1242
+ if (event.key === "F6") {
1243
+ event.preventDefault();
1244
+ wm.cycleFocus(event.shiftKey ? -1 : 1);
1245
+ }
1246
+ };
1247
+ element.addEventListener("keydown", onDesktopKeydown);
1248
+ cleanup.push(() => element.removeEventListener("keydown", onDesktopKeydown));
1249
+ }
1250
+ function endDrag(cancelled) {
1251
+ if (drag) drag.finish(cancelled);
1130
1252
  }
1131
1253
  function attachWindow(id, windowElement, windowOptions = {}) {
1132
1254
  const win = wm.get(id);
@@ -1137,6 +1259,7 @@ function attachDesktop(wm, element, options = {}) {
1137
1259
  handle: null,
1138
1260
  handles: [],
1139
1261
  lastState: null,
1262
+ lastZ: -1,
1140
1263
  cleanup: []
1141
1264
  };
1142
1265
  windowElement.dataset.wmWindow = id;
@@ -1189,24 +1312,11 @@ function attachDesktop(wm, element, options = {}) {
1189
1312
  attached.cleanup.push(() => handle.removeEventListener("dblclick", onDoubleClick));
1190
1313
  }
1191
1314
  if (windowOptions.resizeHandles !== false) {
1192
- for (const direction of RESIZE_DIRECTIONS) {
1193
- const resizeHandle = doc.createElement("div");
1194
- resizeHandle.dataset.wmResize = direction;
1195
- resizeHandle.setAttribute("aria-hidden", "true");
1196
- const base = "position:absolute;touch-action:none;user-select:none;-webkit-user-select:none;";
1197
- const size = 8;
1198
- const corner = 12;
1199
- const styles = {
1200
- n: `top:${-size / 2}px;left:${corner}px;right:${corner}px;height:${size}px`,
1201
- s: `bottom:${-size / 2}px;left:${corner}px;right:${corner}px;height:${size}px`,
1202
- e: `right:${-size / 2}px;top:${corner}px;bottom:${corner}px;width:${size}px`,
1203
- w: `left:${-size / 2}px;top:${corner}px;bottom:${corner}px;width:${size}px`,
1204
- ne: `top:${-size / 2}px;right:${-size / 2}px;width:${corner}px;height:${corner}px`,
1205
- nw: `top:${-size / 2}px;left:${-size / 2}px;width:${corner}px;height:${corner}px`,
1206
- se: `bottom:${-size / 2}px;right:${-size / 2}px;width:${corner}px;height:${corner}px`,
1207
- sw: `bottom:${-size / 2}px;left:${-size / 2}px;width:${corner}px;height:${corner}px`
1208
- };
1209
- resizeHandle.style.cssText = `${base}${styles[direction]};cursor:${RESIZE_CURSORS[direction]}`;
1315
+ for (const { element: resizeHandle, direction } of createResizeHandles(
1316
+ doc,
1317
+ hitEdge,
1318
+ hitCorner
1319
+ )) {
1210
1320
  const onResizeDown = (event) => startResize(id, direction, event);
1211
1321
  resizeHandle.addEventListener("pointerdown", onResizeDown);
1212
1322
  attached.cleanup.push(() => resizeHandle.removeEventListener("pointerdown", onResizeDown));
@@ -1264,16 +1374,25 @@ function attachDesktop(wm, element, options = {}) {
1264
1374
  };
1265
1375
  windowElement.addEventListener("keydown", onModalTrap);
1266
1376
  attached.cleanup.push(() => windowElement.removeEventListener("keydown", onModalTrap));
1267
- registry.set(id, attached);
1268
- lastOrder = null;
1269
- lastFocused = null;
1270
- syncAll();
1271
- return () => {
1377
+ const detach = () => {
1272
1378
  if (drag?.id === id) endDrag(true);
1273
1379
  for (const dispose of attached.cleanup) dispose();
1274
1380
  for (const resizeHandle of attached.handles) resizeHandle.remove();
1275
1381
  registry.delete(id);
1276
1382
  };
1383
+ if (windowOptions.removeOnClose) {
1384
+ const stopOnClose = wm.on("close", ({ window: closed }) => {
1385
+ if (closed.id !== id) return;
1386
+ detach();
1387
+ windowElement.remove();
1388
+ });
1389
+ attached.cleanup.push(stopOnClose);
1390
+ }
1391
+ registry.set(id, attached);
1392
+ lastOrder = null;
1393
+ lastFocused = null;
1394
+ syncAll();
1395
+ return detach;
1277
1396
  }
1278
1397
  return {
1279
1398
  element,
@@ -1292,6 +1411,8 @@ function attachDesktop(wm, element, options = {}) {
1292
1411
  }
1293
1412
  };
1294
1413
  }
1414
+
1415
+ // src/dom/binder.ts
1295
1416
  function createDesktopBinder(wm, options = {}) {
1296
1417
  let controller = null;
1297
1418
  const entries = /* @__PURE__ */ new Set();
@@ -1345,5 +1466,5 @@ exports.detectSnapZone = detectSnapZone;
1345
1466
  exports.flipToTarget = flipToTarget;
1346
1467
  exports.prefersReducedMotion = prefersReducedMotion;
1347
1468
  exports.zoneBounds = zoneBounds;
1348
- //# sourceMappingURL=chunk-KWLI7JIY.cjs.map
1349
- //# sourceMappingURL=chunk-KWLI7JIY.cjs.map
1469
+ //# sourceMappingURL=chunk-KQCVZ2W7.cjs.map
1470
+ //# sourceMappingURL=chunk-KQCVZ2W7.cjs.map