@surdeddd/wmkit 0.1.1 → 0.3.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.
- package/README.md +58 -12
- package/README.ru.md +21 -12
- package/dist/angular.cjs +52 -0
- package/dist/angular.cjs.map +1 -0
- package/dist/angular.d.cts +15 -0
- package/dist/angular.d.ts +15 -0
- package/dist/angular.js +47 -0
- package/dist/angular.js.map +1 -0
- package/dist/{controller-1uedRvQw.d.ts → binder-0Oku22Nc.d.ts} +21 -3
- package/dist/{controller-BfUF1k04.d.cts → binder-Dq_AZRbo.d.cts} +21 -3
- package/dist/{chunk-TXINVEK3.cjs → chunk-B35DANTX.cjs} +596 -241
- package/dist/chunk-B35DANTX.cjs.map +1 -0
- package/dist/{chunk-N5KNWRKU.js → chunk-YCHJXSTC.js} +596 -242
- package/dist/chunk-YCHJXSTC.js.map +1 -0
- package/dist/index.cjs +19 -15
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +1 -1
- package/dist/persist.cjs +1 -1
- package/dist/persist.cjs.map +1 -1
- package/dist/persist.d.cts +1 -1
- package/dist/persist.d.ts +1 -1
- package/dist/persist.js +1 -1
- package/dist/persist.js.map +1 -1
- package/dist/popout.cjs +1 -1
- package/dist/popout.cjs.map +1 -1
- package/dist/popout.d.cts +1 -1
- package/dist/popout.d.ts +1 -1
- package/dist/popout.js +1 -1
- package/dist/popout.js.map +1 -1
- package/dist/react.cjs +3 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/solid.cjs +3 -3
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.d.cts +2 -2
- package/dist/solid.d.ts +2 -2
- package/dist/solid.js +1 -1
- package/dist/solid.js.map +1 -1
- package/dist/svelte.cjs +4 -4
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.d.cts +2 -2
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +2 -2
- package/dist/svelte.js.map +1 -1
- package/dist/themes/glass.css +0 -1
- package/dist/themes/light.css +202 -0
- package/dist/themes/retro.css +152 -0
- package/dist/{types-BYx7gCuT.d.cts → types-DdCtDHgt.d.cts} +23 -1
- package/dist/{types-BYx7gCuT.d.ts → types-DdCtDHgt.d.ts} +23 -1
- package/dist/vue.cjs +3 -3
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.d.cts +2 -2
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +1 -1
- package/dist/vue.js.map +1 -1
- package/package.json +34 -7
- package/dist/chunk-N5KNWRKU.js.map +0 -1
- package/dist/chunk-TXINVEK3.cjs.map +0 -1
|
@@ -99,6 +99,42 @@ function detectSnapZone(x, y, viewport, options = {}) {
|
|
|
99
99
|
}
|
|
100
100
|
return null;
|
|
101
101
|
}
|
|
102
|
+
function nearestEdge(low, high, targetLow, targetHigh) {
|
|
103
|
+
return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high];
|
|
104
|
+
}
|
|
105
|
+
function magnetize(bounds, targets, threshold) {
|
|
106
|
+
const result = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false };
|
|
107
|
+
if (threshold <= 0 || targets.length === 0) return result;
|
|
108
|
+
let bestX = threshold + 1;
|
|
109
|
+
let bestY = threshold + 1;
|
|
110
|
+
for (const target of targets) {
|
|
111
|
+
for (const delta of nearestEdge(
|
|
112
|
+
bounds.x,
|
|
113
|
+
bounds.x + bounds.width,
|
|
114
|
+
target.x,
|
|
115
|
+
target.x + target.width
|
|
116
|
+
)) {
|
|
117
|
+
if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta;
|
|
118
|
+
}
|
|
119
|
+
for (const delta of nearestEdge(
|
|
120
|
+
bounds.y,
|
|
121
|
+
bounds.y + bounds.height,
|
|
122
|
+
target.y,
|
|
123
|
+
target.y + target.height
|
|
124
|
+
)) {
|
|
125
|
+
if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (Math.abs(bestX) <= threshold) {
|
|
129
|
+
result.x = bounds.x + bestX;
|
|
130
|
+
result.snappedX = true;
|
|
131
|
+
}
|
|
132
|
+
if (Math.abs(bestY) <= threshold) {
|
|
133
|
+
result.y = bounds.y + bestY;
|
|
134
|
+
result.snappedY = true;
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
102
138
|
|
|
103
139
|
// src/core/manager.ts
|
|
104
140
|
var LAYER_RANK = { normal: 0, floating: 1, modal: 2 };
|
|
@@ -122,6 +158,7 @@ function createWindowManager(options = {}) {
|
|
|
122
158
|
const cascadeOffset = options.cascadeOffset ?? 32;
|
|
123
159
|
const cascadeOrigin = options.cascadeOrigin ?? { x: 32, y: 32 };
|
|
124
160
|
const idPrefix = options.idPrefix ?? "wm";
|
|
161
|
+
const historyLimit = options.historyLimit ?? 50;
|
|
125
162
|
let windows = {};
|
|
126
163
|
let order = [];
|
|
127
164
|
let focusedId = null;
|
|
@@ -133,6 +170,13 @@ function createWindowManager(options = {}) {
|
|
|
133
170
|
let batchDepth = 0;
|
|
134
171
|
let batchDirty = false;
|
|
135
172
|
const pendingEvents = [];
|
|
173
|
+
const past = [];
|
|
174
|
+
let future = [];
|
|
175
|
+
let interactionDepth = 0;
|
|
176
|
+
let interactionRecorded = false;
|
|
177
|
+
let skipNextHistory = false;
|
|
178
|
+
let pendingHistory = null;
|
|
179
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
136
180
|
function getState() {
|
|
137
181
|
if (!snapshot) {
|
|
138
182
|
snapshot = { windows, order, focusedId, viewport };
|
|
@@ -517,47 +561,164 @@ function createWindowManager(options = {}) {
|
|
|
517
561
|
function setViewport(next) {
|
|
518
562
|
if (next.width === viewport.width && next.height === viewport.height) return;
|
|
519
563
|
viewport = { ...next };
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (win.stage === "maximized") {
|
|
524
|
-
const updated = { ...win, bounds: fullBounds() };
|
|
525
|
-
setWindow(updated);
|
|
526
|
-
queueEvent(() => emitter.emit("resize", { window: updated }));
|
|
527
|
-
} else if (win.stage === "snapped" && win.snapZone) {
|
|
528
|
-
const updated = { ...win, bounds: zoneBounds(win.snapZone, viewport) };
|
|
529
|
-
setWindow(updated);
|
|
530
|
-
queueEvent(() => emitter.emit("resize", { window: updated }));
|
|
531
|
-
} else if (win.stage === "normal" && keepInViewport) {
|
|
532
|
-
const bounds = clampToViewport(win.bounds, viewport, minVisible);
|
|
533
|
-
if (!boundsEqual(bounds, win.bounds)) {
|
|
534
|
-
const updated = { ...win, bounds };
|
|
535
|
-
setWindow(updated);
|
|
536
|
-
queueEvent(() => emitter.emit("move", { window: updated }));
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
snapshot = null;
|
|
541
|
-
batchDirty = true;
|
|
542
|
-
});
|
|
564
|
+
reflowViewport();
|
|
565
|
+
snapshot = null;
|
|
566
|
+
batchDirty = true;
|
|
543
567
|
}
|
|
544
568
|
function minimized() {
|
|
545
569
|
return Object.values(windows).filter((win) => win.stage === "minimized").sort((a, b) => a.openedSeq - b.openedSeq);
|
|
546
570
|
}
|
|
571
|
+
function captureEntry() {
|
|
572
|
+
return { windows, order, focusedId };
|
|
573
|
+
}
|
|
574
|
+
function recordHistory() {
|
|
575
|
+
if (skipNextHistory) {
|
|
576
|
+
skipNextHistory = false;
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (historyLimit <= 0) return;
|
|
580
|
+
if (interactionDepth > 0) {
|
|
581
|
+
if (interactionRecorded) return;
|
|
582
|
+
interactionRecorded = true;
|
|
583
|
+
}
|
|
584
|
+
past.push(pendingHistory);
|
|
585
|
+
if (past.length > historyLimit) past.shift();
|
|
586
|
+
future = [];
|
|
587
|
+
}
|
|
547
588
|
function transact(run) {
|
|
589
|
+
if (batchDepth === 0) pendingHistory = captureEntry();
|
|
548
590
|
batchDepth += 1;
|
|
549
591
|
try {
|
|
550
592
|
return run();
|
|
551
593
|
} finally {
|
|
552
594
|
batchDepth -= 1;
|
|
553
|
-
if (batchDepth === 0
|
|
554
|
-
batchDirty
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
595
|
+
if (batchDepth === 0) {
|
|
596
|
+
if (batchDirty) {
|
|
597
|
+
batchDirty = false;
|
|
598
|
+
recordHistory();
|
|
599
|
+
snapshot = null;
|
|
600
|
+
flushEvents();
|
|
601
|
+
emitter.emit("change", { state: getState() });
|
|
602
|
+
}
|
|
603
|
+
pendingHistory = null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function reflowViewport() {
|
|
608
|
+
for (const id of order) {
|
|
609
|
+
const win = windows[id];
|
|
610
|
+
if (win.stage === "maximized") {
|
|
611
|
+
const updated = { ...win, bounds: fullBounds() };
|
|
612
|
+
setWindow(updated);
|
|
613
|
+
queueEvent(() => emitter.emit("resize", { window: updated }));
|
|
614
|
+
} else if (win.stage === "snapped" && win.snapZone) {
|
|
615
|
+
const updated = { ...win, bounds: zoneBounds(win.snapZone, viewport) };
|
|
616
|
+
setWindow(updated);
|
|
617
|
+
queueEvent(() => emitter.emit("resize", { window: updated }));
|
|
618
|
+
} else if (win.stage === "normal" && keepInViewport) {
|
|
619
|
+
const bounds = clampToViewport(win.bounds, viewport, minVisible);
|
|
620
|
+
if (!boundsEqual(bounds, win.bounds)) {
|
|
621
|
+
const updated = { ...win, bounds };
|
|
622
|
+
setWindow(updated);
|
|
623
|
+
queueEvent(() => emitter.emit("move", { window: updated }));
|
|
624
|
+
}
|
|
558
625
|
}
|
|
559
626
|
}
|
|
560
627
|
}
|
|
628
|
+
function applyEntry(entry) {
|
|
629
|
+
windows = entry.windows;
|
|
630
|
+
order = [...entry.order];
|
|
631
|
+
focusedId = entry.focusedId;
|
|
632
|
+
reflowViewport();
|
|
633
|
+
commit();
|
|
634
|
+
}
|
|
635
|
+
function undo() {
|
|
636
|
+
const entry = past.pop();
|
|
637
|
+
if (!entry) return false;
|
|
638
|
+
future.push(captureEntry());
|
|
639
|
+
skipNextHistory = true;
|
|
640
|
+
applyEntry(entry);
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
function redo() {
|
|
644
|
+
const entry = future.pop();
|
|
645
|
+
if (!entry) return false;
|
|
646
|
+
past.push(captureEntry());
|
|
647
|
+
skipNextHistory = true;
|
|
648
|
+
applyEntry(entry);
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
function beginInteraction() {
|
|
652
|
+
interactionDepth += 1;
|
|
653
|
+
if (interactionDepth === 1) interactionRecorded = false;
|
|
654
|
+
}
|
|
655
|
+
function endInteraction() {
|
|
656
|
+
if (interactionDepth > 0) interactionDepth -= 1;
|
|
657
|
+
}
|
|
658
|
+
function clearHistory() {
|
|
659
|
+
past.length = 0;
|
|
660
|
+
future = [];
|
|
661
|
+
}
|
|
662
|
+
function saveLayout(name) {
|
|
663
|
+
const data = serialize();
|
|
664
|
+
layouts.set(name, structuredClone(data));
|
|
665
|
+
return data;
|
|
666
|
+
}
|
|
667
|
+
function loadLayout(name) {
|
|
668
|
+
const data = layouts.get(name);
|
|
669
|
+
if (!data) return false;
|
|
670
|
+
return hydrate(structuredClone(data));
|
|
671
|
+
}
|
|
672
|
+
function getLayout(name) {
|
|
673
|
+
const data = layouts.get(name);
|
|
674
|
+
return data ? structuredClone(data) : void 0;
|
|
675
|
+
}
|
|
676
|
+
function setLayout(name, data) {
|
|
677
|
+
if (!isValidSerialized(data)) return false;
|
|
678
|
+
layouts.set(name, structuredClone(data));
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
function deleteLayout(name) {
|
|
682
|
+
return layouts.delete(name);
|
|
683
|
+
}
|
|
684
|
+
function layoutNames() {
|
|
685
|
+
return [...layouts.keys()];
|
|
686
|
+
}
|
|
687
|
+
function arrange(mode) {
|
|
688
|
+
const ids = order.filter((id) => windows[id].stage !== "minimized");
|
|
689
|
+
if (ids.length === 0) return;
|
|
690
|
+
if (mode === "cascade") {
|
|
691
|
+
ids.forEach((id, index) => {
|
|
692
|
+
const win = windows[id];
|
|
693
|
+
const size = win.restoreBounds ?? win.bounds;
|
|
694
|
+
restoreTo(id, {
|
|
695
|
+
x: cascadeOrigin.x + index % 10 * cascadeOffset,
|
|
696
|
+
y: cascadeOrigin.y + index % 10 * cascadeOffset,
|
|
697
|
+
width: size.width,
|
|
698
|
+
height: size.height
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const cols = Math.ceil(Math.sqrt(ids.length));
|
|
704
|
+
const rows = Math.ceil(ids.length / cols);
|
|
705
|
+
const cellWidth = Math.floor(viewport.width / cols);
|
|
706
|
+
const cellHeight = Math.floor(viewport.height / rows);
|
|
707
|
+
ids.forEach((id, index) => {
|
|
708
|
+
restoreTo(id, {
|
|
709
|
+
x: index % cols * cellWidth,
|
|
710
|
+
y: Math.floor(index / cols) * cellHeight,
|
|
711
|
+
width: cellWidth,
|
|
712
|
+
height: cellHeight
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
function minimizeAll() {
|
|
717
|
+
for (const id of [...order]) minimize(id);
|
|
718
|
+
}
|
|
719
|
+
function restoreAll() {
|
|
720
|
+
for (const win of minimized()) restore(win.id);
|
|
721
|
+
}
|
|
561
722
|
function serialize() {
|
|
562
723
|
return {
|
|
563
724
|
version: 1,
|
|
@@ -604,10 +765,16 @@ function createWindowManager(options = {}) {
|
|
|
604
765
|
const win = value;
|
|
605
766
|
return typeof win.id === "string" && win.id.length > 0 && isBounds(win.bounds) && STAGES.includes(win.stage) && LAYERS.includes(win.layer);
|
|
606
767
|
}
|
|
607
|
-
function
|
|
768
|
+
function isValidSerialized(data) {
|
|
608
769
|
if (typeof data !== "object" || data === null) return false;
|
|
609
770
|
if (data.version !== 1 || !Array.isArray(data.windows)) return false;
|
|
610
771
|
if (!data.windows.every(isSerializedWindow)) return false;
|
|
772
|
+
const ids = new Set(data.windows.map((win) => win.id));
|
|
773
|
+
return ids.size === data.windows.length;
|
|
774
|
+
}
|
|
775
|
+
function hydrate(data) {
|
|
776
|
+
if (!isValidSerialized(data)) return false;
|
|
777
|
+
const previousWindows = windows;
|
|
611
778
|
const nextWindows = {};
|
|
612
779
|
let maxSeq = 0;
|
|
613
780
|
for (const raw of data.windows) {
|
|
@@ -659,6 +826,21 @@ function createWindowManager(options = {}) {
|
|
|
659
826
|
const focusedWin = focusedId ? windows[focusedId] : null;
|
|
660
827
|
if (focusedWin?.layer !== "modal") focusedId = modal;
|
|
661
828
|
}
|
|
829
|
+
for (const id of Object.keys(previousWindows)) {
|
|
830
|
+
if (!windows[id]) {
|
|
831
|
+
const closed = previousWindows[id];
|
|
832
|
+
queueEvent(() => emitter.emit("close", { window: closed }));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
for (const id of order) {
|
|
836
|
+
if (!previousWindows[id]) {
|
|
837
|
+
const opened = windows[id];
|
|
838
|
+
queueEvent(() => emitter.emit("open", { window: opened }));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
queueEvent(() => emitter.emit("order", { order }));
|
|
842
|
+
clearHistory();
|
|
843
|
+
skipNextHistory = true;
|
|
662
844
|
commit();
|
|
663
845
|
return true;
|
|
664
846
|
}
|
|
@@ -689,6 +871,21 @@ function createWindowManager(options = {}) {
|
|
|
689
871
|
batch: (run) => transact(run),
|
|
690
872
|
serialize,
|
|
691
873
|
hydrate: (data) => transact(() => hydrate(data)),
|
|
874
|
+
undo: () => transact(undo),
|
|
875
|
+
redo: () => transact(redo),
|
|
876
|
+
canUndo: () => past.length > 0,
|
|
877
|
+
canRedo: () => future.length > 0,
|
|
878
|
+
beginInteraction,
|
|
879
|
+
endInteraction,
|
|
880
|
+
saveLayout,
|
|
881
|
+
loadLayout: (name) => transact(() => loadLayout(name)),
|
|
882
|
+
getLayout,
|
|
883
|
+
setLayout,
|
|
884
|
+
deleteLayout,
|
|
885
|
+
layoutNames,
|
|
886
|
+
arrange: (mode) => transact(() => arrange(mode)),
|
|
887
|
+
minimizeAll: () => transact(minimizeAll),
|
|
888
|
+
restoreAll: () => transact(restoreAll),
|
|
692
889
|
subscribe: (listener) => emitter.on("change", ({ state }) => listener(state)),
|
|
693
890
|
on: (event, listener) => emitter.on(event, listener),
|
|
694
891
|
destroy
|
|
@@ -699,6 +896,34 @@ function createWindowManager(options = {}) {
|
|
|
699
896
|
function prefersReducedMotion(win) {
|
|
700
897
|
return win.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
701
898
|
}
|
|
899
|
+
function flipFromTarget(source, target, options = {}) {
|
|
900
|
+
const view = source.ownerDocument.defaultView;
|
|
901
|
+
if (!view || prefersReducedMotion(view)) return;
|
|
902
|
+
if (typeof source.animate !== "function") return;
|
|
903
|
+
const from = target.getBoundingClientRect();
|
|
904
|
+
const to = source.getBoundingClientRect();
|
|
905
|
+
if (from.width === 0 || to.width === 0) return;
|
|
906
|
+
const ghost = source.ownerDocument.createElement("div");
|
|
907
|
+
const style = view.getComputedStyle(source);
|
|
908
|
+
ghost.style.cssText = `position:fixed;left:${to.left}px;top:${to.top}px;width:${to.width}px;height:${to.height}px;margin:0;pointer-events:none;z-index:2147483647;border-radius:${style.borderRadius};background:${style.backgroundColor};box-shadow:${style.boxShadow};transform-origin:top left;will-change:transform,opacity`;
|
|
909
|
+
source.ownerDocument.body.append(ghost);
|
|
910
|
+
const dx = from.left + from.width / 2 - (to.left + to.width / 2);
|
|
911
|
+
const dy = from.top + from.height / 2 - (to.top + to.height / 2);
|
|
912
|
+
const scaleX = Math.max(from.width / to.width, 0.05);
|
|
913
|
+
const scaleY = Math.max(from.height / to.height, 0.05);
|
|
914
|
+
const animation = ghost.animate(
|
|
915
|
+
[
|
|
916
|
+
{ transform: `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`, opacity: 0.2 },
|
|
917
|
+
{ transform: "translate(0, 0) scale(1, 1)", opacity: 0.9 }
|
|
918
|
+
],
|
|
919
|
+
{
|
|
920
|
+
duration: options.duration ?? 260,
|
|
921
|
+
easing: options.easing ?? "cubic-bezier(0.32, 0.72, 0, 1)"
|
|
922
|
+
}
|
|
923
|
+
);
|
|
924
|
+
animation.onfinish = () => ghost.remove();
|
|
925
|
+
animation.oncancel = () => ghost.remove();
|
|
926
|
+
}
|
|
702
927
|
function flipToTarget(source, target, options = {}) {
|
|
703
928
|
const view = source.ownerDocument.defaultView;
|
|
704
929
|
if (!view || prefersReducedMotion(view)) return;
|
|
@@ -776,175 +1001,25 @@ function createAnnouncer(wm, container, messages = {}) {
|
|
|
776
1001
|
};
|
|
777
1002
|
}
|
|
778
1003
|
|
|
779
|
-
// src/dom/
|
|
780
|
-
var RESIZE_DIRECTIONS = ["n", "e", "s", "w", "ne", "nw", "se", "sw"];
|
|
781
|
-
var RESIZE_CURSORS = {
|
|
782
|
-
n: "ns-resize",
|
|
783
|
-
s: "ns-resize",
|
|
784
|
-
e: "ew-resize",
|
|
785
|
-
w: "ew-resize",
|
|
786
|
-
ne: "nesw-resize",
|
|
787
|
-
sw: "nesw-resize",
|
|
788
|
-
nw: "nwse-resize",
|
|
789
|
-
se: "nwse-resize"
|
|
790
|
-
};
|
|
1004
|
+
// src/dom/shared.ts
|
|
791
1005
|
var INTERACTIVE_SELECTOR = "button, input, select, textarea, a[href], [contenteditable], [data-wm-close], [data-wm-minimize], [data-wm-maximize]";
|
|
792
1006
|
function windowOf(element) {
|
|
793
1007
|
const view = element.ownerDocument.defaultView;
|
|
794
1008
|
if (!view) throw new Error("wmkit: desktop element is not attached to a document");
|
|
795
1009
|
return view;
|
|
796
1010
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
const snapPreviewEnabled = snapOptions.preview !== false;
|
|
803
|
-
const topEdge = snapOptions.topEdge ?? "maximize";
|
|
804
|
-
const keyboardEnabled = options.keyboard !== false;
|
|
805
|
-
const keyboardOptions = typeof options.keyboard === "object" ? options.keyboard : {};
|
|
806
|
-
const moveStep = keyboardOptions.moveStep ?? 16;
|
|
807
|
-
const cycleEnabled = keyboardOptions.cycle !== false;
|
|
808
|
-
element.dataset.wmDesktop = "";
|
|
809
|
-
if (view.getComputedStyle(element).position === "static") {
|
|
810
|
-
element.style.position = "relative";
|
|
811
|
-
}
|
|
812
|
-
const registry = /* @__PURE__ */ new Map();
|
|
813
|
-
const cleanup = [];
|
|
814
|
-
let lastOrder = null;
|
|
815
|
-
let lastFocused = null;
|
|
816
|
-
let drag = null;
|
|
817
|
-
let announcer = null;
|
|
818
|
-
if (options.announce !== false) {
|
|
819
|
-
announcer = createAnnouncer(
|
|
820
|
-
wm,
|
|
821
|
-
element,
|
|
822
|
-
typeof options.announce === "object" ? options.announce : {}
|
|
823
|
-
);
|
|
824
|
-
cleanup.push(() => announcer?.destroy());
|
|
825
|
-
}
|
|
826
|
-
let preview = null;
|
|
827
|
-
function showPreview(bounds) {
|
|
828
|
-
if (!snapPreviewEnabled) return;
|
|
829
|
-
if (!preview) {
|
|
830
|
-
preview = doc.createElement("div");
|
|
831
|
-
preview.dataset.wmSnapPreview = "";
|
|
832
|
-
preview.style.cssText = "position:absolute;left:0;top:0;pointer-events:none;display:none;z-index:2147483646";
|
|
833
|
-
element.append(preview);
|
|
834
|
-
}
|
|
835
|
-
preview.style.display = "block";
|
|
836
|
-
preview.style.transform = `translate3d(${bounds.x}px, ${bounds.y}px, 0)`;
|
|
837
|
-
preview.style.width = `${bounds.width}px`;
|
|
838
|
-
preview.style.height = `${bounds.height}px`;
|
|
839
|
-
}
|
|
840
|
-
function hidePreview() {
|
|
841
|
-
if (preview) preview.style.display = "none";
|
|
842
|
-
}
|
|
843
|
-
if (options.autoViewport !== false) {
|
|
844
|
-
const applyViewport = () => wm.setViewport({ width: element.clientWidth, height: element.clientHeight });
|
|
845
|
-
applyViewport();
|
|
846
|
-
const observer = new ResizeObserver(applyViewport);
|
|
847
|
-
observer.observe(element);
|
|
848
|
-
cleanup.push(() => observer.disconnect());
|
|
849
|
-
}
|
|
850
|
-
function syncWindow(attached, win, zIndex) {
|
|
851
|
-
const el = attached.element;
|
|
852
|
-
const firstSync = attached.lastState === null;
|
|
853
|
-
if (firstSync) el.style.transition = "none";
|
|
854
|
-
if (attached.lastState !== win) {
|
|
855
|
-
el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
|
|
856
|
-
el.style.width = `${win.bounds.width}px`;
|
|
857
|
-
el.style.height = `${win.bounds.height}px`;
|
|
858
|
-
el.dataset.wmStage = win.stage;
|
|
859
|
-
el.dataset.wmLayer = win.layer;
|
|
860
|
-
el.hidden = win.stage === "minimized";
|
|
861
|
-
el.setAttribute("aria-label", win.title);
|
|
862
|
-
if (win.layer === "modal") el.setAttribute("aria-modal", "true");
|
|
863
|
-
else el.removeAttribute("aria-modal");
|
|
864
|
-
for (const handle of attached.handles) {
|
|
865
|
-
handle.style.display = win.resizable && win.stage === "normal" ? "" : "none";
|
|
866
|
-
}
|
|
867
|
-
attached.lastState = win;
|
|
868
|
-
}
|
|
869
|
-
el.style.zIndex = String(zIndex + 1);
|
|
870
|
-
if (firstSync) {
|
|
871
|
-
void el.offsetWidth;
|
|
872
|
-
el.style.transition = "";
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
function syncAll() {
|
|
876
|
-
const state = wm.getState();
|
|
877
|
-
const orderChanged = state.order !== lastOrder;
|
|
878
|
-
state.order.forEach((id, index) => {
|
|
879
|
-
const attached = registry.get(id);
|
|
880
|
-
const win = state.windows[id];
|
|
881
|
-
if (!attached || !win) return;
|
|
882
|
-
if (orderChanged || attached.lastState !== win) syncWindow(attached, win, index);
|
|
883
|
-
});
|
|
884
|
-
if (state.focusedId !== lastFocused) {
|
|
885
|
-
if (lastFocused) {
|
|
886
|
-
const prev = registry.get(lastFocused);
|
|
887
|
-
if (prev) delete prev.element.dataset.wmFocused;
|
|
888
|
-
}
|
|
889
|
-
if (state.focusedId) {
|
|
890
|
-
const next = registry.get(state.focusedId);
|
|
891
|
-
if (next) next.element.dataset.wmFocused = "";
|
|
892
|
-
}
|
|
893
|
-
lastFocused = state.focusedId;
|
|
894
|
-
}
|
|
895
|
-
lastOrder = state.order;
|
|
896
|
-
}
|
|
897
|
-
cleanup.push(wm.subscribe(syncAll));
|
|
898
|
-
cleanup.push(
|
|
899
|
-
wm.on("focus", ({ window: win }) => {
|
|
900
|
-
const attached = registry.get(win.id);
|
|
901
|
-
if (!attached) return;
|
|
902
|
-
if (!attached.element.contains(doc.activeElement)) {
|
|
903
|
-
attached.element.focus({ preventScroll: true });
|
|
904
|
-
}
|
|
905
|
-
})
|
|
906
|
-
);
|
|
907
|
-
cleanup.push(
|
|
908
|
-
wm.on("modalblocked", ({ window: win }) => {
|
|
909
|
-
const attached = registry.get(win.id);
|
|
910
|
-
if (!attached) return;
|
|
911
|
-
delete attached.element.dataset.wmFlash;
|
|
912
|
-
void attached.element.offsetWidth;
|
|
913
|
-
attached.element.dataset.wmFlash = "";
|
|
914
|
-
})
|
|
915
|
-
);
|
|
916
|
-
cleanup.push(
|
|
917
|
-
wm.on("stage", ({ window: win, previous }) => {
|
|
918
|
-
if (win.stage !== "minimized" || previous === "minimized") return;
|
|
919
|
-
const attached = registry.get(win.id);
|
|
920
|
-
const target = options.minimizeTarget?.(win);
|
|
921
|
-
if (attached && target) flipToTarget(attached.element, target);
|
|
922
|
-
})
|
|
923
|
-
);
|
|
924
|
-
if (keyboardEnabled && cycleEnabled) {
|
|
925
|
-
const onDesktopKeydown = (event) => {
|
|
926
|
-
if (event.key === "F6") {
|
|
927
|
-
event.preventDefault();
|
|
928
|
-
wm.cycleFocus(event.shiftKey ? -1 : 1);
|
|
929
|
-
}
|
|
930
|
-
};
|
|
931
|
-
element.addEventListener("keydown", onDesktopKeydown);
|
|
932
|
-
cleanup.push(() => element.removeEventListener("keydown", onDesktopKeydown));
|
|
933
|
-
}
|
|
934
|
-
function desktopPoint(event) {
|
|
935
|
-
const rect = element.getBoundingClientRect();
|
|
936
|
-
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
937
|
-
}
|
|
938
|
-
function endDrag(cancelled) {
|
|
939
|
-
if (drag) drag.finish(cancelled);
|
|
940
|
-
}
|
|
941
|
-
function startDrag(id, handle, event) {
|
|
1011
|
+
|
|
1012
|
+
// src/dom/drag.ts
|
|
1013
|
+
function createDragStarter(ctx) {
|
|
1014
|
+
const { wm, doc, view } = ctx;
|
|
1015
|
+
return function startDrag(id, handle, event) {
|
|
942
1016
|
const win = wm.get(id);
|
|
943
|
-
if (!win?.draggable || event.button !== 0 ||
|
|
1017
|
+
if (!win?.draggable || event.button !== 0 || ctx.currentDrag()) return;
|
|
944
1018
|
const target = event.target;
|
|
945
1019
|
if (target?.closest(INTERACTIVE_SELECTOR)) return;
|
|
946
1020
|
event.preventDefault();
|
|
947
|
-
const
|
|
1021
|
+
const releaseRect = ctx.trackRect();
|
|
1022
|
+
const point = ctx.toLocal(event);
|
|
948
1023
|
const startBounds = win.bounds;
|
|
949
1024
|
const session = {
|
|
950
1025
|
id,
|
|
@@ -966,10 +1041,9 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
966
1041
|
finish: () => {
|
|
967
1042
|
}
|
|
968
1043
|
};
|
|
969
|
-
const
|
|
970
|
-
const el = attached?.element;
|
|
1044
|
+
const el = ctx.windowElement(id);
|
|
971
1045
|
function flush() {
|
|
972
|
-
if (!session.hasPending ||
|
|
1046
|
+
if (!session.hasPending || ctx.currentDrag() !== session) return;
|
|
973
1047
|
session.hasPending = false;
|
|
974
1048
|
session.raf = 0;
|
|
975
1049
|
const current = wm.get(id);
|
|
@@ -992,33 +1066,56 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
992
1066
|
}
|
|
993
1067
|
return;
|
|
994
1068
|
}
|
|
995
|
-
|
|
996
|
-
|
|
1069
|
+
let nextX = session.pendingX - session.grabDX;
|
|
1070
|
+
let nextY = session.pendingY - session.grabDY;
|
|
1071
|
+
if (ctx.magnetThreshold > 0) {
|
|
1072
|
+
const state = wm.getState();
|
|
1073
|
+
const targets = [
|
|
1074
|
+
{ x: 0, y: 0, width: state.viewport.width, height: state.viewport.height }
|
|
1075
|
+
];
|
|
1076
|
+
for (const otherId of state.order) {
|
|
1077
|
+
if (otherId === id) continue;
|
|
1078
|
+
const other = state.windows[otherId];
|
|
1079
|
+
if (other && other.stage !== "minimized") targets.push(other.bounds);
|
|
1080
|
+
}
|
|
1081
|
+
const magnet = magnetize(
|
|
1082
|
+
{ x: nextX, y: nextY, width: current.bounds.width, height: current.bounds.height },
|
|
1083
|
+
targets,
|
|
1084
|
+
ctx.magnetThreshold
|
|
1085
|
+
);
|
|
1086
|
+
nextX = magnet.x;
|
|
1087
|
+
nextY = magnet.y;
|
|
1088
|
+
}
|
|
1089
|
+
wm.move(id, nextX, nextY);
|
|
1090
|
+
if (ctx.snapEnabled && current.snappable) {
|
|
997
1091
|
const viewport = wm.getState().viewport;
|
|
998
|
-
const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport,
|
|
1092
|
+
const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect);
|
|
999
1093
|
let zone = rawZone;
|
|
1000
1094
|
if (rawZone === "top") {
|
|
1001
|
-
if (topEdge === "maximize") zone = current.maximizable ? "maximize" : null;
|
|
1002
|
-
else if (topEdge === "none") zone = null;
|
|
1095
|
+
if (ctx.topEdge === "maximize") zone = current.maximizable ? "maximize" : null;
|
|
1096
|
+
else if (ctx.topEdge === "none") zone = null;
|
|
1003
1097
|
}
|
|
1004
1098
|
session.zone = zone;
|
|
1005
1099
|
if (zone) {
|
|
1006
|
-
showPreview(
|
|
1100
|
+
ctx.showPreview(
|
|
1007
1101
|
zone === "maximize" ? { x: 0, y: 0, width: viewport.width, height: viewport.height } : zoneBounds(zone, viewport)
|
|
1008
1102
|
);
|
|
1009
1103
|
} else {
|
|
1010
|
-
hidePreview();
|
|
1104
|
+
ctx.hidePreview();
|
|
1011
1105
|
}
|
|
1012
1106
|
}
|
|
1013
1107
|
}
|
|
1014
1108
|
function onMove(moveEvent) {
|
|
1015
1109
|
if (moveEvent.pointerId !== session.pointerId) return;
|
|
1016
|
-
const movePoint =
|
|
1110
|
+
const movePoint = ctx.toLocal(moveEvent);
|
|
1017
1111
|
if (!session.moved) {
|
|
1018
1112
|
const travelled = Math.abs(movePoint.x - (session.startBounds.x + session.grabDX)) + Math.abs(movePoint.y - (session.startBounds.y + session.grabDY));
|
|
1019
1113
|
if (travelled < 3 && session.restored) return;
|
|
1020
1114
|
session.moved = true;
|
|
1021
|
-
if (el)
|
|
1115
|
+
if (el) {
|
|
1116
|
+
el.dataset.wmDragging = "";
|
|
1117
|
+
el.style.willChange = "transform";
|
|
1118
|
+
}
|
|
1022
1119
|
}
|
|
1023
1120
|
session.pendingX = movePoint.x;
|
|
1024
1121
|
session.pendingY = movePoint.y;
|
|
@@ -1040,10 +1137,11 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1040
1137
|
}
|
|
1041
1138
|
}
|
|
1042
1139
|
function finish(cancelled) {
|
|
1043
|
-
if (
|
|
1044
|
-
if (session.raf !== 0) view
|
|
1140
|
+
if (ctx.currentDrag() !== session) return;
|
|
1141
|
+
if (session.raf !== 0) view.cancelAnimationFrame(session.raf);
|
|
1045
1142
|
if (session.hasPending && !cancelled) flush();
|
|
1046
|
-
|
|
1143
|
+
ctx.releaseDrag(session);
|
|
1144
|
+
releaseRect();
|
|
1047
1145
|
handle.removeEventListener("pointermove", onMove);
|
|
1048
1146
|
handle.removeEventListener("pointerup", onUp);
|
|
1049
1147
|
handle.removeEventListener("pointercancel", onCancel);
|
|
@@ -1051,8 +1149,11 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1051
1149
|
if (handle.hasPointerCapture(session.pointerId)) {
|
|
1052
1150
|
handle.releasePointerCapture(session.pointerId);
|
|
1053
1151
|
}
|
|
1054
|
-
if (el)
|
|
1055
|
-
|
|
1152
|
+
if (el) {
|
|
1153
|
+
delete el.dataset.wmDragging;
|
|
1154
|
+
el.style.willChange = "";
|
|
1155
|
+
}
|
|
1156
|
+
ctx.hidePreview();
|
|
1056
1157
|
if (cancelled) {
|
|
1057
1158
|
if (session.moved && session.restored) {
|
|
1058
1159
|
if (session.startStage === "maximized") {
|
|
@@ -1065,28 +1166,49 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1065
1166
|
wm.move(id, session.startBounds.x, session.startBounds.y);
|
|
1066
1167
|
}
|
|
1067
1168
|
}
|
|
1169
|
+
wm.endInteraction();
|
|
1068
1170
|
return;
|
|
1069
1171
|
}
|
|
1070
1172
|
if (session.moved && session.zone) {
|
|
1071
1173
|
if (session.zone === "maximize") wm.maximize(id);
|
|
1072
1174
|
else wm.snap(id, session.zone);
|
|
1073
1175
|
}
|
|
1176
|
+
wm.endInteraction();
|
|
1074
1177
|
}
|
|
1075
1178
|
session.finish = finish;
|
|
1076
|
-
|
|
1179
|
+
wm.beginInteraction();
|
|
1180
|
+
ctx.claimDrag(session);
|
|
1077
1181
|
handle.setPointerCapture(event.pointerId);
|
|
1078
1182
|
handle.addEventListener("pointermove", onMove);
|
|
1079
1183
|
handle.addEventListener("pointerup", onUp);
|
|
1080
1184
|
handle.addEventListener("pointercancel", onCancel);
|
|
1081
1185
|
doc.addEventListener("keydown", onKeydown, true);
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/dom/resize.ts
|
|
1190
|
+
var RESIZE_DIRECTIONS = ["n", "e", "s", "w", "ne", "nw", "se", "sw"];
|
|
1191
|
+
var RESIZE_CURSORS = {
|
|
1192
|
+
n: "ns-resize",
|
|
1193
|
+
s: "ns-resize",
|
|
1194
|
+
e: "ew-resize",
|
|
1195
|
+
w: "ew-resize",
|
|
1196
|
+
ne: "nesw-resize",
|
|
1197
|
+
sw: "nesw-resize",
|
|
1198
|
+
nw: "nwse-resize",
|
|
1199
|
+
se: "nwse-resize"
|
|
1200
|
+
};
|
|
1201
|
+
function createResizeStarter(ctx) {
|
|
1202
|
+
const { wm, doc, view } = ctx;
|
|
1203
|
+
return function startResize(id, direction, event) {
|
|
1084
1204
|
const win = wm.get(id);
|
|
1085
|
-
if (!win?.resizable || event.button !== 0 ||
|
|
1205
|
+
if (!win?.resizable || event.button !== 0 || ctx.currentDrag()) return;
|
|
1086
1206
|
event.preventDefault();
|
|
1087
1207
|
event.stopPropagation();
|
|
1088
1208
|
const handleEl = event.currentTarget;
|
|
1089
|
-
|
|
1209
|
+
wm.beginInteraction();
|
|
1210
|
+
const releaseRect = ctx.trackRect();
|
|
1211
|
+
const startPoint = ctx.toLocal(event);
|
|
1090
1212
|
const start = win.bounds;
|
|
1091
1213
|
const minSize = win.minSize;
|
|
1092
1214
|
const maxSize = win.maxSize;
|
|
@@ -1094,8 +1216,8 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1094
1216
|
let pendingX = 0;
|
|
1095
1217
|
let pendingY = 0;
|
|
1096
1218
|
let hasPending = false;
|
|
1097
|
-
const
|
|
1098
|
-
if (
|
|
1219
|
+
const el = ctx.windowElement(id);
|
|
1220
|
+
if (el) el.dataset.wmResizing = direction;
|
|
1099
1221
|
function clampWidth(width) {
|
|
1100
1222
|
return clamp(width, minSize.width, maxSize ? maxSize.width : Number.POSITIVE_INFINITY);
|
|
1101
1223
|
}
|
|
@@ -1123,15 +1245,16 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1123
1245
|
}
|
|
1124
1246
|
function onMove(moveEvent) {
|
|
1125
1247
|
if (moveEvent.pointerId !== event.pointerId) return;
|
|
1126
|
-
const movePoint =
|
|
1248
|
+
const movePoint = ctx.toLocal(moveEvent);
|
|
1127
1249
|
pendingX = movePoint.x;
|
|
1128
1250
|
pendingY = movePoint.y;
|
|
1129
1251
|
hasPending = true;
|
|
1130
|
-
if (raf === 0) raf = view
|
|
1252
|
+
if (raf === 0) raf = view.requestAnimationFrame(flush);
|
|
1131
1253
|
}
|
|
1132
1254
|
function finish(cancelled) {
|
|
1133
|
-
if (raf !== 0) view
|
|
1255
|
+
if (raf !== 0) view.cancelAnimationFrame(raf);
|
|
1134
1256
|
if (hasPending && !cancelled) flush();
|
|
1257
|
+
releaseRect();
|
|
1135
1258
|
handleEl.removeEventListener("pointermove", onMove);
|
|
1136
1259
|
handleEl.removeEventListener("pointerup", onUp);
|
|
1137
1260
|
handleEl.removeEventListener("pointercancel", onCancelPointer);
|
|
@@ -1139,8 +1262,9 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1139
1262
|
if (handleEl.hasPointerCapture(event.pointerId)) {
|
|
1140
1263
|
handleEl.releasePointerCapture(event.pointerId);
|
|
1141
1264
|
}
|
|
1142
|
-
if (
|
|
1265
|
+
if (el) delete el.dataset.wmResizing;
|
|
1143
1266
|
if (cancelled) wm.resize(id, start);
|
|
1267
|
+
wm.endInteraction();
|
|
1144
1268
|
}
|
|
1145
1269
|
function onUp(upEvent) {
|
|
1146
1270
|
if (upEvent.pointerId !== event.pointerId) return;
|
|
@@ -1161,6 +1285,227 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1161
1285
|
handleEl.addEventListener("pointerup", onUp);
|
|
1162
1286
|
handleEl.addEventListener("pointercancel", onCancelPointer);
|
|
1163
1287
|
doc.addEventListener("keydown", onKeydown, true);
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function createResizeHandles(doc, edge, corner) {
|
|
1291
|
+
const base = "position:absolute;touch-action:none;user-select:none;-webkit-user-select:none;";
|
|
1292
|
+
const styles = {
|
|
1293
|
+
n: `top:${-edge / 2}px;left:${corner}px;right:${corner}px;height:${edge}px`,
|
|
1294
|
+
s: `bottom:${-edge / 2}px;left:${corner}px;right:${corner}px;height:${edge}px`,
|
|
1295
|
+
e: `right:${-edge / 2}px;top:${corner}px;bottom:${corner}px;width:${edge}px`,
|
|
1296
|
+
w: `left:${-edge / 2}px;top:${corner}px;bottom:${corner}px;width:${edge}px`,
|
|
1297
|
+
ne: `top:${-edge / 2}px;right:${-edge / 2}px;width:${corner}px;height:${corner}px`,
|
|
1298
|
+
nw: `top:${-edge / 2}px;left:${-edge / 2}px;width:${corner}px;height:${corner}px`,
|
|
1299
|
+
se: `bottom:${-edge / 2}px;right:${-edge / 2}px;width:${corner}px;height:${corner}px`,
|
|
1300
|
+
sw: `bottom:${-edge / 2}px;left:${-edge / 2}px;width:${corner}px;height:${corner}px`
|
|
1301
|
+
};
|
|
1302
|
+
return RESIZE_DIRECTIONS.map((direction) => {
|
|
1303
|
+
const element = doc.createElement("div");
|
|
1304
|
+
element.dataset.wmResize = direction;
|
|
1305
|
+
element.setAttribute("aria-hidden", "true");
|
|
1306
|
+
element.style.cssText = `${base}${styles[direction]};cursor:${RESIZE_CURSORS[direction]}`;
|
|
1307
|
+
return { element, direction };
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/dom/controller.ts
|
|
1312
|
+
function attachDesktop(wm, element, options = {}) {
|
|
1313
|
+
const doc = element.ownerDocument;
|
|
1314
|
+
const view = windowOf(element);
|
|
1315
|
+
const coarsePointer = typeof view.matchMedia === "function" && view.matchMedia("(pointer: coarse)").matches;
|
|
1316
|
+
const snapEnabled = options.snap !== false;
|
|
1317
|
+
const snapOptions = typeof options.snap === "object" ? options.snap : {};
|
|
1318
|
+
const snapPreviewEnabled = snapOptions.preview !== false;
|
|
1319
|
+
const topEdge = snapOptions.topEdge ?? "maximize";
|
|
1320
|
+
const keyboardEnabled = options.keyboard !== false;
|
|
1321
|
+
const keyboardOptions = typeof options.keyboard === "object" ? options.keyboard : {};
|
|
1322
|
+
const moveStep = keyboardOptions.moveStep ?? 16;
|
|
1323
|
+
const cycleEnabled = keyboardOptions.cycle !== false;
|
|
1324
|
+
const hitEdge = options.hitAreas?.edge ?? (coarsePointer ? 16 : 8);
|
|
1325
|
+
const hitCorner = options.hitAreas?.corner ?? (coarsePointer ? 24 : 12);
|
|
1326
|
+
const magnetThreshold = options.magnetism === false ? 0 : (typeof options.magnetism === "object" ? options.magnetism.threshold : void 0) ?? (coarsePointer ? 12 : 8);
|
|
1327
|
+
element.dataset.wmDesktop = "";
|
|
1328
|
+
if (view.getComputedStyle(element).position === "static") {
|
|
1329
|
+
element.style.position = "relative";
|
|
1330
|
+
}
|
|
1331
|
+
const registry = /* @__PURE__ */ new Map();
|
|
1332
|
+
const cleanup = [];
|
|
1333
|
+
let lastOrder = null;
|
|
1334
|
+
let lastFocused = null;
|
|
1335
|
+
let drag = null;
|
|
1336
|
+
let cachedRect = null;
|
|
1337
|
+
let rectUsers = 0;
|
|
1338
|
+
let announcer = null;
|
|
1339
|
+
if (options.announce !== false) {
|
|
1340
|
+
announcer = createAnnouncer(
|
|
1341
|
+
wm,
|
|
1342
|
+
element,
|
|
1343
|
+
typeof options.announce === "object" ? options.announce : {}
|
|
1344
|
+
);
|
|
1345
|
+
cleanup.push(() => announcer?.destroy());
|
|
1346
|
+
}
|
|
1347
|
+
let preview = null;
|
|
1348
|
+
function showPreview(bounds) {
|
|
1349
|
+
if (!snapPreviewEnabled) return;
|
|
1350
|
+
if (!preview) {
|
|
1351
|
+
preview = doc.createElement("div");
|
|
1352
|
+
preview.dataset.wmSnapPreview = "";
|
|
1353
|
+
preview.style.cssText = "position:absolute;left:0;top:0;pointer-events:none;display:none;z-index:2147483646";
|
|
1354
|
+
element.append(preview);
|
|
1355
|
+
}
|
|
1356
|
+
preview.style.display = "block";
|
|
1357
|
+
preview.style.transform = `translate3d(${bounds.x}px, ${bounds.y}px, 0)`;
|
|
1358
|
+
preview.style.width = `${bounds.width}px`;
|
|
1359
|
+
preview.style.height = `${bounds.height}px`;
|
|
1360
|
+
}
|
|
1361
|
+
function hidePreview() {
|
|
1362
|
+
if (preview) preview.style.display = "none";
|
|
1363
|
+
}
|
|
1364
|
+
const ctx = {
|
|
1365
|
+
wm,
|
|
1366
|
+
doc,
|
|
1367
|
+
view,
|
|
1368
|
+
toLocal(event) {
|
|
1369
|
+
const rect = cachedRect ?? element.getBoundingClientRect();
|
|
1370
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
1371
|
+
},
|
|
1372
|
+
trackRect() {
|
|
1373
|
+
if (rectUsers === 0) {
|
|
1374
|
+
const rect = element.getBoundingClientRect();
|
|
1375
|
+
cachedRect = { left: rect.left, top: rect.top };
|
|
1376
|
+
}
|
|
1377
|
+
rectUsers += 1;
|
|
1378
|
+
let released = false;
|
|
1379
|
+
return () => {
|
|
1380
|
+
if (released) return;
|
|
1381
|
+
released = true;
|
|
1382
|
+
rectUsers -= 1;
|
|
1383
|
+
if (rectUsers === 0) cachedRect = null;
|
|
1384
|
+
};
|
|
1385
|
+
},
|
|
1386
|
+
windowElement: (id) => registry.get(id)?.element,
|
|
1387
|
+
showPreview,
|
|
1388
|
+
hidePreview,
|
|
1389
|
+
snapEnabled,
|
|
1390
|
+
snapDetect: {
|
|
1391
|
+
threshold: snapOptions.threshold ?? (coarsePointer ? 20 : 12),
|
|
1392
|
+
cornerSize: snapOptions.cornerSize ?? (coarsePointer ? 96 : 64)
|
|
1393
|
+
},
|
|
1394
|
+
topEdge,
|
|
1395
|
+
hitEdge,
|
|
1396
|
+
hitCorner,
|
|
1397
|
+
magnetThreshold,
|
|
1398
|
+
currentDrag: () => drag,
|
|
1399
|
+
claimDrag(session) {
|
|
1400
|
+
drag = session;
|
|
1401
|
+
},
|
|
1402
|
+
releaseDrag(session) {
|
|
1403
|
+
if (drag === session) drag = null;
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
const startDrag = createDragStarter(ctx);
|
|
1407
|
+
const startResize = createResizeStarter(ctx);
|
|
1408
|
+
if (options.autoViewport !== false) {
|
|
1409
|
+
const applyViewport = () => wm.setViewport({ width: element.clientWidth, height: element.clientHeight });
|
|
1410
|
+
applyViewport();
|
|
1411
|
+
const observer = new ResizeObserver(applyViewport);
|
|
1412
|
+
observer.observe(element);
|
|
1413
|
+
cleanup.push(() => observer.disconnect());
|
|
1414
|
+
}
|
|
1415
|
+
function syncWindow(attached, win, zIndex) {
|
|
1416
|
+
const el = attached.element;
|
|
1417
|
+
const firstSync = attached.lastState === null;
|
|
1418
|
+
if (firstSync) el.style.transition = "none";
|
|
1419
|
+
if (attached.lastState !== win) {
|
|
1420
|
+
el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`;
|
|
1421
|
+
el.style.width = `${win.bounds.width}px`;
|
|
1422
|
+
el.style.height = `${win.bounds.height}px`;
|
|
1423
|
+
el.dataset.wmStage = win.stage;
|
|
1424
|
+
el.dataset.wmLayer = win.layer;
|
|
1425
|
+
el.hidden = win.stage === "minimized";
|
|
1426
|
+
el.setAttribute("aria-label", win.title);
|
|
1427
|
+
if (win.layer === "modal") el.setAttribute("aria-modal", "true");
|
|
1428
|
+
else el.removeAttribute("aria-modal");
|
|
1429
|
+
for (const handle of attached.handles) {
|
|
1430
|
+
handle.style.display = win.resizable && win.stage === "normal" ? "" : "none";
|
|
1431
|
+
}
|
|
1432
|
+
attached.lastState = win;
|
|
1433
|
+
}
|
|
1434
|
+
if (attached.lastZ !== zIndex) {
|
|
1435
|
+
el.style.zIndex = String(zIndex + 1);
|
|
1436
|
+
attached.lastZ = zIndex;
|
|
1437
|
+
}
|
|
1438
|
+
if (firstSync) {
|
|
1439
|
+
void el.offsetWidth;
|
|
1440
|
+
el.style.transition = "";
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function syncAll() {
|
|
1444
|
+
const state = wm.getState();
|
|
1445
|
+
const orderChanged = state.order !== lastOrder;
|
|
1446
|
+
state.order.forEach((id, index) => {
|
|
1447
|
+
const attached = registry.get(id);
|
|
1448
|
+
const win = state.windows[id];
|
|
1449
|
+
if (!attached || !win) return;
|
|
1450
|
+
if (orderChanged || attached.lastState !== win) syncWindow(attached, win, index);
|
|
1451
|
+
});
|
|
1452
|
+
if (state.focusedId !== lastFocused) {
|
|
1453
|
+
if (lastFocused) {
|
|
1454
|
+
const prev = registry.get(lastFocused);
|
|
1455
|
+
if (prev) delete prev.element.dataset.wmFocused;
|
|
1456
|
+
}
|
|
1457
|
+
if (state.focusedId) {
|
|
1458
|
+
const next = registry.get(state.focusedId);
|
|
1459
|
+
if (next) next.element.dataset.wmFocused = "";
|
|
1460
|
+
}
|
|
1461
|
+
lastFocused = state.focusedId;
|
|
1462
|
+
}
|
|
1463
|
+
lastOrder = state.order;
|
|
1464
|
+
}
|
|
1465
|
+
cleanup.push(wm.subscribe(syncAll));
|
|
1466
|
+
cleanup.push(
|
|
1467
|
+
wm.on("focus", ({ window: win }) => {
|
|
1468
|
+
const attached = registry.get(win.id);
|
|
1469
|
+
if (!attached) return;
|
|
1470
|
+
if (!attached.element.contains(doc.activeElement)) {
|
|
1471
|
+
attached.element.focus({ preventScroll: true });
|
|
1472
|
+
}
|
|
1473
|
+
})
|
|
1474
|
+
);
|
|
1475
|
+
cleanup.push(
|
|
1476
|
+
wm.on("modalblocked", ({ window: win }) => {
|
|
1477
|
+
const attached = registry.get(win.id);
|
|
1478
|
+
if (!attached) return;
|
|
1479
|
+
delete attached.element.dataset.wmFlash;
|
|
1480
|
+
void attached.element.offsetWidth;
|
|
1481
|
+
attached.element.dataset.wmFlash = "";
|
|
1482
|
+
})
|
|
1483
|
+
);
|
|
1484
|
+
cleanup.push(
|
|
1485
|
+
wm.on("stage", ({ window: win, previous }) => {
|
|
1486
|
+
const attached = registry.get(win.id);
|
|
1487
|
+
if (!attached) return;
|
|
1488
|
+
if (win.stage === "minimized" && previous !== "minimized") {
|
|
1489
|
+
const target = options.minimizeTarget?.(win);
|
|
1490
|
+
if (target) flipToTarget(attached.element, target);
|
|
1491
|
+
} else if (previous === "minimized" && win.stage !== "minimized") {
|
|
1492
|
+
const target = options.minimizeTarget?.(win);
|
|
1493
|
+
if (target) flipFromTarget(attached.element, target);
|
|
1494
|
+
}
|
|
1495
|
+
})
|
|
1496
|
+
);
|
|
1497
|
+
if (keyboardEnabled && cycleEnabled) {
|
|
1498
|
+
const onDesktopKeydown = (event) => {
|
|
1499
|
+
if (event.key === "F6") {
|
|
1500
|
+
event.preventDefault();
|
|
1501
|
+
wm.cycleFocus(event.shiftKey ? -1 : 1);
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
element.addEventListener("keydown", onDesktopKeydown);
|
|
1505
|
+
cleanup.push(() => element.removeEventListener("keydown", onDesktopKeydown));
|
|
1506
|
+
}
|
|
1507
|
+
function endDrag(cancelled) {
|
|
1508
|
+
if (drag) drag.finish(cancelled);
|
|
1164
1509
|
}
|
|
1165
1510
|
function attachWindow(id, windowElement, windowOptions = {}) {
|
|
1166
1511
|
const win = wm.get(id);
|
|
@@ -1171,6 +1516,7 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1171
1516
|
handle: null,
|
|
1172
1517
|
handles: [],
|
|
1173
1518
|
lastState: null,
|
|
1519
|
+
lastZ: -1,
|
|
1174
1520
|
cleanup: []
|
|
1175
1521
|
};
|
|
1176
1522
|
windowElement.dataset.wmWindow = id;
|
|
@@ -1221,26 +1567,23 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1221
1567
|
};
|
|
1222
1568
|
handle.addEventListener("dblclick", onDoubleClick);
|
|
1223
1569
|
attached.cleanup.push(() => handle.removeEventListener("dblclick", onDoubleClick));
|
|
1570
|
+
if (options.onTitlebarContextMenu) {
|
|
1571
|
+
const onContextMenu = (event) => {
|
|
1572
|
+
const current = wm.get(id);
|
|
1573
|
+
if (!current) return;
|
|
1574
|
+
event.preventDefault();
|
|
1575
|
+
options.onTitlebarContextMenu?.(current, event);
|
|
1576
|
+
};
|
|
1577
|
+
handle.addEventListener("contextmenu", onContextMenu);
|
|
1578
|
+
attached.cleanup.push(() => handle.removeEventListener("contextmenu", onContextMenu));
|
|
1579
|
+
}
|
|
1224
1580
|
}
|
|
1225
1581
|
if (windowOptions.resizeHandles !== false) {
|
|
1226
|
-
for (const direction of
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const size = 8;
|
|
1232
|
-
const corner = 12;
|
|
1233
|
-
const styles = {
|
|
1234
|
-
n: `top:${-size / 2}px;left:${corner}px;right:${corner}px;height:${size}px`,
|
|
1235
|
-
s: `bottom:${-size / 2}px;left:${corner}px;right:${corner}px;height:${size}px`,
|
|
1236
|
-
e: `right:${-size / 2}px;top:${corner}px;bottom:${corner}px;width:${size}px`,
|
|
1237
|
-
w: `left:${-size / 2}px;top:${corner}px;bottom:${corner}px;width:${size}px`,
|
|
1238
|
-
ne: `top:${-size / 2}px;right:${-size / 2}px;width:${corner}px;height:${corner}px`,
|
|
1239
|
-
nw: `top:${-size / 2}px;left:${-size / 2}px;width:${corner}px;height:${corner}px`,
|
|
1240
|
-
se: `bottom:${-size / 2}px;right:${-size / 2}px;width:${corner}px;height:${corner}px`,
|
|
1241
|
-
sw: `bottom:${-size / 2}px;left:${-size / 2}px;width:${corner}px;height:${corner}px`
|
|
1242
|
-
};
|
|
1243
|
-
resizeHandle.style.cssText = `${base}${styles[direction]};cursor:${RESIZE_CURSORS[direction]}`;
|
|
1582
|
+
for (const { element: resizeHandle, direction } of createResizeHandles(
|
|
1583
|
+
doc,
|
|
1584
|
+
hitEdge,
|
|
1585
|
+
hitCorner
|
|
1586
|
+
)) {
|
|
1244
1587
|
const onResizeDown = (event) => startResize(id, direction, event);
|
|
1245
1588
|
resizeHandle.addEventListener("pointerdown", onResizeDown);
|
|
1246
1589
|
attached.cleanup.push(() => resizeHandle.removeEventListener("pointerdown", onResizeDown));
|
|
@@ -1298,16 +1641,25 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1298
1641
|
};
|
|
1299
1642
|
windowElement.addEventListener("keydown", onModalTrap);
|
|
1300
1643
|
attached.cleanup.push(() => windowElement.removeEventListener("keydown", onModalTrap));
|
|
1301
|
-
|
|
1302
|
-
lastOrder = null;
|
|
1303
|
-
lastFocused = null;
|
|
1304
|
-
syncAll();
|
|
1305
|
-
return () => {
|
|
1644
|
+
const detach = () => {
|
|
1306
1645
|
if (drag?.id === id) endDrag(true);
|
|
1307
1646
|
for (const dispose of attached.cleanup) dispose();
|
|
1308
1647
|
for (const resizeHandle of attached.handles) resizeHandle.remove();
|
|
1309
1648
|
registry.delete(id);
|
|
1310
1649
|
};
|
|
1650
|
+
if (windowOptions.removeOnClose) {
|
|
1651
|
+
const stopOnClose = wm.on("close", ({ window: closed }) => {
|
|
1652
|
+
if (closed.id !== id) return;
|
|
1653
|
+
detach();
|
|
1654
|
+
windowElement.remove();
|
|
1655
|
+
});
|
|
1656
|
+
attached.cleanup.push(stopOnClose);
|
|
1657
|
+
}
|
|
1658
|
+
registry.set(id, attached);
|
|
1659
|
+
lastOrder = null;
|
|
1660
|
+
lastFocused = null;
|
|
1661
|
+
syncAll();
|
|
1662
|
+
return detach;
|
|
1311
1663
|
}
|
|
1312
1664
|
return {
|
|
1313
1665
|
element,
|
|
@@ -1326,6 +1678,8 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1326
1678
|
}
|
|
1327
1679
|
};
|
|
1328
1680
|
}
|
|
1681
|
+
|
|
1682
|
+
// src/dom/binder.ts
|
|
1329
1683
|
function createDesktopBinder(wm, options = {}) {
|
|
1330
1684
|
let controller = null;
|
|
1331
1685
|
const entries = /* @__PURE__ */ new Set();
|
|
@@ -1377,7 +1731,8 @@ exports.createWindowManager = createWindowManager;
|
|
|
1377
1731
|
exports.defaultMessages = defaultMessages;
|
|
1378
1732
|
exports.detectSnapZone = detectSnapZone;
|
|
1379
1733
|
exports.flipToTarget = flipToTarget;
|
|
1734
|
+
exports.magnetize = magnetize;
|
|
1380
1735
|
exports.prefersReducedMotion = prefersReducedMotion;
|
|
1381
1736
|
exports.zoneBounds = zoneBounds;
|
|
1382
|
-
//# sourceMappingURL=chunk-
|
|
1383
|
-
//# sourceMappingURL=chunk-
|
|
1737
|
+
//# sourceMappingURL=chunk-B35DANTX.cjs.map
|
|
1738
|
+
//# sourceMappingURL=chunk-B35DANTX.cjs.map
|