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