import "./chunk-KWPVD4H7.js"; // node_modules/@surdeddd/wmkit/dist/chunk-YCHJXSTC.js function createEmitter() { const listeners = /* @__PURE__ */ new Map(); return { on(event, listener) { let set = listeners.get(event); if (!set) { set = /* @__PURE__ */ new Set(); listeners.set(event, set); } set.add(listener); return () => { set.delete(listener); }; }, emit(event, payload) { const set = listeners.get(event); if (!set) return; for (const listener of [...set]) { listener(payload); } }, clear() { listeners.clear(); } }; } function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function clampSize(size, min, max) { return { width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY), height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY) }; } function clampToViewport(bounds, viewport, minVisible) { if (viewport.width <= 0 || viewport.height <= 0) return bounds; const x = clamp(bounds.x, minVisible - bounds.width, viewport.width - minVisible); const y = clamp(bounds.y, 0, Math.max(0, viewport.height - minVisible)); return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y }; } function boundsEqual(a, b) { return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height; } function zoneBounds(zone, viewport) { const w = viewport.width; const h = viewport.height; const halfW = Math.round(w / 2); const halfH = Math.round(h / 2); switch (zone) { case "left": return { x: 0, y: 0, width: halfW, height: h }; case "right": return { x: halfW, y: 0, width: w - halfW, height: h }; case "top": return { x: 0, y: 0, width: w, height: halfH }; case "bottom": return { x: 0, y: halfH, width: w, height: h - halfH }; case "top-left": return { x: 0, y: 0, width: halfW, height: halfH }; case "top-right": return { x: halfW, y: 0, width: w - halfW, height: halfH }; case "bottom-left": return { x: 0, y: halfH, width: halfW, height: h - halfH }; case "bottom-right": return { x: halfW, y: halfH, width: w - halfW, height: h - halfH }; } } function detectSnapZone(x, y, viewport, options = {}) { const threshold = options.threshold ?? 12; const cornerSize = options.cornerSize ?? 64; const w = viewport.width; const h = viewport.height; if (w <= 0 || h <= 0) return null; const nearLeft = x <= threshold; const nearRight = x >= w - threshold; const nearTop = y <= threshold; const nearBottom = y >= h - threshold; if (nearLeft || nearRight) { const side = nearLeft ? "left" : "right"; if (y <= cornerSize) return `top-${side}`; if (y >= h - cornerSize) return `bottom-${side}`; return side; } if (nearTop) { if (x <= cornerSize) return "top-left"; if (x >= w - cornerSize) return "top-right"; return "top"; } if (nearBottom) { if (x <= cornerSize) return "bottom-left"; if (x >= w - cornerSize) return "bottom-right"; return "bottom"; } return null; } function nearestEdge(low, high, targetLow, targetHigh) { return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high]; } function magnetize(bounds, targets, threshold) { const result = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false }; if (threshold <= 0 || targets.length === 0) return result; let bestX = threshold + 1; let bestY = threshold + 1; for (const target of targets) { for (const delta of nearestEdge( bounds.x, bounds.x + bounds.width, target.x, target.x + target.width )) { if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta; } for (const delta of nearestEdge( bounds.y, bounds.y + bounds.height, target.y, target.y + target.height )) { if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta; } } if (Math.abs(bestX) <= threshold) { result.x = bounds.x + bestX; result.snappedX = true; } if (Math.abs(bestY) <= threshold) { result.y = bounds.y + bestY; result.snappedY = true; } return result; } var LAYER_RANK = { normal: 0, floating: 1, modal: 2 }; var STAGES = ["normal", "minimized", "maximized", "snapped"]; var LAYERS = ["normal", "floating", "modal"]; var ZONES = [ "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right" ]; function createWindowManager(options = {}) { const emitter = createEmitter(); const keepInViewport = options.keepInViewport ?? true; const minVisible = options.minVisible ?? 48; const defaultSize = options.defaultSize ?? { width: 480, height: 320 }; const cascadeOffset = options.cascadeOffset ?? 32; const cascadeOrigin = options.cascadeOrigin ?? { x: 32, y: 32 }; const idPrefix = options.idPrefix ?? "wm"; const historyLimit = options.historyLimit ?? 50; let windows = {}; let order = []; let focusedId = null; let viewport = options.viewport ?? { width: 0, height: 0 }; let seq = 0; let idCounter = 0; let cascadeIndex = 0; let snapshot = null; let batchDepth = 0; let batchDirty = false; const pendingEvents = []; const past = []; let future = []; let interactionDepth = 0; let interactionRecorded = false; let skipNextHistory = false; let pendingHistory = null; const layouts = /* @__PURE__ */ new Map(); function getState() { if (!snapshot) { snapshot = { windows, order, focusedId, viewport }; } return snapshot; } function commit() { snapshot = null; batchDirty = true; } function flushEvents() { while (pendingEvents.length > 0) { for (const emit of pendingEvents.splice(0)) emit(); } } function queueEvent(emit) { pendingEvents.push(emit); } function setWindow(next) { windows = { ...windows, [next.id]: next }; } function removeWindow(id) { const { [id]: _removed, ...rest } = windows; windows = rest; order = order.filter((entry) => entry !== id); } function layerRankOf(id) { return LAYER_RANK[windows[id].layer]; } function sortByLayer(ids) { return ids.map((id, index) => ({ id, index, rank: layerRankOf(id) })).sort((a, b) => a.rank !== b.rank ? a.rank - b.rank : a.index - b.index).map((entry) => entry.id); } function raise(id) { const win = windows[id]; const without = order.filter((entry) => entry !== id); const rank = LAYER_RANK[win.layer]; let insertAt = without.length; for (let i = without.length - 1; i >= 0; i -= 1) { if (layerRankOf(without[i]) > rank) insertAt = i; else break; } const next = [...without.slice(0, insertAt), id, ...without.slice(insertAt)]; const changed = next.length !== order.length || next.some((entry, i) => entry !== order[i]); order = next; return changed; } function topModalId() { for (let i = order.length - 1; i >= 0; i -= 1) { const win = windows[order[i]]; if (win.layer === "modal" && win.stage !== "minimized") return win.id; } return null; } function focusTargets() { const modal = topModalId(); return order.filter((id) => { const win = windows[id]; if (win.stage === "minimized") return false; if (modal && win.layer !== "modal") return false; return true; }); } function focusTop() { const targets = focusTargets(); focusedId = targets.length > 0 ? targets[targets.length - 1] : null; } function emitFocus(win, previous) { queueEvent(() => emitter.emit("focus", { window: win, previous })); } function normalizeSize(size, win) { return clampSize(size, win.minSize, win.maxSize); } function positionForOpen(init) { if (init.x !== void 0 && init.y !== void 0) return { x: init.x, y: init.y }; const offset = cascadeIndex % 10 * cascadeOffset; cascadeIndex += 1; const base = { x: cascadeOrigin.x + offset, y: cascadeOrigin.y + offset }; return { x: init.x ?? base.x, y: init.y ?? base.y }; } function open(init = {}) { let id = init.id; if (id === void 0) { do { id = `${idPrefix}-${++idCounter}`; } while (windows[id]); } if (windows[id]) throw new Error(`wmkit: window id "${id}" already exists`); const minSize = { width: init.minWidth ?? 160, height: init.minHeight ?? 100 }; const maxSize = init.maxWidth !== void 0 || init.maxHeight !== void 0 ? { width: init.maxWidth ?? Number.POSITIVE_INFINITY, height: init.maxHeight ?? Number.POSITIVE_INFINITY } : null; const size = clampSize( { width: init.width ?? defaultSize.width, height: init.height ?? defaultSize.height }, minSize, maxSize ); const position = positionForOpen(init); let bounds = { ...position, ...size }; if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible); const requestedStage = init.stage ?? "normal"; const stage = requestedStage === "snapped" ? "normal" : requestedStage; let win = { id, title: init.title ?? "Window", bounds, restoreBounds: null, restoreStage: null, stage: "normal", snapZone: null, layer: init.layer ?? "normal", minSize, maxSize, openedSeq: ++seq, draggable: init.draggable ?? true, resizable: init.resizable ?? true, closable: init.closable ?? true, minimizable: init.minimizable ?? true, maximizable: init.maximizable ?? true, snappable: init.snappable ?? true, meta: init.meta ?? {} }; if (stage === "maximized") { win = { ...win, stage, restoreBounds: bounds, bounds: fullBounds() }; } else if (stage === "minimized") { win = { ...win, stage, restoreStage: "normal" }; } setWindow(win); raise(id); let focusPayload = null; if (stage !== "minimized") { const previous = focusedId; const modal = topModalId(); if (!modal || win.layer === "modal") { focusedId = id; focusPayload = { window: win, previous }; } else { focusTop(); } } queueEvent(() => emitter.emit("open", { window: win })); if (focusPayload) { const payload = focusPayload; queueEvent(() => emitter.emit("focus", payload)); } queueEvent(() => emitter.emit("order", { order })); commit(); return win; } function close(id) { const win = windows[id]; if (!win) return false; removeWindow(id); queueEvent(() => emitter.emit("close", { window: win })); if (focusedId === id) { focusTop(); if (focusedId) emitFocus(windows[focusedId], id); } queueEvent(() => emitter.emit("order", { order })); commit(); return true; } function closeAll() { for (const id of [...order]) close(id); } function focus(id) { const win = windows[id]; if (!win) return false; const modal = topModalId(); if (modal && modal !== id && win.layer !== "modal") { const modalWin = windows[modal]; if (modalWin) queueEvent(() => emitter.emit("modalblocked", { window: modalWin })); commit(); return false; } if (win.stage === "minimized") { restore(id); return focusedId === id; } const previous = focusedId; const raised = raise(id); const changed = focusedId !== id || raised; focusedId = id; if (changed) { emitFocus(win, previous); if (raised) queueEvent(() => emitter.emit("order", { order })); commit(); } return true; } function blur() { if (focusedId === null) return; focusedId = null; commit(); } function cycleFocus(direction = 1) { const targets = focusTargets(); if (targets.length === 0) return null; const current = focusedId ? targets.indexOf(focusedId) : -1; const nextIndex = current === -1 ? direction === 1 ? 0 : targets.length - 1 : (current + direction + targets.length) % targets.length; const id = targets[nextIndex]; focus(id); return id; } function fullBounds() { return { x: 0, y: 0, width: viewport.width, height: viewport.height }; } function applyStage(id, build) { const win = windows[id]; if (!win) return false; const next = build(win); if (!next) return false; setWindow(next); queueEvent(() => emitter.emit("stage", { window: next, previous: win.stage })); commit(); return true; } function minimize(id) { const result = applyStage(id, (win) => { if (win.stage === "minimized") return null; return { ...win, stage: "minimized", restoreStage: win.stage }; }); if (result && focusedId === id) { const previous = focusedId; focusTop(); if (focusedId) emitFocus(windows[focusedId], previous); commit(); } return result; } function maximize(id) { const result = applyStage(id, (win) => { if (win.stage === "maximized") return null; const restoreBounds = win.stage === "normal" ? win.bounds : win.restoreBounds; return { ...win, stage: "maximized", snapZone: null, restoreBounds, restoreStage: null, bounds: fullBounds() }; }); if (result) focus(id); return result; } function toggleMaximize(id) { const win = windows[id]; if (!win) return false; return win.stage === "maximized" ? restore(id) : maximize(id); } function restore(id) { const result = applyStage(id, (win) => { if (win.stage === "minimized") { const target = win.restoreStage ?? "normal"; if (target === "maximized") { return { ...win, stage: "maximized", restoreStage: null, bounds: fullBounds() }; } if (target === "snapped" && win.snapZone) { return { ...win, stage: "snapped", restoreStage: null, bounds: zoneBounds(win.snapZone, viewport) }; } return { ...win, stage: "normal", restoreStage: null }; } if (win.stage === "normal") return null; const bounds = win.restoreBounds ?? win.bounds; return { ...win, stage: "normal", snapZone: null, restoreBounds: null, restoreStage: null, bounds: keepInViewport ? clampToViewport(bounds, viewport, minVisible) : bounds }; }); if (result) focus(id); return result; } function restoreTo(id, bounds) { const result = applyStage(id, (win) => { const size = normalizeSize(bounds, win); const next = { x: bounds.x, y: bounds.y, ...size }; return { ...win, stage: "normal", snapZone: null, restoreBounds: null, restoreStage: null, bounds: keepInViewport ? clampToViewport(next, viewport, minVisible) : next }; }); if (result) focus(id); return result; } function snap(id, zone) { const result = applyStage(id, (win) => { const restoreBounds = win.stage === "normal" ? win.bounds : win.restoreBounds; return { ...win, stage: "snapped", snapZone: zone, restoreBounds, restoreStage: null, bounds: zoneBounds(zone, viewport) }; }); if (result) focus(id); return result; } function move(id, x, y) { const win = windows[id]; if ((win == null ? void 0 : win.stage) !== "normal") return false; let bounds = { ...win.bounds, x, y }; if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible); if (boundsEqual(bounds, win.bounds)) return true; const next = { ...win, bounds }; setWindow(next); queueEvent(() => emitter.emit("move", { window: next })); commit(); return true; } function moveBy(id, dx, dy) { const win = windows[id]; if (!win) return false; return move(id, win.bounds.x + dx, win.bounds.y + dy); } function resize(id, patch) { const win = windows[id]; if (!win || win.stage === "maximized" || win.stage === "minimized") return false; const merged = { ...win.bounds, ...patch }; const size = normalizeSize(merged, win); let bounds = { x: merged.x, y: merged.y, ...size }; if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible); const becameNormal = win.stage === "snapped"; if (boundsEqual(bounds, win.bounds) && !becameNormal) return true; const next = becameNormal ? { ...win, stage: "normal", snapZone: null, restoreBounds: null, bounds } : { ...win, bounds }; setWindow(next); if (becameNormal) queueEvent(() => emitter.emit("stage", { window: next, previous: "snapped" })); queueEvent(() => emitter.emit("resize", { window: next })); commit(); return true; } function update(id, patch) { const win = windows[id]; if (!win) return false; const next = { ...win, title: patch.title ?? win.title, layer: patch.layer ?? win.layer, minSize: patch.minSize ?? win.minSize, maxSize: patch.maxSize === void 0 ? win.maxSize : patch.maxSize, draggable: patch.draggable ?? win.draggable, resizable: patch.resizable ?? win.resizable, closable: patch.closable ?? win.closable, minimizable: patch.minimizable ?? win.minimizable, maximizable: patch.maximizable ?? win.maximizable, snappable: patch.snappable ?? win.snappable, meta: patch.meta ? { ...win.meta, ...patch.meta } : win.meta }; const size = normalizeSize(next.bounds, next); const resized = size.width !== next.bounds.width || size.height !== next.bounds.height; const finalWin = resized ? { ...next, bounds: { ...next.bounds, ...size } } : next; setWindow(finalWin); if (patch.layer && patch.layer !== win.layer) { order = sortByLayer(order); queueEvent(() => emitter.emit("order", { order })); if (patch.layer === "modal" && topModalId() === id && focusedId !== id) { const previous = focusedId; focusedId = id; emitFocus(finalWin, previous); } } queueEvent(() => emitter.emit("update", { window: finalWin })); if (resized) queueEvent(() => emitter.emit("resize", { window: finalWin })); commit(); return true; } function setViewport(next) { if (next.width === viewport.width && next.height === viewport.height) return; viewport = { ...next }; reflowViewport(); snapshot = null; batchDirty = true; } function minimized() { return Object.values(windows).filter((win) => win.stage === "minimized").sort((a, b) => a.openedSeq - b.openedSeq); } function captureEntry() { return { windows, order, focusedId }; } function recordHistory() { if (skipNextHistory) { skipNextHistory = false; return; } if (historyLimit <= 0) return; if (interactionDepth > 0) { if (interactionRecorded) return; interactionRecorded = true; } past.push(pendingHistory); if (past.length > historyLimit) past.shift(); future = []; } function transact(run) { if (batchDepth === 0) pendingHistory = captureEntry(); batchDepth += 1; try { return run(); } finally { batchDepth -= 1; if (batchDepth === 0) { if (batchDirty) { batchDirty = false; recordHistory(); snapshot = null; flushEvents(); emitter.emit("change", { state: getState() }); } pendingHistory = null; } } } function reflowViewport() { for (const id of order) { const win = windows[id]; if (win.stage === "maximized") { const updated = { ...win, bounds: fullBounds() }; setWindow(updated); queueEvent(() => emitter.emit("resize", { window: updated })); } else if (win.stage === "snapped" && win.snapZone) { const updated = { ...win, bounds: zoneBounds(win.snapZone, viewport) }; setWindow(updated); queueEvent(() => emitter.emit("resize", { window: updated })); } else if (win.stage === "normal" && keepInViewport) { const bounds = clampToViewport(win.bounds, viewport, minVisible); if (!boundsEqual(bounds, win.bounds)) { const updated = { ...win, bounds }; setWindow(updated); queueEvent(() => emitter.emit("move", { window: updated })); } } } } function applyEntry(entry) { windows = entry.windows; order = [...entry.order]; focusedId = entry.focusedId; reflowViewport(); commit(); } function undo() { const entry = past.pop(); if (!entry) return false; future.push(captureEntry()); skipNextHistory = true; applyEntry(entry); return true; } function redo() { const entry = future.pop(); if (!entry) return false; past.push(captureEntry()); skipNextHistory = true; applyEntry(entry); return true; } function beginInteraction() { interactionDepth += 1; if (interactionDepth === 1) interactionRecorded = false; } function endInteraction() { if (interactionDepth > 0) interactionDepth -= 1; } function clearHistory() { past.length = 0; future = []; } function saveLayout(name) { const data = serialize(); layouts.set(name, structuredClone(data)); return data; } function loadLayout(name) { const data = layouts.get(name); if (!data) return false; return hydrate(structuredClone(data)); } function getLayout(name) { const data = layouts.get(name); return data ? structuredClone(data) : void 0; } function setLayout(name, data) { if (!isValidSerialized(data)) return false; layouts.set(name, structuredClone(data)); return true; } function deleteLayout(name) { return layouts.delete(name); } function layoutNames() { return [...layouts.keys()]; } function arrange(mode) { const ids = order.filter((id) => windows[id].stage !== "minimized"); if (ids.length === 0) return; if (mode === "cascade") { ids.forEach((id, index) => { const win = windows[id]; const size = win.restoreBounds ?? win.bounds; restoreTo(id, { x: cascadeOrigin.x + index % 10 * cascadeOffset, y: cascadeOrigin.y + index % 10 * cascadeOffset, width: size.width, height: size.height }); }); return; } const cols = Math.ceil(Math.sqrt(ids.length)); const rows = Math.ceil(ids.length / cols); const cellWidth = Math.floor(viewport.width / cols); const cellHeight = Math.floor(viewport.height / rows); ids.forEach((id, index) => { restoreTo(id, { x: index % cols * cellWidth, y: Math.floor(index / cols) * cellHeight, width: cellWidth, height: cellHeight }); }); } function minimizeAll() { for (const id of [...order]) minimize(id); } function restoreAll() { for (const win of minimized()) restore(win.id); } function serialize() { return { version: 1, windows: order.map((id) => windows[id]).map((win) => ({ ...win, bounds: { ...win.bounds }, restoreBounds: win.restoreBounds ? { ...win.restoreBounds } : null, minSize: { ...win.minSize }, maxSize: win.maxSize ? { width: Number.isFinite(win.maxSize.width) ? win.maxSize.width : null, height: Number.isFinite(win.maxSize.height) ? win.maxSize.height : null } : null, meta: { ...win.meta } })), order: [...order], focusedId }; } function isBounds(value) { if (typeof value !== "object" || value === null) return false; const bounds = value; return typeof bounds.x === "number" && typeof bounds.y === "number" && typeof bounds.width === "number" && typeof bounds.height === "number"; } function isSize(value) { if (typeof value !== "object" || value === null) return false; const size = value; return typeof size.width === "number" && typeof size.height === "number"; } function isSizeAxis(value) { return value === null || typeof value === "number"; } function readMaxSize(value) { if (typeof value !== "object" || value === null) return null; const size = value; if (!isSizeAxis(size.width) || !isSizeAxis(size.height)) return null; if (size.width === null && size.height === null) return null; return { width: size.width ?? Number.POSITIVE_INFINITY, height: size.height ?? Number.POSITIVE_INFINITY }; } function isSerializedWindow(value) { if (typeof value !== "object" || value === null) return false; const win = value; return typeof win.id === "string" && win.id.length > 0 && isBounds(win.bounds) && STAGES.includes(win.stage) && LAYERS.includes(win.layer); } function isValidSerialized(data) { if (typeof data !== "object" || data === null) return false; if (data.version !== 1 || !Array.isArray(data.windows)) return false; if (!data.windows.every(isSerializedWindow)) return false; const ids = new Set(data.windows.map((win) => win.id)); return ids.size === data.windows.length; } function hydrate(data) { var _a; if (!isValidSerialized(data)) return false; const previousWindows = windows; const nextWindows = {}; let maxSeq = 0; for (const raw of data.windows) { if (nextWindows[raw.id]) return false; const win = { id: raw.id, title: typeof raw.title === "string" ? raw.title : "Window", bounds: { ...raw.bounds }, restoreBounds: raw.restoreBounds && isBounds(raw.restoreBounds) ? { ...raw.restoreBounds } : null, restoreStage: STAGES.includes(raw.restoreStage) ? raw.restoreStage : null, stage: raw.stage, snapZone: ZONES.includes(raw.snapZone) ? raw.snapZone : null, layer: raw.layer, minSize: isSize(raw.minSize) ? { ...raw.minSize } : { width: 160, height: 100 }, maxSize: readMaxSize(raw.maxSize), openedSeq: typeof raw.openedSeq === "number" ? raw.openedSeq : ++maxSeq, draggable: raw.draggable !== false, resizable: raw.resizable !== false, closable: raw.closable !== false, minimizable: raw.minimizable !== false, maximizable: raw.maximizable !== false, snappable: raw.snappable !== false, meta: raw.meta && typeof raw.meta === "object" ? raw.meta : {} }; maxSeq = Math.max(maxSeq, win.openedSeq); nextWindows[win.id] = win; } const requestedOrder = Array.isArray(data.order) ? data.order : []; const seen = /* @__PURE__ */ new Set(); const nextOrder = []; for (const id of requestedOrder) { if (typeof id === "string" && nextWindows[id] && !seen.has(id)) { seen.add(id); nextOrder.push(id); } } for (const id of Object.keys(nextWindows)) { if (!seen.has(id)) nextOrder.push(id); } windows = nextWindows; order = nextOrder; order = sortByLayer(order); seq = maxSeq; focusedId = data.focusedId && windows[data.focusedId] ? data.focusedId : null; if (focusedId && ((_a = windows[focusedId]) == null ? void 0 : _a.stage) === "minimized") focusedId = null; if (!focusedId) focusTop(); const modal = topModalId(); if (modal) { const focusedWin = focusedId ? windows[focusedId] : null; if ((focusedWin == null ? void 0 : focusedWin.layer) !== "modal") focusedId = modal; } for (const id of Object.keys(previousWindows)) { if (!windows[id]) { const closed = previousWindows[id]; queueEvent(() => emitter.emit("close", { window: closed })); } } for (const id of order) { if (!previousWindows[id]) { const opened = windows[id]; queueEvent(() => emitter.emit("open", { window: opened })); } } queueEvent(() => emitter.emit("order", { order })); clearHistory(); skipNextHistory = true; commit(); return true; } function destroy() { emitter.clear(); } return { open: (init) => transact(() => open(init)), close: (id) => transact(() => close(id)), closeAll: () => transact(closeAll), focus: (id) => transact(() => focus(id)), blur: () => transact(blur), cycleFocus: (direction) => transact(() => cycleFocus(direction)), minimize: (id) => transact(() => minimize(id)), maximize: (id) => transact(() => maximize(id)), toggleMaximize: (id) => transact(() => toggleMaximize(id)), restore: (id) => transact(() => restore(id)), restoreTo: (id, bounds) => transact(() => restoreTo(id, bounds)), snap: (id, zone) => transact(() => snap(id, zone)), move: (id, x, y) => transact(() => move(id, x, y)), moveBy: (id, dx, dy) => transact(() => moveBy(id, dx, dy)), resize: (id, patch) => transact(() => resize(id, patch)), update: (id, patch) => transact(() => update(id, patch)), get: (id) => windows[id], getState, minimized, setViewport: (next) => transact(() => setViewport(next)), batch: (run) => transact(run), serialize, hydrate: (data) => transact(() => hydrate(data)), undo: () => transact(undo), redo: () => transact(redo), canUndo: () => past.length > 0, canRedo: () => future.length > 0, beginInteraction, endInteraction, saveLayout, loadLayout: (name) => transact(() => loadLayout(name)), getLayout, setLayout, deleteLayout, layoutNames, arrange: (mode) => transact(() => arrange(mode)), minimizeAll: () => transact(minimizeAll), restoreAll: () => transact(restoreAll), subscribe: (listener) => emitter.on("change", ({ state }) => listener(state)), on: (event, listener) => emitter.on(event, listener), destroy }; } function prefersReducedMotion(win) { return win.matchMedia("(prefers-reduced-motion: reduce)").matches; } function flipFromTarget(source, target, options = {}) { const view = source.ownerDocument.defaultView; if (!view || prefersReducedMotion(view)) return; if (typeof source.animate !== "function") return; const from = target.getBoundingClientRect(); const to = source.getBoundingClientRect(); if (from.width === 0 || to.width === 0) return; const ghost = source.ownerDocument.createElement("div"); const style = view.getComputedStyle(source); 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`; source.ownerDocument.body.append(ghost); const dx = from.left + from.width / 2 - (to.left + to.width / 2); const dy = from.top + from.height / 2 - (to.top + to.height / 2); const scaleX = Math.max(from.width / to.width, 0.05); const scaleY = Math.max(from.height / to.height, 0.05); const animation = ghost.animate( [ { transform: `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`, opacity: 0.2 }, { transform: "translate(0, 0) scale(1, 1)", opacity: 0.9 } ], { duration: options.duration ?? 260, easing: options.easing ?? "cubic-bezier(0.32, 0.72, 0, 1)" } ); animation.onfinish = () => ghost.remove(); animation.oncancel = () => ghost.remove(); } function flipToTarget(source, target, options = {}) { const view = source.ownerDocument.defaultView; if (!view || prefersReducedMotion(view)) return; if (typeof source.animate !== "function") return; const from = source.getBoundingClientRect(); const to = target.getBoundingClientRect(); if (from.width === 0 || to.width === 0) return; const ghost = source.ownerDocument.createElement("div"); const style = view.getComputedStyle(source); ghost.style.cssText = `position:fixed;left:${from.left}px;top:${from.top}px;width:${from.width}px;height:${from.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`; source.ownerDocument.body.append(ghost); const dx = to.left + to.width / 2 - (from.left + from.width / 2); const dy = to.top + to.height / 2 - (from.top + from.height / 2); const scaleX = Math.max(to.width / from.width, 0.05); const scaleY = Math.max(to.height / from.height, 0.05); const animation = ghost.animate( [ { transform: "translate(0, 0) scale(1, 1)", opacity: 0.9 }, { transform: `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`, opacity: 0.2 } ], { duration: options.duration ?? 260, easing: options.easing ?? "cubic-bezier(0.32, 0.72, 0, 1)" } ); animation.onfinish = () => ghost.remove(); animation.oncancel = () => ghost.remove(); } var defaultMessages = { opened: (title) => `${title} window opened`, closed: (title) => `${title} window closed`, minimized: (title) => `${title} minimized`, restored: (title) => `${title} restored`, maximized: (title) => `${title} maximized`, snapped: (title, zone) => `${title} snapped to ${zone.replace("-", " ")}`, focused: (title) => `${title} focused` }; function createAnnouncer(wm, container, messages = {}) { const dict = { ...defaultMessages, ...messages }; const element = container.ownerDocument.createElement("div"); element.setAttribute("role", "status"); element.setAttribute("aria-live", "polite"); element.dataset.wmAnnouncer = ""; element.style.cssText = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0"; container.append(element); let clearTimer; function announce(message) { element.textContent = message; if (clearTimer !== void 0) clearTimeout(clearTimer); clearTimer = setTimeout(() => { element.textContent = ""; }, 2e3); } const unsubscribers = [ wm.on("open", ({ window: win }) => announce(dict.opened(win.title))), wm.on("close", ({ window: win }) => announce(dict.closed(win.title))), wm.on("stage", ({ window: win, previous }) => { if (win.stage === "minimized") announce(dict.minimized(win.title)); else if (win.stage === "maximized") announce(dict.maximized(win.title)); else if (win.stage === "snapped" && win.snapZone) announce(dict.snapped(win.title, win.snapZone)); else if (win.stage === "normal" && previous !== "normal") announce(dict.restored(win.title)); }) ]; return { element, announce, destroy() { for (const unsubscribe of unsubscribers) unsubscribe(); if (clearTimer !== void 0) clearTimeout(clearTimer); element.remove(); } }; } var INTERACTIVE_SELECTOR = "button, input, select, textarea, a[href], [contenteditable], [data-wm-close], [data-wm-minimize], [data-wm-maximize]"; function windowOf(element) { const view = element.ownerDocument.defaultView; if (!view) throw new Error("wmkit: desktop element is not attached to a document"); return view; } function createDragStarter(ctx) { const { wm, doc, view } = ctx; return function startDrag(id, handle, event) { const win = wm.get(id); if (!(win == null ? void 0 : win.draggable) || event.button !== 0 || ctx.currentDrag()) return; const target = event.target; if (target == null ? void 0 : target.closest(INTERACTIVE_SELECTOR)) return; event.preventDefault(); const releaseRect = ctx.trackRect(); const point = ctx.toLocal(event); const startBounds = win.bounds; const session = { id, pointerId: event.pointerId, grabDX: point.x - startBounds.x, grabDY: point.y - startBounds.y, grabRatio: clamp((point.x - startBounds.x) / startBounds.width, 0.05, 0.95), startBounds, startStage: win.stage, startZone: win.snapZone, startRestoreBounds: win.restoreBounds, restored: win.stage === "normal", moved: false, zone: null, raf: 0, pendingX: 0, pendingY: 0, hasPending: false, finish: () => { } }; const el = ctx.windowElement(id); function flush() { if (!session.hasPending || ctx.currentDrag() !== session) return; session.hasPending = false; session.raf = 0; const current = wm.get(id); if (!current) return; if (!session.restored) { const source = current.restoreBounds ?? session.startBounds; const width = source.width; const height = source.height; wm.restoreTo(id, { x: session.pendingX - width * session.grabRatio, y: session.pendingY - Math.min(session.grabDY, 24), width, height }); session.restored = true; const restoredWin = wm.get(id); if (restoredWin) { session.grabDX = session.pendingX - restoredWin.bounds.x; session.grabDY = session.pendingY - restoredWin.bounds.y; } return; } let nextX = session.pendingX - session.grabDX; let nextY = session.pendingY - session.grabDY; if (ctx.magnetThreshold > 0) { const state = wm.getState(); const targets = [ { x: 0, y: 0, width: state.viewport.width, height: state.viewport.height } ]; for (const otherId of state.order) { if (otherId === id) continue; const other = state.windows[otherId]; if (other && other.stage !== "minimized") targets.push(other.bounds); } const magnet = magnetize( { x: nextX, y: nextY, width: current.bounds.width, height: current.bounds.height }, targets, ctx.magnetThreshold ); nextX = magnet.x; nextY = magnet.y; } wm.move(id, nextX, nextY); if (ctx.snapEnabled && current.snappable) { const viewport = wm.getState().viewport; const rawZone = detectSnapZone(session.pendingX, session.pendingY, viewport, ctx.snapDetect); let zone = rawZone; if (rawZone === "top") { if (ctx.topEdge === "maximize") zone = current.maximizable ? "maximize" : null; else if (ctx.topEdge === "none") zone = null; } session.zone = zone; if (zone) { ctx.showPreview( zone === "maximize" ? { x: 0, y: 0, width: viewport.width, height: viewport.height } : zoneBounds(zone, viewport) ); } else { ctx.hidePreview(); } } } function onMove(moveEvent) { if (moveEvent.pointerId !== session.pointerId) return; const movePoint = ctx.toLocal(moveEvent); if (!session.moved) { const travelled = Math.abs(movePoint.x - (session.startBounds.x + session.grabDX)) + Math.abs(movePoint.y - (session.startBounds.y + session.grabDY)); if (travelled < 3 && session.restored) return; session.moved = true; if (el) { el.dataset.wmDragging = ""; el.style.willChange = "transform"; } } session.pendingX = movePoint.x; session.pendingY = movePoint.y; session.hasPending = true; if (session.raf === 0) session.raf = view.requestAnimationFrame(flush); } function onUp(upEvent) { if (upEvent.pointerId !== session.pointerId) return; finish(false); } function onCancel(cancelEvent) { if (cancelEvent.pointerId !== session.pointerId) return; finish(true); } function onKeydown(keyEvent) { if (keyEvent.key === "Escape") { keyEvent.preventDefault(); finish(true); } } function finish(cancelled) { if (ctx.currentDrag() !== session) return; if (session.raf !== 0) view.cancelAnimationFrame(session.raf); if (session.hasPending && !cancelled) flush(); ctx.releaseDrag(session); releaseRect(); handle.removeEventListener("pointermove", onMove); handle.removeEventListener("pointerup", onUp); handle.removeEventListener("pointercancel", onCancel); doc.removeEventListener("keydown", onKeydown, true); if (handle.hasPointerCapture(session.pointerId)) { handle.releasePointerCapture(session.pointerId); } if (el) { delete el.dataset.wmDragging; el.style.willChange = ""; } ctx.hidePreview(); if (cancelled) { if (session.moved && session.restored) { if (session.startStage === "maximized") { wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds); wm.maximize(id); } else if (session.startStage === "snapped" && session.startZone) { wm.restoreTo(id, session.startRestoreBounds ?? session.startBounds); wm.snap(id, session.startZone); } else { wm.move(id, session.startBounds.x, session.startBounds.y); } } wm.endInteraction(); return; } if (session.moved && session.zone) { if (session.zone === "maximize") wm.maximize(id); else wm.snap(id, session.zone); } wm.endInteraction(); } session.finish = finish; wm.beginInteraction(); ctx.claimDrag(session); handle.setPointerCapture(event.pointerId); handle.addEventListener("pointermove", onMove); handle.addEventListener("pointerup", onUp); handle.addEventListener("pointercancel", onCancel); doc.addEventListener("keydown", onKeydown, true); }; } var RESIZE_DIRECTIONS = ["n", "e", "s", "w", "ne", "nw", "se", "sw"]; var RESIZE_CURSORS = { n: "ns-resize", s: "ns-resize", e: "ew-resize", w: "ew-resize", ne: "nesw-resize", sw: "nesw-resize", nw: "nwse-resize", se: "nwse-resize" }; function createResizeStarter(ctx) { const { wm, doc, view } = ctx; return function startResize(id, direction, event) { const win = wm.get(id); if (!(win == null ? void 0 : win.resizable) || event.button !== 0 || ctx.currentDrag()) return; event.preventDefault(); event.stopPropagation(); const handleEl = event.currentTarget; wm.beginInteraction(); const releaseRect = ctx.trackRect(); const startPoint = ctx.toLocal(event); const start = win.bounds; const minSize = win.minSize; const maxSize = win.maxSize; let raf = 0; let pendingX = 0; let pendingY = 0; let hasPending = false; const el = ctx.windowElement(id); if (el) el.dataset.wmResizing = direction; function clampWidth(width) { return clamp(width, minSize.width, maxSize ? maxSize.width : Number.POSITIVE_INFINITY); } function clampHeight(height) { return clamp(height, minSize.height, maxSize ? maxSize.height : Number.POSITIVE_INFINITY); } function flush() { if (!hasPending) return; hasPending = false; raf = 0; const dx = pendingX - startPoint.x; const dy = pendingY - startPoint.y; const next = { ...start }; if (direction.includes("e")) next.width = clampWidth(start.width + dx); if (direction.includes("s")) next.height = clampHeight(start.height + dy); if (direction.includes("w")) { next.width = clampWidth(start.width - dx); next.x = start.x + (start.width - next.width); } if (direction.includes("n")) { next.height = clampHeight(start.height - dy); next.y = start.y + (start.height - next.height); } wm.resize(id, next); } function onMove(moveEvent) { if (moveEvent.pointerId !== event.pointerId) return; const movePoint = ctx.toLocal(moveEvent); pendingX = movePoint.x; pendingY = movePoint.y; hasPending = true; if (raf === 0) raf = view.requestAnimationFrame(flush); } function finish(cancelled) { if (raf !== 0) view.cancelAnimationFrame(raf); if (hasPending && !cancelled) flush(); releaseRect(); handleEl.removeEventListener("pointermove", onMove); handleEl.removeEventListener("pointerup", onUp); handleEl.removeEventListener("pointercancel", onCancelPointer); doc.removeEventListener("keydown", onKeydown, true); if (handleEl.hasPointerCapture(event.pointerId)) { handleEl.releasePointerCapture(event.pointerId); } if (el) delete el.dataset.wmResizing; if (cancelled) wm.resize(id, start); wm.endInteraction(); } function onUp(upEvent) { if (upEvent.pointerId !== event.pointerId) return; finish(false); } function onCancelPointer(cancelEvent) { if (cancelEvent.pointerId !== event.pointerId) return; finish(true); } function onKeydown(keyEvent) { if (keyEvent.key === "Escape") { keyEvent.preventDefault(); finish(true); } } handleEl.setPointerCapture(event.pointerId); handleEl.addEventListener("pointermove", onMove); handleEl.addEventListener("pointerup", onUp); handleEl.addEventListener("pointercancel", onCancelPointer); doc.addEventListener("keydown", onKeydown, true); }; } function createResizeHandles(doc, edge, corner) { const base = "position:absolute;touch-action:none;user-select:none;-webkit-user-select:none;"; const styles = { n: `top:${-edge / 2}px;left:${corner}px;right:${corner}px;height:${edge}px`, s: `bottom:${-edge / 2}px;left:${corner}px;right:${corner}px;height:${edge}px`, e: `right:${-edge / 2}px;top:${corner}px;bottom:${corner}px;width:${edge}px`, w: `left:${-edge / 2}px;top:${corner}px;bottom:${corner}px;width:${edge}px`, ne: `top:${-edge / 2}px;right:${-edge / 2}px;width:${corner}px;height:${corner}px`, nw: `top:${-edge / 2}px;left:${-edge / 2}px;width:${corner}px;height:${corner}px`, se: `bottom:${-edge / 2}px;right:${-edge / 2}px;width:${corner}px;height:${corner}px`, sw: `bottom:${-edge / 2}px;left:${-edge / 2}px;width:${corner}px;height:${corner}px` }; return RESIZE_DIRECTIONS.map((direction) => { const element = doc.createElement("div"); element.dataset.wmResize = direction; element.setAttribute("aria-hidden", "true"); element.style.cssText = `${base}${styles[direction]};cursor:${RESIZE_CURSORS[direction]}`; return { element, direction }; }); } function attachDesktop(wm, element, options = {}) { var _a, _b; const doc = element.ownerDocument; const view = windowOf(element); const coarsePointer = typeof view.matchMedia === "function" && view.matchMedia("(pointer: coarse)").matches; const snapEnabled = options.snap !== false; const snapOptions = typeof options.snap === "object" ? options.snap : {}; const snapPreviewEnabled = snapOptions.preview !== false; const topEdge = snapOptions.topEdge ?? "maximize"; const keyboardEnabled = options.keyboard !== false; const keyboardOptions = typeof options.keyboard === "object" ? options.keyboard : {}; const moveStep = keyboardOptions.moveStep ?? 16; const cycleEnabled = keyboardOptions.cycle !== false; const hitEdge = ((_a = options.hitAreas) == null ? void 0 : _a.edge) ?? (coarsePointer ? 16 : 8); const hitCorner = ((_b = options.hitAreas) == null ? void 0 : _b.corner) ?? (coarsePointer ? 24 : 12); const magnetThreshold = options.magnetism === false ? 0 : (typeof options.magnetism === "object" ? options.magnetism.threshold : void 0) ?? (coarsePointer ? 12 : 8); element.dataset.wmDesktop = ""; if (view.getComputedStyle(element).position === "static") { element.style.position = "relative"; } const registry = /* @__PURE__ */ new Map(); const cleanup = []; let lastOrder = null; let lastFocused = null; let drag = null; let cachedRect = null; let rectUsers = 0; let announcer = null; if (options.announce !== false) { announcer = createAnnouncer( wm, element, typeof options.announce === "object" ? options.announce : {} ); cleanup.push(() => announcer == null ? void 0 : announcer.destroy()); } let preview = null; function showPreview(bounds) { if (!snapPreviewEnabled) return; if (!preview) { preview = doc.createElement("div"); preview.dataset.wmSnapPreview = ""; preview.style.cssText = "position:absolute;left:0;top:0;pointer-events:none;display:none;z-index:2147483646"; element.append(preview); } preview.style.display = "block"; preview.style.transform = `translate3d(${bounds.x}px, ${bounds.y}px, 0)`; preview.style.width = `${bounds.width}px`; preview.style.height = `${bounds.height}px`; } function hidePreview() { if (preview) preview.style.display = "none"; } const ctx = { wm, doc, view, toLocal(event) { const rect = cachedRect ?? element.getBoundingClientRect(); return { x: event.clientX - rect.left, y: event.clientY - rect.top }; }, trackRect() { if (rectUsers === 0) { const rect = element.getBoundingClientRect(); cachedRect = { left: rect.left, top: rect.top }; } rectUsers += 1; let released = false; return () => { if (released) return; released = true; rectUsers -= 1; if (rectUsers === 0) cachedRect = null; }; }, windowElement: (id) => { var _a2; return (_a2 = registry.get(id)) == null ? void 0 : _a2.element; }, showPreview, hidePreview, snapEnabled, snapDetect: { threshold: snapOptions.threshold ?? (coarsePointer ? 20 : 12), cornerSize: snapOptions.cornerSize ?? (coarsePointer ? 96 : 64) }, topEdge, hitEdge, hitCorner, magnetThreshold, currentDrag: () => drag, claimDrag(session) { drag = session; }, releaseDrag(session) { if (drag === session) drag = null; } }; const startDrag = createDragStarter(ctx); const startResize = createResizeStarter(ctx); if (options.autoViewport !== false) { const applyViewport = () => wm.setViewport({ width: element.clientWidth, height: element.clientHeight }); applyViewport(); const observer = new ResizeObserver(applyViewport); observer.observe(element); cleanup.push(() => observer.disconnect()); } function syncWindow(attached, win, zIndex) { const el = attached.element; const firstSync = attached.lastState === null; if (firstSync) el.style.transition = "none"; if (attached.lastState !== win) { el.style.transform = `translate3d(${win.bounds.x}px, ${win.bounds.y}px, 0)`; el.style.width = `${win.bounds.width}px`; el.style.height = `${win.bounds.height}px`; el.dataset.wmStage = win.stage; el.dataset.wmLayer = win.layer; el.hidden = win.stage === "minimized"; el.setAttribute("aria-label", win.title); if (win.layer === "modal") el.setAttribute("aria-modal", "true"); else el.removeAttribute("aria-modal"); for (const handle of attached.handles) { handle.style.display = win.resizable && win.stage === "normal" ? "" : "none"; } attached.lastState = win; } if (attached.lastZ !== zIndex) { el.style.zIndex = String(zIndex + 1); attached.lastZ = zIndex; } if (firstSync) { void el.offsetWidth; el.style.transition = ""; } } function syncAll() { const state = wm.getState(); const orderChanged = state.order !== lastOrder; state.order.forEach((id, index) => { const attached = registry.get(id); const win = state.windows[id]; if (!attached || !win) return; if (orderChanged || attached.lastState !== win) syncWindow(attached, win, index); }); if (state.focusedId !== lastFocused) { if (lastFocused) { const prev = registry.get(lastFocused); if (prev) delete prev.element.dataset.wmFocused; } if (state.focusedId) { const next = registry.get(state.focusedId); if (next) next.element.dataset.wmFocused = ""; } lastFocused = state.focusedId; } lastOrder = state.order; } cleanup.push(wm.subscribe(syncAll)); cleanup.push( wm.on("focus", ({ window: win }) => { const attached = registry.get(win.id); if (!attached) return; if (!attached.element.contains(doc.activeElement)) { attached.element.focus({ preventScroll: true }); } }) ); cleanup.push( wm.on("modalblocked", ({ window: win }) => { const attached = registry.get(win.id); if (!attached) return; delete attached.element.dataset.wmFlash; void attached.element.offsetWidth; attached.element.dataset.wmFlash = ""; }) ); cleanup.push( wm.on("stage", ({ window: win, previous }) => { var _a2, _b2; const attached = registry.get(win.id); if (!attached) return; if (win.stage === "minimized" && previous !== "minimized") { const target = (_a2 = options.minimizeTarget) == null ? void 0 : _a2.call(options, win); if (target) flipToTarget(attached.element, target); } else if (previous === "minimized" && win.stage !== "minimized") { const target = (_b2 = options.minimizeTarget) == null ? void 0 : _b2.call(options, win); if (target) flipFromTarget(attached.element, target); } }) ); if (keyboardEnabled && cycleEnabled) { const onDesktopKeydown = (event) => { if (event.key === "F6") { event.preventDefault(); wm.cycleFocus(event.shiftKey ? -1 : 1); } }; element.addEventListener("keydown", onDesktopKeydown); cleanup.push(() => element.removeEventListener("keydown", onDesktopKeydown)); } function endDrag(cancelled) { if (drag) drag.finish(cancelled); } function attachWindow(id, windowElement, windowOptions = {}) { const win = wm.get(id); if (!win) throw new Error(`wmkit: cannot attach unknown window "${id}"`); if (registry.has(id)) throw new Error(`wmkit: window "${id}" is already attached`); const attached = { element: windowElement, handle: null, handles: [], lastState: null, lastZ: -1, cleanup: [] }; windowElement.dataset.wmWindow = id; windowElement.setAttribute("role", "dialog"); windowElement.tabIndex = -1; windowElement.style.position = "absolute"; windowElement.style.left = "0"; windowElement.style.top = "0"; const titleEl = windowElement.querySelector("[data-wm-title]"); if (titleEl) { if (!titleEl.id) titleEl.id = `wmkit-title-${id}`; windowElement.setAttribute("aria-labelledby", titleEl.id); } const handle = typeof windowOptions.handle === "string" ? windowElement.querySelector(windowOptions.handle) : windowOptions.handle ?? windowElement.querySelector("[data-wm-drag]"); attached.handle = handle; const onPointerDownFocus = () => { wm.focus(id); }; windowElement.addEventListener("pointerdown", onPointerDownFocus, true); attached.cleanup.push( () => windowElement.removeEventListener("pointerdown", onPointerDownFocus, true) ); const onClick = (event) => { const target = event.target; if (!target) return; const current = wm.get(id); if (!current) return; if (target.closest("[data-wm-close]")) { if (current.closable) wm.close(id); } else if (target.closest("[data-wm-minimize]")) { if (current.minimizable) wm.minimize(id); } else if (target.closest("[data-wm-maximize]")) { if (current.maximizable) wm.toggleMaximize(id); } }; windowElement.addEventListener("click", onClick); attached.cleanup.push(() => windowElement.removeEventListener("click", onClick)); if (handle) { handle.style.touchAction = "none"; const onHandleDown = (event) => startDrag(id, handle, event); handle.addEventListener("pointerdown", onHandleDown); attached.cleanup.push(() => handle.removeEventListener("pointerdown", onHandleDown)); const onDoubleClick = (event) => { const target = event.target; if (target == null ? void 0 : target.closest(INTERACTIVE_SELECTOR)) return; const current = wm.get(id); if (current == null ? void 0 : current.maximizable) wm.toggleMaximize(id); }; handle.addEventListener("dblclick", onDoubleClick); attached.cleanup.push(() => handle.removeEventListener("dblclick", onDoubleClick)); if (options.onTitlebarContextMenu) { const onContextMenu = (event) => { var _a2; const current = wm.get(id); if (!current) return; event.preventDefault(); (_a2 = options.onTitlebarContextMenu) == null ? void 0 : _a2.call(options, current, event); }; handle.addEventListener("contextmenu", onContextMenu); attached.cleanup.push(() => handle.removeEventListener("contextmenu", onContextMenu)); } } if (windowOptions.resizeHandles !== false) { for (const { element: resizeHandle, direction } of createResizeHandles( doc, hitEdge, hitCorner )) { const onResizeDown = (event) => startResize(id, direction, event); resizeHandle.addEventListener("pointerdown", onResizeDown); attached.cleanup.push(() => resizeHandle.removeEventListener("pointerdown", onResizeDown)); windowElement.append(resizeHandle); attached.handles.push(resizeHandle); } } const onWindowKeydown = (event) => { const target = event.target; if (target == null ? void 0 : target.closest(INTERACTIVE_SELECTOR)) return; const current = wm.get(id); if (!current) return; const arrows = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }; const vector = arrows[event.key]; if (!vector || !keyboardEnabled) return; event.preventDefault(); const step = event.altKey ? 1 : moveStep; const [dx, dy] = vector; if (event.shiftKey) { if (current.resizable) { wm.resize(id, { width: current.bounds.width + dx * step, height: current.bounds.height + dy * step }); } } else if (current.draggable && current.stage === "normal") { wm.moveBy(id, dx * step, dy * step); } }; windowElement.addEventListener("keydown", onWindowKeydown); attached.cleanup.push(() => windowElement.removeEventListener("keydown", onWindowKeydown)); const onModalTrap = (event) => { if (event.key !== "Tab") return; const current = wm.get(id); if ((current == null ? void 0 : current.layer) !== "modal") return; const focusables = windowElement.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); if (focusables.length === 0) return; const first = focusables[0]; const last = focusables[focusables.length - 1]; if (!first || !last) return; if (event.shiftKey && doc.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && doc.activeElement === last) { event.preventDefault(); first.focus(); } }; windowElement.addEventListener("keydown", onModalTrap); attached.cleanup.push(() => windowElement.removeEventListener("keydown", onModalTrap)); const detach = () => { if ((drag == null ? void 0 : drag.id) === id) endDrag(true); for (const dispose of attached.cleanup) dispose(); for (const resizeHandle of attached.handles) resizeHandle.remove(); registry.delete(id); }; if (windowOptions.removeOnClose) { const stopOnClose = wm.on("close", ({ window: closed }) => { if (closed.id !== id) return; detach(); windowElement.remove(); }); attached.cleanup.push(stopOnClose); } registry.set(id, attached); lastOrder = null; lastFocused = null; syncAll(); return detach; } return { element, wm, attachWindow, destroy() { endDrag(true); for (const [, attached] of registry) { for (const dispose of attached.cleanup) dispose(); for (const resizeHandle of attached.handles) resizeHandle.remove(); } registry.clear(); for (const dispose of cleanup) dispose(); preview == null ? void 0 : preview.remove(); delete element.dataset.wmDesktop; } }; } function createDesktopBinder(wm, options = {}) { let controller = null; const entries = /* @__PURE__ */ new Set(); function attachEntry(entry) { if (controller && !entry.detach && wm.get(entry.id)) { entry.detach = controller.attachWindow(entry.id, entry.element, entry.options); } } wm.on("open", ({ window: win }) => { for (const entry of entries) { if (entry.id === win.id) attachEntry(entry); } }); return { wm, controller: () => controller, bindDesktop(element) { if (controller) throw new Error("wmkit: desktop is already bound"); controller = attachDesktop(wm, element, options); for (const entry of entries) attachEntry(entry); return () => { for (const entry of entries) entry.detach = null; controller == null ? void 0 : controller.destroy(); controller = null; }; }, bindWindow(id, element, windowOptions) { const entry = { id, element, options: windowOptions, detach: null }; entries.add(entry); attachEntry(entry); return () => { var _a; (_a = entry.detach) == null ? void 0 : _a.call(entry); entry.detach = null; entries.delete(entry); }; } }; } // node_modules/@surdeddd/wmkit/dist/svelte.js function createManager(options) { return createWindowManager(options); } function wmStore(wm) { return { subscribe(run) { run(wm.getState()); return wm.subscribe(run); } }; } function wmWindowStore(wm, id) { return { subscribe(run) { run(wm.get(id)); let last = wm.get(id); return wm.subscribe(() => { const next = wm.get(id); if (next !== last) { last = next; run(next); } }); } }; } function createDesktop(wm, options) { const binder = createDesktopBinder(wm, options); return { binder, desktop(node) { const unbind = binder.bindDesktop(node); return { destroy: unbind }; }, window(node, params) { let unbind = binder.bindWindow(params.id, node, params); return { update(next) { unbind(); unbind = binder.bindWindow(next.id, node, next); }, destroy() { unbind(); } }; } }; } export { createDesktop, createManager, wmStore, wmWindowStore }; //# sourceMappingURL=@surdeddd_wmkit_svelte.js.map