@ai-matrx/summon 0.0.0 → 0.1.1
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +379 -1
- package/dist/closeBinding-C6raC3qJ.d.cts +20 -0
- package/dist/closeBinding-C6raC3qJ.d.ts +20 -0
- package/dist/dialog.cjs +60 -0
- package/dist/dialog.cjs.map +1 -0
- package/dist/dialog.d.cts +16 -0
- package/dist/dialog.d.ts +16 -0
- package/dist/dialog.js +38 -0
- package/dist/dialog.js.map +1 -0
- package/dist/errors-eJrKXq33.d.cts +30 -0
- package/dist/errors-eJrKXq33.d.ts +30 -0
- package/dist/handles.cjs +411 -0
- package/dist/handles.cjs.map +1 -0
- package/dist/handles.d.cts +166 -0
- package/dist/handles.d.ts +166 -0
- package/dist/handles.js +388 -0
- package/dist/handles.js.map +1 -0
- package/dist/index.cjs +2383 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1259 -0
- package/dist/index.d.ts +1259 -0
- package/dist/index.js +2363 -0
- package/dist/index.js.map +1 -0
- package/dist/lite.cjs +460 -0
- package/dist/lite.cjs.map +1 -0
- package/dist/lite.d.cts +56 -0
- package/dist/lite.d.ts +56 -0
- package/dist/lite.js +444 -0
- package/dist/lite.js.map +1 -0
- package/dist/next.cjs +61 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +5 -0
- package/dist/next.d.ts +5 -0
- package/dist/next.js +39 -0
- package/dist/next.js.map +1 -0
- package/dist/persistence-data.cjs +143 -0
- package/dist/persistence-data.cjs.map +1 -0
- package/dist/persistence-data.d.cts +72 -0
- package/dist/persistence-data.d.ts +72 -0
- package/dist/persistence-data.js +120 -0
- package/dist/persistence-data.js.map +1 -0
- package/dist/persistence.cjs +1073 -0
- package/dist/persistence.cjs.map +1 -0
- package/dist/persistence.d.cts +236 -0
- package/dist/persistence.d.ts +236 -0
- package/dist/persistence.js +1077 -0
- package/dist/persistence.js.map +1 -0
- package/dist/react.cjs +738 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +266 -0
- package/dist/react.d.ts +266 -0
- package/dist/react.js +723 -0
- package/dist/react.js.map +1 -0
- package/dist/sheet.cjs +71 -0
- package/dist/sheet.cjs.map +1 -0
- package/dist/sheet.d.cts +17 -0
- package/dist/sheet.d.ts +17 -0
- package/dist/sheet.js +49 -0
- package/dist/sheet.js.map +1 -0
- package/dist/styles.css +78 -0
- package/dist/testing.cjs +132 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +44 -0
- package/dist/testing.d.ts +44 -0
- package/dist/testing.js +115 -0
- package/dist/testing.js.map +1 -0
- package/dist/url.cjs +362 -0
- package/dist/url.cjs.map +1 -0
- package/dist/url.d.cts +153 -0
- package/dist/url.d.ts +153 -0
- package/dist/url.js +352 -0
- package/dist/url.js.map +1 -0
- package/dist/window-host.cjs +123 -0
- package/dist/window-host.cjs.map +1 -0
- package/dist/window-host.d.cts +72 -0
- package/dist/window-host.d.ts +72 -0
- package/dist/window-host.js +104 -0
- package/dist/window-host.js.map +1 -0
- package/dist/window.cjs +3977 -0
- package/dist/window.cjs.map +1 -0
- package/dist/window.d.cts +796 -0
- package/dist/window.d.ts +796 -0
- package/dist/window.js +4002 -0
- package/dist/window.js.map +1 -0
- package/package.json +239 -1
package/dist/window.js
ADDED
|
@@ -0,0 +1,4002 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/window/WindowPanel.tsx
|
|
4
|
+
import React, { useCallback as useCallback4, useEffect as useEffect5, useId as useId2, useLayoutEffect, useRef as useRef4, useState as useState5 } from "react";
|
|
5
|
+
import { announceLayerFront, FloatingLayer as FloatingLayer2, useFloatingLayerZIndex as useFloatingLayerZIndex2, usePortalContainer, PortalContainerProvider as PortalContainerProvider2 } from "@ai-matrx/design-system";
|
|
6
|
+
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@ai-matrx/design-system";
|
|
7
|
+
import { cn as cn9 } from "@ai-matrx/design-system/cn";
|
|
8
|
+
import { useIsMobile } from "@ai-matrx/kit/media-query";
|
|
9
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
10
|
+
import {
|
|
11
|
+
arrangeActiveWindows,
|
|
12
|
+
clampWindowRect as clampWindowRect2,
|
|
13
|
+
overlaysRawActions,
|
|
14
|
+
DEFAULT_INSTANCE_ID as DEFAULT_INSTANCE_ID2,
|
|
15
|
+
dockWindow as dockWindow3,
|
|
16
|
+
buildDockWindowPayload as buildDockWindowPayload3,
|
|
17
|
+
getOverlayMeta as getOverlayMeta3,
|
|
18
|
+
safeViewportDims as safeViewportDims2,
|
|
19
|
+
selectAllWindows,
|
|
20
|
+
selectOverlayData as selectOverlayData2,
|
|
21
|
+
selectPopoutCandidateId,
|
|
22
|
+
selectPopoutMode as selectPopoutMode3,
|
|
23
|
+
selectWindowPersistence,
|
|
24
|
+
selectWindowsHidden,
|
|
25
|
+
updateWindowPersistence,
|
|
26
|
+
updateWindowRect as updateWindowRect2,
|
|
27
|
+
ackOverlayRender,
|
|
28
|
+
clearOverlayRender
|
|
29
|
+
} from "@ai-matrx/summon";
|
|
30
|
+
import { useOptionalOverlayInstance, useSummonDispatch as useSummonDispatch4, useSummonPorts as useSummonPorts3, useSummonSelector as useSummonSelector5, useSummonStore as useSummonStore2 } from "@ai-matrx/summon/react";
|
|
31
|
+
|
|
32
|
+
// src/window/icons.tsx
|
|
33
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
34
|
+
function glyph(children) {
|
|
35
|
+
return function Glyph(props) {
|
|
36
|
+
return /* @__PURE__ */ jsx(
|
|
37
|
+
"svg",
|
|
38
|
+
{
|
|
39
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
40
|
+
width: 24,
|
|
41
|
+
height: 24,
|
|
42
|
+
viewBox: "0 0 24 24",
|
|
43
|
+
fill: "none",
|
|
44
|
+
stroke: "currentColor",
|
|
45
|
+
strokeWidth: 2,
|
|
46
|
+
strokeLinecap: "round",
|
|
47
|
+
strokeLinejoin: "round",
|
|
48
|
+
"aria-hidden": true,
|
|
49
|
+
...props,
|
|
50
|
+
children
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
var XIcon = glyph(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
57
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
58
|
+
] }));
|
|
59
|
+
var MinusIcon = glyph(/* @__PURE__ */ jsx("path", { d: "M5 12h14" }));
|
|
60
|
+
var Maximize2Icon = glyph(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
61
|
+
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
62
|
+
/* @__PURE__ */ jsx("path", { d: "m21 3-7 7" }),
|
|
63
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
64
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" })
|
|
65
|
+
] }));
|
|
66
|
+
var Minimize2Icon = glyph(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx("path", { d: "m14 10 7-7" }),
|
|
68
|
+
/* @__PURE__ */ jsx("path", { d: "M20 10h-6V4" }),
|
|
69
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
70
|
+
/* @__PURE__ */ jsx("path", { d: "M4 14h6v6" })
|
|
71
|
+
] }));
|
|
72
|
+
var RectangleVerticalIcon = glyph(/* @__PURE__ */ jsx("rect", { width: "12", height: "20", x: "6", y: "2", rx: "2" }));
|
|
73
|
+
var PanelLeftCloseIcon = glyph(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
74
|
+
/* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
|
|
75
|
+
/* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
|
|
76
|
+
/* @__PURE__ */ jsx("path", { d: "m16 15-3-3 3-3" })
|
|
77
|
+
] }));
|
|
78
|
+
var PanelLeftIcon = glyph(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
79
|
+
/* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
|
|
80
|
+
/* @__PURE__ */ jsx("path", { d: "M9 3v18" })
|
|
81
|
+
] }));
|
|
82
|
+
var AppWindowIcon = glyph(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
83
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
|
|
84
|
+
/* @__PURE__ */ jsx("path", { d: "M10 4v4" }),
|
|
85
|
+
/* @__PURE__ */ jsx("path", { d: "M2 8h20" }),
|
|
86
|
+
/* @__PURE__ */ jsx("path", { d: "M6 4v4" })
|
|
87
|
+
] }));
|
|
88
|
+
var SaveIcon = glyph(
|
|
89
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
90
|
+
/* @__PURE__ */ jsx("path", { d: "M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" }),
|
|
91
|
+
/* @__PURE__ */ jsx("path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7" }),
|
|
92
|
+
/* @__PURE__ */ jsx("path", { d: "M7 3v4a1 1 0 0 0 1 1h7" })
|
|
93
|
+
] })
|
|
94
|
+
);
|
|
95
|
+
var TriangleAlertIcon = glyph(
|
|
96
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
97
|
+
/* @__PURE__ */ jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }),
|
|
98
|
+
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
99
|
+
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
100
|
+
] })
|
|
101
|
+
);
|
|
102
|
+
var ArrowLeftToLineIcon = glyph(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
103
|
+
/* @__PURE__ */ jsx("path", { d: "M3 19V5" }),
|
|
104
|
+
/* @__PURE__ */ jsx("path", { d: "m13 6-6 6 6 6" }),
|
|
105
|
+
/* @__PURE__ */ jsx("path", { d: "M7 12h14" })
|
|
106
|
+
] }));
|
|
107
|
+
|
|
108
|
+
// src/window/LayoutIcon.tsx
|
|
109
|
+
import { cn } from "@ai-matrx/design-system/cn";
|
|
110
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
111
|
+
function LayoutIcon({
|
|
112
|
+
type,
|
|
113
|
+
className
|
|
114
|
+
}) {
|
|
115
|
+
const baseBox = "w-[18px] h-[18px] border-[1.5px] border-current rounded-sm shrink-0 flex items-center justify-center p-[1px] gap-[1px] overflow-hidden";
|
|
116
|
+
const filled = "bg-current rounded-[1px]";
|
|
117
|
+
const flexRow = "flex flex-row w-full h-full gap-[1px]";
|
|
118
|
+
const flexCol = "flex flex-col w-full h-full gap-[1px]";
|
|
119
|
+
let content = null;
|
|
120
|
+
switch (type) {
|
|
121
|
+
case "left-half":
|
|
122
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
123
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-1/2 h-full", filled) }),
|
|
124
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/2 h-full" })
|
|
125
|
+
] });
|
|
126
|
+
break;
|
|
127
|
+
case "right-half":
|
|
128
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
129
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/2 h-full" }),
|
|
130
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-1/2 h-full", filled) })
|
|
131
|
+
] });
|
|
132
|
+
break;
|
|
133
|
+
case "top-half":
|
|
134
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexCol, children: [
|
|
135
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-full h-1/2", filled) }),
|
|
136
|
+
/* @__PURE__ */ jsx2("div", { className: "w-full h-1/2" })
|
|
137
|
+
] });
|
|
138
|
+
break;
|
|
139
|
+
case "bottom-half":
|
|
140
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexCol, children: [
|
|
141
|
+
/* @__PURE__ */ jsx2("div", { className: "w-full h-1/2" }),
|
|
142
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-full h-1/2", filled) })
|
|
143
|
+
] });
|
|
144
|
+
break;
|
|
145
|
+
case "centre":
|
|
146
|
+
content = /* @__PURE__ */ jsx2("div", { className: cn("w-1/2 h-1/2", filled) });
|
|
147
|
+
break;
|
|
148
|
+
case "full":
|
|
149
|
+
content = /* @__PURE__ */ jsx2("div", { className: cn("w-full h-full", filled) });
|
|
150
|
+
break;
|
|
151
|
+
// Grid variants
|
|
152
|
+
case "grid4":
|
|
153
|
+
content = /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-2 grid-rows-2 gap-[1px] w-full h-full", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx2("div", { className: filled }, i)) });
|
|
154
|
+
break;
|
|
155
|
+
case "grid6":
|
|
156
|
+
content = /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-3 grid-rows-2 gap-[1px] w-full h-full", children: [1, 2, 3, 4, 5, 6].map((i) => /* @__PURE__ */ jsx2("div", { className: filled }, i)) });
|
|
157
|
+
break;
|
|
158
|
+
case "grid8":
|
|
159
|
+
content = /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-4 grid-rows-2 gap-[1px] w-full h-full", children: [1, 2, 3, 4, 5, 6, 7, 8].map((i) => /* @__PURE__ */ jsx2("div", { className: filled }, i)) });
|
|
160
|
+
break;
|
|
161
|
+
case "grid9":
|
|
162
|
+
content = /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-3 grid-rows-3 gap-[1px] w-full h-full", children: [1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => /* @__PURE__ */ jsx2("div", { className: filled }, i)) });
|
|
163
|
+
break;
|
|
164
|
+
case "grid12":
|
|
165
|
+
content = /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-4 grid-rows-3 gap-[1px] w-full h-full", children: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((i) => /* @__PURE__ */ jsx2("div", { className: filled }, i)) });
|
|
166
|
+
break;
|
|
167
|
+
// Stack variants
|
|
168
|
+
case "stackRight2":
|
|
169
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
170
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/2 h-full" }),
|
|
171
|
+
/* @__PURE__ */ jsxs2("div", { className: "w-1/2 h-full flex flex-col gap-[1px]", children: [
|
|
172
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-full h-1/2", filled) }),
|
|
173
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-full h-1/2", filled) })
|
|
174
|
+
] })
|
|
175
|
+
] });
|
|
176
|
+
break;
|
|
177
|
+
case "stackRight3":
|
|
178
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
179
|
+
/* @__PURE__ */ jsx2("div", { className: "w-2/3 h-full" }),
|
|
180
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/3 h-full flex flex-col gap-[1px]", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx2("div", { className: cn("w-full flex-1", filled) }, i)) })
|
|
181
|
+
] });
|
|
182
|
+
break;
|
|
183
|
+
case "stackRight4":
|
|
184
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
185
|
+
/* @__PURE__ */ jsx2("div", { className: "w-3/4 h-full" }),
|
|
186
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/4 h-full flex flex-col gap-[1px]", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx2("div", { className: cn("w-full flex-1", filled) }, i)) })
|
|
187
|
+
] });
|
|
188
|
+
break;
|
|
189
|
+
case "stackRight5":
|
|
190
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
191
|
+
/* @__PURE__ */ jsx2("div", { className: "w-4/5 h-full" }),
|
|
192
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/5 h-full flex flex-col gap-[1px]", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx2("div", { className: cn("w-full flex-1", filled) }, i)) })
|
|
193
|
+
] });
|
|
194
|
+
break;
|
|
195
|
+
case "stackLeft2":
|
|
196
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
197
|
+
/* @__PURE__ */ jsxs2("div", { className: "w-1/2 h-full flex flex-col gap-[1px]", children: [
|
|
198
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-full h-1/2", filled) }),
|
|
199
|
+
/* @__PURE__ */ jsx2("div", { className: cn("w-full h-1/2", filled) })
|
|
200
|
+
] }),
|
|
201
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/2 h-full" })
|
|
202
|
+
] });
|
|
203
|
+
break;
|
|
204
|
+
case "stackLeft3":
|
|
205
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
206
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/3 h-full flex flex-col gap-[1px]", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx2("div", { className: cn("w-full flex-1", filled) }, i)) }),
|
|
207
|
+
/* @__PURE__ */ jsx2("div", { className: "w-2/3 h-full" })
|
|
208
|
+
] });
|
|
209
|
+
break;
|
|
210
|
+
case "stackLeft4":
|
|
211
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
212
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/4 h-full flex flex-col gap-[1px]", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx2("div", { className: cn("w-full flex-1", filled) }, i)) }),
|
|
213
|
+
/* @__PURE__ */ jsx2("div", { className: "w-3/4 h-full" })
|
|
214
|
+
] });
|
|
215
|
+
break;
|
|
216
|
+
case "stackLeft5":
|
|
217
|
+
content = /* @__PURE__ */ jsxs2("div", { className: flexRow, children: [
|
|
218
|
+
/* @__PURE__ */ jsx2("div", { className: "w-1/5 h-full flex flex-col gap-[1px]", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx2("div", { className: cn("w-full flex-1", filled) }, i)) }),
|
|
219
|
+
/* @__PURE__ */ jsx2("div", { className: "w-4/5 h-full" })
|
|
220
|
+
] });
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
return /* @__PURE__ */ jsx2("div", { className: cn(baseBox, className), children: content });
|
|
224
|
+
}
|
|
225
|
+
function LayoutIconButton({
|
|
226
|
+
onClick,
|
|
227
|
+
type,
|
|
228
|
+
className
|
|
229
|
+
}) {
|
|
230
|
+
return /* @__PURE__ */ jsx2(
|
|
231
|
+
"button",
|
|
232
|
+
{
|
|
233
|
+
type: "button",
|
|
234
|
+
className: cn(
|
|
235
|
+
"flex items-center justify-center flex-1 h-8 rounded-md transition-colors",
|
|
236
|
+
"hover:bg-accent/80 text-foreground/50 hover:text-foreground",
|
|
237
|
+
className
|
|
238
|
+
),
|
|
239
|
+
onClick,
|
|
240
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
241
|
+
title: type,
|
|
242
|
+
children: /* @__PURE__ */ jsx2(LayoutIcon, { type })
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/window/useWindowPanel.ts
|
|
248
|
+
import { useEffect, useRef, useCallback, useId, useState } from "react";
|
|
249
|
+
import { useSummonDispatch, useSummonSelector, useSummonStore } from "@ai-matrx/summon/react";
|
|
250
|
+
import {
|
|
251
|
+
registerWindow,
|
|
252
|
+
unregisterWindow,
|
|
253
|
+
focusWindow,
|
|
254
|
+
restoreWindow,
|
|
255
|
+
maximizeWindow,
|
|
256
|
+
minimizeWindow,
|
|
257
|
+
updateWindowRect,
|
|
258
|
+
updateWindowTitle,
|
|
259
|
+
setPopoutCandidate,
|
|
260
|
+
clampWindowRect,
|
|
261
|
+
selectWindow,
|
|
262
|
+
confirmWindowRestored,
|
|
263
|
+
windowSessionKey
|
|
264
|
+
} from "@ai-matrx/summon";
|
|
265
|
+
|
|
266
|
+
// src/window/popout/popoutDragDetector.ts
|
|
267
|
+
var DEFAULT_DRAG_OUT_CONFIG = {
|
|
268
|
+
outsideThreshold: 80,
|
|
269
|
+
dwellMs: 250
|
|
270
|
+
};
|
|
271
|
+
var INITIAL_DRAG_OUT_STATE = {
|
|
272
|
+
isCandidate: false,
|
|
273
|
+
outsideSinceMs: null
|
|
274
|
+
};
|
|
275
|
+
function evaluateDragOut(e, prev, cfg, vpW, vpH, now) {
|
|
276
|
+
const { clientX, clientY } = e;
|
|
277
|
+
const { outsideThreshold, dwellMs } = cfg;
|
|
278
|
+
const pastLeft = -clientX;
|
|
279
|
+
const pastRight = clientX - vpW;
|
|
280
|
+
const pastTop = -clientY;
|
|
281
|
+
const pastBottom = clientY - vpH;
|
|
282
|
+
const maxPast = Math.max(pastLeft, pastRight, pastTop, pastBottom);
|
|
283
|
+
const isOutsideEnough = maxPast >= outsideThreshold;
|
|
284
|
+
if (!isOutsideEnough) {
|
|
285
|
+
if (prev.isCandidate || prev.outsideSinceMs !== null) {
|
|
286
|
+
return {
|
|
287
|
+
state: { isCandidate: false, outsideSinceMs: null },
|
|
288
|
+
shouldTrigger: false
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return { state: prev, shouldTrigger: false };
|
|
292
|
+
}
|
|
293
|
+
const outsideSince = prev.outsideSinceMs ?? now;
|
|
294
|
+
const dwellMet = now - outsideSince >= dwellMs;
|
|
295
|
+
const wasCandidate = prev.isCandidate;
|
|
296
|
+
const nowCandidate = dwellMet;
|
|
297
|
+
return {
|
|
298
|
+
state: { isCandidate: nowCandidate, outsideSinceMs: outsideSince },
|
|
299
|
+
shouldTrigger: !wasCandidate && nowCandidate
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/window/useWindowPanel.ts
|
|
304
|
+
import { safeViewportDims } from "@ai-matrx/summon";
|
|
305
|
+
|
|
306
|
+
// src/window/utils/autoOpenPlacement.ts
|
|
307
|
+
import { globalSlot } from "@ai-matrx/kit/global-slot";
|
|
308
|
+
var GAP = 12;
|
|
309
|
+
var SIBLING_GAP = 8;
|
|
310
|
+
var AUTO_OPEN_MIN_WIDTH = 280;
|
|
311
|
+
function marks() {
|
|
312
|
+
return globalSlot("ai-matrx.summon.window.autoOpenedMarks", () => /* @__PURE__ */ new Set());
|
|
313
|
+
}
|
|
314
|
+
var markKey = (overlayId, instanceId) => `${overlayId}::${instanceId}`;
|
|
315
|
+
function markAutoOpened(overlayId, instanceId) {
|
|
316
|
+
marks().add(markKey(overlayId, instanceId));
|
|
317
|
+
}
|
|
318
|
+
function isAutoOpened(overlayId, instanceId) {
|
|
319
|
+
return marks().has(markKey(overlayId, instanceId));
|
|
320
|
+
}
|
|
321
|
+
function clearAutoOpened(overlayId, instanceId) {
|
|
322
|
+
marks().delete(markKey(overlayId, instanceId));
|
|
323
|
+
}
|
|
324
|
+
function canvasWidth() {
|
|
325
|
+
if (typeof document === "undefined") return 0;
|
|
326
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue("--shell-canvas-w");
|
|
327
|
+
const px = Number.parseFloat(raw);
|
|
328
|
+
return Number.isFinite(px) && px > 0 ? px : 0;
|
|
329
|
+
}
|
|
330
|
+
var CONTROL_SELECTOR = 'button, a[href], input, textarea, select, [role="button"], [role="tab"], [data-clickable]';
|
|
331
|
+
function measureWorkingColumn() {
|
|
332
|
+
if (typeof document === "undefined") return null;
|
|
333
|
+
const root = document.querySelector("[data-matrx-page-scroll]") ?? document.querySelector("main") ?? document.querySelector(".shell-main");
|
|
334
|
+
if (!root) return null;
|
|
335
|
+
const rootRect = root.getBoundingClientRect();
|
|
336
|
+
let left = Infinity;
|
|
337
|
+
let right = -Infinity;
|
|
338
|
+
const nodes = root.querySelectorAll(CONTROL_SELECTOR);
|
|
339
|
+
const limit = Math.min(nodes.length, 600);
|
|
340
|
+
for (let i = 0; i < limit; i += 1) {
|
|
341
|
+
const el = nodes[i];
|
|
342
|
+
if (!el) continue;
|
|
343
|
+
if (el.closest("[data-window-panel], [data-matrx-window]")) continue;
|
|
344
|
+
const r = el.getBoundingClientRect();
|
|
345
|
+
if (r.width < 8 || r.height < 8) continue;
|
|
346
|
+
if (r.bottom < rootRect.top || r.top > rootRect.bottom) continue;
|
|
347
|
+
if (r.right < 0 || r.left > window.innerWidth) continue;
|
|
348
|
+
left = Math.min(left, r.left);
|
|
349
|
+
right = Math.max(right, r.right);
|
|
350
|
+
}
|
|
351
|
+
return Number.isFinite(left) ? { left, right } : null;
|
|
352
|
+
}
|
|
353
|
+
var AUTO_OPEN_NO_ROOM_EVENT = "matrx:auto-open-no-room";
|
|
354
|
+
function autoOpenHasRoom() {
|
|
355
|
+
if (typeof window === "undefined") return false;
|
|
356
|
+
const vw = window.innerWidth;
|
|
357
|
+
if (vw < 768) return false;
|
|
358
|
+
const column = measureWorkingColumn();
|
|
359
|
+
if (!column) return true;
|
|
360
|
+
const leftRoot = document.querySelector("main")?.getBoundingClientRect().left ?? 0;
|
|
361
|
+
const free = Math.max(vw - canvasWidth() - column.right - GAP * 2, column.left - leftRoot - GAP * 2);
|
|
362
|
+
return free >= AUTO_OPEN_MIN_WIDTH;
|
|
363
|
+
}
|
|
364
|
+
function resolveAutoOpenPlacement(args) {
|
|
365
|
+
const { vh } = args.viewport;
|
|
366
|
+
const vw = args.viewport.vw - canvasWidth();
|
|
367
|
+
const column = measureWorkingColumn();
|
|
368
|
+
const rightFree = column ? vw - column.right - GAP * 2 : vw;
|
|
369
|
+
const leftRoot = typeof document !== "undefined" ? document.querySelector("main")?.getBoundingClientRect().left ?? 0 : 0;
|
|
370
|
+
const leftFree = column ? column.left - leftRoot - GAP * 2 : 0;
|
|
371
|
+
const side = rightFree >= leftFree ? "right" : "left";
|
|
372
|
+
const free = Math.max(rightFree, leftFree);
|
|
373
|
+
const need = Math.min(args.minWidth, AUTO_OPEN_MIN_WIDTH);
|
|
374
|
+
const parked = {
|
|
375
|
+
rect: {
|
|
376
|
+
x: Math.max(GAP, vw - args.width - GAP),
|
|
377
|
+
y: args.top,
|
|
378
|
+
width: args.width,
|
|
379
|
+
height: args.height
|
|
380
|
+
},
|
|
381
|
+
minimized: true
|
|
382
|
+
};
|
|
383
|
+
if (free < need) return parked;
|
|
384
|
+
const width = Math.max(need, Math.min(args.width, Math.floor(free)));
|
|
385
|
+
const x = side === "right" ? vw - width - GAP : Math.max(GAP, Math.round(leftRoot + GAP));
|
|
386
|
+
const inGutter = args.siblings.filter(
|
|
387
|
+
(s) => s.x + s.width > x && s.x < x + width
|
|
388
|
+
);
|
|
389
|
+
const y = inGutter.length ? Math.max(...inGutter.map((s) => s.y + s.height)) + SIBLING_GAP : args.top;
|
|
390
|
+
const room = vh - y - GAP;
|
|
391
|
+
if (room < args.minHeight) return parked;
|
|
392
|
+
const share = inGutter.length ? room : Math.floor((vh - args.top - GAP) / 2);
|
|
393
|
+
const height = Math.min(room, Math.max(args.minHeight, Math.min(args.height, share)));
|
|
394
|
+
return { rect: { x, y, width, height }, minimized: false };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/window/useWindowPanel.ts
|
|
398
|
+
import { holdDragCursor } from "@ai-matrx/design-system";
|
|
399
|
+
var MIN_WIDTH = 180;
|
|
400
|
+
var MIN_HEIGHT = 80;
|
|
401
|
+
var DEFAULT_WIDTH = 320;
|
|
402
|
+
var DEFAULT_HEIGHT = 400;
|
|
403
|
+
var RESIZE_CURSOR = {
|
|
404
|
+
e: "ew-resize",
|
|
405
|
+
w: "ew-resize",
|
|
406
|
+
n: "ns-resize",
|
|
407
|
+
s: "ns-resize",
|
|
408
|
+
se: "nwse-resize",
|
|
409
|
+
nw: "nwse-resize",
|
|
410
|
+
ne: "nesw-resize",
|
|
411
|
+
sw: "nesw-resize"
|
|
412
|
+
};
|
|
413
|
+
function resolveSize(value, fallback) {
|
|
414
|
+
if (value === void 0) return fallback;
|
|
415
|
+
if (typeof value === "number") return value;
|
|
416
|
+
const { vw, vh } = safeViewportDims();
|
|
417
|
+
if (value.endsWith("vh")) return Math.round(parseFloat(value) / 100 * vh);
|
|
418
|
+
if (value.endsWith("vw")) return Math.round(parseFloat(value) / 100 * vw);
|
|
419
|
+
return parseFloat(value) || fallback;
|
|
420
|
+
}
|
|
421
|
+
var CASCADE_COUNT = /* @__PURE__ */ Symbol.for("matrx.windowPanels.defaultCascade");
|
|
422
|
+
var CASCADE_STEP = 28;
|
|
423
|
+
var CASCADE_WRAP = 6;
|
|
424
|
+
function resetDefaultWindowCascade() {
|
|
425
|
+
globalThis[CASCADE_COUNT] = 0;
|
|
426
|
+
}
|
|
427
|
+
function nextCascadeStep() {
|
|
428
|
+
const host = globalThis;
|
|
429
|
+
const seen = host[CASCADE_COUNT] ?? 0;
|
|
430
|
+
host[CASCADE_COUNT] = seen + 1;
|
|
431
|
+
return seen % CASCADE_WRAP * CASCADE_STEP;
|
|
432
|
+
}
|
|
433
|
+
var NARROW_VIEWPORT = 768;
|
|
434
|
+
var autoDocked = /* @__PURE__ */ new Set();
|
|
435
|
+
var autoOrder = /* @__PURE__ */ new Map();
|
|
436
|
+
var autoOrderNext = 0;
|
|
437
|
+
var earlierDocked = (id) => (other) => other.id !== id && other.state === "windowed" && autoDocked.has(other.id) && (autoOrder.get(other.id) ?? 0) < (autoOrder.get(id) ?? 0);
|
|
438
|
+
var AUTO_REPLACE_TICKS = 40;
|
|
439
|
+
function shellHeaderClearance() {
|
|
440
|
+
const GAP2 = 12;
|
|
441
|
+
if (typeof document === "undefined") return 44 + GAP2;
|
|
442
|
+
const root = getComputedStyle(document.documentElement);
|
|
443
|
+
const raw = root.getPropertyValue("--shell-header-h").trim();
|
|
444
|
+
const n = parseFloat(raw);
|
|
445
|
+
const h = raw.endsWith("rem") ? n * (parseFloat(root.fontSize) || 16) : n;
|
|
446
|
+
return (Number.isFinite(h) && h > 0 ? h : 44) + GAP2;
|
|
447
|
+
}
|
|
448
|
+
function resolvePosition(pos, w, h) {
|
|
449
|
+
const { vw, vh } = safeViewportDims();
|
|
450
|
+
const pad = 40;
|
|
451
|
+
const top = shellHeaderClearance();
|
|
452
|
+
switch (pos) {
|
|
453
|
+
case "top-left":
|
|
454
|
+
return { x: pad, y: top };
|
|
455
|
+
case "top-right":
|
|
456
|
+
return { x: Math.max(0, vw - w - pad), y: top };
|
|
457
|
+
case "bottom-left":
|
|
458
|
+
return { x: pad, y: Math.max(0, vh - h - pad) };
|
|
459
|
+
case "bottom-right":
|
|
460
|
+
return { x: Math.max(0, vw - w - pad), y: Math.max(0, vh - h - pad) };
|
|
461
|
+
case "center":
|
|
462
|
+
return {
|
|
463
|
+
x: Math.max(0, (vw - w) / 2),
|
|
464
|
+
y: Math.max(top, (vh - h) / 4)
|
|
465
|
+
};
|
|
466
|
+
default: {
|
|
467
|
+
const cascade = nextCascadeStep();
|
|
468
|
+
if (vw < NARROW_VIEWPORT) {
|
|
469
|
+
return {
|
|
470
|
+
x: Math.max(0, Math.min(vw - w, (vw - w) / 2)),
|
|
471
|
+
y: Math.max(0, Math.min(vh - h, top + cascade))
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
x: Math.max(0, vw - w - pad - cascade),
|
|
476
|
+
y: Math.max(0, Math.min(vh - h, top + cascade))
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function useWindowPanel(opts = {}) {
|
|
482
|
+
const reactId = useId();
|
|
483
|
+
const id = opts.id ?? reactId;
|
|
484
|
+
const dispatch = useSummonDispatch();
|
|
485
|
+
const store = useSummonStore();
|
|
486
|
+
const entry = useSummonSelector(selectWindow(id));
|
|
487
|
+
const minWidth = opts.minWidth ?? MIN_WIDTH;
|
|
488
|
+
const minHeight = opts.minHeight ?? MIN_HEIGHT;
|
|
489
|
+
const maxWidth = opts.maxWidth ?? Infinity;
|
|
490
|
+
const maxHeight = opts.maxHeight ?? Infinity;
|
|
491
|
+
const [isInteracting, setIsInteracting] = useState(false);
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
const w = opts.initialRect?.width ?? resolveSize(opts.width, DEFAULT_WIDTH);
|
|
494
|
+
const h = opts.initialRect?.height ?? resolveSize(opts.height, DEFAULT_HEIGHT);
|
|
495
|
+
const { vw, vh } = safeViewportDims();
|
|
496
|
+
let autoPlaced = null;
|
|
497
|
+
if (opts.autoOpened && vw >= NARROW_VIEWPORT) {
|
|
498
|
+
if (!autoOrder.has(id)) autoOrder.set(id, autoOrderNext++);
|
|
499
|
+
const siblings = Object.values(store.getState().windowManager.windows).filter(earlierDocked(id)).map((win) => win.windowed);
|
|
500
|
+
autoPlaced = resolveAutoOpenPlacement({
|
|
501
|
+
width: w,
|
|
502
|
+
height: h,
|
|
503
|
+
minWidth,
|
|
504
|
+
minHeight,
|
|
505
|
+
top: shellHeaderClearance(),
|
|
506
|
+
viewport: { vw, vh },
|
|
507
|
+
siblings
|
|
508
|
+
});
|
|
509
|
+
if (!autoPlaced.minimized) autoDocked.add(id);
|
|
510
|
+
else if (opts.onAutoNoRoom) window.setTimeout(() => opts.onAutoNoRoom?.(), 0);
|
|
511
|
+
}
|
|
512
|
+
const pos = autoPlaced ? { x: autoPlaced.rect.x, y: autoPlaced.rect.y } : opts.initialRect?.x !== void 0 || opts.initialRect?.y !== void 0 ? {
|
|
513
|
+
x: opts.initialRect?.x ?? Math.max(0, (vw - w) / 2),
|
|
514
|
+
y: opts.initialRect?.y ?? Math.max(0, (vh - h) / 4)
|
|
515
|
+
} : resolvePosition(opts.position, w, h);
|
|
516
|
+
const initial = autoPlaced ? { ...autoPlaced.rect } : { ...pos, width: w, height: h };
|
|
517
|
+
dispatch(
|
|
518
|
+
registerWindow({
|
|
519
|
+
id,
|
|
520
|
+
...opts.title === void 0 ? {} : { title: opts.title },
|
|
521
|
+
initial,
|
|
522
|
+
...autoPlaced?.minimized ? { initialState: "minimized" } : opts.initialState === void 0 ? {} : { initialState: opts.initialState },
|
|
523
|
+
...opts.persistence === void 0 ? {} : { persistence: opts.persistence },
|
|
524
|
+
// Sanitized dims: a degenerate 0×0 measurement must never become the
|
|
525
|
+
// clamp target, or every rect collapses to garbage (see safeViewportDims).
|
|
526
|
+
viewport: { width: vw, height: vh }
|
|
527
|
+
})
|
|
528
|
+
);
|
|
529
|
+
const pendingKey = opts.persistence ? windowSessionKey(
|
|
530
|
+
opts.persistence.overlayId,
|
|
531
|
+
opts.persistence.instanceId
|
|
532
|
+
) : null;
|
|
533
|
+
const confirmationTimer = pendingKey ? window.setTimeout(() => {
|
|
534
|
+
dispatch(confirmWindowRestored(pendingKey));
|
|
535
|
+
}, 0) : null;
|
|
536
|
+
return () => {
|
|
537
|
+
if (confirmationTimer !== null) window.clearTimeout(confirmationTimer);
|
|
538
|
+
autoDocked.delete(id);
|
|
539
|
+
autoOrder.delete(id);
|
|
540
|
+
dispatch(unregisterWindow(id));
|
|
541
|
+
};
|
|
542
|
+
}, [id]);
|
|
543
|
+
const autoTouchedRef = useRef(false);
|
|
544
|
+
useEffect(() => {
|
|
545
|
+
if (isInteracting) autoTouchedRef.current = true;
|
|
546
|
+
}, [isInteracting]);
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
if (!opts.autoOpened) return void 0;
|
|
549
|
+
let ticks = 0;
|
|
550
|
+
let lastMinimized = null;
|
|
551
|
+
const timer = window.setInterval(() => {
|
|
552
|
+
ticks += 1;
|
|
553
|
+
if (ticks > AUTO_REPLACE_TICKS) {
|
|
554
|
+
window.clearInterval(timer);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const win = store.getState().windowManager.windows[id];
|
|
558
|
+
if (!win || autoTouchedRef.current) return;
|
|
559
|
+
const { vw, vh } = safeViewportDims();
|
|
560
|
+
if (vw < NARROW_VIEWPORT) return;
|
|
561
|
+
const isMin = win.state === "minimized";
|
|
562
|
+
if (lastMinimized !== null && isMin !== lastMinimized) {
|
|
563
|
+
autoTouchedRef.current = true;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (win.state !== "windowed" && !isMin) return;
|
|
567
|
+
const siblings = Object.values(store.getState().windowManager.windows).filter(earlierDocked(id)).map((o) => o.windowed);
|
|
568
|
+
const next = resolveAutoOpenPlacement({
|
|
569
|
+
width: win.windowed.width,
|
|
570
|
+
height: win.windowed.height,
|
|
571
|
+
minWidth,
|
|
572
|
+
minHeight,
|
|
573
|
+
top: shellHeaderClearance(),
|
|
574
|
+
viewport: { vw, vh },
|
|
575
|
+
siblings
|
|
576
|
+
});
|
|
577
|
+
if (next.minimized) {
|
|
578
|
+
autoDocked.delete(id);
|
|
579
|
+
if (opts.onAutoNoRoom) {
|
|
580
|
+
window.clearInterval(timer);
|
|
581
|
+
opts.onAutoNoRoom();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (!isMin) {
|
|
585
|
+
dispatch(minimizeWindow({ id, viewportWidth: vw, viewportHeight: vh }));
|
|
586
|
+
}
|
|
587
|
+
lastMinimized = true;
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
autoDocked.add(id);
|
|
591
|
+
if (isMin) dispatch(restoreWindow(id));
|
|
592
|
+
const cur = store.getState().windowManager.windows[id]?.windowed;
|
|
593
|
+
if (!cur || Math.abs(cur.x - next.rect.x) > 4 || Math.abs(cur.y - next.rect.y) > 4 || Math.abs(cur.width - next.rect.width) > 4 || Math.abs(cur.height - next.rect.height) > 4) {
|
|
594
|
+
dispatch(updateWindowRect({ id, rect: next.rect }));
|
|
595
|
+
}
|
|
596
|
+
lastMinimized = false;
|
|
597
|
+
}, 1e3);
|
|
598
|
+
return () => window.clearInterval(timer);
|
|
599
|
+
}, [opts.autoOpened, id, minWidth, minHeight, store, dispatch]);
|
|
600
|
+
useEffect(() => {
|
|
601
|
+
if (opts.title !== void 0) {
|
|
602
|
+
dispatch(updateWindowTitle({ id, title: opts.title }));
|
|
603
|
+
}
|
|
604
|
+
}, [id, opts.title, dispatch]);
|
|
605
|
+
const dragStart = useRef(null);
|
|
606
|
+
const onTriggerPopout = opts.onTriggerPopout;
|
|
607
|
+
const onTriggerPopoutRef = useRef(onTriggerPopout);
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
onTriggerPopoutRef.current = onTriggerPopout;
|
|
610
|
+
}, [onTriggerPopout]);
|
|
611
|
+
const onDragStart = useCallback(
|
|
612
|
+
(e) => {
|
|
613
|
+
e.preventDefault();
|
|
614
|
+
dispatch(focusWindow(id));
|
|
615
|
+
if (!entry || entry.state === "maximized") return;
|
|
616
|
+
dragStart.current = {
|
|
617
|
+
mx: e.clientX,
|
|
618
|
+
my: e.clientY,
|
|
619
|
+
wx: entry.windowed.x,
|
|
620
|
+
wy: entry.windowed.y
|
|
621
|
+
};
|
|
622
|
+
setIsInteracting(true);
|
|
623
|
+
const releaseCursor = holdDragCursor();
|
|
624
|
+
let dragOutState = INITIAL_DRAG_OUT_STATE;
|
|
625
|
+
let lastClientX = e.clientX;
|
|
626
|
+
let lastClientY = e.clientY;
|
|
627
|
+
const popoutEnabled = onTriggerPopoutRef.current !== void 0;
|
|
628
|
+
const onMove = (ev) => {
|
|
629
|
+
if (!dragStart.current) return;
|
|
630
|
+
const nx = dragStart.current.wx + (ev.clientX - dragStart.current.mx);
|
|
631
|
+
const ny = dragStart.current.wy + (ev.clientY - dragStart.current.my);
|
|
632
|
+
dispatch(updateWindowRect({ id, rect: { x: nx, y: ny } }));
|
|
633
|
+
lastClientX = ev.clientX;
|
|
634
|
+
lastClientY = ev.clientY;
|
|
635
|
+
if (!popoutEnabled) return;
|
|
636
|
+
const result = evaluateDragOut(
|
|
637
|
+
{ clientX: ev.clientX, clientY: ev.clientY },
|
|
638
|
+
dragOutState,
|
|
639
|
+
DEFAULT_DRAG_OUT_CONFIG,
|
|
640
|
+
window.innerWidth,
|
|
641
|
+
window.innerHeight,
|
|
642
|
+
performance.now()
|
|
643
|
+
);
|
|
644
|
+
if (result.state.isCandidate !== dragOutState.isCandidate) {
|
|
645
|
+
dispatch(
|
|
646
|
+
setPopoutCandidate({ id: result.state.isCandidate ? id : null })
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
dragOutState = result.state;
|
|
650
|
+
};
|
|
651
|
+
const onUp = () => {
|
|
652
|
+
const wasCandidate = dragOutState.isCandidate;
|
|
653
|
+
const dragOrigin = dragStart.current;
|
|
654
|
+
dragStart.current = null;
|
|
655
|
+
setIsInteracting(false);
|
|
656
|
+
releaseCursor();
|
|
657
|
+
document.removeEventListener("pointermove", onMove);
|
|
658
|
+
document.removeEventListener("pointerup", onUp);
|
|
659
|
+
document.removeEventListener("pointercancel", onUp);
|
|
660
|
+
if (popoutEnabled && dragOutState.isCandidate) {
|
|
661
|
+
dispatch(setPopoutCandidate({ id: null }));
|
|
662
|
+
}
|
|
663
|
+
if (wasCandidate && onTriggerPopoutRef.current && entry) {
|
|
664
|
+
const lastRect = {
|
|
665
|
+
x: dragOrigin?.wx ?? entry.windowed.x,
|
|
666
|
+
y: dragOrigin?.wy ?? entry.windowed.y,
|
|
667
|
+
width: entry.windowed.width,
|
|
668
|
+
height: entry.windowed.height
|
|
669
|
+
};
|
|
670
|
+
onTriggerPopoutRef.current(lastRect);
|
|
671
|
+
} else {
|
|
672
|
+
const { vw, vh, degenerate } = safeViewportDims();
|
|
673
|
+
if (!degenerate) {
|
|
674
|
+
dispatch(
|
|
675
|
+
clampWindowRect({ id, viewportWidth: vw, viewportHeight: vh })
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
void lastClientX;
|
|
680
|
+
void lastClientY;
|
|
681
|
+
};
|
|
682
|
+
document.addEventListener("pointermove", onMove);
|
|
683
|
+
document.addEventListener("pointerup", onUp);
|
|
684
|
+
document.addEventListener("pointercancel", onUp);
|
|
685
|
+
},
|
|
686
|
+
[dispatch, id, entry]
|
|
687
|
+
);
|
|
688
|
+
const onResizeStart = useCallback(
|
|
689
|
+
(edge) => (e) => {
|
|
690
|
+
e.preventDefault();
|
|
691
|
+
e.stopPropagation();
|
|
692
|
+
dispatch(focusWindow(id));
|
|
693
|
+
if (!entry || entry.state !== "windowed") return;
|
|
694
|
+
const startMx = e.clientX;
|
|
695
|
+
const startMy = e.clientY;
|
|
696
|
+
const { x, y, width, height } = entry.windowed;
|
|
697
|
+
setIsInteracting(true);
|
|
698
|
+
const releaseCursor = holdDragCursor(RESIZE_CURSOR[edge]);
|
|
699
|
+
const onMove = (ev) => {
|
|
700
|
+
const dx = ev.clientX - startMx;
|
|
701
|
+
const dy = ev.clientY - startMy;
|
|
702
|
+
let nx = x, ny = y, nw = width, nh = height;
|
|
703
|
+
if (edge.includes("e"))
|
|
704
|
+
nw = Math.min(maxWidth, Math.max(minWidth, width + dx));
|
|
705
|
+
if (edge.includes("s"))
|
|
706
|
+
nh = Math.min(maxHeight, Math.max(minHeight, height + dy));
|
|
707
|
+
if (edge.includes("w")) {
|
|
708
|
+
nw = Math.min(maxWidth, Math.max(minWidth, width - dx));
|
|
709
|
+
nx = x + (width - nw);
|
|
710
|
+
}
|
|
711
|
+
if (edge.includes("n")) {
|
|
712
|
+
nh = Math.min(maxHeight, Math.max(minHeight, height - dy));
|
|
713
|
+
ny = y + (height - nh);
|
|
714
|
+
}
|
|
715
|
+
dispatch(
|
|
716
|
+
updateWindowRect({
|
|
717
|
+
id,
|
|
718
|
+
rect: { x: nx, y: ny, width: nw, height: nh }
|
|
719
|
+
})
|
|
720
|
+
);
|
|
721
|
+
};
|
|
722
|
+
const onUp = () => {
|
|
723
|
+
setIsInteracting(false);
|
|
724
|
+
releaseCursor();
|
|
725
|
+
document.removeEventListener("pointermove", onMove);
|
|
726
|
+
document.removeEventListener("pointerup", onUp);
|
|
727
|
+
document.removeEventListener("pointercancel", onUp);
|
|
728
|
+
};
|
|
729
|
+
document.addEventListener("pointermove", onMove);
|
|
730
|
+
document.addEventListener("pointerup", onUp);
|
|
731
|
+
document.addEventListener("pointercancel", onUp);
|
|
732
|
+
},
|
|
733
|
+
[dispatch, id, entry, minWidth, minHeight, maxWidth, maxHeight]
|
|
734
|
+
);
|
|
735
|
+
const onFocus = useCallback(() => dispatch(focusWindow(id)), [dispatch, id]);
|
|
736
|
+
const onRestore = useCallback(
|
|
737
|
+
() => dispatch(restoreWindow(id)),
|
|
738
|
+
[dispatch, id]
|
|
739
|
+
);
|
|
740
|
+
const onMaximize = useCallback(
|
|
741
|
+
() => dispatch(maximizeWindow(id)),
|
|
742
|
+
[dispatch, id]
|
|
743
|
+
);
|
|
744
|
+
const onMinimize = useCallback(() => {
|
|
745
|
+
const { vw, vh } = safeViewportDims();
|
|
746
|
+
dispatch(
|
|
747
|
+
minimizeWindow({ id, viewportWidth: vw, viewportHeight: vh })
|
|
748
|
+
);
|
|
749
|
+
}, [dispatch, id]);
|
|
750
|
+
const onToggleMaximize = useCallback(() => {
|
|
751
|
+
if (entry?.state === "maximized") dispatch(restoreWindow(id));
|
|
752
|
+
else dispatch(maximizeWindow(id));
|
|
753
|
+
}, [dispatch, id, entry]);
|
|
754
|
+
const rect = entry?.windowed ?? {
|
|
755
|
+
x: 0,
|
|
756
|
+
y: 0,
|
|
757
|
+
width: opts.initialRect?.width ?? resolveSize(opts.width, DEFAULT_WIDTH),
|
|
758
|
+
height: opts.initialRect?.height ?? resolveSize(opts.height, DEFAULT_HEIGHT)
|
|
759
|
+
};
|
|
760
|
+
return {
|
|
761
|
+
id,
|
|
762
|
+
windowState: entry?.state ?? "windowed",
|
|
763
|
+
rect,
|
|
764
|
+
zIndex: entry?.zIndex ?? 1e3,
|
|
765
|
+
onDragStart,
|
|
766
|
+
onResizeStart,
|
|
767
|
+
onFocus,
|
|
768
|
+
onRestore,
|
|
769
|
+
onMaximize,
|
|
770
|
+
onMinimize,
|
|
771
|
+
onToggleMaximize,
|
|
772
|
+
isInteracting
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/window/utils/useIsClient.ts
|
|
777
|
+
import { useSyncExternalStore } from "react";
|
|
778
|
+
var subscribe = () => () => {
|
|
779
|
+
};
|
|
780
|
+
function useIsClient() {
|
|
781
|
+
return useSyncExternalStore(subscribe, () => true, () => false);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/window/utils/useDebugFlag.ts
|
|
785
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
786
|
+
var noSubscribe = () => () => {
|
|
787
|
+
};
|
|
788
|
+
function useDebugFlag(debug) {
|
|
789
|
+
return useSyncExternalStore2(
|
|
790
|
+
debug.subscribe ? (listener) => debug.subscribe?.(listener) ?? (() => {
|
|
791
|
+
}) : noSubscribe,
|
|
792
|
+
() => debug.enabled(),
|
|
793
|
+
() => false
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/window/WindowPanel.tsx
|
|
798
|
+
import { useOverlayLink } from "@ai-matrx/summon/url";
|
|
799
|
+
|
|
800
|
+
// src/window/utils/urlSyncIdentity.ts
|
|
801
|
+
function resolveWindowUrlSyncKey(registryKey, explicitKey) {
|
|
802
|
+
return registryKey ?? explicitKey;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/window/utils/sanitizeWindowData.ts
|
|
806
|
+
var DROPPED = /* @__PURE__ */ Symbol("dropped-window-value");
|
|
807
|
+
function isRecord(value) {
|
|
808
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
809
|
+
}
|
|
810
|
+
function sanitizeWindowData(value, allowedKeys) {
|
|
811
|
+
if (!isRecord(value) || allowedKeys.length === 0) return {};
|
|
812
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
813
|
+
const copy = (item) => {
|
|
814
|
+
if (item === void 0 || typeof item === "function" || typeof item === "symbol") return DROPPED;
|
|
815
|
+
if (typeof item === "bigint") return item.toString();
|
|
816
|
+
if (typeof item === "number" && !Number.isFinite(item)) return null;
|
|
817
|
+
if (item === null || typeof item !== "object") return item;
|
|
818
|
+
if (item instanceof Date) return item.toJSON();
|
|
819
|
+
if (ancestors.has(item)) return DROPPED;
|
|
820
|
+
ancestors.add(item);
|
|
821
|
+
let result;
|
|
822
|
+
if (Array.isArray(item)) {
|
|
823
|
+
result = item.map((child) => {
|
|
824
|
+
const copied = copy(child);
|
|
825
|
+
return copied === DROPPED ? null : copied;
|
|
826
|
+
});
|
|
827
|
+
} else if (Object.getPrototypeOf(item) === Object.prototype || Object.getPrototypeOf(item) === null) {
|
|
828
|
+
result = Object.fromEntries(
|
|
829
|
+
Object.entries(item).flatMap(([key, child]) => {
|
|
830
|
+
const copied = copy(child);
|
|
831
|
+
return copied === DROPPED ? [] : [[key, copied]];
|
|
832
|
+
})
|
|
833
|
+
);
|
|
834
|
+
} else {
|
|
835
|
+
result = DROPPED;
|
|
836
|
+
}
|
|
837
|
+
ancestors.delete(item);
|
|
838
|
+
return result;
|
|
839
|
+
};
|
|
840
|
+
try {
|
|
841
|
+
const allowed = new Set(allowedKeys);
|
|
842
|
+
const selected = Object.fromEntries(Object.entries(value).filter(([key]) => allowed.has(key)));
|
|
843
|
+
ancestors.add(value);
|
|
844
|
+
const copied = copy(selected);
|
|
845
|
+
const parsed = JSON.parse(JSON.stringify(copied === DROPPED ? {} : copied) || "{}");
|
|
846
|
+
return isRecord(parsed) ? parsed : {};
|
|
847
|
+
} catch {
|
|
848
|
+
return {};
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/window/WindowPanel.tsx
|
|
853
|
+
import { useWindowConfig, WindowSelectionSurface } from "@ai-matrx/summon/window-host";
|
|
854
|
+
|
|
855
|
+
// src/window/mobile/MobileDrawerSurface.tsx
|
|
856
|
+
import { useState as useState2 } from "react";
|
|
857
|
+
import {
|
|
858
|
+
Drawer,
|
|
859
|
+
DrawerClose,
|
|
860
|
+
DrawerContent,
|
|
861
|
+
DrawerDescription,
|
|
862
|
+
DrawerTitle
|
|
863
|
+
} from "@ai-matrx/design-system";
|
|
864
|
+
import { Button } from "@ai-matrx/design-system";
|
|
865
|
+
import { cn as cn3 } from "@ai-matrx/design-system/cn";
|
|
866
|
+
|
|
867
|
+
// src/window/chrome/MobileTitleRow.tsx
|
|
868
|
+
import { cn as cn2 } from "@ai-matrx/design-system/cn";
|
|
869
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
870
|
+
var MOBILE_WINDOW_TITLE_TEXT = "line-clamp-2 break-words text-sm font-medium leading-snug text-foreground";
|
|
871
|
+
function isTextTitle(title) {
|
|
872
|
+
return typeof title === "string" || typeof title === "number";
|
|
873
|
+
}
|
|
874
|
+
function mobileWindowTitleClass(title) {
|
|
875
|
+
return isTextTitle(title) ? cn2("min-w-0 flex-1", MOBILE_WINDOW_TITLE_TEXT) : "flex min-w-0 flex-1 items-center overflow-hidden text-sm font-medium";
|
|
876
|
+
}
|
|
877
|
+
function MobileWindowTitle({
|
|
878
|
+
title,
|
|
879
|
+
className
|
|
880
|
+
}) {
|
|
881
|
+
if (title == null || title === "") return /* @__PURE__ */ jsx3("div", { className: cn2("min-w-0 flex-1", className) });
|
|
882
|
+
return /* @__PURE__ */ jsx3("div", { "data-window-mobile-title": "", className: cn2(mobileWindowTitleClass(title), className), children: title });
|
|
883
|
+
}
|
|
884
|
+
function MobileWindowTitleRow({
|
|
885
|
+
leading,
|
|
886
|
+
trailing,
|
|
887
|
+
children,
|
|
888
|
+
className
|
|
889
|
+
}) {
|
|
890
|
+
return /* @__PURE__ */ jsxs3(
|
|
891
|
+
"div",
|
|
892
|
+
{
|
|
893
|
+
"data-window-mobile-title-row": "",
|
|
894
|
+
className: cn2("flex min-h-11 items-center gap-1.5 py-1", className),
|
|
895
|
+
children: [
|
|
896
|
+
leading,
|
|
897
|
+
children,
|
|
898
|
+
trailing
|
|
899
|
+
]
|
|
900
|
+
}
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
function MobileWindowActionsRow({
|
|
904
|
+
leading,
|
|
905
|
+
actions,
|
|
906
|
+
className
|
|
907
|
+
}) {
|
|
908
|
+
if (!leading && !actions) return null;
|
|
909
|
+
return /* @__PURE__ */ jsxs3(
|
|
910
|
+
"div",
|
|
911
|
+
{
|
|
912
|
+
"data-window-mobile-actions-row": "",
|
|
913
|
+
className: cn2("flex min-h-10 items-center gap-1.5 pb-1", className),
|
|
914
|
+
children: [
|
|
915
|
+
leading,
|
|
916
|
+
actions ? /* @__PURE__ */ jsx3("div", { className: "ml-auto flex items-center gap-1", children: actions }) : null
|
|
917
|
+
]
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/window/mobile/MobileDrawerSurface.tsx
|
|
923
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
924
|
+
function MobileDrawerSurface({
|
|
925
|
+
title,
|
|
926
|
+
children,
|
|
927
|
+
onClose,
|
|
928
|
+
sidebar,
|
|
929
|
+
footer,
|
|
930
|
+
isOpen,
|
|
931
|
+
actionsLeft,
|
|
932
|
+
actionsRight,
|
|
933
|
+
bodyClassName,
|
|
934
|
+
surfaceLayer,
|
|
935
|
+
sizeToContent = false,
|
|
936
|
+
hideClose = false,
|
|
937
|
+
sidebarAs = "drawer"
|
|
938
|
+
}) {
|
|
939
|
+
const [sidebarOpen, setSidebarOpen] = useState2(false);
|
|
940
|
+
const hasSidebar = !!sidebar;
|
|
941
|
+
return /* @__PURE__ */ jsxs4(
|
|
942
|
+
Drawer,
|
|
943
|
+
{
|
|
944
|
+
open: isOpen,
|
|
945
|
+
onOpenChange: (next) => {
|
|
946
|
+
if (!next) onClose();
|
|
947
|
+
},
|
|
948
|
+
children: [
|
|
949
|
+
/* @__PURE__ */ jsxs4(
|
|
950
|
+
DrawerContent,
|
|
951
|
+
{
|
|
952
|
+
"data-surface-layer": surfaceLayer,
|
|
953
|
+
className: cn3(
|
|
954
|
+
"max-h-[85dvh] px-0 pb-safe flex flex-col",
|
|
955
|
+
sizeToContent ? "h-auto" : "h-[85dvh]"
|
|
956
|
+
),
|
|
957
|
+
children: [
|
|
958
|
+
/* @__PURE__ */ jsxs4("div", { className: "-mt-3 flex flex-col px-3 border-b border-border/50 shrink-0", children: [
|
|
959
|
+
/* @__PURE__ */ jsx4(
|
|
960
|
+
MobileWindowTitleRow,
|
|
961
|
+
{
|
|
962
|
+
className: "py-0",
|
|
963
|
+
trailing: hideClose ? null : /* @__PURE__ */ jsx4(DrawerClose, { asChild: true, children: /* @__PURE__ */ jsx4(
|
|
964
|
+
Button,
|
|
965
|
+
{
|
|
966
|
+
type: "button",
|
|
967
|
+
variant: "ghost",
|
|
968
|
+
size: "icon",
|
|
969
|
+
className: "h-8 w-8 shrink-0",
|
|
970
|
+
"aria-label": "Close",
|
|
971
|
+
children: /* @__PURE__ */ jsx4(XIcon, { className: "w-4 h-4" })
|
|
972
|
+
}
|
|
973
|
+
) }),
|
|
974
|
+
children: /* @__PURE__ */ jsx4(
|
|
975
|
+
DrawerTitle,
|
|
976
|
+
{
|
|
977
|
+
"data-window-mobile-title": "",
|
|
978
|
+
className: mobileWindowTitleClass(title),
|
|
979
|
+
children: title
|
|
980
|
+
}
|
|
981
|
+
)
|
|
982
|
+
}
|
|
983
|
+
),
|
|
984
|
+
/* @__PURE__ */ jsx4(
|
|
985
|
+
MobileWindowActionsRow,
|
|
986
|
+
{
|
|
987
|
+
leading: actionsLeft ? /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-1 shrink-0", children: actionsLeft }) : null,
|
|
988
|
+
actions: actionsRight || hasSidebar ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
989
|
+
actionsRight,
|
|
990
|
+
hasSidebar && /* @__PURE__ */ jsx4(
|
|
991
|
+
Button,
|
|
992
|
+
{
|
|
993
|
+
type: "button",
|
|
994
|
+
variant: "ghost",
|
|
995
|
+
size: "icon",
|
|
996
|
+
className: "h-8 w-8",
|
|
997
|
+
"aria-label": sidebarOpen ? "Close sidebar" : "Open sidebar",
|
|
998
|
+
onClick: () => setSidebarOpen((v) => !v),
|
|
999
|
+
children: /* @__PURE__ */ jsx4(PanelLeftIcon, { className: "w-4 h-4" })
|
|
1000
|
+
}
|
|
1001
|
+
)
|
|
1002
|
+
] }) : null
|
|
1003
|
+
}
|
|
1004
|
+
)
|
|
1005
|
+
] }),
|
|
1006
|
+
/* @__PURE__ */ jsx4(DrawerDescription, { className: "sr-only", children: typeof title === "string" ? title : "Mobile window" }),
|
|
1007
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex-1 flex min-h-0 overflow-hidden", children: [
|
|
1008
|
+
hasSidebar && sidebarAs === "inline" && sidebarOpen && /* @__PURE__ */ jsx4("div", { className: "w-2/5 min-w-[160px] border-r border-border/50 overflow-y-auto", children: sidebar }),
|
|
1009
|
+
/* @__PURE__ */ jsx4(
|
|
1010
|
+
"div",
|
|
1011
|
+
{
|
|
1012
|
+
className: cn3(
|
|
1013
|
+
"flex-1 min-h-0 overflow-auto",
|
|
1014
|
+
bodyClassName
|
|
1015
|
+
),
|
|
1016
|
+
children
|
|
1017
|
+
}
|
|
1018
|
+
)
|
|
1019
|
+
] }),
|
|
1020
|
+
footer && /* @__PURE__ */ jsx4("div", { className: "border-t border-border/50 shrink-0", children: footer })
|
|
1021
|
+
]
|
|
1022
|
+
}
|
|
1023
|
+
),
|
|
1024
|
+
hasSidebar && sidebarAs === "drawer" && /* @__PURE__ */ jsx4(
|
|
1025
|
+
Drawer,
|
|
1026
|
+
{
|
|
1027
|
+
open: sidebarOpen,
|
|
1028
|
+
onOpenChange: setSidebarOpen,
|
|
1029
|
+
direction: "right",
|
|
1030
|
+
children: /* @__PURE__ */ jsxs4(DrawerContent, { className: "h-[85dvh] max-h-[85dvh] w-[85vw] max-w-[420px] ml-auto flex flex-col", children: [
|
|
1031
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border/50 shrink-0", children: [
|
|
1032
|
+
/* @__PURE__ */ jsx4(DrawerTitle, { className: "flex-1 text-sm font-medium", children: "Menu" }),
|
|
1033
|
+
/* @__PURE__ */ jsx4(DrawerClose, { asChild: true, children: /* @__PURE__ */ jsx4(
|
|
1034
|
+
Button,
|
|
1035
|
+
{
|
|
1036
|
+
type: "button",
|
|
1037
|
+
variant: "ghost",
|
|
1038
|
+
size: "icon",
|
|
1039
|
+
className: "h-8 w-8",
|
|
1040
|
+
"aria-label": "Close sidebar",
|
|
1041
|
+
children: /* @__PURE__ */ jsx4(XIcon, { className: "w-4 h-4" })
|
|
1042
|
+
}
|
|
1043
|
+
) })
|
|
1044
|
+
] }),
|
|
1045
|
+
/* @__PURE__ */ jsx4(DrawerDescription, { className: "sr-only", children: "Sidebar menu" }),
|
|
1046
|
+
/* @__PURE__ */ jsx4("div", { className: "flex-1 min-h-0 overflow-auto", children: sidebar })
|
|
1047
|
+
] })
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
]
|
|
1051
|
+
}
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/window/mobile/MobileCardSurface.tsx
|
|
1056
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
1057
|
+
import { createPortal } from "react-dom";
|
|
1058
|
+
import { FloatingLayer, useFloatingLayerZIndex } from "@ai-matrx/design-system";
|
|
1059
|
+
import { Button as Button2 } from "@ai-matrx/design-system";
|
|
1060
|
+
import { cn as cn4 } from "@ai-matrx/design-system/cn";
|
|
1061
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1062
|
+
function MobileCardSurface({
|
|
1063
|
+
title,
|
|
1064
|
+
children,
|
|
1065
|
+
onClose,
|
|
1066
|
+
footer,
|
|
1067
|
+
isOpen,
|
|
1068
|
+
actionsRight,
|
|
1069
|
+
bodyClassName,
|
|
1070
|
+
surfaceLayer,
|
|
1071
|
+
maxHeight = "60dvh"
|
|
1072
|
+
}) {
|
|
1073
|
+
const cardRef = useRef2(null);
|
|
1074
|
+
useEffect2(() => {
|
|
1075
|
+
if (!isOpen) return void 0;
|
|
1076
|
+
const onKey = (e) => {
|
|
1077
|
+
if (e.key === "Escape" && !e.defaultPrevented) onClose();
|
|
1078
|
+
};
|
|
1079
|
+
document.addEventListener("keydown", onKey);
|
|
1080
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
1081
|
+
}, [isOpen, onClose]);
|
|
1082
|
+
const zIndex = useFloatingLayerZIndex(9999);
|
|
1083
|
+
if (!isOpen) return null;
|
|
1084
|
+
if (typeof document === "undefined") return null;
|
|
1085
|
+
return createPortal(
|
|
1086
|
+
/* @__PURE__ */ jsx5(FloatingLayer, { children: /* @__PURE__ */ jsxs5(
|
|
1087
|
+
"div",
|
|
1088
|
+
{
|
|
1089
|
+
ref: cardRef,
|
|
1090
|
+
"data-surface-layer": surfaceLayer,
|
|
1091
|
+
role: "dialog",
|
|
1092
|
+
"aria-modal": "false",
|
|
1093
|
+
className: cn4(
|
|
1094
|
+
"fixed right-3 bottom-3 left-3 sm:left-auto sm:right-3",
|
|
1095
|
+
"w-auto sm:w-[92vw] sm:max-w-[420px]",
|
|
1096
|
+
"flex flex-col",
|
|
1097
|
+
"rounded-xl overflow-hidden",
|
|
1098
|
+
"bg-card/97 backdrop-blur-xl border border-border/60 shadow-2xl",
|
|
1099
|
+
// Themes can override via --window-surface-* tokens in globals.css
|
|
1100
|
+
"animate-[card-enter_var(--window-surface-enter-duration,220ms)_var(--window-surface-easing,ease-out)]"
|
|
1101
|
+
),
|
|
1102
|
+
style: { maxHeight, zIndex, paddingBottom: "env(safe-area-inset-bottom)" },
|
|
1103
|
+
children: [
|
|
1104
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col pl-3 pr-1 border-b border-border/50 shrink-0", children: [
|
|
1105
|
+
/* @__PURE__ */ jsx5(
|
|
1106
|
+
MobileWindowTitleRow,
|
|
1107
|
+
{
|
|
1108
|
+
trailing: /* @__PURE__ */ jsx5(
|
|
1109
|
+
Button2,
|
|
1110
|
+
{
|
|
1111
|
+
type: "button",
|
|
1112
|
+
variant: "ghost",
|
|
1113
|
+
size: "icon",
|
|
1114
|
+
className: "h-11 w-11 shrink-0",
|
|
1115
|
+
"aria-label": "Close",
|
|
1116
|
+
onClick: onClose,
|
|
1117
|
+
children: /* @__PURE__ */ jsx5(XIcon, { className: "w-3.5 h-3.5" })
|
|
1118
|
+
}
|
|
1119
|
+
),
|
|
1120
|
+
children: /* @__PURE__ */ jsx5(MobileWindowTitle, { title })
|
|
1121
|
+
}
|
|
1122
|
+
),
|
|
1123
|
+
/* @__PURE__ */ jsx5(MobileWindowActionsRow, { actions: actionsRight })
|
|
1124
|
+
] }),
|
|
1125
|
+
/* @__PURE__ */ jsx5("div", { className: cn4("flex-1 min-h-0 overflow-auto", bodyClassName), children }),
|
|
1126
|
+
footer && /* @__PURE__ */ jsx5("div", { className: "border-t border-border/50 shrink-0", children: footer })
|
|
1127
|
+
]
|
|
1128
|
+
}
|
|
1129
|
+
) }),
|
|
1130
|
+
document.body
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/window/chrome/DebugStrip.tsx
|
|
1135
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
1136
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1137
|
+
function DebugStrip({ rect, zIndex }) {
|
|
1138
|
+
const [vp, setVp] = useState3(
|
|
1139
|
+
() => typeof window === "undefined" ? { vw: 0, vh: 0, sw: 0, sh: 0, dpr: 1 } : {
|
|
1140
|
+
vw: window.innerWidth,
|
|
1141
|
+
vh: window.innerHeight,
|
|
1142
|
+
sw: window.screen.width,
|
|
1143
|
+
sh: window.screen.height,
|
|
1144
|
+
dpr: window.devicePixelRatio ?? 1
|
|
1145
|
+
}
|
|
1146
|
+
);
|
|
1147
|
+
useEffect3(() => {
|
|
1148
|
+
const update = () => setVp({
|
|
1149
|
+
vw: window.innerWidth,
|
|
1150
|
+
vh: window.innerHeight,
|
|
1151
|
+
sw: window.screen.width,
|
|
1152
|
+
sh: window.screen.height,
|
|
1153
|
+
dpr: window.devicePixelRatio ?? 1
|
|
1154
|
+
});
|
|
1155
|
+
window.addEventListener("resize", update);
|
|
1156
|
+
return () => window.removeEventListener("resize", update);
|
|
1157
|
+
}, []);
|
|
1158
|
+
const windowEntries = [
|
|
1159
|
+
["x", rect.x],
|
|
1160
|
+
["y", rect.y],
|
|
1161
|
+
["w", rect.width],
|
|
1162
|
+
["h", rect.height],
|
|
1163
|
+
["z", zIndex]
|
|
1164
|
+
];
|
|
1165
|
+
const viewportEntries = [
|
|
1166
|
+
["vw", vp.vw],
|
|
1167
|
+
["vh", vp.vh],
|
|
1168
|
+
["sw", vp.sw],
|
|
1169
|
+
["sh", vp.sh],
|
|
1170
|
+
["dpr", vp.dpr]
|
|
1171
|
+
];
|
|
1172
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-0.5 px-3 py-1.5 border-b border-warning/20 bg-warning/5 shrink-0 font-mono text-[10px]", children: [
|
|
1173
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3 flex-wrap", children: [
|
|
1174
|
+
/* @__PURE__ */ jsx6("span", { className: "text-warning uppercase tracking-wide text-[9px]", children: "win" }),
|
|
1175
|
+
windowEntries.map(([label, val]) => /* @__PURE__ */ jsxs6(
|
|
1176
|
+
"span",
|
|
1177
|
+
{
|
|
1178
|
+
className: "inline-flex items-center gap-0.5 leading-none",
|
|
1179
|
+
children: [
|
|
1180
|
+
/* @__PURE__ */ jsxs6("span", { className: "text-warning", children: [
|
|
1181
|
+
label,
|
|
1182
|
+
":"
|
|
1183
|
+
] }),
|
|
1184
|
+
/* @__PURE__ */ jsx6("span", { className: "text-warning font-bold tabular-nums", children: Math.round(val) })
|
|
1185
|
+
]
|
|
1186
|
+
},
|
|
1187
|
+
label
|
|
1188
|
+
))
|
|
1189
|
+
] }),
|
|
1190
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3 flex-wrap", children: [
|
|
1191
|
+
/* @__PURE__ */ jsx6("span", { className: "text-info uppercase tracking-wide text-[9px]", children: "vp" }),
|
|
1192
|
+
viewportEntries.map(([label, val]) => /* @__PURE__ */ jsxs6(
|
|
1193
|
+
"span",
|
|
1194
|
+
{
|
|
1195
|
+
className: "inline-flex items-center gap-0.5 leading-none",
|
|
1196
|
+
children: [
|
|
1197
|
+
/* @__PURE__ */ jsxs6("span", { className: "text-info", children: [
|
|
1198
|
+
label,
|
|
1199
|
+
":"
|
|
1200
|
+
] }),
|
|
1201
|
+
/* @__PURE__ */ jsx6("span", { className: "text-info font-bold tabular-nums", children: typeof val === "number" && !Number.isInteger(val) ? val.toFixed(2) : Math.round(val) })
|
|
1202
|
+
]
|
|
1203
|
+
},
|
|
1204
|
+
label
|
|
1205
|
+
))
|
|
1206
|
+
] })
|
|
1207
|
+
] });
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/window/chrome/chromeClasses.ts
|
|
1211
|
+
import { cn as cn5 } from "@ai-matrx/design-system/cn";
|
|
1212
|
+
var WINDOW_CHROME_INTERACTIVE = "[&_button:not(:disabled)]:cursor-pointer [&_a]:cursor-pointer [&_[role=button]:not([aria-disabled=true])]:cursor-pointer [&_summary]:cursor-pointer [&_label:has(input,[role=checkbox],[role=radio])]:cursor-pointer";
|
|
1213
|
+
var WINDOW_CHROME_ACTIONS = cn5(
|
|
1214
|
+
"flex items-center gap-0.5 shrink-0 text-foreground/80 [&_svg]:text-foreground/80",
|
|
1215
|
+
WINDOW_CHROME_INTERACTIVE
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
// src/window/chrome/MobileHeader.tsx
|
|
1219
|
+
import { SegmentedControl } from "@ai-matrx/design-system/controls";
|
|
1220
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1221
|
+
function MobileWindowHeader({
|
|
1222
|
+
title,
|
|
1223
|
+
actionsRight,
|
|
1224
|
+
onMinimize,
|
|
1225
|
+
onClose,
|
|
1226
|
+
hasSidebar,
|
|
1227
|
+
activePaneMobile,
|
|
1228
|
+
onSetActivePane
|
|
1229
|
+
}) {
|
|
1230
|
+
const trafficLights = onClose || onMinimize ? /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5 shrink-0", children: [
|
|
1231
|
+
onClose && /* @__PURE__ */ jsx7(
|
|
1232
|
+
"button",
|
|
1233
|
+
{
|
|
1234
|
+
type: "button",
|
|
1235
|
+
className: "flex h-11 w-11 shrink-0 cursor-pointer items-center justify-center",
|
|
1236
|
+
onClick: onClose,
|
|
1237
|
+
"aria-label": "Close",
|
|
1238
|
+
children: /* @__PURE__ */ jsx7("span", { className: "flex h-6 w-6 items-center justify-center rounded-full matrx-window-light-close", children: /* @__PURE__ */ jsx7(XIcon, { className: "h-2.5 w-2.5 matrx-window-light-glyph" }) })
|
|
1239
|
+
}
|
|
1240
|
+
),
|
|
1241
|
+
onMinimize && /* @__PURE__ */ jsx7(
|
|
1242
|
+
"button",
|
|
1243
|
+
{
|
|
1244
|
+
type: "button",
|
|
1245
|
+
className: "flex h-11 w-11 shrink-0 cursor-pointer items-center justify-center",
|
|
1246
|
+
onClick: onMinimize,
|
|
1247
|
+
"aria-label": "Minimize",
|
|
1248
|
+
children: /* @__PURE__ */ jsx7("span", { className: "flex h-6 w-6 items-center justify-center rounded-full matrx-window-light-minimize", children: /* @__PURE__ */ jsx7(
|
|
1249
|
+
MinusIcon,
|
|
1250
|
+
{
|
|
1251
|
+
className: "h-2.5 w-2.5 matrx-window-light-glyph"
|
|
1252
|
+
}
|
|
1253
|
+
) })
|
|
1254
|
+
}
|
|
1255
|
+
)
|
|
1256
|
+
] }) : null;
|
|
1257
|
+
const paneToggle = hasSidebar ? /* @__PURE__ */ jsx7(SegmentedControl, { "aria-label": "Pane", value: activePaneMobile, onValueChange: onSetActivePane, data: [{ value: "sidebar", label: "Sidebar" }, { value: "main", label: "Content" }] }) : null;
|
|
1258
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex shrink-0 flex-col border-b border-border/50 bg-muted/40 px-2 select-none", children: [
|
|
1259
|
+
/* @__PURE__ */ jsx7(MobileWindowTitleRow, { leading: trafficLights, children: /* @__PURE__ */ jsx7(MobileWindowTitle, { title, className: "pr-1" }) }),
|
|
1260
|
+
/* @__PURE__ */ jsx7(
|
|
1261
|
+
MobileWindowActionsRow,
|
|
1262
|
+
{
|
|
1263
|
+
leading: paneToggle,
|
|
1264
|
+
actions: actionsRight ? /* @__PURE__ */ jsx7("div", { className: WINDOW_CHROME_ACTIONS, children: actionsRight }) : null
|
|
1265
|
+
}
|
|
1266
|
+
)
|
|
1267
|
+
] });
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/window/chrome/DeprecationBanner.tsx
|
|
1271
|
+
import { useState as useState4 } from "react";
|
|
1272
|
+
import { cn as cn6 } from "@ai-matrx/design-system/cn";
|
|
1273
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1274
|
+
function DeprecationBanner({
|
|
1275
|
+
replacedBy,
|
|
1276
|
+
note,
|
|
1277
|
+
className
|
|
1278
|
+
}) {
|
|
1279
|
+
const [dismissed, setDismissed] = useState4(false);
|
|
1280
|
+
if (dismissed) return null;
|
|
1281
|
+
const message = note ?? (replacedBy ? `Deprecated \u2014 moved to "${replacedBy}". This window will be removed soon.` : "Deprecated \u2014 this window will be removed soon.");
|
|
1282
|
+
return /* @__PURE__ */ jsxs8(
|
|
1283
|
+
"div",
|
|
1284
|
+
{
|
|
1285
|
+
className: cn6(
|
|
1286
|
+
"shrink-0 flex items-start gap-2 px-2 py-1 border-b border-destructive/40 bg-destructive/10 text-destructive-ink text-[11px] leading-tight",
|
|
1287
|
+
className
|
|
1288
|
+
),
|
|
1289
|
+
children: [
|
|
1290
|
+
/* @__PURE__ */ jsx8(TriangleAlertIcon, { className: "w-3.5 h-3.5 mt-[1px] shrink-0" }),
|
|
1291
|
+
/* @__PURE__ */ jsx8("span", { className: "flex-1 break-words", children: message }),
|
|
1292
|
+
/* @__PURE__ */ jsx8(
|
|
1293
|
+
"button",
|
|
1294
|
+
{
|
|
1295
|
+
type: "button",
|
|
1296
|
+
"aria-label": "Dismiss deprecation notice",
|
|
1297
|
+
className: "shrink-0 rounded hover:bg-destructive/20 p-0.5 transition-colors",
|
|
1298
|
+
onClick: () => setDismissed(true),
|
|
1299
|
+
children: /* @__PURE__ */ jsx8(XIcon, { className: "w-3 h-3" })
|
|
1300
|
+
}
|
|
1301
|
+
)
|
|
1302
|
+
]
|
|
1303
|
+
}
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/window/chrome/PopoutTopBar.tsx
|
|
1308
|
+
import { cn as cn7 } from "@ai-matrx/design-system/cn";
|
|
1309
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1310
|
+
function PopoutTopBar({
|
|
1311
|
+
title,
|
|
1312
|
+
actionsLeft,
|
|
1313
|
+
actionsRight,
|
|
1314
|
+
onDock
|
|
1315
|
+
}) {
|
|
1316
|
+
return /* @__PURE__ */ jsxs9(
|
|
1317
|
+
"div",
|
|
1318
|
+
{
|
|
1319
|
+
className: cn7(
|
|
1320
|
+
"flex items-center gap-1.5 px-2 py-1 min-h-[32px] shrink-0",
|
|
1321
|
+
"border-b border-border/50 bg-muted/40 select-none"
|
|
1322
|
+
),
|
|
1323
|
+
children: [
|
|
1324
|
+
actionsLeft && /* @__PURE__ */ jsx9("div", { className: WINDOW_CHROME_ACTIONS, children: actionsLeft }),
|
|
1325
|
+
/* @__PURE__ */ jsx9("div", { className: "flex-1 flex items-center justify-center min-w-0", children: /* @__PURE__ */ jsx9("span", { className: "text-xs font-medium text-foreground/80 truncate", children: title ?? "" }) }),
|
|
1326
|
+
actionsRight && /* @__PURE__ */ jsx9("div", { className: WINDOW_CHROME_ACTIONS, children: actionsRight }),
|
|
1327
|
+
/* @__PURE__ */ jsx9(
|
|
1328
|
+
"button",
|
|
1329
|
+
{
|
|
1330
|
+
type: "button",
|
|
1331
|
+
className: cn7(
|
|
1332
|
+
"ml-1 p-1 rounded hover:bg-accent/60 transition-colors shrink-0 cursor-pointer",
|
|
1333
|
+
"text-foreground/60 hover:text-foreground"
|
|
1334
|
+
),
|
|
1335
|
+
onClick: onDock,
|
|
1336
|
+
title: "Dock window",
|
|
1337
|
+
"aria-label": "Dock window back to parent",
|
|
1338
|
+
children: /* @__PURE__ */ jsx9(ArrowLeftToLineIcon, { className: "w-3.5 h-3.5" })
|
|
1339
|
+
}
|
|
1340
|
+
)
|
|
1341
|
+
]
|
|
1342
|
+
}
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/window/popout/featureDetection.ts
|
|
1347
|
+
function detectPopoutCapability() {
|
|
1348
|
+
if (typeof window === "undefined") return "none";
|
|
1349
|
+
const w = window;
|
|
1350
|
+
if (w.documentPictureInPicture && typeof w.documentPictureInPicture.requestWindow === "function") {
|
|
1351
|
+
return "pip";
|
|
1352
|
+
}
|
|
1353
|
+
if (typeof window.open === "function") {
|
|
1354
|
+
return "popup";
|
|
1355
|
+
}
|
|
1356
|
+
return "none";
|
|
1357
|
+
}
|
|
1358
|
+
function supportsDocumentPictureInPicture() {
|
|
1359
|
+
return detectPopoutCapability() === "pip";
|
|
1360
|
+
}
|
|
1361
|
+
function supportsAnyPopout() {
|
|
1362
|
+
return detectPopoutCapability() !== "none";
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/window/popout/usePopoutWindow.ts
|
|
1366
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
1367
|
+
import { useSummonDispatch as useSummonDispatch2, useSummonPorts, useSummonSelector as useSummonSelector2 } from "@ai-matrx/summon/react";
|
|
1368
|
+
import {
|
|
1369
|
+
popOutWindow,
|
|
1370
|
+
dockWindow,
|
|
1371
|
+
selectActivePipWindowId,
|
|
1372
|
+
selectPopoutMode
|
|
1373
|
+
} from "@ai-matrx/summon";
|
|
1374
|
+
|
|
1375
|
+
// src/window/popout/popoutWindowMap.ts
|
|
1376
|
+
import { useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
1377
|
+
import { globalSlot as globalSlot2 } from "@ai-matrx/kit/global-slot";
|
|
1378
|
+
var slot = () => globalSlot2("ai-matrx.summon.window.popoutWindows", () => ({ map: /* @__PURE__ */ new Map(), listeners: /* @__PURE__ */ new Set() }));
|
|
1379
|
+
function notify() {
|
|
1380
|
+
for (const fn of slot().listeners) fn();
|
|
1381
|
+
}
|
|
1382
|
+
function setPopoutWindow(id, win) {
|
|
1383
|
+
const { map } = slot();
|
|
1384
|
+
const prev = map.get(id);
|
|
1385
|
+
if (prev === win) return;
|
|
1386
|
+
map.set(id, win);
|
|
1387
|
+
notify();
|
|
1388
|
+
}
|
|
1389
|
+
function deletePopoutWindow(id) {
|
|
1390
|
+
const { map } = slot();
|
|
1391
|
+
if (!map.has(id)) return;
|
|
1392
|
+
map.delete(id);
|
|
1393
|
+
notify();
|
|
1394
|
+
}
|
|
1395
|
+
function getPopoutWindow(id) {
|
|
1396
|
+
return slot().map.get(id) ?? null;
|
|
1397
|
+
}
|
|
1398
|
+
function forEachPopoutWindow(cb) {
|
|
1399
|
+
for (const [id, win] of slot().map) cb(win, id);
|
|
1400
|
+
}
|
|
1401
|
+
function subscribePopoutWindowMap(listener) {
|
|
1402
|
+
const { listeners } = slot();
|
|
1403
|
+
listeners.add(listener);
|
|
1404
|
+
return () => {
|
|
1405
|
+
listeners.delete(listener);
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function usePopoutWindowRef(id) {
|
|
1409
|
+
return useSyncExternalStore3(
|
|
1410
|
+
subscribePopoutWindowMap,
|
|
1411
|
+
() => getPopoutWindow(id),
|
|
1412
|
+
// SSR snapshot — popouts are never present during SSR.
|
|
1413
|
+
() => null
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// src/window/popout/cloneStyles.ts
|
|
1418
|
+
var RESET_CSS = `
|
|
1419
|
+
html, body {
|
|
1420
|
+
margin: 0;
|
|
1421
|
+
padding: 0;
|
|
1422
|
+
height: 100%;
|
|
1423
|
+
overflow: hidden;
|
|
1424
|
+
}
|
|
1425
|
+
body {
|
|
1426
|
+
font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
|
|
1427
|
+
background: hsl(var(--background));
|
|
1428
|
+
color: hsl(var(--foreground));
|
|
1429
|
+
-webkit-font-smoothing: antialiased;
|
|
1430
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1431
|
+
}
|
|
1432
|
+
`.trim();
|
|
1433
|
+
var POPOUT_DATA_ATTR = "data-popout-clone";
|
|
1434
|
+
var RESET_MARKER = "reset";
|
|
1435
|
+
function cloneStylesIntoDocument(source, target) {
|
|
1436
|
+
injectResetStylesheet(target);
|
|
1437
|
+
cloneAllStyleAndLinkNodes(source, target);
|
|
1438
|
+
cloneAdoptedStylesheets(source, target);
|
|
1439
|
+
mirrorHtmlAttributes(source, target);
|
|
1440
|
+
const headObserver = new MutationObserver((mutations) => {
|
|
1441
|
+
handleHeadMutations(mutations, target);
|
|
1442
|
+
});
|
|
1443
|
+
headObserver.observe(source.head, {
|
|
1444
|
+
childList: true,
|
|
1445
|
+
subtree: true,
|
|
1446
|
+
characterData: true,
|
|
1447
|
+
attributes: true,
|
|
1448
|
+
attributeFilter: ["href", "media"]
|
|
1449
|
+
});
|
|
1450
|
+
const htmlObserver = new MutationObserver(() => {
|
|
1451
|
+
mirrorHtmlAttributes(source, target);
|
|
1452
|
+
});
|
|
1453
|
+
htmlObserver.observe(source.documentElement, {
|
|
1454
|
+
attributes: true,
|
|
1455
|
+
attributeFilter: ["class", "data-theme", "data-scroll-behavior"]
|
|
1456
|
+
});
|
|
1457
|
+
return {
|
|
1458
|
+
dispose() {
|
|
1459
|
+
headObserver.disconnect();
|
|
1460
|
+
htmlObserver.disconnect();
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
function injectResetStylesheet(target) {
|
|
1465
|
+
const existing = target.head.querySelector(
|
|
1466
|
+
`style[${POPOUT_DATA_ATTR}="${RESET_MARKER}"]`
|
|
1467
|
+
);
|
|
1468
|
+
if (existing) return;
|
|
1469
|
+
const style = target.createElement("style");
|
|
1470
|
+
style.setAttribute(POPOUT_DATA_ATTR, RESET_MARKER);
|
|
1471
|
+
style.textContent = RESET_CSS;
|
|
1472
|
+
target.head.insertBefore(style, target.head.firstChild);
|
|
1473
|
+
}
|
|
1474
|
+
function cloneAllStyleAndLinkNodes(source, target) {
|
|
1475
|
+
const nodes = source.head.querySelectorAll("style, link[rel='stylesheet']");
|
|
1476
|
+
nodes.forEach((node) => {
|
|
1477
|
+
cloneOneStyleOrLink(node, target);
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
function cloneOneStyleOrLink(node, target) {
|
|
1481
|
+
if (node.tagName === "STYLE") {
|
|
1482
|
+
const clone = target.createElement("style");
|
|
1483
|
+
clone.textContent = node.textContent ?? "";
|
|
1484
|
+
copyDataAttributes(node, clone);
|
|
1485
|
+
clone.setAttribute(POPOUT_DATA_ATTR, getNodeKey(node));
|
|
1486
|
+
target.head.appendChild(clone);
|
|
1487
|
+
return clone;
|
|
1488
|
+
}
|
|
1489
|
+
if (node.tagName === "LINK" && node.rel === "stylesheet") {
|
|
1490
|
+
const link = node;
|
|
1491
|
+
const clone = target.createElement("link");
|
|
1492
|
+
clone.rel = "stylesheet";
|
|
1493
|
+
if (link.href) clone.href = link.href;
|
|
1494
|
+
if (link.media) clone.media = link.media;
|
|
1495
|
+
if (link.crossOrigin) clone.crossOrigin = link.crossOrigin;
|
|
1496
|
+
if (link.integrity) clone.integrity = link.integrity;
|
|
1497
|
+
copyDataAttributes(link, clone);
|
|
1498
|
+
clone.setAttribute(POPOUT_DATA_ATTR, getNodeKey(node));
|
|
1499
|
+
target.head.appendChild(clone);
|
|
1500
|
+
return clone;
|
|
1501
|
+
}
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
function getNodeKey(node) {
|
|
1505
|
+
if (node.tagName === "STYLE") {
|
|
1506
|
+
return "style:" + cheapHash(node.textContent ?? "");
|
|
1507
|
+
}
|
|
1508
|
+
if (node.tagName === "LINK") {
|
|
1509
|
+
const href = node.href || "";
|
|
1510
|
+
return "link:" + href;
|
|
1511
|
+
}
|
|
1512
|
+
return "other:" + Math.random().toString(36).slice(2);
|
|
1513
|
+
}
|
|
1514
|
+
function cheapHash(s) {
|
|
1515
|
+
let h = 5381;
|
|
1516
|
+
for (let i = 0; i < s.length; i++) {
|
|
1517
|
+
h = (h << 5) + h + s.charCodeAt(i) | 0;
|
|
1518
|
+
}
|
|
1519
|
+
return (h >>> 0).toString(36);
|
|
1520
|
+
}
|
|
1521
|
+
function copyDataAttributes(from, to) {
|
|
1522
|
+
for (const attr of Array.from(from.attributes)) {
|
|
1523
|
+
if (attr.name.startsWith("data-")) {
|
|
1524
|
+
if (attr.name === POPOUT_DATA_ATTR) continue;
|
|
1525
|
+
to.setAttribute(attr.name, attr.value);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function cloneAdoptedStylesheets(source, target) {
|
|
1530
|
+
if (Array.isArray(source.adoptedStyleSheets) && source.adoptedStyleSheets.length > 0) {
|
|
1531
|
+
try {
|
|
1532
|
+
target.adoptedStyleSheets = [...source.adoptedStyleSheets];
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
function mirrorHtmlAttributes(source, target) {
|
|
1538
|
+
const src = source.documentElement;
|
|
1539
|
+
const tgt = target.documentElement;
|
|
1540
|
+
if (tgt.className !== src.className) {
|
|
1541
|
+
tgt.className = src.className;
|
|
1542
|
+
}
|
|
1543
|
+
for (const attr of ["data-theme", "data-scroll-behavior", "lang"]) {
|
|
1544
|
+
const v = src.getAttribute(attr);
|
|
1545
|
+
if (v === null) {
|
|
1546
|
+
if (tgt.hasAttribute(attr)) tgt.removeAttribute(attr);
|
|
1547
|
+
} else if (tgt.getAttribute(attr) !== v) {
|
|
1548
|
+
tgt.setAttribute(attr, v);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function handleHeadMutations(mutations, target) {
|
|
1553
|
+
for (const m of mutations) {
|
|
1554
|
+
m.addedNodes.forEach((n) => {
|
|
1555
|
+
if (!(n instanceof Element)) return;
|
|
1556
|
+
if (n.tagName === "STYLE" || n.tagName === "LINK" && n.rel === "stylesheet") {
|
|
1557
|
+
cloneOneStyleOrLink(n, target);
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
m.removedNodes.forEach((n) => {
|
|
1561
|
+
if (!(n instanceof Element)) return;
|
|
1562
|
+
if (n.tagName !== "STYLE" && n.tagName !== "LINK") return;
|
|
1563
|
+
const key = getNodeKey(n);
|
|
1564
|
+
const cloneEl = target.head.querySelector(
|
|
1565
|
+
`[${POPOUT_DATA_ATTR}="${cssEscape(key)}"]`
|
|
1566
|
+
);
|
|
1567
|
+
if (cloneEl) cloneEl.remove();
|
|
1568
|
+
});
|
|
1569
|
+
if (m.type === "characterData" && m.target.parentElement?.tagName === "STYLE") {
|
|
1570
|
+
const styleEl = m.target.parentElement;
|
|
1571
|
+
const oldKey = findExistingMirrorByContent(target, styleEl.textContent ?? "");
|
|
1572
|
+
const newKey = getNodeKey(styleEl);
|
|
1573
|
+
if (oldKey && oldKey !== newKey) {
|
|
1574
|
+
const mirror = target.head.querySelector(
|
|
1575
|
+
`[${POPOUT_DATA_ATTR}="${cssEscape(oldKey)}"]`
|
|
1576
|
+
);
|
|
1577
|
+
if (mirror) {
|
|
1578
|
+
mirror.textContent = styleEl.textContent ?? "";
|
|
1579
|
+
mirror.setAttribute(POPOUT_DATA_ATTR, newKey);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (m.type === "attributes" && m.target instanceof HTMLLinkElement && m.target.rel === "stylesheet") {
|
|
1584
|
+
const link = m.target;
|
|
1585
|
+
const allClones = target.head.querySelectorAll(
|
|
1586
|
+
`link[${POPOUT_DATA_ATTR}]`
|
|
1587
|
+
);
|
|
1588
|
+
cloneOneStyleOrLink(link, target);
|
|
1589
|
+
void allClones;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
function findExistingMirrorByContent(target, text) {
|
|
1594
|
+
const styles = target.head.querySelectorAll(
|
|
1595
|
+
`style[${POPOUT_DATA_ATTR}^="style:"]`
|
|
1596
|
+
);
|
|
1597
|
+
for (const s of Array.from(styles)) {
|
|
1598
|
+
if (s.textContent === text) return null;
|
|
1599
|
+
}
|
|
1600
|
+
if (styles.length > 0) {
|
|
1601
|
+
return styles[0]?.getAttribute(POPOUT_DATA_ATTR) ?? null;
|
|
1602
|
+
}
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
function cssEscape(s) {
|
|
1606
|
+
return s.replace(/(["\\])/g, "\\$1");
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// src/window/popout/popoutPendingStorage.ts
|
|
1610
|
+
var STORAGE_KEY = "matrx_popout_pending";
|
|
1611
|
+
function readState() {
|
|
1612
|
+
if (typeof window === "undefined") return { windowIds: [] };
|
|
1613
|
+
try {
|
|
1614
|
+
const raw = window.sessionStorage.getItem(STORAGE_KEY);
|
|
1615
|
+
if (!raw) return { windowIds: [] };
|
|
1616
|
+
const parsed = JSON.parse(raw);
|
|
1617
|
+
if (!Array.isArray(parsed.windowIds)) return { windowIds: [] };
|
|
1618
|
+
return {
|
|
1619
|
+
windowIds: parsed.windowIds.filter(
|
|
1620
|
+
(id) => typeof id === "string"
|
|
1621
|
+
)
|
|
1622
|
+
};
|
|
1623
|
+
} catch {
|
|
1624
|
+
return { windowIds: [] };
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function writeState(state2) {
|
|
1628
|
+
if (typeof window === "undefined") return;
|
|
1629
|
+
try {
|
|
1630
|
+
if (state2.windowIds.length === 0) {
|
|
1631
|
+
window.sessionStorage.removeItem(STORAGE_KEY);
|
|
1632
|
+
} else {
|
|
1633
|
+
window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state2));
|
|
1634
|
+
}
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
function markPopoutPending(windowId) {
|
|
1639
|
+
const state2 = readState();
|
|
1640
|
+
if (!state2.windowIds.includes(windowId)) {
|
|
1641
|
+
state2.windowIds.push(windowId);
|
|
1642
|
+
writeState(state2);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
function clearPopoutPending(windowId) {
|
|
1646
|
+
const state2 = readState();
|
|
1647
|
+
const next = state2.windowIds.filter((id) => id !== windowId);
|
|
1648
|
+
if (next.length !== state2.windowIds.length) {
|
|
1649
|
+
writeState({ windowIds: next });
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
function getPendingPopoutWindowIds() {
|
|
1653
|
+
return readState().windowIds;
|
|
1654
|
+
}
|
|
1655
|
+
function clearAllPopoutPending() {
|
|
1656
|
+
writeState({ windowIds: [] });
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// src/window/popout/usePopoutWindow.ts
|
|
1660
|
+
import { buildDockWindowPayload } from "@ai-matrx/summon";
|
|
1661
|
+
import { openOutbound } from "@ai-matrx/kit/outbound";
|
|
1662
|
+
function usePopoutWindow(windowId) {
|
|
1663
|
+
const dispatch = useSummonDispatch2();
|
|
1664
|
+
const { notify: notify3 } = useSummonPorts();
|
|
1665
|
+
const popoutMode = useSummonSelector2(selectPopoutMode(windowId));
|
|
1666
|
+
const activePipWindowId = useSummonSelector2(selectActivePipWindowId);
|
|
1667
|
+
const stylesDisposeRef = useRef3(null);
|
|
1668
|
+
const handleClose = useCallback2(() => {
|
|
1669
|
+
const win = getPopoutWindow(windowId);
|
|
1670
|
+
if (!win && process.env.NODE_ENV !== "production") {
|
|
1671
|
+
}
|
|
1672
|
+
stylesDisposeRef.current?.();
|
|
1673
|
+
stylesDisposeRef.current = null;
|
|
1674
|
+
deletePopoutWindow(windowId);
|
|
1675
|
+
clearPopoutPending(windowId);
|
|
1676
|
+
dispatch(dockWindow(buildDockWindowPayload(windowId)));
|
|
1677
|
+
try {
|
|
1678
|
+
win?.close();
|
|
1679
|
+
} catch {
|
|
1680
|
+
}
|
|
1681
|
+
if (process.env.NODE_ENV !== "production" && win) {
|
|
1682
|
+
console.info(`[popout] closed window for "${windowId}"`);
|
|
1683
|
+
}
|
|
1684
|
+
}, [dispatch, windowId]);
|
|
1685
|
+
const openPopout = useCallback2(
|
|
1686
|
+
async (opts) => {
|
|
1687
|
+
const capability = detectPopoutCapability();
|
|
1688
|
+
if (capability === "none") {
|
|
1689
|
+
notify3.error("Pop-out not supported", {
|
|
1690
|
+
description: "Your browser doesn't support floating windows."
|
|
1691
|
+
});
|
|
1692
|
+
return { ok: false, reason: "no-capability" };
|
|
1693
|
+
}
|
|
1694
|
+
const slotTakenByOther = activePipWindowId !== null && activePipWindowId !== windowId;
|
|
1695
|
+
let pipWin = null;
|
|
1696
|
+
let mode = "popup";
|
|
1697
|
+
if (capability === "pip" && !slotTakenByOther) {
|
|
1698
|
+
try {
|
|
1699
|
+
const w = window;
|
|
1700
|
+
if (w.documentPictureInPicture) {
|
|
1701
|
+
pipWin = await w.documentPictureInPicture.requestWindow({
|
|
1702
|
+
width: Math.max(200, Math.round(opts.width)),
|
|
1703
|
+
height: Math.max(120, Math.round(opts.height))
|
|
1704
|
+
});
|
|
1705
|
+
mode = "pip";
|
|
1706
|
+
}
|
|
1707
|
+
} catch (err) {
|
|
1708
|
+
console.warn(
|
|
1709
|
+
"[popout] Document PiP request failed, falling back to popup:",
|
|
1710
|
+
err
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
if (!pipWin) {
|
|
1715
|
+
const features = [
|
|
1716
|
+
"popup=yes",
|
|
1717
|
+
`width=${Math.max(200, Math.round(opts.width))}`,
|
|
1718
|
+
`height=${Math.max(120, Math.round(opts.height))}`
|
|
1719
|
+
].join(",");
|
|
1720
|
+
try {
|
|
1721
|
+
pipWin = openOutbound("", `popout-${windowId}`, features);
|
|
1722
|
+
mode = "popup";
|
|
1723
|
+
} catch (err) {
|
|
1724
|
+
console.error("[popout] window.open failed:", err);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (!pipWin) {
|
|
1728
|
+
notify3.error("Pop-up blocked", {
|
|
1729
|
+
description: "Please allow pop-ups for this site to use floating windows."
|
|
1730
|
+
});
|
|
1731
|
+
return { ok: false, reason: "popup-blocked" };
|
|
1732
|
+
}
|
|
1733
|
+
try {
|
|
1734
|
+
pipWin.document.title = opts.title;
|
|
1735
|
+
} catch {
|
|
1736
|
+
}
|
|
1737
|
+
stylesDisposeRef.current = cloneStylesIntoDocument(
|
|
1738
|
+
window.document,
|
|
1739
|
+
pipWin.document
|
|
1740
|
+
).dispose;
|
|
1741
|
+
pipWin.addEventListener("pagehide", handleClose, { once: true });
|
|
1742
|
+
setPopoutWindow(windowId, pipWin);
|
|
1743
|
+
markPopoutPending(windowId);
|
|
1744
|
+
dispatch(popOutWindow({ id: windowId, mode }));
|
|
1745
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1746
|
+
console.info(
|
|
1747
|
+
`[popout] opened ${mode} window for "${windowId}" (${opts.width}x${opts.height})`
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
return { ok: true, mode };
|
|
1751
|
+
},
|
|
1752
|
+
[activePipWindowId, dispatch, handleClose, notify3, windowId]
|
|
1753
|
+
);
|
|
1754
|
+
const close = useCallback2(() => {
|
|
1755
|
+
const win = getPopoutWindow(windowId);
|
|
1756
|
+
if (!win) {
|
|
1757
|
+
dispatch(dockWindow(buildDockWindowPayload(windowId)));
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
try {
|
|
1761
|
+
win.close();
|
|
1762
|
+
} catch {
|
|
1763
|
+
handleClose();
|
|
1764
|
+
}
|
|
1765
|
+
}, [dispatch, handleClose, windowId]);
|
|
1766
|
+
useEffect4(() => {
|
|
1767
|
+
if (popoutMode === null && getPopoutWindow(windowId) !== null) {
|
|
1768
|
+
try {
|
|
1769
|
+
getPopoutWindow(windowId)?.close();
|
|
1770
|
+
} catch {
|
|
1771
|
+
handleClose();
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}, [popoutMode, windowId, handleClose]);
|
|
1775
|
+
useEffect4(() => {
|
|
1776
|
+
const onBeforeUnload = () => {
|
|
1777
|
+
try {
|
|
1778
|
+
getPopoutWindow(windowId)?.close();
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
1783
|
+
return () => {
|
|
1784
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
1785
|
+
};
|
|
1786
|
+
}, [windowId]);
|
|
1787
|
+
return { openPopout, close };
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// src/window/popout/usePopoutControl.ts
|
|
1791
|
+
import { useCallback as useCallback3 } from "react";
|
|
1792
|
+
import { useSummonDispatch as useSummonDispatch3 } from "@ai-matrx/summon/react";
|
|
1793
|
+
import {
|
|
1794
|
+
dockWindow as dockWindow2
|
|
1795
|
+
} from "@ai-matrx/summon";
|
|
1796
|
+
import { buildDockWindowPayload as buildDockWindowPayload2 } from "@ai-matrx/summon";
|
|
1797
|
+
import { globalSlot as globalSlot3 } from "@ai-matrx/kit/global-slot";
|
|
1798
|
+
var openers = () => globalSlot3("ai-matrx.summon.window.popoutOpeners", () => /* @__PURE__ */ new Map());
|
|
1799
|
+
function registerPopoutOpener(windowId, fn) {
|
|
1800
|
+
openers().set(windowId, fn);
|
|
1801
|
+
return () => {
|
|
1802
|
+
if (openers().get(windowId) === fn) {
|
|
1803
|
+
openers().delete(windowId);
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
function getPopoutOpener(windowId) {
|
|
1808
|
+
return openers().get(windowId) ?? null;
|
|
1809
|
+
}
|
|
1810
|
+
function usePopoutControl() {
|
|
1811
|
+
const dispatch = useSummonDispatch3();
|
|
1812
|
+
const popOut = useCallback3(
|
|
1813
|
+
async (windowId, opts) => {
|
|
1814
|
+
const opener = getPopoutOpener(windowId);
|
|
1815
|
+
if (!opener) {
|
|
1816
|
+
return { ok: false, reason: "not-mounted" };
|
|
1817
|
+
}
|
|
1818
|
+
return opener(opts);
|
|
1819
|
+
},
|
|
1820
|
+
[]
|
|
1821
|
+
);
|
|
1822
|
+
const dock = useCallback3(
|
|
1823
|
+
(windowId) => {
|
|
1824
|
+
const payload = buildDockWindowPayload2(windowId);
|
|
1825
|
+
dispatch(dockWindow2(payload));
|
|
1826
|
+
},
|
|
1827
|
+
[dispatch]
|
|
1828
|
+
);
|
|
1829
|
+
return { popOut, dock };
|
|
1830
|
+
}
|
|
1831
|
+
function buildPopoutOpts(rect, opts) {
|
|
1832
|
+
return {
|
|
1833
|
+
width: opts?.width ?? rect?.width ?? 480,
|
|
1834
|
+
height: opts?.height ?? rect?.height ?? 320,
|
|
1835
|
+
title: opts?.title ?? "Window"
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/window/popout/PopoutPortal.tsx
|
|
1840
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1841
|
+
import { useSummonSelector as useSummonSelector3 } from "@ai-matrx/summon/react";
|
|
1842
|
+
import { selectPopoutMode as selectPopoutMode2 } from "@ai-matrx/summon";
|
|
1843
|
+
|
|
1844
|
+
// src/window/popout/PopoutShell.tsx
|
|
1845
|
+
import { useMemo } from "react";
|
|
1846
|
+
import { PortalContainerProvider } from "@ai-matrx/design-system";
|
|
1847
|
+
|
|
1848
|
+
// src/window/popout/PopoutContext.tsx
|
|
1849
|
+
import { createContext, useContext } from "react";
|
|
1850
|
+
var DEFAULT_VALUE = {
|
|
1851
|
+
popoutDocument: null,
|
|
1852
|
+
popoutWindow: null,
|
|
1853
|
+
popoutContainer: null,
|
|
1854
|
+
mode: null
|
|
1855
|
+
};
|
|
1856
|
+
var PopoutContext = createContext(DEFAULT_VALUE);
|
|
1857
|
+
function usePopout() {
|
|
1858
|
+
return useContext(PopoutContext);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// src/window/popout/PopoutShell.tsx
|
|
1862
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1863
|
+
function PopoutShell({
|
|
1864
|
+
popoutWindow,
|
|
1865
|
+
mode,
|
|
1866
|
+
children
|
|
1867
|
+
}) {
|
|
1868
|
+
const value = useMemo(
|
|
1869
|
+
() => ({
|
|
1870
|
+
popoutDocument: popoutWindow.document,
|
|
1871
|
+
popoutWindow,
|
|
1872
|
+
popoutContainer: popoutWindow.document.body,
|
|
1873
|
+
mode
|
|
1874
|
+
}),
|
|
1875
|
+
[popoutWindow, mode]
|
|
1876
|
+
);
|
|
1877
|
+
return /* @__PURE__ */ jsx10(PopoutContext.Provider, { value, children: /* @__PURE__ */ jsx10(PortalContainerProvider, { container: value.popoutContainer, children }) });
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// src/window/popout/PopoutPortal.tsx
|
|
1881
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1882
|
+
function PopoutPortal({ windowId, children }) {
|
|
1883
|
+
const popoutWin = usePopoutWindowRef(windowId);
|
|
1884
|
+
const popoutMode = useSummonSelector3(selectPopoutMode2(windowId));
|
|
1885
|
+
if (!popoutWin || popoutMode === null) return null;
|
|
1886
|
+
return createPortal2(
|
|
1887
|
+
/* @__PURE__ */ jsx11(PopoutShell, { popoutWindow: popoutWin, mode: popoutMode, children }),
|
|
1888
|
+
popoutWin.document.body
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// src/window/tray/traySnapshotMap.ts
|
|
1893
|
+
import { globalSlot as globalSlot4 } from "@ai-matrx/kit/global-slot";
|
|
1894
|
+
var MAX_SNAPSHOTS = 16;
|
|
1895
|
+
var state = () => globalSlot4("ai-matrx.summon.window.traySnapshots", () => ({ map: /* @__PURE__ */ new Map(), listeners: /* @__PURE__ */ new Set() }));
|
|
1896
|
+
function notify2() {
|
|
1897
|
+
for (const fn of state().listeners) fn();
|
|
1898
|
+
}
|
|
1899
|
+
function revoke(url) {
|
|
1900
|
+
if (url && typeof URL !== "undefined" && typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(url);
|
|
1901
|
+
}
|
|
1902
|
+
function setTraySnapshot(id, blob) {
|
|
1903
|
+
const { map } = state();
|
|
1904
|
+
revoke(map.get(id));
|
|
1905
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
1906
|
+
map.delete(id);
|
|
1907
|
+
map.set(id, objectUrl);
|
|
1908
|
+
while (map.size > MAX_SNAPSHOTS) {
|
|
1909
|
+
const oldestId = map.keys().next().value;
|
|
1910
|
+
if (oldestId === void 0) break;
|
|
1911
|
+
revoke(map.get(oldestId));
|
|
1912
|
+
map.delete(oldestId);
|
|
1913
|
+
}
|
|
1914
|
+
notify2();
|
|
1915
|
+
}
|
|
1916
|
+
function clearTraySnapshot(id) {
|
|
1917
|
+
const { map } = state();
|
|
1918
|
+
const objectUrl = map.get(id);
|
|
1919
|
+
if (!objectUrl) return;
|
|
1920
|
+
map.delete(id);
|
|
1921
|
+
revoke(objectUrl);
|
|
1922
|
+
notify2();
|
|
1923
|
+
}
|
|
1924
|
+
function getTraySnapshot(id) {
|
|
1925
|
+
return state().map.get(id) ?? null;
|
|
1926
|
+
}
|
|
1927
|
+
function subscribeTraySnapshotMap(listener) {
|
|
1928
|
+
const { listeners } = state();
|
|
1929
|
+
listeners.add(listener);
|
|
1930
|
+
return () => {
|
|
1931
|
+
listeners.delete(listener);
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// src/window/tray/defaultTraySnapshotCapture.ts
|
|
1936
|
+
var MAX_CAPTURE_ELEMENT_COUNT = 3e3;
|
|
1937
|
+
var TRANSPARENT_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
1938
|
+
async function loadToCanvas() {
|
|
1939
|
+
try {
|
|
1940
|
+
const mod = await import("html-to-image");
|
|
1941
|
+
return mod.toCanvas;
|
|
1942
|
+
} catch {
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
async function captureElementThumbnail(element, options = {}) {
|
|
1947
|
+
const toCanvas = await loadToCanvas();
|
|
1948
|
+
if (!toCanvas) return null;
|
|
1949
|
+
const sourceWidth = Math.max(1, element.offsetWidth);
|
|
1950
|
+
const sourceHeight = Math.max(1, element.offsetHeight);
|
|
1951
|
+
const maxEdge = Math.max(64, options.maxEdge ?? 320);
|
|
1952
|
+
const scale = Math.min(1, maxEdge / Math.max(sourceWidth, sourceHeight));
|
|
1953
|
+
const canvas = await toCanvas(element, {
|
|
1954
|
+
pixelRatio: 1,
|
|
1955
|
+
canvasWidth: Math.max(1, Math.round(sourceWidth * scale)),
|
|
1956
|
+
canvasHeight: Math.max(1, Math.round(sourceHeight * scale)),
|
|
1957
|
+
skipFonts: true,
|
|
1958
|
+
imagePlaceholder: TRANSPARENT_PIXEL,
|
|
1959
|
+
onImageErrorHandler: () => void 0
|
|
1960
|
+
});
|
|
1961
|
+
return new Promise((resolve) => {
|
|
1962
|
+
canvas.toBlob(resolve, "image/webp", options.quality ?? 0.62);
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
async function defaultTraySnapshotCapture(bodyEl) {
|
|
1966
|
+
try {
|
|
1967
|
+
if (bodyEl.querySelectorAll("*").length > MAX_CAPTURE_ELEMENT_COUNT || bodyEl.offsetWidth < 8 || bodyEl.offsetHeight < 8) {
|
|
1968
|
+
return null;
|
|
1969
|
+
}
|
|
1970
|
+
const blob = await captureElementThumbnail(bodyEl);
|
|
1971
|
+
return blob && await isFlatImage(blob) ? null : blob;
|
|
1972
|
+
} catch {
|
|
1973
|
+
return null;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
async function isFlatImage(blob) {
|
|
1977
|
+
try {
|
|
1978
|
+
const bitmap = await createImageBitmap(blob);
|
|
1979
|
+
const size = 24;
|
|
1980
|
+
const canvas = document.createElement("canvas");
|
|
1981
|
+
canvas.width = size;
|
|
1982
|
+
canvas.height = size;
|
|
1983
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
1984
|
+
if (!ctx) return false;
|
|
1985
|
+
ctx.drawImage(bitmap, 0, 0, size, size);
|
|
1986
|
+
bitmap.close();
|
|
1987
|
+
const { data } = ctx.getImageData(0, 0, size, size);
|
|
1988
|
+
const r0 = data[0] ?? 0;
|
|
1989
|
+
const g0 = data[1] ?? 0;
|
|
1990
|
+
const b0 = data[2] ?? 0;
|
|
1991
|
+
const a0 = data[3] ?? 0;
|
|
1992
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
1993
|
+
if (Math.abs((data[i] ?? 0) - r0) > 6 || Math.abs((data[i + 1] ?? 0) - g0) > 6 || Math.abs((data[i + 2] ?? 0) - b0) > 6 || Math.abs((data[i + 3] ?? 0) - a0) > 6) {
|
|
1994
|
+
return false;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return true;
|
|
1998
|
+
} catch {
|
|
1999
|
+
return false;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// src/window/tray/MinimizedWindowContent.tsx
|
|
2004
|
+
import { getOverlayMeta as getOverlayMeta2 } from "@ai-matrx/summon";
|
|
2005
|
+
|
|
2006
|
+
// src/window/tray/TrayChipPreview.tsx
|
|
2007
|
+
import { Component, memo, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
2008
|
+
import { cn as cn8 } from "@ai-matrx/design-system/cn";
|
|
2009
|
+
import { getOverlayMeta, selectOverlayData, DEFAULT_INSTANCE_ID } from "@ai-matrx/summon";
|
|
2010
|
+
import { useSummonPorts as useSummonPorts2, useSummonSelector as useSummonSelector4 } from "@ai-matrx/summon/react";
|
|
2011
|
+
import { getTrayPreviewEntry } from "@ai-matrx/summon/window-host";
|
|
2012
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2013
|
+
function isPlainRecord(value) {
|
|
2014
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2015
|
+
}
|
|
2016
|
+
var TrayChipPreview = memo(function TrayChipPreview2({
|
|
2017
|
+
registryKey,
|
|
2018
|
+
snapshotKey,
|
|
2019
|
+
overlayInstanceId = DEFAULT_INSTANCE_ID,
|
|
2020
|
+
previewData,
|
|
2021
|
+
title
|
|
2022
|
+
}) {
|
|
2023
|
+
const meta = getOverlayMeta(registryKey);
|
|
2024
|
+
const { Preview } = getTrayPreviewEntry(registryKey);
|
|
2025
|
+
const ports = useSummonPorts2();
|
|
2026
|
+
const overlayData = useSummonSelector4((state2) => meta ? selectOverlayData(state2, meta.id, overlayInstanceId) : null);
|
|
2027
|
+
const collectedData = useSummonSelector4((state2) => state2.windowManager.windows[snapshotKey]?.persistence?.data);
|
|
2028
|
+
const registryLabel = meta?.label ?? null;
|
|
2029
|
+
const fallback = /* @__PURE__ */ jsx12(TraySnapshotImage, { snapshotKey, registryLabel, title });
|
|
2030
|
+
if (Preview) {
|
|
2031
|
+
const ctx = {
|
|
2032
|
+
data: previewData ?? collectedData ?? (isPlainRecord(overlayData) ? overlayData : {}),
|
|
2033
|
+
overlayId: meta?.id ?? registryKey,
|
|
2034
|
+
instanceId: overlayInstanceId,
|
|
2035
|
+
title
|
|
2036
|
+
};
|
|
2037
|
+
return /* @__PURE__ */ jsx12(
|
|
2038
|
+
PreviewBoundary,
|
|
2039
|
+
{
|
|
2040
|
+
fallback,
|
|
2041
|
+
onError: (error) => ports.errors.capture(error, { where: "tray-preview", overlayId: registryKey }),
|
|
2042
|
+
children: /* @__PURE__ */ jsx12("div", { className: "flex-1 px-3 py-1 overflow-hidden text-xs text-muted-foreground", children: /* @__PURE__ */ jsx12(Preview, { ...ctx }) })
|
|
2043
|
+
}
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
return fallback;
|
|
2047
|
+
});
|
|
2048
|
+
var PreviewBoundary = class extends Component {
|
|
2049
|
+
state = { failed: false };
|
|
2050
|
+
static getDerivedStateFromError() {
|
|
2051
|
+
return { failed: true };
|
|
2052
|
+
}
|
|
2053
|
+
componentDidCatch(error) {
|
|
2054
|
+
this.props.onError(error);
|
|
2055
|
+
}
|
|
2056
|
+
render() {
|
|
2057
|
+
return this.state.failed ? this.props.fallback : this.props.children;
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
var DefaultTrayChipBody = memo(function DefaultTrayChipBody2({ label }) {
|
|
2061
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col justify-between overflow-hidden bg-gradient-to-br from-muted/45 via-background to-primary/5 px-3 py-2.5", children: [
|
|
2062
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
2063
|
+
/* @__PURE__ */ jsxs10(
|
|
2064
|
+
"div",
|
|
2065
|
+
{
|
|
2066
|
+
"aria-hidden": "true",
|
|
2067
|
+
className: "flex h-8 w-8 shrink-0 flex-col gap-1 rounded-md border border-border/60 bg-background/70 p-1.5 shadow-sm",
|
|
2068
|
+
children: [
|
|
2069
|
+
/* @__PURE__ */ jsx12("span", { className: "h-1 w-4 rounded-full bg-primary/45" }),
|
|
2070
|
+
/* @__PURE__ */ jsx12("span", { className: "h-1 w-3 rounded-full bg-muted-foreground/25" }),
|
|
2071
|
+
/* @__PURE__ */ jsx12("span", { className: "h-1 w-4 rounded-full bg-muted-foreground/15" })
|
|
2072
|
+
]
|
|
2073
|
+
}
|
|
2074
|
+
),
|
|
2075
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 truncate text-xs font-medium text-foreground", children: label })
|
|
2076
|
+
] }),
|
|
2077
|
+
/* @__PURE__ */ jsxs10("div", { className: "matrx-window-tile-meta flex items-center justify-between gap-2 text-muted-foreground", children: [
|
|
2078
|
+
/* @__PURE__ */ jsx12("span", { className: "truncate uppercase tracking-wide", children: "Window" }),
|
|
2079
|
+
/* @__PURE__ */ jsx12("span", { className: "shrink-0", children: "Click to restore" })
|
|
2080
|
+
] })
|
|
2081
|
+
] });
|
|
2082
|
+
});
|
|
2083
|
+
var TraySnapshotImage = memo(function TraySnapshotImage2({
|
|
2084
|
+
snapshotKey,
|
|
2085
|
+
registryLabel,
|
|
2086
|
+
title
|
|
2087
|
+
}) {
|
|
2088
|
+
const snapshot = useSyncExternalStore4(
|
|
2089
|
+
subscribeTraySnapshotMap,
|
|
2090
|
+
() => getTraySnapshot(snapshotKey),
|
|
2091
|
+
() => null
|
|
2092
|
+
);
|
|
2093
|
+
if (!snapshot) return /* @__PURE__ */ jsx12(DefaultTrayChipBody, { label: registryLabel ?? title });
|
|
2094
|
+
return /* @__PURE__ */ jsx12("div", { className: "flex-1 overflow-hidden bg-muted/30 relative", children: /* @__PURE__ */ jsx12(
|
|
2095
|
+
"img",
|
|
2096
|
+
{
|
|
2097
|
+
src: snapshot,
|
|
2098
|
+
alt: "",
|
|
2099
|
+
className: cn8("w-full h-full object-cover", "opacity-90 hover:opacity-100 transition-opacity"),
|
|
2100
|
+
draggable: false
|
|
2101
|
+
}
|
|
2102
|
+
) });
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
// src/window/tray/MinimizedWindowContent.tsx
|
|
2106
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2107
|
+
function MinimizedWindowContent({
|
|
2108
|
+
registryKey,
|
|
2109
|
+
snapshotKey,
|
|
2110
|
+
overlayInstanceId,
|
|
2111
|
+
previewData,
|
|
2112
|
+
title,
|
|
2113
|
+
onRestore
|
|
2114
|
+
}) {
|
|
2115
|
+
const label = title ?? getOverlayMeta2(registryKey)?.label ?? "";
|
|
2116
|
+
return /* @__PURE__ */ jsx13(
|
|
2117
|
+
"button",
|
|
2118
|
+
{
|
|
2119
|
+
type: "button",
|
|
2120
|
+
onClick: onRestore,
|
|
2121
|
+
title: "Click to restore",
|
|
2122
|
+
className: "flex min-h-0 flex-1 flex-col text-left focus:outline-none",
|
|
2123
|
+
children: /* @__PURE__ */ jsx13(
|
|
2124
|
+
TrayChipPreview,
|
|
2125
|
+
{
|
|
2126
|
+
registryKey,
|
|
2127
|
+
snapshotKey,
|
|
2128
|
+
overlayInstanceId,
|
|
2129
|
+
previewData,
|
|
2130
|
+
title: label
|
|
2131
|
+
}
|
|
2132
|
+
)
|
|
2133
|
+
}
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// src/window/WindowPanel.tsx
|
|
2138
|
+
import { getTrayPreviewEntry as getTrayPreviewEntry2 } from "@ai-matrx/summon/window-host";
|
|
2139
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2140
|
+
var GLIDE_SETTLE_MS = 800;
|
|
2141
|
+
var HANDLES = [
|
|
2142
|
+
{
|
|
2143
|
+
edge: "e",
|
|
2144
|
+
className: "absolute right-0 top-2 bottom-2 w-2.5 translate-x-1/2 cursor-ew-resize"
|
|
2145
|
+
},
|
|
2146
|
+
{
|
|
2147
|
+
edge: "w",
|
|
2148
|
+
className: "absolute left-0 top-2 bottom-2 w-2.5 -translate-x-1/2 cursor-ew-resize"
|
|
2149
|
+
},
|
|
2150
|
+
{
|
|
2151
|
+
edge: "s",
|
|
2152
|
+
className: "absolute bottom-0 left-2 right-2 h-2.5 translate-y-1/2 cursor-ns-resize"
|
|
2153
|
+
},
|
|
2154
|
+
{
|
|
2155
|
+
// Start after the traffic-light hot zone (w-28) — top-left is close/minimize/maximize.
|
|
2156
|
+
edge: "n",
|
|
2157
|
+
className: "absolute top-0 left-2 right-2 h-2.5 -translate-y-1/2 cursor-ns-resize"
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
edge: "se",
|
|
2161
|
+
className: "absolute bottom-0 right-0 w-2.5 h-2.5 -translate-x-1/4 -translate-y-1/4 cursor-nwse-resize"
|
|
2162
|
+
},
|
|
2163
|
+
{
|
|
2164
|
+
edge: "sw",
|
|
2165
|
+
className: "absolute bottom-0 left-0 w-2.5 h-2.5 -translate-x-1/4 -translate-y-1/4 cursor-nesw-resize"
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
edge: "ne",
|
|
2169
|
+
className: "absolute top-0 right-0 w-2.5 h-2.5 -translate-x-1/4 -translate-y-1/4 cursor-nesw-resize"
|
|
2170
|
+
},
|
|
2171
|
+
{
|
|
2172
|
+
edge: "nw",
|
|
2173
|
+
className: "absolute top-0 left-0 w-2.5 h-2.5 -translate-x-1/4 -translate-y-1/4 cursor-nwse-resize"
|
|
2174
|
+
}
|
|
2175
|
+
];
|
|
2176
|
+
function WindowPanelBodyShell({
|
|
2177
|
+
bodyRef,
|
|
2178
|
+
fitContent,
|
|
2179
|
+
isMaximized,
|
|
2180
|
+
bodyClassName,
|
|
2181
|
+
captureDimensions,
|
|
2182
|
+
children
|
|
2183
|
+
}) {
|
|
2184
|
+
return /* @__PURE__ */ jsx14(
|
|
2185
|
+
"div",
|
|
2186
|
+
{
|
|
2187
|
+
ref: bodyRef,
|
|
2188
|
+
className: cn9(
|
|
2189
|
+
// The structural guard ring is part of the body surface, not window
|
|
2190
|
+
// chrome. Painting it with the canonical page background keeps the
|
|
2191
|
+
// 2px resize-handle gutter visually continuous with full-bleed bodies
|
|
2192
|
+
// such as Chat instead of exposing the card-coloured window shell.
|
|
2193
|
+
"relative z-0 min-h-0 flex-1 overflow-hidden bg-background pointer-events-none",
|
|
2194
|
+
!isMaximized && "p-0.5",
|
|
2195
|
+
fitContent && "overflow-visible",
|
|
2196
|
+
captureDimensions && "matrx-window-body-offscreen"
|
|
2197
|
+
),
|
|
2198
|
+
"data-window-panel-body-shell": true,
|
|
2199
|
+
style: captureDimensions ?? void 0,
|
|
2200
|
+
children: /* @__PURE__ */ jsx14(
|
|
2201
|
+
"div",
|
|
2202
|
+
{
|
|
2203
|
+
className: cn9(
|
|
2204
|
+
"h-full min-h-0 overflow-auto pointer-events-auto",
|
|
2205
|
+
!isMaximized && "matrx-window-body-inner",
|
|
2206
|
+
bodyClassName
|
|
2207
|
+
),
|
|
2208
|
+
children
|
|
2209
|
+
}
|
|
2210
|
+
)
|
|
2211
|
+
}
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
var MINIMIZED_TILE_Z_FLOOR = 5e3;
|
|
2215
|
+
function WindowPanel({
|
|
2216
|
+
children,
|
|
2217
|
+
title,
|
|
2218
|
+
titleNode,
|
|
2219
|
+
actions,
|
|
2220
|
+
actionsLeft,
|
|
2221
|
+
actionsRight,
|
|
2222
|
+
onClose,
|
|
2223
|
+
bodyClassName,
|
|
2224
|
+
className,
|
|
2225
|
+
mobilePresentationOverride,
|
|
2226
|
+
surfaceLayer,
|
|
2227
|
+
mobileSizeToContent,
|
|
2228
|
+
closeOnEscape = false,
|
|
2229
|
+
mobileHideClose = false,
|
|
2230
|
+
minWidth,
|
|
2231
|
+
minHeight,
|
|
2232
|
+
urlSyncKey,
|
|
2233
|
+
urlSyncId,
|
|
2234
|
+
urlSyncArgs,
|
|
2235
|
+
sidebar,
|
|
2236
|
+
sidebarDefaultSize = 200,
|
|
2237
|
+
sidebarMinSize = 100,
|
|
2238
|
+
defaultSidebarOpen = true,
|
|
2239
|
+
sidebarClassName,
|
|
2240
|
+
sidebarExpandsWindow = false,
|
|
2241
|
+
secondaryPanel,
|
|
2242
|
+
secondaryPanelOpen,
|
|
2243
|
+
secondaryPanelDefaultSize = 360,
|
|
2244
|
+
secondaryPanelMinSize = 240,
|
|
2245
|
+
secondaryPanelClassName,
|
|
2246
|
+
footer,
|
|
2247
|
+
footerLeft,
|
|
2248
|
+
footerCenter,
|
|
2249
|
+
footerRight,
|
|
2250
|
+
footerVariant = "bar",
|
|
2251
|
+
fitContent = false,
|
|
2252
|
+
overlayId: overlayIdProp,
|
|
2253
|
+
boundToOverlay,
|
|
2254
|
+
onCollectData,
|
|
2255
|
+
captureTraySnapshot,
|
|
2256
|
+
overlayInstanceId: overlayInstanceIdProp,
|
|
2257
|
+
hidePopOutButton = false,
|
|
2258
|
+
...hookOpts
|
|
2259
|
+
}) {
|
|
2260
|
+
const instance = useOptionalOverlayInstance();
|
|
2261
|
+
const instanceMatches = instance !== null && (boundToOverlay === true || instance.overlayId === overlayIdProp);
|
|
2262
|
+
const overlayId = overlayIdProp ?? (boundToOverlay && instance ? instance.overlayId : void 0);
|
|
2263
|
+
const overlayInstanceId = overlayInstanceIdProp ?? (instanceMatches ? instance.instanceId : void 0);
|
|
2264
|
+
const ports = useSummonPorts3();
|
|
2265
|
+
const windowConfig = useWindowConfig();
|
|
2266
|
+
const reactId = useId2();
|
|
2267
|
+
const id = hookOpts.id ?? reactId;
|
|
2268
|
+
const trayRegistryKey = overlayId ?? id;
|
|
2269
|
+
const isMobile = useIsMobile();
|
|
2270
|
+
const surfaceLayerAttrs = surfaceLayer ? { "data-surface-layer": surfaceLayer } : void 0;
|
|
2271
|
+
const isMounted = useIsClient();
|
|
2272
|
+
const registryMeta = overlayId ? getOverlayMeta3(overlayId) : void 0;
|
|
2273
|
+
const registryEntry = registryMeta?.window;
|
|
2274
|
+
const effectiveInstanceId = overlayInstanceId ?? DEFAULT_INSTANCE_ID2;
|
|
2275
|
+
const [autoOpened] = useState5(
|
|
2276
|
+
() => Boolean(overlayId) && isAutoOpened(overlayId, effectiveInstanceId)
|
|
2277
|
+
);
|
|
2278
|
+
useEffect5(() => {
|
|
2279
|
+
if (!overlayId) return void 0;
|
|
2280
|
+
let timer;
|
|
2281
|
+
return () => {
|
|
2282
|
+
timer = window.setTimeout(
|
|
2283
|
+
() => clearAutoOpened(overlayId, effectiveInstanceId),
|
|
2284
|
+
100
|
|
2285
|
+
);
|
|
2286
|
+
void timer;
|
|
2287
|
+
};
|
|
2288
|
+
}, [overlayId, effectiveInstanceId]);
|
|
2289
|
+
const preservationEnabled = Boolean(
|
|
2290
|
+
overlayId && registryEntry?.preservation && !registryEntry.ephemeral && (registryMeta?.instance !== "multi" || overlayInstanceId)
|
|
2291
|
+
);
|
|
2292
|
+
const overlayLaunchData = useSummonSelector5(
|
|
2293
|
+
(state2) => overlayId ? selectOverlayData2(state2, overlayId, effectiveInstanceId) : null
|
|
2294
|
+
);
|
|
2295
|
+
const initialPreservationData = registryEntry?.preservation ? sanitizeWindowData(overlayLaunchData, registryEntry.preservation.dataKeys) : {};
|
|
2296
|
+
const popoutCapability = isMounted ? detectPopoutCapability() : null;
|
|
2297
|
+
const popout = usePopoutWindow(id);
|
|
2298
|
+
useEffect5(() => {
|
|
2299
|
+
return registerPopoutOpener(id, popout.openPopout);
|
|
2300
|
+
}, [id, popout.openPopout]);
|
|
2301
|
+
const handleDragOutPopout = useCallback4(
|
|
2302
|
+
(draggedRect) => {
|
|
2303
|
+
void popout.openPopout({
|
|
2304
|
+
width: draggedRect.width,
|
|
2305
|
+
height: draggedRect.height,
|
|
2306
|
+
title: typeof title === "string" ? title : "Window"
|
|
2307
|
+
});
|
|
2308
|
+
},
|
|
2309
|
+
[popout, title]
|
|
2310
|
+
);
|
|
2311
|
+
const onTriggerPopout = !isMobile && popoutCapability !== "none" ? handleDragOutPopout : void 0;
|
|
2312
|
+
const autoDispatch = useSummonDispatch4();
|
|
2313
|
+
const {
|
|
2314
|
+
windowState,
|
|
2315
|
+
rect,
|
|
2316
|
+
zIndex: managerZIndex,
|
|
2317
|
+
onDragStart,
|
|
2318
|
+
onResizeStart,
|
|
2319
|
+
onFocus,
|
|
2320
|
+
onRestore,
|
|
2321
|
+
onMinimize,
|
|
2322
|
+
onToggleMaximize,
|
|
2323
|
+
isInteracting
|
|
2324
|
+
} = useWindowPanel({
|
|
2325
|
+
...hookOpts,
|
|
2326
|
+
id,
|
|
2327
|
+
title,
|
|
2328
|
+
autoOpened,
|
|
2329
|
+
onAutoNoRoom: autoOpened ? () => {
|
|
2330
|
+
window.dispatchEvent(
|
|
2331
|
+
new CustomEvent(AUTO_OPEN_NO_ROOM_EVENT, { detail: { overlayId, instanceId: effectiveInstanceId } })
|
|
2332
|
+
);
|
|
2333
|
+
if (overlayId) autoDispatch(overlaysRawActions.closeOverlay({ overlayId, instanceId: effectiveInstanceId }));
|
|
2334
|
+
} : void 0,
|
|
2335
|
+
minWidth,
|
|
2336
|
+
minHeight,
|
|
2337
|
+
onTriggerPopout,
|
|
2338
|
+
...preservationEnabled && overlayId ? {
|
|
2339
|
+
persistence: {
|
|
2340
|
+
overlayId,
|
|
2341
|
+
instanceId: effectiveInstanceId,
|
|
2342
|
+
data: initialPreservationData,
|
|
2343
|
+
sidebarOpen: defaultSidebarOpen,
|
|
2344
|
+
sidebarSize: sidebarDefaultSize
|
|
2345
|
+
}
|
|
2346
|
+
} : {}
|
|
2347
|
+
});
|
|
2348
|
+
const liftedZIndex = useFloatingLayerZIndex2(managerZIndex);
|
|
2349
|
+
const zIndex = windowState === "minimized" ? Math.max(liftedZIndex, MINIMIZED_TILE_Z_FLOOR + managerZIndex) : liftedZIndex;
|
|
2350
|
+
const dispatch = useSummonDispatch4();
|
|
2351
|
+
const windowsHidden = useSummonSelector5(selectWindowsHidden);
|
|
2352
|
+
const isDebugMode = useDebugFlag(ports.debug);
|
|
2353
|
+
useEffect5(() => {
|
|
2354
|
+
announceLayerFront(
|
|
2355
|
+
document.querySelector(`[data-window-id="${CSS.escape(id)}"]`)
|
|
2356
|
+
);
|
|
2357
|
+
}, [id, managerZIndex]);
|
|
2358
|
+
const claimFront = (event) => {
|
|
2359
|
+
announceLayerFront(event.currentTarget.closest("[data-window-id]") ?? event.currentTarget);
|
|
2360
|
+
};
|
|
2361
|
+
const allWindows = useSummonSelector5(selectAllWindows);
|
|
2362
|
+
const isTopWindow = !isMobile ? true : allWindows.find((w) => w.state !== "minimized")?.id === id;
|
|
2363
|
+
const [activePaneMobile, setActivePaneMobile] = useState5(
|
|
2364
|
+
"main"
|
|
2365
|
+
);
|
|
2366
|
+
const effectiveUrlSyncKey = resolveWindowUrlSyncKey(registryMeta?.link?.key, urlSyncKey);
|
|
2367
|
+
const effectiveUrlSyncId = urlSyncId ?? (effectiveUrlSyncKey ? overlayId : void 0);
|
|
2368
|
+
useOverlayLink(effectiveUrlSyncKey, effectiveUrlSyncId, urlSyncArgs);
|
|
2369
|
+
const deprecation = registryEntry?.deprecated;
|
|
2370
|
+
const isDeprecated = !!deprecation;
|
|
2371
|
+
const deprecatedRingClass = isDeprecated ? "ring-2 ring-destructive ring-offset-2 ring-offset-background" : "";
|
|
2372
|
+
const hasSidebar = !!sidebar;
|
|
2373
|
+
const [sidebarOpen, setSidebarOpen] = useState5(defaultSidebarOpen);
|
|
2374
|
+
const [sidebarSize, setSidebarSize] = useState5(
|
|
2375
|
+
sidebarDefaultSize
|
|
2376
|
+
);
|
|
2377
|
+
const restoredPersistence = useSummonSelector5(selectWindowPersistence(id));
|
|
2378
|
+
const sidebarPanelRef = useRef4(null);
|
|
2379
|
+
const restoredChromeAppliedRef = useRef4(false);
|
|
2380
|
+
const handleSaveWindowState = useCallback4(() => {
|
|
2381
|
+
if (!overlayId || !preservationEnabled || !registryEntry?.preservation) {
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
let collected = {};
|
|
2385
|
+
try {
|
|
2386
|
+
collected = onCollectData?.() ?? {};
|
|
2387
|
+
} catch (error) {
|
|
2388
|
+
ports.errors.capture(error, { where: "window-preservation collector", overlayId, instanceId: effectiveInstanceId });
|
|
2389
|
+
}
|
|
2390
|
+
const base = sanitizeWindowData({ ...initialPreservationData, ...collected }, registryEntry.preservation.dataKeys);
|
|
2391
|
+
dispatch(
|
|
2392
|
+
updateWindowPersistence({
|
|
2393
|
+
id,
|
|
2394
|
+
data: base,
|
|
2395
|
+
sidebarOpen,
|
|
2396
|
+
...sidebarSize === null ? {} : { sidebarSize }
|
|
2397
|
+
})
|
|
2398
|
+
);
|
|
2399
|
+
}, [
|
|
2400
|
+
overlayId,
|
|
2401
|
+
preservationEnabled,
|
|
2402
|
+
registryEntry,
|
|
2403
|
+
effectiveInstanceId,
|
|
2404
|
+
initialPreservationData,
|
|
2405
|
+
id,
|
|
2406
|
+
sidebarOpen,
|
|
2407
|
+
sidebarSize,
|
|
2408
|
+
onCollectData,
|
|
2409
|
+
ports,
|
|
2410
|
+
dispatch
|
|
2411
|
+
]);
|
|
2412
|
+
const saveRef = useRef4(handleSaveWindowState);
|
|
2413
|
+
useEffect5(() => {
|
|
2414
|
+
saveRef.current = handleSaveWindowState;
|
|
2415
|
+
}, [handleSaveWindowState]);
|
|
2416
|
+
useEffect5(() => {
|
|
2417
|
+
if (!preservationEnabled) return;
|
|
2418
|
+
saveRef.current();
|
|
2419
|
+
}, [preservationEnabled, overlayLaunchData, onCollectData]);
|
|
2420
|
+
const handleClose = useCallback4(() => {
|
|
2421
|
+
if (instanceMatches && instance && instance.overlayId === overlayId) {
|
|
2422
|
+
instance.close();
|
|
2423
|
+
} else if (boundToOverlay) {
|
|
2424
|
+
ports.errors.capture(new Error("[summon] WindowPanel boundToOverlay rendered outside an overlay instance"), { windowId: id });
|
|
2425
|
+
} else if (overlayId) {
|
|
2426
|
+
const hasExactOverlayIdentity = registryMeta?.instance !== "multi" || Boolean(overlayInstanceId);
|
|
2427
|
+
if (hasExactOverlayIdentity) {
|
|
2428
|
+
dispatch(overlaysRawActions.closeOverlay({ overlayId, instanceId: effectiveInstanceId }));
|
|
2429
|
+
} else if (!onClose) {
|
|
2430
|
+
ports.errors.capture(new Error(`[summon] Cannot close multi-instance ${overlayId} without overlayInstanceId or onClose.`), { overlayId });
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
onClose?.();
|
|
2434
|
+
}, [boundToOverlay, dispatch, effectiveInstanceId, id, instance, instanceMatches, onClose, overlayId, overlayInstanceId, ports, registryMeta?.instance]);
|
|
2435
|
+
useEffect5(() => {
|
|
2436
|
+
if (!closeOnEscape || isMobile) return void 0;
|
|
2437
|
+
const onKey = (e) => {
|
|
2438
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
2439
|
+
const layered = Array.from(
|
|
2440
|
+
document.querySelectorAll(
|
|
2441
|
+
`[data-radix-popper-content-wrapper], [role="menu"], [role="listbox"], [role="dialog"][data-state="open"], [role="alertdialog"], ${windowConfig.escapeYieldSelector}`
|
|
2442
|
+
)
|
|
2443
|
+
).some(
|
|
2444
|
+
(el2) => el2.getClientRects().length > 0 && getComputedStyle(el2).visibility !== "hidden" && !el2.closest(`[data-window-id="${CSS.escape(id)}"]`)
|
|
2445
|
+
);
|
|
2446
|
+
if (layered) return;
|
|
2447
|
+
const mine = document.querySelector(
|
|
2448
|
+
`[data-window-panel][data-window-id="${CSS.escape(id)}"]`
|
|
2449
|
+
);
|
|
2450
|
+
if (!mine) return;
|
|
2451
|
+
const z = (el2) => Number(getComputedStyle(el2).zIndex) || 0;
|
|
2452
|
+
const top = Array.from(
|
|
2453
|
+
document.querySelectorAll("[data-window-panel][data-window-id]")
|
|
2454
|
+
).reduce((best, el2) => !best || z(el2) > z(best) ? el2 : best, null);
|
|
2455
|
+
if (top !== mine) return;
|
|
2456
|
+
e.preventDefault();
|
|
2457
|
+
handleClose();
|
|
2458
|
+
};
|
|
2459
|
+
document.addEventListener("keydown", onKey);
|
|
2460
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
2461
|
+
}, [closeOnEscape, isMobile, id, handleClose, windowConfig.escapeYieldSelector]);
|
|
2462
|
+
useEffect5(() => {
|
|
2463
|
+
if (!restoredPersistence || restoredChromeAppliedRef.current) return;
|
|
2464
|
+
restoredChromeAppliedRef.current = true;
|
|
2465
|
+
setSidebarOpen(restoredPersistence.sidebarOpen);
|
|
2466
|
+
setSidebarSize(restoredPersistence.sidebarSize);
|
|
2467
|
+
if (!restoredPersistence.sidebarOpen) {
|
|
2468
|
+
sidebarPanelRef.current?.collapse();
|
|
2469
|
+
} else if (restoredPersistence.sidebarSize) {
|
|
2470
|
+
sidebarPanelRef.current?.resize(restoredPersistence.sidebarSize);
|
|
2471
|
+
}
|
|
2472
|
+
}, [restoredPersistence]);
|
|
2473
|
+
const [fitContentEl, fitContentRef] = useState5(null);
|
|
2474
|
+
const fitContentStore = useSummonStore2();
|
|
2475
|
+
const fitPlacedRef = useRef4(false);
|
|
2476
|
+
const [glideReady, setGlideReady] = useState5(false);
|
|
2477
|
+
useEffect5(() => {
|
|
2478
|
+
const timer = window.setTimeout(() => setGlideReady(true), GLIDE_SETTLE_MS);
|
|
2479
|
+
return () => window.clearTimeout(timer);
|
|
2480
|
+
}, []);
|
|
2481
|
+
useEffect5(() => {
|
|
2482
|
+
if (isInteracting) fitPlacedRef.current = true;
|
|
2483
|
+
}, [isInteracting]);
|
|
2484
|
+
const fitPresetPosition = hookOpts.position;
|
|
2485
|
+
const fitMayReplace = hookOpts.initialRect?.x === void 0 && hookOpts.initialRect?.y === void 0;
|
|
2486
|
+
useEffect5(() => {
|
|
2487
|
+
if (!fitContent || isMobile || windowState !== "windowed") return void 0;
|
|
2488
|
+
const el2 = fitContentEl;
|
|
2489
|
+
if (!el2) return void 0;
|
|
2490
|
+
let active = true;
|
|
2491
|
+
const ro = new ResizeObserver((entries) => {
|
|
2492
|
+
if (!active || fitContentStore.getState().windowManager.windows[id]?.state !== "windowed") return;
|
|
2493
|
+
const entry = entries[0];
|
|
2494
|
+
if (!entry) return;
|
|
2495
|
+
const { width, height } = entry.contentRect;
|
|
2496
|
+
const borderH = el2.offsetHeight - el2.clientHeight;
|
|
2497
|
+
const borderW = el2.offsetWidth - el2.clientWidth;
|
|
2498
|
+
const fitWidth = Math.ceil(width + borderW);
|
|
2499
|
+
const fitHeight = Math.ceil(height + borderH);
|
|
2500
|
+
const placeNow = !fitPlacedRef.current && fitMayReplace && fitPresetPosition !== void 0;
|
|
2501
|
+
dispatch(
|
|
2502
|
+
updateWindowRect2({
|
|
2503
|
+
id,
|
|
2504
|
+
rect: {
|
|
2505
|
+
width: fitWidth,
|
|
2506
|
+
height: fitHeight,
|
|
2507
|
+
...placeNow ? resolvePosition(fitPresetPosition, fitWidth, fitHeight) : {}
|
|
2508
|
+
}
|
|
2509
|
+
})
|
|
2510
|
+
);
|
|
2511
|
+
});
|
|
2512
|
+
ro.observe(el2);
|
|
2513
|
+
return () => {
|
|
2514
|
+
active = false;
|
|
2515
|
+
ro.disconnect();
|
|
2516
|
+
};
|
|
2517
|
+
}, [fitContent, fitContentEl, isMobile, windowState, id, dispatch, fitContentStore, fitPresetPosition, fitMayReplace]);
|
|
2518
|
+
const toggleSidebar = useCallback4(() => {
|
|
2519
|
+
const panel = sidebarPanelRef.current;
|
|
2520
|
+
if (!panel) return;
|
|
2521
|
+
if (sidebarOpen) {
|
|
2522
|
+
if (sidebarExpandsWindow) {
|
|
2523
|
+
dispatch(
|
|
2524
|
+
updateWindowRect2({
|
|
2525
|
+
id,
|
|
2526
|
+
rect: { width: rect.width - sidebarDefaultSize }
|
|
2527
|
+
})
|
|
2528
|
+
);
|
|
2529
|
+
requestAnimationFrame(() => panel.collapse());
|
|
2530
|
+
} else {
|
|
2531
|
+
panel.collapse();
|
|
2532
|
+
}
|
|
2533
|
+
} else {
|
|
2534
|
+
if (sidebarExpandsWindow) {
|
|
2535
|
+
dispatch(
|
|
2536
|
+
updateWindowRect2({
|
|
2537
|
+
id,
|
|
2538
|
+
rect: { width: rect.width + sidebarDefaultSize }
|
|
2539
|
+
})
|
|
2540
|
+
);
|
|
2541
|
+
requestAnimationFrame(() => panel.resize(sidebarDefaultSize));
|
|
2542
|
+
} else {
|
|
2543
|
+
panel.resize(sidebarDefaultSize);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
}, [
|
|
2547
|
+
sidebarOpen,
|
|
2548
|
+
sidebarDefaultSize,
|
|
2549
|
+
sidebarExpandsWindow,
|
|
2550
|
+
dispatch,
|
|
2551
|
+
id,
|
|
2552
|
+
rect.width
|
|
2553
|
+
]);
|
|
2554
|
+
const handleSidebarResize = useCallback4(
|
|
2555
|
+
(panelSize) => {
|
|
2556
|
+
setSidebarOpen(panelSize.asPercentage > 0);
|
|
2557
|
+
if (panelSize.inPixels > 0) setSidebarSize(panelSize.inPixels);
|
|
2558
|
+
if (preservationEnabled) {
|
|
2559
|
+
dispatch(
|
|
2560
|
+
updateWindowPersistence({
|
|
2561
|
+
id,
|
|
2562
|
+
sidebarOpen: panelSize.asPercentage > 0,
|
|
2563
|
+
sidebarSize: panelSize.inPixels > 0 ? panelSize.inPixels : sidebarSize
|
|
2564
|
+
})
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
[dispatch, id, preservationEnabled, sidebarSize]
|
|
2569
|
+
);
|
|
2570
|
+
const portalTarget = isMounted ? document.body : null;
|
|
2571
|
+
const [bodyHost] = useState5(() => {
|
|
2572
|
+
if (typeof document === "undefined") return null;
|
|
2573
|
+
const host = document.createElement("div");
|
|
2574
|
+
host.className = "matrx-window-body-host";
|
|
2575
|
+
host.setAttribute("data-window-body-host", "");
|
|
2576
|
+
return host;
|
|
2577
|
+
});
|
|
2578
|
+
useEffect5(() => {
|
|
2579
|
+
if (!overlayId) return void 0;
|
|
2580
|
+
ackOverlayRender(overlayId, id);
|
|
2581
|
+
return () => clearOverlayRender(overlayId, id);
|
|
2582
|
+
}, [overlayId, id]);
|
|
2583
|
+
const resolvedActionsRight = actionsRight ?? actions ?? null;
|
|
2584
|
+
const snapLeft = useCallback4(() => {
|
|
2585
|
+
const { vw, vh } = safeViewportDims2();
|
|
2586
|
+
dispatch(
|
|
2587
|
+
updateWindowRect2({
|
|
2588
|
+
id,
|
|
2589
|
+
rect: { x: 0, y: 0, width: Math.round(vw / 2), height: vh }
|
|
2590
|
+
})
|
|
2591
|
+
);
|
|
2592
|
+
}, [dispatch, id]);
|
|
2593
|
+
const snapRight = useCallback4(() => {
|
|
2594
|
+
const { vw, vh } = safeViewportDims2();
|
|
2595
|
+
const half = Math.round(vw / 2);
|
|
2596
|
+
dispatch(
|
|
2597
|
+
updateWindowRect2({
|
|
2598
|
+
id,
|
|
2599
|
+
rect: { x: half, y: 0, width: vw - half, height: vh }
|
|
2600
|
+
})
|
|
2601
|
+
);
|
|
2602
|
+
}, [dispatch, id]);
|
|
2603
|
+
const snapTop = useCallback4(() => {
|
|
2604
|
+
const { vw, vh } = safeViewportDims2();
|
|
2605
|
+
dispatch(
|
|
2606
|
+
updateWindowRect2({
|
|
2607
|
+
id,
|
|
2608
|
+
rect: { x: 0, y: 0, width: vw, height: Math.round(vh / 2) }
|
|
2609
|
+
})
|
|
2610
|
+
);
|
|
2611
|
+
}, [dispatch, id]);
|
|
2612
|
+
const snapBottom = useCallback4(() => {
|
|
2613
|
+
const { vw, vh } = safeViewportDims2();
|
|
2614
|
+
const half = Math.round(vh / 2);
|
|
2615
|
+
dispatch(
|
|
2616
|
+
updateWindowRect2({
|
|
2617
|
+
id,
|
|
2618
|
+
rect: { x: 0, y: half, width: vw, height: vh - half }
|
|
2619
|
+
})
|
|
2620
|
+
);
|
|
2621
|
+
}, [dispatch, id]);
|
|
2622
|
+
const snapCentre = useCallback4(() => {
|
|
2623
|
+
const { vw, vh } = safeViewportDims2();
|
|
2624
|
+
const w = Math.min(rect.width, vw);
|
|
2625
|
+
const h = Math.min(rect.height, vh);
|
|
2626
|
+
dispatch(
|
|
2627
|
+
updateWindowRect2({
|
|
2628
|
+
id,
|
|
2629
|
+
rect: {
|
|
2630
|
+
x: Math.round((vw - w) / 2),
|
|
2631
|
+
y: Math.round((vh - h) / 2),
|
|
2632
|
+
width: w,
|
|
2633
|
+
height: h
|
|
2634
|
+
}
|
|
2635
|
+
})
|
|
2636
|
+
);
|
|
2637
|
+
}, [dispatch, id, rect.width, rect.height]);
|
|
2638
|
+
const arrangeAll = useCallback4(
|
|
2639
|
+
(layout) => {
|
|
2640
|
+
const { vw, vh } = safeViewportDims2();
|
|
2641
|
+
dispatch(
|
|
2642
|
+
arrangeActiveWindows({
|
|
2643
|
+
layout,
|
|
2644
|
+
viewportWidth: vw,
|
|
2645
|
+
viewportHeight: vh
|
|
2646
|
+
})
|
|
2647
|
+
);
|
|
2648
|
+
},
|
|
2649
|
+
[dispatch]
|
|
2650
|
+
);
|
|
2651
|
+
const popoutMode = useSummonSelector5(selectPopoutMode3(id));
|
|
2652
|
+
const outerPopout = usePopout();
|
|
2653
|
+
const outerPortalContainer = usePortalContainer();
|
|
2654
|
+
const popoutWin = usePopoutWindowRef(id);
|
|
2655
|
+
const ownPopout = popoutMode !== null && popoutWin !== null;
|
|
2656
|
+
const bodyPopoutValue = React.useMemo(
|
|
2657
|
+
() => ownPopout && popoutWin ? { popoutDocument: popoutWin.document, popoutWindow: popoutWin, popoutContainer: popoutWin.document.body, mode: popoutMode } : outerPopout,
|
|
2658
|
+
[ownPopout, popoutWin, popoutMode, outerPopout]
|
|
2659
|
+
);
|
|
2660
|
+
const bodyPortalContainer = ownPopout && popoutWin ? popoutWin.document.body : outerPortalContainer;
|
|
2661
|
+
const popoutCandidateId = useSummonSelector5(selectPopoutCandidateId);
|
|
2662
|
+
const canShowPopOut = !hidePopOutButton && !isMobile && popoutCapability !== "none" && windowState !== "minimized" && popoutMode === null;
|
|
2663
|
+
const handlePopOut = useCallback4(() => {
|
|
2664
|
+
void popout.openPopout({
|
|
2665
|
+
width: rect.width,
|
|
2666
|
+
height: rect.height,
|
|
2667
|
+
title: typeof title === "string" ? title : "Window"
|
|
2668
|
+
});
|
|
2669
|
+
}, [popout, rect.width, rect.height, title]);
|
|
2670
|
+
const handleDockBack = useCallback4(() => {
|
|
2671
|
+
dispatch(dockWindow3(buildDockWindowPayload3(id)));
|
|
2672
|
+
}, [dispatch, id]);
|
|
2673
|
+
const bodyRef = useRef4(null);
|
|
2674
|
+
const retainedBodyDimensionsRef = useRef4(null);
|
|
2675
|
+
const captureGenerationRef = useRef4(0);
|
|
2676
|
+
const trayPreviewEntry = getTrayPreviewEntry2(trayRegistryKey);
|
|
2677
|
+
const effectiveTrayCapture = captureTraySnapshot ?? trayPreviewEntry.captureTraySnapshot ?? (trayPreviewEntry.Preview ? void 0 : defaultTraySnapshotCapture);
|
|
2678
|
+
const [pendingTrayCapture, setPendingTrayCapture] = useState5(null);
|
|
2679
|
+
const [trayPreviewData, setTrayPreviewData] = useState5(void 0);
|
|
2680
|
+
const handleMinimize = useCallback4(() => {
|
|
2681
|
+
let collected;
|
|
2682
|
+
try {
|
|
2683
|
+
collected = onCollectData?.();
|
|
2684
|
+
} catch (error) {
|
|
2685
|
+
ports.errors.capture(error, { where: "window preview collector", windowId: trayRegistryKey });
|
|
2686
|
+
}
|
|
2687
|
+
if (collected && typeof collected === "object") {
|
|
2688
|
+
setTrayPreviewData(collected);
|
|
2689
|
+
}
|
|
2690
|
+
if (preservationEnabled) saveRef.current();
|
|
2691
|
+
const bodyEl = bodyRef.current;
|
|
2692
|
+
const generation = ++captureGenerationRef.current;
|
|
2693
|
+
if (bodyEl) {
|
|
2694
|
+
const bounds = bodyEl.getBoundingClientRect();
|
|
2695
|
+
retainedBodyDimensionsRef.current = {
|
|
2696
|
+
width: Math.max(1, Math.round(bounds.width)),
|
|
2697
|
+
height: Math.max(1, Math.round(bounds.height))
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
if (effectiveTrayCapture && bodyEl) {
|
|
2701
|
+
const bounds = bodyEl.getBoundingClientRect();
|
|
2702
|
+
setPendingTrayCapture({
|
|
2703
|
+
capture: effectiveTrayCapture,
|
|
2704
|
+
width: Math.max(1, Math.round(bounds.width)),
|
|
2705
|
+
height: Math.max(1, Math.round(bounds.height)),
|
|
2706
|
+
generation
|
|
2707
|
+
});
|
|
2708
|
+
} else {
|
|
2709
|
+
setPendingTrayCapture(null);
|
|
2710
|
+
}
|
|
2711
|
+
onMinimize();
|
|
2712
|
+
}, [
|
|
2713
|
+
effectiveTrayCapture,
|
|
2714
|
+
onCollectData,
|
|
2715
|
+
onMinimize,
|
|
2716
|
+
ports,
|
|
2717
|
+
preservationEnabled,
|
|
2718
|
+
trayRegistryKey
|
|
2719
|
+
]);
|
|
2720
|
+
useEffect5(() => {
|
|
2721
|
+
if (windowState !== "minimized" || !pendingTrayCapture) return void 0;
|
|
2722
|
+
const bodyEl = bodyRef.current;
|
|
2723
|
+
if (!bodyEl) {
|
|
2724
|
+
setPendingTrayCapture(null);
|
|
2725
|
+
return void 0;
|
|
2726
|
+
}
|
|
2727
|
+
let cancelled = false;
|
|
2728
|
+
const { capture, generation } = pendingTrayCapture;
|
|
2729
|
+
let timeoutId;
|
|
2730
|
+
const timeout = new Promise((resolve) => {
|
|
2731
|
+
timeoutId = setTimeout(() => resolve(null), 800);
|
|
2732
|
+
});
|
|
2733
|
+
void Promise.race([capture(bodyEl), timeout]).then((blob) => {
|
|
2734
|
+
if (blob && !cancelled && captureGenerationRef.current === generation) {
|
|
2735
|
+
setTraySnapshot(id, blob);
|
|
2736
|
+
}
|
|
2737
|
+
}).catch((err) => {
|
|
2738
|
+
if (!cancelled) ports.errors.capture(err, { where: "captureTraySnapshot", windowId: id });
|
|
2739
|
+
}).finally(() => {
|
|
2740
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
2741
|
+
if (!cancelled && captureGenerationRef.current === generation) {
|
|
2742
|
+
setPendingTrayCapture(
|
|
2743
|
+
(current) => current?.generation === generation ? null : current
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2746
|
+
});
|
|
2747
|
+
return () => {
|
|
2748
|
+
cancelled = true;
|
|
2749
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
2750
|
+
};
|
|
2751
|
+
}, [id, pendingTrayCapture, ports, windowState]);
|
|
2752
|
+
const handleRestoreClearingSnapshot = useCallback4(() => {
|
|
2753
|
+
captureGenerationRef.current += 1;
|
|
2754
|
+
setPendingTrayCapture(null);
|
|
2755
|
+
clearTraySnapshot(id);
|
|
2756
|
+
onRestore();
|
|
2757
|
+
}, [id, onRestore]);
|
|
2758
|
+
useEffect5(() => {
|
|
2759
|
+
return () => {
|
|
2760
|
+
captureGenerationRef.current += 1;
|
|
2761
|
+
clearTraySnapshot(id);
|
|
2762
|
+
};
|
|
2763
|
+
}, [id]);
|
|
2764
|
+
const previousTrayStateRef = useRef4(windowState);
|
|
2765
|
+
useEffect5(() => {
|
|
2766
|
+
const wasMinimized = previousTrayStateRef.current === "minimized";
|
|
2767
|
+
previousTrayStateRef.current = windowState;
|
|
2768
|
+
if (!wasMinimized || windowState === "minimized") return;
|
|
2769
|
+
const invalidGeneration = ++captureGenerationRef.current;
|
|
2770
|
+
clearTraySnapshot(id);
|
|
2771
|
+
queueMicrotask(() => {
|
|
2772
|
+
setPendingTrayCapture(
|
|
2773
|
+
(current) => current && current.generation < invalidGeneration ? null : current
|
|
2774
|
+
);
|
|
2775
|
+
});
|
|
2776
|
+
}, [id, windowState]);
|
|
2777
|
+
const prevStateRef = useRef4(windowState);
|
|
2778
|
+
const prevPopoutModeRef = useRef4(popoutMode);
|
|
2779
|
+
useEffect5(() => {
|
|
2780
|
+
const wasNonWindowed = prevStateRef.current !== "windowed" || prevPopoutModeRef.current !== null;
|
|
2781
|
+
const isNowWindowed = windowState === "windowed" && popoutMode === null;
|
|
2782
|
+
prevStateRef.current = windowState;
|
|
2783
|
+
prevPopoutModeRef.current = popoutMode;
|
|
2784
|
+
if (wasNonWindowed && isNowWindowed) {
|
|
2785
|
+
const { vw, vh, degenerate } = safeViewportDims2();
|
|
2786
|
+
if (degenerate) return;
|
|
2787
|
+
dispatch(clampWindowRect2({ id, viewportWidth: vw, viewportHeight: vh }));
|
|
2788
|
+
}
|
|
2789
|
+
}, [dispatch, id, windowState, popoutMode]);
|
|
2790
|
+
const isMinimized = windowState === "minimized";
|
|
2791
|
+
const isMaximized = windowState === "maximized";
|
|
2792
|
+
const isPopoutCandidate = popoutCandidateId === id;
|
|
2793
|
+
useEffect5(() => {
|
|
2794
|
+
if (!isMobile || !isMinimized) return;
|
|
2795
|
+
let cancelled = false;
|
|
2796
|
+
queueMicrotask(() => {
|
|
2797
|
+
if (!cancelled) handleRestoreClearingSnapshot();
|
|
2798
|
+
});
|
|
2799
|
+
return () => {
|
|
2800
|
+
cancelled = true;
|
|
2801
|
+
};
|
|
2802
|
+
}, [handleRestoreClearingSnapshot, isMinimized, isMobile]);
|
|
2803
|
+
const minimizedTitle = title?.trim() || getOverlayMeta3(trayRegistryKey)?.label || "Window";
|
|
2804
|
+
const header = /* @__PURE__ */ jsx14(
|
|
2805
|
+
WindowHeader,
|
|
2806
|
+
{
|
|
2807
|
+
title: titleNode ?? title,
|
|
2808
|
+
minimizedTitle,
|
|
2809
|
+
actionsLeft,
|
|
2810
|
+
actionsRight: resolvedActionsRight,
|
|
2811
|
+
onDragStart,
|
|
2812
|
+
onMinimize: handleMinimize,
|
|
2813
|
+
onToggleMaximize,
|
|
2814
|
+
onClose: handleClose,
|
|
2815
|
+
onRestore: handleRestoreClearingSnapshot,
|
|
2816
|
+
isMaximized,
|
|
2817
|
+
isMinimized,
|
|
2818
|
+
snapLeft,
|
|
2819
|
+
snapRight,
|
|
2820
|
+
snapTop,
|
|
2821
|
+
snapBottom,
|
|
2822
|
+
snapCentre,
|
|
2823
|
+
arrangeAll,
|
|
2824
|
+
hasSidebar,
|
|
2825
|
+
sidebarOpen,
|
|
2826
|
+
onToggleSidebar: toggleSidebar,
|
|
2827
|
+
onSaveWindowState: overlayId ? handleSaveWindowState : void 0,
|
|
2828
|
+
onPopOut: canShowPopOut ? handlePopOut : void 0
|
|
2829
|
+
}
|
|
2830
|
+
);
|
|
2831
|
+
const hasSecondaryPanel = secondaryPanel != null && (secondaryPanelOpen ?? true);
|
|
2832
|
+
const innerBody = hasSidebar || hasSecondaryPanel ? /* @__PURE__ */ jsxs11(ResizablePanelGroup, { orientation: "horizontal", className: "h-full min-h-0", children: [
|
|
2833
|
+
hasSidebar && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
2834
|
+
/* @__PURE__ */ jsx14(
|
|
2835
|
+
ResizablePanel,
|
|
2836
|
+
{
|
|
2837
|
+
id: "sidebar",
|
|
2838
|
+
panelRef: sidebarPanelRef,
|
|
2839
|
+
defaultSize: sidebarOpen ? sidebarDefaultSize : 0,
|
|
2840
|
+
minSize: sidebarMinSize,
|
|
2841
|
+
collapsible: true,
|
|
2842
|
+
collapsedSize: 0,
|
|
2843
|
+
groupResizeBehavior: "preserve-pixel-size",
|
|
2844
|
+
onResize: handleSidebarResize,
|
|
2845
|
+
style: { overflow: "hidden" },
|
|
2846
|
+
children: /* @__PURE__ */ jsx14(
|
|
2847
|
+
"div",
|
|
2848
|
+
{
|
|
2849
|
+
className: cn9(
|
|
2850
|
+
"h-full flex flex-col min-h-0 overflow-y-auto scrollbar-thin",
|
|
2851
|
+
sidebarClassName
|
|
2852
|
+
),
|
|
2853
|
+
children: sidebar
|
|
2854
|
+
}
|
|
2855
|
+
)
|
|
2856
|
+
}
|
|
2857
|
+
),
|
|
2858
|
+
/* @__PURE__ */ jsx14(
|
|
2859
|
+
ResizableHandle,
|
|
2860
|
+
{
|
|
2861
|
+
className: sidebarOpen ? void 0 : "w-0 opacity-0 pointer-events-none"
|
|
2862
|
+
}
|
|
2863
|
+
)
|
|
2864
|
+
] }),
|
|
2865
|
+
/* @__PURE__ */ jsx14(ResizablePanel, { id: "body", minSize: 200, className: "min-h-0", children: /* @__PURE__ */ jsx14("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children }) }),
|
|
2866
|
+
hasSecondaryPanel && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
2867
|
+
/* @__PURE__ */ jsx14(ResizableHandle, {}),
|
|
2868
|
+
/* @__PURE__ */ jsx14(
|
|
2869
|
+
ResizablePanel,
|
|
2870
|
+
{
|
|
2871
|
+
id: "secondary",
|
|
2872
|
+
defaultSize: secondaryPanelDefaultSize,
|
|
2873
|
+
minSize: secondaryPanelMinSize,
|
|
2874
|
+
groupResizeBehavior: "preserve-pixel-size",
|
|
2875
|
+
style: { overflow: "hidden" },
|
|
2876
|
+
children: /* @__PURE__ */ jsx14(
|
|
2877
|
+
"div",
|
|
2878
|
+
{
|
|
2879
|
+
className: cn9(
|
|
2880
|
+
"h-full flex flex-col min-h-0",
|
|
2881
|
+
secondaryPanelClassName
|
|
2882
|
+
),
|
|
2883
|
+
children: secondaryPanel
|
|
2884
|
+
}
|
|
2885
|
+
)
|
|
2886
|
+
}
|
|
2887
|
+
)
|
|
2888
|
+
] })
|
|
2889
|
+
] }) : children;
|
|
2890
|
+
const bodyContent = /* @__PURE__ */ jsx14(WindowSelectionSurface, { children: isDeprecated ? /* @__PURE__ */ jsxs11("div", { className: "h-full flex flex-col min-h-0", children: [
|
|
2891
|
+
/* @__PURE__ */ jsx14(
|
|
2892
|
+
DeprecationBanner,
|
|
2893
|
+
{
|
|
2894
|
+
replacedBy: deprecation?.replacement,
|
|
2895
|
+
note: deprecation?.reason
|
|
2896
|
+
}
|
|
2897
|
+
),
|
|
2898
|
+
/* @__PURE__ */ jsx14("div", { className: "flex-1 min-h-0", children: innerBody })
|
|
2899
|
+
] }) : innerBody });
|
|
2900
|
+
const hasZonedFooter = footerLeft || footerCenter || footerRight;
|
|
2901
|
+
const hasFooter = footer || hasZonedFooter;
|
|
2902
|
+
const zonedFooter = /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
2903
|
+
/* @__PURE__ */ jsx14("div", { className: "flex items-center gap-1 shrink-0", children: footerLeft }),
|
|
2904
|
+
/* @__PURE__ */ jsx14("div", { className: "flex-1 flex items-center justify-center gap-1", children: footerCenter }),
|
|
2905
|
+
/* @__PURE__ */ jsx14("div", { className: "flex items-center gap-1 shrink-0", children: footerRight })
|
|
2906
|
+
] });
|
|
2907
|
+
const footerBar = hasFooter ? /* @__PURE__ */ jsx14(
|
|
2908
|
+
"div",
|
|
2909
|
+
{
|
|
2910
|
+
className: cn9(
|
|
2911
|
+
// Always: detach from scroll + separate from the body with a top border.
|
|
2912
|
+
"shrink-0 border-t border-border/50",
|
|
2913
|
+
WINDOW_CHROME_INTERACTIVE,
|
|
2914
|
+
footerVariant === "bar" && // Compact metadata-bar chrome — thin chips, tiny buttons/icons. Crushes
|
|
2915
|
+
// rich content; consumers with a composer/input bar pass footerVariant="rich".
|
|
2916
|
+
"flex items-center gap-1 px-2 py-1 rounded-b-xl bg-muted/40 select-none text-xs [&_svg]:h-3 [&_svg]:w-3 [&_button]:h-5 [&_button]:text-xs"
|
|
2917
|
+
),
|
|
2918
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
2919
|
+
children: footer ?? (footerVariant === "bar" ? zonedFooter : /* @__PURE__ */ jsx14("div", { className: "flex items-center gap-1", children: zonedFooter }))
|
|
2920
|
+
}
|
|
2921
|
+
) : null;
|
|
2922
|
+
const bodyPortal = isMounted && bodyHost && !isMobile ? createPortal3(
|
|
2923
|
+
/* @__PURE__ */ jsx14(PopoutContext.Provider, { value: bodyPopoutValue, children: /* @__PURE__ */ jsx14(PortalContainerProvider2, { container: bodyPortalContainer, children: /* @__PURE__ */ jsx14("div", { className: "contents", onPointerDown: onFocus, onPointerDownCapture: claimFront, onFocusCapture: claimFront, children: bodyContent }) }) }),
|
|
2924
|
+
bodyHost
|
|
2925
|
+
) : null;
|
|
2926
|
+
if (isMobile) {
|
|
2927
|
+
const mobilePresentation = mobilePresentationOverride ?? registryEntry?.mobile ?? "fullscreen";
|
|
2928
|
+
const mobileSidebarAs = registryEntry?.mobileSidebarAs ?? "drawer";
|
|
2929
|
+
if (mobilePresentation === "hidden") {
|
|
2930
|
+
if (process.env.NODE_ENV !== "production" && overlayId) {
|
|
2931
|
+
console.warn(
|
|
2932
|
+
`[WindowPanel] overlay "${overlayId}" has window.mobile: "hidden" but was opened on a phone. Give its definition another presentation or gate opening.`
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
if (mobilePresentation === "drawer") {
|
|
2938
|
+
return /* @__PURE__ */ jsx14(
|
|
2939
|
+
MobileDrawerSurface,
|
|
2940
|
+
{
|
|
2941
|
+
isOpen: true,
|
|
2942
|
+
title: titleNode ?? title,
|
|
2943
|
+
onClose: handleClose,
|
|
2944
|
+
sidebar,
|
|
2945
|
+
sidebarAs: mobileSidebarAs,
|
|
2946
|
+
footer: footerBar,
|
|
2947
|
+
actionsLeft,
|
|
2948
|
+
actionsRight: resolvedActionsRight,
|
|
2949
|
+
bodyClassName,
|
|
2950
|
+
surfaceLayer,
|
|
2951
|
+
sizeToContent: mobileSizeToContent,
|
|
2952
|
+
hideClose: mobileHideClose,
|
|
2953
|
+
children: /* @__PURE__ */ jsx14(WindowSelectionSurface, { children })
|
|
2954
|
+
}
|
|
2955
|
+
);
|
|
2956
|
+
}
|
|
2957
|
+
if (mobilePresentation === "card") {
|
|
2958
|
+
return /* @__PURE__ */ jsx14(
|
|
2959
|
+
MobileCardSurface,
|
|
2960
|
+
{
|
|
2961
|
+
isOpen: true,
|
|
2962
|
+
title: titleNode ?? title,
|
|
2963
|
+
onClose: handleClose,
|
|
2964
|
+
footer: footerBar,
|
|
2965
|
+
actionsRight: resolvedActionsRight,
|
|
2966
|
+
bodyClassName,
|
|
2967
|
+
surfaceLayer,
|
|
2968
|
+
children: /* @__PURE__ */ jsx14(WindowSelectionSurface, { children })
|
|
2969
|
+
}
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
const mobileBody = hasSidebar && activePaneMobile === "sidebar" ? /* @__PURE__ */ jsx14("div", { className: cn9("h-full overflow-y-auto", sidebarClassName), children: sidebar }) : children;
|
|
2973
|
+
const mobileEl = /* @__PURE__ */ jsxs11(
|
|
2974
|
+
"div",
|
|
2975
|
+
{
|
|
2976
|
+
...surfaceLayerAttrs,
|
|
2977
|
+
className: cn9(
|
|
2978
|
+
"fixed inset-0 flex flex-col",
|
|
2979
|
+
"bg-card/98 backdrop-blur-md",
|
|
2980
|
+
"overflow-hidden",
|
|
2981
|
+
deprecatedRingClass,
|
|
2982
|
+
className
|
|
2983
|
+
),
|
|
2984
|
+
style: {
|
|
2985
|
+
top: windowConfig.mobileTopOffset,
|
|
2986
|
+
zIndex,
|
|
2987
|
+
display: isTopWindow && !isMinimized ? void 0 : "none",
|
|
2988
|
+
visibility: windowsHidden ? "hidden" : void 0
|
|
2989
|
+
},
|
|
2990
|
+
onPointerDown: onFocus,
|
|
2991
|
+
onPointerDownCapture: claimFront,
|
|
2992
|
+
onFocusCapture: claimFront,
|
|
2993
|
+
children: [
|
|
2994
|
+
/* @__PURE__ */ jsx14(
|
|
2995
|
+
MobileWindowHeader,
|
|
2996
|
+
{
|
|
2997
|
+
title: titleNode ?? title,
|
|
2998
|
+
actionsRight: resolvedActionsRight,
|
|
2999
|
+
onClose: handleClose,
|
|
3000
|
+
hasSidebar,
|
|
3001
|
+
activePaneMobile,
|
|
3002
|
+
onSetActivePane: setActivePaneMobile
|
|
3003
|
+
}
|
|
3004
|
+
),
|
|
3005
|
+
isDebugMode && /* @__PURE__ */ jsx14(DebugStrip, { rect, zIndex }),
|
|
3006
|
+
/* @__PURE__ */ jsx14("div", { className: cn9("flex-1 overflow-auto min-h-0", bodyClassName), children: /* @__PURE__ */ jsx14(WindowSelectionSurface, { children: mobileBody }) }),
|
|
3007
|
+
footerBar
|
|
3008
|
+
]
|
|
3009
|
+
}
|
|
3010
|
+
);
|
|
3011
|
+
return portalTarget ? createPortal3(/* @__PURE__ */ jsx14(FloatingLayer2, { children: mobileEl }), portalTarget) : null;
|
|
3012
|
+
}
|
|
3013
|
+
if (popoutMode !== null) {
|
|
3014
|
+
return /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3015
|
+
/* @__PURE__ */ jsx14(PopoutPortal, { windowId: id, children: /* @__PURE__ */ jsxs11(
|
|
3016
|
+
"div",
|
|
3017
|
+
{
|
|
3018
|
+
...surfaceLayerAttrs,
|
|
3019
|
+
className: "h-full w-full flex flex-col bg-card text-foreground",
|
|
3020
|
+
children: [
|
|
3021
|
+
/* @__PURE__ */ jsx14(
|
|
3022
|
+
PopoutTopBar,
|
|
3023
|
+
{
|
|
3024
|
+
title: titleNode ?? title,
|
|
3025
|
+
actionsRight: resolvedActionsRight,
|
|
3026
|
+
onDock: handleDockBack
|
|
3027
|
+
}
|
|
3028
|
+
),
|
|
3029
|
+
isDebugMode && /* @__PURE__ */ jsx14(DebugStrip, { rect, zIndex }),
|
|
3030
|
+
/* @__PURE__ */ jsx14("div", { className: cn9("flex-1 overflow-auto min-h-0", bodyClassName), children: /* @__PURE__ */ jsx14(BodyMount, { host: bodyHost }) }),
|
|
3031
|
+
footerBar
|
|
3032
|
+
]
|
|
3033
|
+
}
|
|
3034
|
+
) }),
|
|
3035
|
+
bodyPortal
|
|
3036
|
+
] });
|
|
3037
|
+
}
|
|
3038
|
+
const el = /* @__PURE__ */ jsxs11(
|
|
3039
|
+
"div",
|
|
3040
|
+
{
|
|
3041
|
+
ref: fitContent ? fitContentRef : void 0,
|
|
3042
|
+
"data-window-panel": "",
|
|
3043
|
+
"data-window-id": id,
|
|
3044
|
+
...surfaceLayerAttrs,
|
|
3045
|
+
...isMinimized ? { "data-matrx-floating-bottom": "" } : {},
|
|
3046
|
+
className: cn9(
|
|
3047
|
+
"fixed",
|
|
3048
|
+
isMaximized ? "overflow-hidden" : "overflow-visible",
|
|
3049
|
+
"matrx-window-enter",
|
|
3050
|
+
// Programmatic rect changes glide; pointer drag/resize stays 1:1.
|
|
3051
|
+
glideReady && !isInteracting && "matrx-window-glide",
|
|
3052
|
+
// Drag-out candidate: ring-2 highlight signals "release here to pop out".
|
|
3053
|
+
isPopoutCandidate && "ring-2 ring-primary ring-offset-2 ring-offset-background transition-shadow",
|
|
3054
|
+
deprecatedRingClass,
|
|
3055
|
+
className
|
|
3056
|
+
),
|
|
3057
|
+
style: {
|
|
3058
|
+
...isMaximized ? { inset: 0, width: "100vw", height: "100dvh" } : {
|
|
3059
|
+
left: rect.x,
|
|
3060
|
+
top: rect.y,
|
|
3061
|
+
...fitContent && !isMinimized ? { width: "max-content", height: "auto" } : { width: rect.width, height: rect.height }
|
|
3062
|
+
},
|
|
3063
|
+
zIndex,
|
|
3064
|
+
// Cap CSS mins to the current rect so arrange/snap geometry wins.
|
|
3065
|
+
// Uncapped minWidth (e.g. 640) was overriding tile widths and stacking
|
|
3066
|
+
// every left-edge window on top of each other.
|
|
3067
|
+
minWidth: isMaximized || isMinimized ? 0 : fitContent ? minWidth ?? 180 : Math.min(minWidth ?? 180, rect.width),
|
|
3068
|
+
minHeight: isMaximized || isMinimized ? 0 : fitContent ? minHeight ?? 80 : Math.min(minHeight ?? 80, rect.height),
|
|
3069
|
+
visibility: windowsHidden ? "hidden" : void 0
|
|
3070
|
+
},
|
|
3071
|
+
onPointerDown: onFocus,
|
|
3072
|
+
onPointerDownCapture: claimFront,
|
|
3073
|
+
onFocusCapture: claimFront,
|
|
3074
|
+
onDoubleClick: isMinimized ? handleRestoreClearingSnapshot : void 0,
|
|
3075
|
+
children: [
|
|
3076
|
+
!isMinimized && !isMaximized && HANDLES.map((h) => /* @__PURE__ */ jsx14(
|
|
3077
|
+
"div",
|
|
3078
|
+
{
|
|
3079
|
+
className: cn9(
|
|
3080
|
+
h.className,
|
|
3081
|
+
"z-50 hover:bg-primary/20 transition-colors"
|
|
3082
|
+
),
|
|
3083
|
+
style: { touchAction: "none" },
|
|
3084
|
+
onPointerDown: onResizeStart(h.edge)
|
|
3085
|
+
},
|
|
3086
|
+
h.edge
|
|
3087
|
+
)),
|
|
3088
|
+
/* @__PURE__ */ jsxs11(
|
|
3089
|
+
"div",
|
|
3090
|
+
{
|
|
3091
|
+
className: cn9(
|
|
3092
|
+
"flex h-full w-full min-h-0 flex-col",
|
|
3093
|
+
// Opaque window surface: a window is a solid card, never glass —
|
|
3094
|
+
// glass belongs only on bars floating over moving content (tap-target
|
|
3095
|
+
// placement rule 1). A see-through window let the page underneath
|
|
3096
|
+
// read through its content (Watch AI, 2026-10-12).
|
|
3097
|
+
isMaximized ? "bg-card border border-border shadow-2xl" : "rounded-xl bg-card border border-border shadow-xl",
|
|
3098
|
+
"overflow-hidden"
|
|
3099
|
+
),
|
|
3100
|
+
children: [
|
|
3101
|
+
header,
|
|
3102
|
+
isDebugMode && !isMinimized && /* @__PURE__ */ jsx14(DebugStrip, { rect, zIndex }),
|
|
3103
|
+
/* @__PURE__ */ jsx14(
|
|
3104
|
+
WindowPanelBodyShell,
|
|
3105
|
+
{
|
|
3106
|
+
bodyRef,
|
|
3107
|
+
fitContent,
|
|
3108
|
+
isMaximized,
|
|
3109
|
+
bodyClassName,
|
|
3110
|
+
captureDimensions: isMinimized ? pendingTrayCapture ? {
|
|
3111
|
+
width: pendingTrayCapture.width,
|
|
3112
|
+
height: pendingTrayCapture.height
|
|
3113
|
+
} : retainedBodyDimensionsRef.current ?? {
|
|
3114
|
+
width: Math.max(1, rect.width),
|
|
3115
|
+
height: Math.max(1, rect.height)
|
|
3116
|
+
} : null,
|
|
3117
|
+
children: /* @__PURE__ */ jsx14(BodyMount, { host: bodyHost })
|
|
3118
|
+
}
|
|
3119
|
+
),
|
|
3120
|
+
isMinimized && /* @__PURE__ */ jsx14(
|
|
3121
|
+
MinimizedWindowContent,
|
|
3122
|
+
{
|
|
3123
|
+
registryKey: trayRegistryKey,
|
|
3124
|
+
snapshotKey: id,
|
|
3125
|
+
overlayInstanceId,
|
|
3126
|
+
previewData: trayPreviewData,
|
|
3127
|
+
title: minimizedTitle,
|
|
3128
|
+
onRestore: handleRestoreClearingSnapshot
|
|
3129
|
+
}
|
|
3130
|
+
),
|
|
3131
|
+
!isMinimized && footerBar,
|
|
3132
|
+
isPopoutCandidate && /* @__PURE__ */ jsx14("div", { className: "absolute inset-0 z-30 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsx14("div", { className: "px-3 py-1.5 rounded-md bg-primary text-primary-foreground text-xs font-medium shadow-lg", children: "Release to pop out" }) })
|
|
3133
|
+
]
|
|
3134
|
+
}
|
|
3135
|
+
)
|
|
3136
|
+
]
|
|
3137
|
+
}
|
|
3138
|
+
);
|
|
3139
|
+
return /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3140
|
+
portalTarget ? createPortal3(/* @__PURE__ */ jsx14(FloatingLayer2, { children: el }), portalTarget) : null,
|
|
3141
|
+
bodyPortal
|
|
3142
|
+
] });
|
|
3143
|
+
}
|
|
3144
|
+
function BodyMount({ host }) {
|
|
3145
|
+
const slotRef = useRef4(null);
|
|
3146
|
+
useLayoutEffect(() => {
|
|
3147
|
+
const slot2 = slotRef.current;
|
|
3148
|
+
if (!slot2 || !host) return void 0;
|
|
3149
|
+
slot2.appendChild(host);
|
|
3150
|
+
return () => {
|
|
3151
|
+
if (host.parentNode === slot2) slot2.removeChild(host);
|
|
3152
|
+
};
|
|
3153
|
+
}, [host]);
|
|
3154
|
+
return /* @__PURE__ */ jsx14("div", { ref: slotRef, className: "contents", "data-window-body-slot": "" });
|
|
3155
|
+
}
|
|
3156
|
+
function PopOutHeaderButton({ onClick }) {
|
|
3157
|
+
return /* @__PURE__ */ jsx14(
|
|
3158
|
+
"button",
|
|
3159
|
+
{
|
|
3160
|
+
type: "button",
|
|
3161
|
+
className: "p-0.5 rounded hover:bg-accent/60 transition-colors text-foreground/60 hover:text-foreground cursor-pointer",
|
|
3162
|
+
onClick,
|
|
3163
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3164
|
+
title: "Pop out into a separate window",
|
|
3165
|
+
"aria-label": "Pop out into a separate window",
|
|
3166
|
+
children: /* @__PURE__ */ jsx14(AppWindowIcon, { className: "w-3.5 h-3.5" })
|
|
3167
|
+
}
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
function WindowHeader({
|
|
3171
|
+
title,
|
|
3172
|
+
minimizedTitle,
|
|
3173
|
+
actionsLeft,
|
|
3174
|
+
actionsRight,
|
|
3175
|
+
onDragStart,
|
|
3176
|
+
onMinimize,
|
|
3177
|
+
onToggleMaximize,
|
|
3178
|
+
onRestore,
|
|
3179
|
+
onClose,
|
|
3180
|
+
isMaximized,
|
|
3181
|
+
isMinimized,
|
|
3182
|
+
snapLeft,
|
|
3183
|
+
snapRight,
|
|
3184
|
+
snapTop,
|
|
3185
|
+
snapBottom,
|
|
3186
|
+
snapCentre,
|
|
3187
|
+
arrangeAll,
|
|
3188
|
+
hasSidebar,
|
|
3189
|
+
sidebarOpen,
|
|
3190
|
+
onToggleSidebar,
|
|
3191
|
+
onSaveWindowState,
|
|
3192
|
+
onPopOut
|
|
3193
|
+
}) {
|
|
3194
|
+
return /* @__PURE__ */ jsxs11(
|
|
3195
|
+
"div",
|
|
3196
|
+
{
|
|
3197
|
+
className: cn9(
|
|
3198
|
+
// 🚨 NEW-8 — the header is a CONTAINER, so a consumer's title/actions can
|
|
3199
|
+
// answer the WINDOW's width instead of the viewport's (`sm:` cannot know
|
|
3200
|
+
// a window is 360px on a 1440px screen — that is how D4 stayed invisible).
|
|
3201
|
+
// The name is `window-header`; `lib/detail/core/headerGeometry.ts` holds it.
|
|
3202
|
+
"@container/window-header",
|
|
3203
|
+
// Open: three in-flow columns — left zone, title, right zone. The two
|
|
3204
|
+
// side columns share the free space equally (never less than their
|
|
3205
|
+
// real content), so the title sits at the WINDOW's centre whatever
|
|
3206
|
+
// each side carries, and still truncates instead of running under an
|
|
3207
|
+
// action cluster when space is tight (D4). Minimized has no title
|
|
3208
|
+
// column, so it keeps the plain row.
|
|
3209
|
+
isMinimized ? "matrx-window-header-row relative flex items-center justify-between gap-1 px-2 py-1 z-20 shrink-0" : "matrx-window-header-grid relative items-center gap-1 px-2 py-1 z-20 shrink-0",
|
|
3210
|
+
"border-b border-border/50 bg-muted/40 select-none",
|
|
3211
|
+
isMaximized || isMinimized ? "cursor-default" : "cursor-grab active:cursor-grabbing",
|
|
3212
|
+
isMinimized && "h-8 min-h-8 py-0 border-b border-border/50"
|
|
3213
|
+
),
|
|
3214
|
+
"data-window-panel-state": isMinimized ? "minimized" : void 0,
|
|
3215
|
+
style: isMaximized || isMinimized ? void 0 : { touchAction: "none" },
|
|
3216
|
+
onPointerDown: isMaximized || isMinimized ? void 0 : onDragStart,
|
|
3217
|
+
children: [
|
|
3218
|
+
/* @__PURE__ */ jsx14(
|
|
3219
|
+
"div",
|
|
3220
|
+
{
|
|
3221
|
+
className: cn9(
|
|
3222
|
+
"group/tl absolute top-0 left-0 bottom-0 flex items-center z-20",
|
|
3223
|
+
hasSidebar ? "w-28" : "w-24"
|
|
3224
|
+
),
|
|
3225
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3226
|
+
children: /* @__PURE__ */ jsx14("div", { className: "pl-2", children: /* @__PURE__ */ jsx14(
|
|
3227
|
+
TrafficLightGroup,
|
|
3228
|
+
{
|
|
3229
|
+
isMinimized,
|
|
3230
|
+
isMaximized,
|
|
3231
|
+
onClose,
|
|
3232
|
+
onMinimize,
|
|
3233
|
+
onRestore,
|
|
3234
|
+
onToggleMaximize,
|
|
3235
|
+
snapLeft,
|
|
3236
|
+
snapRight,
|
|
3237
|
+
snapTop,
|
|
3238
|
+
snapBottom,
|
|
3239
|
+
snapCentre,
|
|
3240
|
+
arrangeAll,
|
|
3241
|
+
hasSidebar,
|
|
3242
|
+
sidebarOpen,
|
|
3243
|
+
onToggleSidebar,
|
|
3244
|
+
onSaveWindowState,
|
|
3245
|
+
onPopOut
|
|
3246
|
+
}
|
|
3247
|
+
) })
|
|
3248
|
+
}
|
|
3249
|
+
),
|
|
3250
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1 z-10 shrink-0", children: [
|
|
3251
|
+
/* @__PURE__ */ jsx14("div", { className: hasSidebar ? "w-28" : "w-24" }),
|
|
3252
|
+
!isMinimized && actionsLeft && /* @__PURE__ */ jsx14(
|
|
3253
|
+
"div",
|
|
3254
|
+
{
|
|
3255
|
+
className: WINDOW_CHROME_ACTIONS,
|
|
3256
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3257
|
+
children: actionsLeft
|
|
3258
|
+
}
|
|
3259
|
+
)
|
|
3260
|
+
] }),
|
|
3261
|
+
isMinimized ? /* @__PURE__ */ jsx14("div", { className: "absolute top-0 bottom-0 left-20 right-2 flex items-center justify-start pointer-events-none", children: /* @__PURE__ */ jsx14(
|
|
3262
|
+
"span",
|
|
3263
|
+
{
|
|
3264
|
+
className: "matrx-window-minimized-title block min-w-0 truncate leading-none font-semibold text-foreground",
|
|
3265
|
+
"data-minimized-window-title": true,
|
|
3266
|
+
children: minimizedTitle
|
|
3267
|
+
}
|
|
3268
|
+
) }) : null,
|
|
3269
|
+
!isMinimized && /* @__PURE__ */ jsx14("div", { className: "flex min-w-0 flex-1 items-center justify-center overflow-hidden", children: typeof title === "string" || title == null ? /* @__PURE__ */ jsx14("span", { className: "min-w-0 truncate text-xs font-medium text-foreground/80", children: title ?? "" }) : /* @__PURE__ */ jsx14(
|
|
3270
|
+
"div",
|
|
3271
|
+
{
|
|
3272
|
+
className: "flex min-w-0 max-w-full items-center overflow-hidden whitespace-nowrap",
|
|
3273
|
+
onPointerDown: (event) => {
|
|
3274
|
+
const target = event.target;
|
|
3275
|
+
if (!(target instanceof Element)) return;
|
|
3276
|
+
if (target.closest(
|
|
3277
|
+
'a, button, input, select, textarea, [role="button"], [role="menuitem"], [data-window-no-drag]'
|
|
3278
|
+
)) {
|
|
3279
|
+
event.stopPropagation();
|
|
3280
|
+
}
|
|
3281
|
+
},
|
|
3282
|
+
children: title
|
|
3283
|
+
}
|
|
3284
|
+
) }),
|
|
3285
|
+
/* @__PURE__ */ jsx14("div", { className: "flex items-center justify-end gap-1 z-10 shrink-0", children: !isMinimized && (onPopOut || actionsRight) && /* @__PURE__ */ jsxs11(
|
|
3286
|
+
"div",
|
|
3287
|
+
{
|
|
3288
|
+
className: WINDOW_CHROME_ACTIONS,
|
|
3289
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3290
|
+
children: [
|
|
3291
|
+
onPopOut ? /* @__PURE__ */ jsx14(PopOutHeaderButton, { onClick: onPopOut }) : null,
|
|
3292
|
+
actionsRight
|
|
3293
|
+
]
|
|
3294
|
+
}
|
|
3295
|
+
) })
|
|
3296
|
+
]
|
|
3297
|
+
}
|
|
3298
|
+
);
|
|
3299
|
+
}
|
|
3300
|
+
function TrafficLightGroup({
|
|
3301
|
+
isMinimized,
|
|
3302
|
+
isMaximized,
|
|
3303
|
+
onClose,
|
|
3304
|
+
onMinimize,
|
|
3305
|
+
onRestore,
|
|
3306
|
+
onToggleMaximize,
|
|
3307
|
+
snapLeft,
|
|
3308
|
+
snapRight,
|
|
3309
|
+
snapTop,
|
|
3310
|
+
snapBottom,
|
|
3311
|
+
snapCentre,
|
|
3312
|
+
arrangeAll,
|
|
3313
|
+
hasSidebar,
|
|
3314
|
+
sidebarOpen,
|
|
3315
|
+
onToggleSidebar,
|
|
3316
|
+
onSaveWindowState,
|
|
3317
|
+
onPopOut
|
|
3318
|
+
}) {
|
|
3319
|
+
return /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 shrink-0 cursor-default", children: [
|
|
3320
|
+
/* @__PURE__ */ jsx14(
|
|
3321
|
+
TrafficLight,
|
|
3322
|
+
{
|
|
3323
|
+
color: "red",
|
|
3324
|
+
icon: /* @__PURE__ */ jsx14(XIcon, { className: "w-2 h-2 matrx-window-light-glyph" }),
|
|
3325
|
+
onClick: onClose ?? void 0,
|
|
3326
|
+
disabled: !onClose,
|
|
3327
|
+
"aria-label": "Close"
|
|
3328
|
+
}
|
|
3329
|
+
),
|
|
3330
|
+
/* @__PURE__ */ jsx14(
|
|
3331
|
+
TrafficLight,
|
|
3332
|
+
{
|
|
3333
|
+
color: "yellow",
|
|
3334
|
+
icon: isMinimized ? /* @__PURE__ */ jsx14(
|
|
3335
|
+
Maximize2Icon,
|
|
3336
|
+
{
|
|
3337
|
+
className: "w-2 h-2 matrx-window-light-glyph"
|
|
3338
|
+
}
|
|
3339
|
+
) : /* @__PURE__ */ jsx14(
|
|
3340
|
+
MinusIcon,
|
|
3341
|
+
{
|
|
3342
|
+
className: "w-2 h-2 matrx-window-light-glyph"
|
|
3343
|
+
}
|
|
3344
|
+
),
|
|
3345
|
+
onClick: isMinimized ? onRestore : onMinimize,
|
|
3346
|
+
"aria-label": isMinimized ? "Restore" : "Minimize"
|
|
3347
|
+
}
|
|
3348
|
+
),
|
|
3349
|
+
!isMinimized && /* @__PURE__ */ jsx14(
|
|
3350
|
+
GreenTrafficLight,
|
|
3351
|
+
{
|
|
3352
|
+
isMaximized,
|
|
3353
|
+
onToggleMaximize,
|
|
3354
|
+
onRestore,
|
|
3355
|
+
snapLeft,
|
|
3356
|
+
snapRight,
|
|
3357
|
+
snapTop,
|
|
3358
|
+
snapBottom,
|
|
3359
|
+
snapCentre,
|
|
3360
|
+
arrangeAll,
|
|
3361
|
+
onSaveWindowState,
|
|
3362
|
+
onPopOut
|
|
3363
|
+
}
|
|
3364
|
+
),
|
|
3365
|
+
hasSidebar && !isMinimized && /* @__PURE__ */ jsx14(
|
|
3366
|
+
"button",
|
|
3367
|
+
{
|
|
3368
|
+
type: "button",
|
|
3369
|
+
className: "ml-0.5 p-0.5 rounded hover:bg-accent/60 transition-colors text-foreground/60 group-hover/tl:text-foreground cursor-pointer",
|
|
3370
|
+
onClick: onToggleSidebar,
|
|
3371
|
+
title: sidebarOpen ? "Hide sidebar" : "Show sidebar",
|
|
3372
|
+
"aria-label": sidebarOpen ? "Hide sidebar" : "Show sidebar",
|
|
3373
|
+
children: sidebarOpen ? /* @__PURE__ */ jsx14(PanelLeftCloseIcon, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsx14(PanelLeftIcon, { className: "w-3.5 h-3.5" })
|
|
3374
|
+
}
|
|
3375
|
+
)
|
|
3376
|
+
] });
|
|
3377
|
+
}
|
|
3378
|
+
function TrafficLight({
|
|
3379
|
+
color,
|
|
3380
|
+
icon,
|
|
3381
|
+
onClick,
|
|
3382
|
+
disabled,
|
|
3383
|
+
"aria-label": label
|
|
3384
|
+
}) {
|
|
3385
|
+
const base = "w-3.5 h-3.5 rounded-full flex items-center justify-center transition-colors shrink-0 relative";
|
|
3386
|
+
const colours = color === "red" ? disabled ? "matrx-window-light-disabled cursor-default" : "matrx-window-light-close cursor-pointer" : "matrx-window-light-minimize cursor-pointer";
|
|
3387
|
+
return /* @__PURE__ */ jsx14(
|
|
3388
|
+
"button",
|
|
3389
|
+
{
|
|
3390
|
+
type: "button",
|
|
3391
|
+
className: cn9(base, colours),
|
|
3392
|
+
onClick: disabled ? void 0 : onClick,
|
|
3393
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3394
|
+
"aria-label": label,
|
|
3395
|
+
disabled,
|
|
3396
|
+
children: /* @__PURE__ */ jsx14(
|
|
3397
|
+
"span",
|
|
3398
|
+
{
|
|
3399
|
+
className: cn9(
|
|
3400
|
+
"opacity-0 transition-opacity duration-100",
|
|
3401
|
+
!disabled && "group-hover/tl:opacity-100"
|
|
3402
|
+
),
|
|
3403
|
+
children: icon
|
|
3404
|
+
}
|
|
3405
|
+
)
|
|
3406
|
+
}
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
function GreenTrafficLight({
|
|
3410
|
+
isMaximized,
|
|
3411
|
+
onToggleMaximize,
|
|
3412
|
+
onRestore,
|
|
3413
|
+
snapLeft,
|
|
3414
|
+
snapRight,
|
|
3415
|
+
snapTop,
|
|
3416
|
+
snapBottom,
|
|
3417
|
+
snapCentre,
|
|
3418
|
+
arrangeAll,
|
|
3419
|
+
onSaveWindowState,
|
|
3420
|
+
onPopOut
|
|
3421
|
+
}) {
|
|
3422
|
+
const [dropdownOpen, setDropdownOpen] = useState5(false);
|
|
3423
|
+
const leaveTimer = useRef4(null);
|
|
3424
|
+
const containerRef = useRef4(null);
|
|
3425
|
+
const openDropdown = () => {
|
|
3426
|
+
if (leaveTimer.current) clearTimeout(leaveTimer.current);
|
|
3427
|
+
setDropdownOpen(true);
|
|
3428
|
+
};
|
|
3429
|
+
const scheduleClose = () => {
|
|
3430
|
+
leaveTimer.current = setTimeout(() => {
|
|
3431
|
+
setDropdownOpen(false);
|
|
3432
|
+
}, 120);
|
|
3433
|
+
};
|
|
3434
|
+
const handleAction = (fn) => {
|
|
3435
|
+
fn();
|
|
3436
|
+
setDropdownOpen(false);
|
|
3437
|
+
};
|
|
3438
|
+
useEffect5(() => {
|
|
3439
|
+
if (!dropdownOpen) return void 0;
|
|
3440
|
+
const onPointerOutside = (e) => {
|
|
3441
|
+
const target = e.target;
|
|
3442
|
+
if (containerRef.current && !containerRef.current.contains(target)) {
|
|
3443
|
+
if (target.closest?.("[data-radix-portal]")) return;
|
|
3444
|
+
setDropdownOpen(false);
|
|
3445
|
+
}
|
|
3446
|
+
};
|
|
3447
|
+
document.addEventListener("pointerdown", onPointerOutside);
|
|
3448
|
+
return () => document.removeEventListener("pointerdown", onPointerOutside);
|
|
3449
|
+
}, [dropdownOpen]);
|
|
3450
|
+
return /* @__PURE__ */ jsxs11(
|
|
3451
|
+
"div",
|
|
3452
|
+
{
|
|
3453
|
+
ref: containerRef,
|
|
3454
|
+
className: "relative",
|
|
3455
|
+
onMouseEnter: openDropdown,
|
|
3456
|
+
onMouseLeave: scheduleClose,
|
|
3457
|
+
children: [
|
|
3458
|
+
/* @__PURE__ */ jsx14(
|
|
3459
|
+
"button",
|
|
3460
|
+
{
|
|
3461
|
+
type: "button",
|
|
3462
|
+
className: cn9(
|
|
3463
|
+
"w-3.5 h-3.5 rounded-full flex items-center justify-center transition-colors shrink-0",
|
|
3464
|
+
"matrx-window-light-maximize cursor-pointer"
|
|
3465
|
+
),
|
|
3466
|
+
onClick: () => {
|
|
3467
|
+
if ("ontouchstart" in window || navigator.maxTouchPoints > 0) {
|
|
3468
|
+
setDropdownOpen((prev) => !prev);
|
|
3469
|
+
} else {
|
|
3470
|
+
handleAction(onToggleMaximize);
|
|
3471
|
+
}
|
|
3472
|
+
},
|
|
3473
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3474
|
+
"aria-label": isMaximized ? "Restore" : "Maximize",
|
|
3475
|
+
children: /* @__PURE__ */ jsx14("span", { className: "opacity-0 group-hover/tl:opacity-100 transition-opacity duration-100 flex items-center justify-center relative w-full h-full", children: isMaximized ? /* @__PURE__ */ jsx14(
|
|
3476
|
+
Minimize2Icon,
|
|
3477
|
+
{
|
|
3478
|
+
className: "w-2 h-2 matrx-window-light-glyph absolute"
|
|
3479
|
+
}
|
|
3480
|
+
) : /* @__PURE__ */ jsx14(
|
|
3481
|
+
Maximize2Icon,
|
|
3482
|
+
{
|
|
3483
|
+
className: "w-2 h-2 matrx-window-light-glyph absolute"
|
|
3484
|
+
}
|
|
3485
|
+
) })
|
|
3486
|
+
}
|
|
3487
|
+
),
|
|
3488
|
+
dropdownOpen && /* @__PURE__ */ jsxs11(
|
|
3489
|
+
"div",
|
|
3490
|
+
{
|
|
3491
|
+
className: cn9(
|
|
3492
|
+
"absolute left-0 top-full mt-1.5 z-50",
|
|
3493
|
+
"w-52 rounded-xl overflow-hidden",
|
|
3494
|
+
"bg-card/95 backdrop-blur-xl border border-border shadow-2xl",
|
|
3495
|
+
"py-1 text-xs"
|
|
3496
|
+
),
|
|
3497
|
+
onMouseEnter: openDropdown,
|
|
3498
|
+
onMouseLeave: scheduleClose,
|
|
3499
|
+
children: [
|
|
3500
|
+
!isMaximized && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3501
|
+
/* @__PURE__ */ jsx14("div", { className: "matrx-window-tile-meta px-3 pt-1.5 pb-1 font-semibold text-muted-foreground uppercase tracking-wider", children: "Move & Resize" }),
|
|
3502
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1 px-2 pb-2", children: [
|
|
3503
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex gap-1 justify-center", children: [
|
|
3504
|
+
/* @__PURE__ */ jsx14(
|
|
3505
|
+
LayoutIconButton,
|
|
3506
|
+
{
|
|
3507
|
+
onClick: () => handleAction(snapLeft),
|
|
3508
|
+
type: "left-half"
|
|
3509
|
+
}
|
|
3510
|
+
),
|
|
3511
|
+
/* @__PURE__ */ jsx14(
|
|
3512
|
+
LayoutIconButton,
|
|
3513
|
+
{
|
|
3514
|
+
onClick: () => handleAction(snapRight),
|
|
3515
|
+
type: "right-half"
|
|
3516
|
+
}
|
|
3517
|
+
),
|
|
3518
|
+
/* @__PURE__ */ jsx14(
|
|
3519
|
+
LayoutIconButton,
|
|
3520
|
+
{
|
|
3521
|
+
onClick: () => handleAction(snapTop),
|
|
3522
|
+
type: "top-half"
|
|
3523
|
+
}
|
|
3524
|
+
),
|
|
3525
|
+
/* @__PURE__ */ jsx14(
|
|
3526
|
+
LayoutIconButton,
|
|
3527
|
+
{
|
|
3528
|
+
onClick: () => handleAction(snapBottom),
|
|
3529
|
+
type: "bottom-half"
|
|
3530
|
+
}
|
|
3531
|
+
)
|
|
3532
|
+
] }),
|
|
3533
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex gap-1 justify-center", children: [
|
|
3534
|
+
/* @__PURE__ */ jsx14(
|
|
3535
|
+
LayoutIconButton,
|
|
3536
|
+
{
|
|
3537
|
+
onClick: () => handleAction(snapCentre),
|
|
3538
|
+
type: "centre"
|
|
3539
|
+
}
|
|
3540
|
+
),
|
|
3541
|
+
/* @__PURE__ */ jsx14(
|
|
3542
|
+
LayoutIconButton,
|
|
3543
|
+
{
|
|
3544
|
+
onClick: () => handleAction(onToggleMaximize),
|
|
3545
|
+
type: "full"
|
|
3546
|
+
}
|
|
3547
|
+
)
|
|
3548
|
+
] })
|
|
3549
|
+
] }),
|
|
3550
|
+
/* @__PURE__ */ jsx14("div", { className: "border-t border-border/50 my-1" }),
|
|
3551
|
+
/* @__PURE__ */ jsx14("div", { className: "matrx-window-tile-meta px-3 pt-1.5 pb-1 font-semibold text-muted-foreground uppercase tracking-wider", children: "Arrange All" }),
|
|
3552
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1 px-2 pb-2", children: [
|
|
3553
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex gap-1 justify-center", children: [
|
|
3554
|
+
/* @__PURE__ */ jsx14(
|
|
3555
|
+
LayoutIconButton,
|
|
3556
|
+
{
|
|
3557
|
+
onClick: () => handleAction(() => arrangeAll("grid4")),
|
|
3558
|
+
type: "grid4"
|
|
3559
|
+
}
|
|
3560
|
+
),
|
|
3561
|
+
/* @__PURE__ */ jsx14(
|
|
3562
|
+
LayoutIconButton,
|
|
3563
|
+
{
|
|
3564
|
+
onClick: () => handleAction(() => arrangeAll("grid6")),
|
|
3565
|
+
type: "grid6"
|
|
3566
|
+
}
|
|
3567
|
+
),
|
|
3568
|
+
/* @__PURE__ */ jsx14(
|
|
3569
|
+
LayoutIconButton,
|
|
3570
|
+
{
|
|
3571
|
+
onClick: () => handleAction(() => arrangeAll("grid8")),
|
|
3572
|
+
type: "grid8"
|
|
3573
|
+
}
|
|
3574
|
+
),
|
|
3575
|
+
/* @__PURE__ */ jsx14(
|
|
3576
|
+
LayoutIconButton,
|
|
3577
|
+
{
|
|
3578
|
+
onClick: () => handleAction(() => arrangeAll("grid9")),
|
|
3579
|
+
type: "grid9"
|
|
3580
|
+
}
|
|
3581
|
+
),
|
|
3582
|
+
/* @__PURE__ */ jsx14(
|
|
3583
|
+
LayoutIconButton,
|
|
3584
|
+
{
|
|
3585
|
+
onClick: () => handleAction(() => arrangeAll("grid12")),
|
|
3586
|
+
type: "grid12"
|
|
3587
|
+
}
|
|
3588
|
+
)
|
|
3589
|
+
] }),
|
|
3590
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex gap-1 justify-center", children: [
|
|
3591
|
+
/* @__PURE__ */ jsx14(
|
|
3592
|
+
LayoutIconButton,
|
|
3593
|
+
{
|
|
3594
|
+
onClick: () => handleAction(() => arrangeAll("stackRight2")),
|
|
3595
|
+
type: "stackRight2"
|
|
3596
|
+
}
|
|
3597
|
+
),
|
|
3598
|
+
/* @__PURE__ */ jsx14(
|
|
3599
|
+
LayoutIconButton,
|
|
3600
|
+
{
|
|
3601
|
+
onClick: () => handleAction(() => arrangeAll("stackRight3")),
|
|
3602
|
+
type: "stackRight3"
|
|
3603
|
+
}
|
|
3604
|
+
),
|
|
3605
|
+
/* @__PURE__ */ jsx14(
|
|
3606
|
+
LayoutIconButton,
|
|
3607
|
+
{
|
|
3608
|
+
onClick: () => handleAction(() => arrangeAll("stackRight4")),
|
|
3609
|
+
type: "stackRight4"
|
|
3610
|
+
}
|
|
3611
|
+
),
|
|
3612
|
+
/* @__PURE__ */ jsx14(
|
|
3613
|
+
LayoutIconButton,
|
|
3614
|
+
{
|
|
3615
|
+
onClick: () => handleAction(() => arrangeAll("stackRight5")),
|
|
3616
|
+
type: "stackRight5"
|
|
3617
|
+
}
|
|
3618
|
+
)
|
|
3619
|
+
] }),
|
|
3620
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex gap-1 justify-center", children: [
|
|
3621
|
+
/* @__PURE__ */ jsx14(
|
|
3622
|
+
LayoutIconButton,
|
|
3623
|
+
{
|
|
3624
|
+
onClick: () => handleAction(() => arrangeAll("stackLeft2")),
|
|
3625
|
+
type: "stackLeft2"
|
|
3626
|
+
}
|
|
3627
|
+
),
|
|
3628
|
+
/* @__PURE__ */ jsx14(
|
|
3629
|
+
LayoutIconButton,
|
|
3630
|
+
{
|
|
3631
|
+
onClick: () => handleAction(() => arrangeAll("stackLeft3")),
|
|
3632
|
+
type: "stackLeft3"
|
|
3633
|
+
}
|
|
3634
|
+
),
|
|
3635
|
+
/* @__PURE__ */ jsx14(
|
|
3636
|
+
LayoutIconButton,
|
|
3637
|
+
{
|
|
3638
|
+
onClick: () => handleAction(() => arrangeAll("stackLeft4")),
|
|
3639
|
+
type: "stackLeft4"
|
|
3640
|
+
}
|
|
3641
|
+
),
|
|
3642
|
+
/* @__PURE__ */ jsx14(
|
|
3643
|
+
LayoutIconButton,
|
|
3644
|
+
{
|
|
3645
|
+
onClick: () => handleAction(() => arrangeAll("stackLeft5")),
|
|
3646
|
+
type: "stackLeft5"
|
|
3647
|
+
}
|
|
3648
|
+
)
|
|
3649
|
+
] })
|
|
3650
|
+
] }),
|
|
3651
|
+
/* @__PURE__ */ jsx14("div", { className: "border-t border-border/50 my-1" })
|
|
3652
|
+
] }),
|
|
3653
|
+
/* @__PURE__ */ jsx14(
|
|
3654
|
+
"button",
|
|
3655
|
+
{
|
|
3656
|
+
type: "button",
|
|
3657
|
+
className: "flex items-center gap-2.5 w-full px-3 py-1.5 hover:bg-accent transition-colors text-foreground/80",
|
|
3658
|
+
onClick: () => handleAction(onToggleMaximize),
|
|
3659
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3660
|
+
children: isMaximized ? /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3661
|
+
/* @__PURE__ */ jsx14(Minimize2Icon, { className: "w-3.5 h-3.5 shrink-0" }),
|
|
3662
|
+
" Exit Full Screen"
|
|
3663
|
+
] }) : /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3664
|
+
/* @__PURE__ */ jsx14(RectangleVerticalIcon, { className: "w-3.5 h-3.5 shrink-0" }),
|
|
3665
|
+
" Enter Full Screen"
|
|
3666
|
+
] })
|
|
3667
|
+
}
|
|
3668
|
+
),
|
|
3669
|
+
isMaximized && /* @__PURE__ */ jsxs11(
|
|
3670
|
+
"button",
|
|
3671
|
+
{
|
|
3672
|
+
type: "button",
|
|
3673
|
+
className: "flex items-center gap-2.5 w-full px-3 py-1.5 hover:bg-accent transition-colors text-foreground/80",
|
|
3674
|
+
onClick: () => handleAction(onRestore),
|
|
3675
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3676
|
+
children: [
|
|
3677
|
+
/* @__PURE__ */ jsx14(Maximize2Icon, { className: "w-3.5 h-3.5 shrink-0" }),
|
|
3678
|
+
"Restore window"
|
|
3679
|
+
]
|
|
3680
|
+
}
|
|
3681
|
+
),
|
|
3682
|
+
onPopOut && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3683
|
+
/* @__PURE__ */ jsx14("div", { className: "border-t border-border/50 my-1" }),
|
|
3684
|
+
/* @__PURE__ */ jsxs11(
|
|
3685
|
+
"button",
|
|
3686
|
+
{
|
|
3687
|
+
type: "button",
|
|
3688
|
+
className: "flex items-center gap-2.5 w-full px-3 py-1.5 transition-colors text-foreground/80 hover:bg-accent",
|
|
3689
|
+
onClick: () => handleAction(onPopOut),
|
|
3690
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3691
|
+
title: "Pop out into a separate window",
|
|
3692
|
+
children: [
|
|
3693
|
+
/* @__PURE__ */ jsx14(AppWindowIcon, { className: "w-3.5 h-3.5 shrink-0" }),
|
|
3694
|
+
/* @__PURE__ */ jsx14("span", { className: "flex-1 text-left", children: "Pop out" })
|
|
3695
|
+
]
|
|
3696
|
+
}
|
|
3697
|
+
)
|
|
3698
|
+
] }),
|
|
3699
|
+
onSaveWindowState && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3700
|
+
/* @__PURE__ */ jsx14("div", { className: "border-t border-border/50 my-1" }),
|
|
3701
|
+
/* @__PURE__ */ jsxs11(
|
|
3702
|
+
"button",
|
|
3703
|
+
{
|
|
3704
|
+
type: "button",
|
|
3705
|
+
className: "flex items-center gap-2.5 w-full px-3 py-1.5 hover:bg-accent transition-colors text-foreground/80",
|
|
3706
|
+
onClick: () => handleAction(onSaveWindowState),
|
|
3707
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3708
|
+
children: [
|
|
3709
|
+
/* @__PURE__ */ jsx14(SaveIcon, { className: "w-3.5 h-3.5 shrink-0" }),
|
|
3710
|
+
"Save window state"
|
|
3711
|
+
]
|
|
3712
|
+
}
|
|
3713
|
+
)
|
|
3714
|
+
] })
|
|
3715
|
+
]
|
|
3716
|
+
}
|
|
3717
|
+
)
|
|
3718
|
+
]
|
|
3719
|
+
}
|
|
3720
|
+
);
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
// src/window.ts
|
|
3724
|
+
import {
|
|
3725
|
+
WindowsProvider,
|
|
3726
|
+
useWindowConfig as useWindowConfig2,
|
|
3727
|
+
WindowSelectionSurface as WindowSelectionSurface2,
|
|
3728
|
+
DEFAULT_WINDOW_CONFIG,
|
|
3729
|
+
DEFAULT_ESCAPE_YIELD_SELECTOR,
|
|
3730
|
+
DEFAULT_MOBILE_TOP_OFFSET
|
|
3731
|
+
} from "@ai-matrx/summon/window-host";
|
|
3732
|
+
import { WindowTraySync } from "@ai-matrx/summon/window-host";
|
|
3733
|
+
|
|
3734
|
+
// src/window/WindowSidebar.tsx
|
|
3735
|
+
import { cn as cn10 } from "@ai-matrx/design-system/cn";
|
|
3736
|
+
import { ScrollArea } from "@ai-matrx/design-system";
|
|
3737
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3738
|
+
function WindowSidebar({
|
|
3739
|
+
items,
|
|
3740
|
+
activeItem,
|
|
3741
|
+
onSelect,
|
|
3742
|
+
className
|
|
3743
|
+
}) {
|
|
3744
|
+
return /* @__PURE__ */ jsx15(ScrollArea, { className: cn10("flex-1 w-full", className), children: /* @__PURE__ */ jsx15("div", { className: "py-2", children: items.map((item) => {
|
|
3745
|
+
const Icon = item.icon;
|
|
3746
|
+
const isActive = item.value === activeItem;
|
|
3747
|
+
return /* @__PURE__ */ jsxs12(
|
|
3748
|
+
"button",
|
|
3749
|
+
{
|
|
3750
|
+
onClick: () => onSelect(item.value),
|
|
3751
|
+
title: item.description,
|
|
3752
|
+
className: cn10(
|
|
3753
|
+
"w-full flex items-center gap-2.5 px-3 py-2 text-left text-xs transition-colors",
|
|
3754
|
+
isActive ? "bg-primary/10 text-primary-ink border-r-2 border-primary" : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
3755
|
+
),
|
|
3756
|
+
children: [
|
|
3757
|
+
/* @__PURE__ */ jsx15(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
3758
|
+
/* @__PURE__ */ jsx15("span", { className: "truncate", children: item.label })
|
|
3759
|
+
]
|
|
3760
|
+
},
|
|
3761
|
+
item.value
|
|
3762
|
+
);
|
|
3763
|
+
}) }) });
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
// src/window/useOverlaySurfaceRenderAck.ts
|
|
3767
|
+
import { useEffect as useEffect6 } from "react";
|
|
3768
|
+
import { ackOverlaySurfaceRender, clearOverlaySurfaceRender } from "@ai-matrx/summon";
|
|
3769
|
+
function useOverlaySurfaceRenderAck(overlayId, active) {
|
|
3770
|
+
useEffect6(() => {
|
|
3771
|
+
if (!active) return void 0;
|
|
3772
|
+
ackOverlaySurfaceRender(overlayId);
|
|
3773
|
+
return () => clearOverlaySurfaceRender(overlayId);
|
|
3774
|
+
}, [active, overlayId]);
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
// src/window.ts
|
|
3778
|
+
import {
|
|
3779
|
+
registerTrayPreview,
|
|
3780
|
+
getTrayPreviewEntry as getTrayPreviewEntry3,
|
|
3781
|
+
resetTrayPreviews
|
|
3782
|
+
} from "@ai-matrx/summon/window-host";
|
|
3783
|
+
|
|
3784
|
+
// src/window/tray/TrayStatusChip.tsx
|
|
3785
|
+
import { cn as cn11 } from "@ai-matrx/design-system/cn";
|
|
3786
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3787
|
+
var TONE_CLASSES = {
|
|
3788
|
+
neutral: {
|
|
3789
|
+
fg: "text-muted-foreground",
|
|
3790
|
+
dot: "bg-muted-foreground/50"
|
|
3791
|
+
},
|
|
3792
|
+
info: {
|
|
3793
|
+
fg: "text-info",
|
|
3794
|
+
dot: "bg-info"
|
|
3795
|
+
},
|
|
3796
|
+
warning: {
|
|
3797
|
+
fg: "text-warning",
|
|
3798
|
+
dot: "bg-warning"
|
|
3799
|
+
},
|
|
3800
|
+
elevated: {
|
|
3801
|
+
fg: "text-warning",
|
|
3802
|
+
dot: "bg-warning"
|
|
3803
|
+
},
|
|
3804
|
+
critical: {
|
|
3805
|
+
fg: "text-destructive",
|
|
3806
|
+
dot: "bg-destructive"
|
|
3807
|
+
}
|
|
3808
|
+
};
|
|
3809
|
+
function TrayStatusChip({
|
|
3810
|
+
icon: Icon,
|
|
3811
|
+
tone,
|
|
3812
|
+
count,
|
|
3813
|
+
caption,
|
|
3814
|
+
segments,
|
|
3815
|
+
pulse
|
|
3816
|
+
}) {
|
|
3817
|
+
const t = TONE_CLASSES[tone];
|
|
3818
|
+
const visibleSegments = (segments ?? []).filter((s) => s.count > 0);
|
|
3819
|
+
const showBreakdown = visibleSegments.length > 1;
|
|
3820
|
+
const hasCount = typeof count === "number";
|
|
3821
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex h-full items-center gap-1.5 overflow-hidden", children: [
|
|
3822
|
+
/* @__PURE__ */ jsx16(
|
|
3823
|
+
Icon,
|
|
3824
|
+
{
|
|
3825
|
+
className: cn11("h-3.5 w-3.5 shrink-0", t.fg, pulse && "animate-pulse"),
|
|
3826
|
+
"aria-hidden": true
|
|
3827
|
+
}
|
|
3828
|
+
),
|
|
3829
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex min-w-0 items-baseline gap-1", children: [
|
|
3830
|
+
hasCount ? /* @__PURE__ */ jsx16("span", { className: cn11("text-xs font-semibold tabular-nums", t.fg), children: count }) : null,
|
|
3831
|
+
caption ? /* @__PURE__ */ jsx16("span", { className: "truncate text-[11px] text-muted-foreground", children: caption }) : null
|
|
3832
|
+
] }),
|
|
3833
|
+
showBreakdown ? /* @__PURE__ */ jsx16("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: visibleSegments.map((s, i) => /* @__PURE__ */ jsxs13(
|
|
3834
|
+
"span",
|
|
3835
|
+
{
|
|
3836
|
+
className: "flex items-center gap-0.5",
|
|
3837
|
+
title: s.label ? `${s.count} ${s.label}` : void 0,
|
|
3838
|
+
children: [
|
|
3839
|
+
/* @__PURE__ */ jsx16(
|
|
3840
|
+
"span",
|
|
3841
|
+
{
|
|
3842
|
+
className: cn11(
|
|
3843
|
+
"h-1.5 w-1.5 rounded-full",
|
|
3844
|
+
TONE_CLASSES[s.tone].dot
|
|
3845
|
+
)
|
|
3846
|
+
}
|
|
3847
|
+
),
|
|
3848
|
+
/* @__PURE__ */ jsx16("span", { className: "text-[10px] tabular-nums text-muted-foreground", children: s.count })
|
|
3849
|
+
]
|
|
3850
|
+
},
|
|
3851
|
+
i
|
|
3852
|
+
)) }) : null
|
|
3853
|
+
] });
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
// src/window/popout/usePopoutContainer.ts
|
|
3857
|
+
function usePopoutContainer() {
|
|
3858
|
+
return usePopout().popoutContainer ?? void 0;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
// src/window/utils/mayRaiseUnbidden.ts
|
|
3862
|
+
import { getOverlayMeta as getOverlayMeta4 } from "@ai-matrx/summon";
|
|
3863
|
+
function isUnderRoute(pathname, route) {
|
|
3864
|
+
if (pathname === route) return true;
|
|
3865
|
+
const base = route.endsWith("/") ? route.slice(0, -1) : route;
|
|
3866
|
+
return pathname === base || pathname.startsWith(`${base}/`);
|
|
3867
|
+
}
|
|
3868
|
+
function describeHome(h) {
|
|
3869
|
+
return typeof h === "string" ? h : `${h.route} (exact)`;
|
|
3870
|
+
}
|
|
3871
|
+
function mayRaiseUnbidden(overlayId, pathname) {
|
|
3872
|
+
const entry = getOverlayMeta4(overlayId);
|
|
3873
|
+
const declared = entry?.window?.unbiddenHome ?? [];
|
|
3874
|
+
const home = declared.map(describeHome);
|
|
3875
|
+
if (home.length === 0) {
|
|
3876
|
+
const reason = entry ? `window "${overlayId}" declared no unbiddenHome in the window registry, so it may not open itself on any route` : `overlay "${overlayId}" is not in the window registry, so it has no declared home and may not open itself anywhere`;
|
|
3877
|
+
console.warn(`[window-panels] unbidden raise refused \u2014 ${reason}`);
|
|
3878
|
+
return { allowed: false, reason, code: "no-declared-home", home };
|
|
3879
|
+
}
|
|
3880
|
+
if (!pathname) {
|
|
3881
|
+
const reason = `window "${overlayId}" was asked to open itself with no current route, so its home (${home.join(", ")}) could not be confirmed`;
|
|
3882
|
+
console.warn(`[window-panels] unbidden raise refused \u2014 ${reason}`);
|
|
3883
|
+
return { allowed: false, reason, code: "away-from-home", home };
|
|
3884
|
+
}
|
|
3885
|
+
const matched = declared.find(
|
|
3886
|
+
(h) => typeof h === "string" ? isUnderRoute(pathname, h) : pathname === h.route
|
|
3887
|
+
);
|
|
3888
|
+
const match = matched === void 0 ? void 0 : describeHome(matched);
|
|
3889
|
+
if (!match) {
|
|
3890
|
+
const reason = `window "${overlayId}" lives on ${home.join(", ")} \u2014 not on "${pathname}" \u2014 so it is deferred until the viewer is on one of its own surfaces`;
|
|
3891
|
+
console.info(`[window-panels] unbidden raise deferred \u2014 ${reason}`);
|
|
3892
|
+
return { allowed: false, reason, code: "away-from-home", home };
|
|
3893
|
+
}
|
|
3894
|
+
return {
|
|
3895
|
+
allowed: true,
|
|
3896
|
+
reason: `window "${overlayId}" is on its declared home "${match}"`,
|
|
3897
|
+
home
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
// src/window/utils/lazy-bundle-guard.ts
|
|
3902
|
+
import { globalSlot as globalSlot5 } from "@ai-matrx/kit/global-slot";
|
|
3903
|
+
var guard = () => globalSlot5("ai-matrx.summon.lazyBundleGuard", () => {
|
|
3904
|
+
const state2 = { bootInProgress: typeof window !== "undefined", reported: /* @__PURE__ */ new Set() };
|
|
3905
|
+
if (state2.bootInProgress) {
|
|
3906
|
+
setTimeout(() => {
|
|
3907
|
+
state2.bootInProgress = false;
|
|
3908
|
+
}, 0);
|
|
3909
|
+
}
|
|
3910
|
+
return state2;
|
|
3911
|
+
});
|
|
3912
|
+
guard();
|
|
3913
|
+
function assertLazyLoaded(moduleId, report) {
|
|
3914
|
+
if (typeof window === "undefined") return;
|
|
3915
|
+
const callSiteStack = (new Error("call site").stack ?? "").split("\n").filter((line) => !/lazy-bundle-guard\.|at Error\s*$|^Error/i.test(line)).join("\n");
|
|
3916
|
+
queueMicrotask(() => {
|
|
3917
|
+
const state2 = guard();
|
|
3918
|
+
if (!state2.bootInProgress || state2.reported.has(moduleId)) return;
|
|
3919
|
+
state2.reported.add(moduleId);
|
|
3920
|
+
const message = `${moduleId} was parsed during initial app boot. Lazily rendered modules MUST load through their definition's render import \u2014 never statically from a route, layout, provider, or any module in the boot graph. (Reported once per session per module.)`;
|
|
3921
|
+
if (report) {
|
|
3922
|
+
report(message, callSiteStack);
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
console.warn(
|
|
3926
|
+
"[overlays bundle leak]",
|
|
3927
|
+
`
|
|
3928
|
+
${message}${callSiteStack ? `
|
|
3929
|
+
|
|
3930
|
+
Eager-import chain (top frame is the file that statically imported it):
|
|
3931
|
+
${callSiteStack}` : ""}`
|
|
3932
|
+
);
|
|
3933
|
+
});
|
|
3934
|
+
}
|
|
3935
|
+
export {
|
|
3936
|
+
AUTO_OPEN_MIN_WIDTH,
|
|
3937
|
+
AUTO_OPEN_NO_ROOM_EVENT,
|
|
3938
|
+
DEFAULT_DRAG_OUT_CONFIG,
|
|
3939
|
+
DEFAULT_ESCAPE_YIELD_SELECTOR,
|
|
3940
|
+
DEFAULT_MOBILE_TOP_OFFSET,
|
|
3941
|
+
DEFAULT_WINDOW_CONFIG,
|
|
3942
|
+
INITIAL_DRAG_OUT_STATE,
|
|
3943
|
+
LayoutIcon,
|
|
3944
|
+
LayoutIconButton,
|
|
3945
|
+
MOBILE_WINDOW_TITLE_TEXT,
|
|
3946
|
+
MinimizedWindowContent,
|
|
3947
|
+
MobileCardSurface,
|
|
3948
|
+
MobileDrawerSurface,
|
|
3949
|
+
MobileWindowActionsRow,
|
|
3950
|
+
MobileWindowTitle,
|
|
3951
|
+
MobileWindowTitleRow,
|
|
3952
|
+
PopoutContext,
|
|
3953
|
+
TrayChipPreview,
|
|
3954
|
+
TrayStatusChip,
|
|
3955
|
+
WindowPanel,
|
|
3956
|
+
WindowSelectionSurface2 as WindowSelectionSurface,
|
|
3957
|
+
WindowSidebar,
|
|
3958
|
+
WindowTraySync,
|
|
3959
|
+
WindowsProvider,
|
|
3960
|
+
assertLazyLoaded,
|
|
3961
|
+
autoOpenHasRoom,
|
|
3962
|
+
buildPopoutOpts,
|
|
3963
|
+
captureElementThumbnail,
|
|
3964
|
+
clearAllPopoutPending,
|
|
3965
|
+
clearAutoOpened,
|
|
3966
|
+
clearTraySnapshot,
|
|
3967
|
+
defaultTraySnapshotCapture,
|
|
3968
|
+
detectPopoutCapability,
|
|
3969
|
+
evaluateDragOut,
|
|
3970
|
+
forEachPopoutWindow,
|
|
3971
|
+
getPendingPopoutWindowIds,
|
|
3972
|
+
getPopoutOpener,
|
|
3973
|
+
getPopoutWindow,
|
|
3974
|
+
getTrayPreviewEntry3 as getTrayPreviewEntry,
|
|
3975
|
+
getTraySnapshot,
|
|
3976
|
+
isAutoOpened,
|
|
3977
|
+
isUnderRoute,
|
|
3978
|
+
markAutoOpened,
|
|
3979
|
+
mayRaiseUnbidden,
|
|
3980
|
+
measureWorkingColumn,
|
|
3981
|
+
mobileWindowTitleClass,
|
|
3982
|
+
registerTrayPreview,
|
|
3983
|
+
resetDefaultWindowCascade,
|
|
3984
|
+
resetTrayPreviews,
|
|
3985
|
+
resolveAutoOpenPlacement,
|
|
3986
|
+
resolvePosition,
|
|
3987
|
+
sanitizeWindowData,
|
|
3988
|
+
setTraySnapshot,
|
|
3989
|
+
subscribePopoutWindowMap,
|
|
3990
|
+
subscribeTraySnapshotMap,
|
|
3991
|
+
supportsAnyPopout,
|
|
3992
|
+
supportsDocumentPictureInPicture,
|
|
3993
|
+
useOverlaySurfaceRenderAck,
|
|
3994
|
+
usePopout,
|
|
3995
|
+
usePopoutContainer,
|
|
3996
|
+
usePopoutControl,
|
|
3997
|
+
usePopoutWindow,
|
|
3998
|
+
usePopoutWindowRef,
|
|
3999
|
+
useWindowConfig2 as useWindowConfig,
|
|
4000
|
+
useWindowPanel
|
|
4001
|
+
};
|
|
4002
|
+
//# sourceMappingURL=window.js.map
|