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