@ai-matrx/summon 0.1.7 → 0.1.9
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 +15 -2
- package/dist/gate-CfjdNWP6.d.cts +397 -0
- package/dist/gate-CfjdNWP6.d.ts +397 -0
- package/dist/handles.cjs +1 -0
- package/dist/handles.cjs.map +1 -1
- package/dist/handles.d.cts +3 -61
- package/dist/handles.d.ts +3 -61
- package/dist/handles.js +1 -0
- package/dist/handles.js.map +1 -1
- package/dist/helpers-2NY1ha0V.d.cts +63 -0
- package/dist/helpers-2NY1ha0V.d.ts +63 -0
- package/dist/index.cjs +194 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -350
- package/dist/index.d.ts +12 -350
- package/dist/index.js +200 -165
- package/dist/index.js.map +1 -1
- package/dist/instanceBody-CgZAFnic.d.cts +23 -0
- package/dist/instanceBody-CgZAFnic.d.ts +23 -0
- package/dist/open.cjs +408 -0
- package/dist/open.cjs.map +1 -0
- package/dist/open.d.cts +23 -0
- package/dist/open.d.ts +23 -0
- package/dist/open.js +375 -0
- package/dist/open.js.map +1 -0
- package/dist/react.cjs +249 -352
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +11 -151
- package/dist/react.d.ts +11 -151
- package/dist/react.js +210 -306
- package/dist/react.js.map +1 -1
- package/dist/root.cjs +277 -0
- package/dist/root.cjs.map +1 -0
- package/dist/root.d.cts +175 -0
- package/dist/root.d.ts +175 -0
- package/dist/root.js +254 -0
- package/dist/root.js.map +1 -0
- package/dist/window-host.cjs +11 -2
- package/dist/window-host.cjs.map +1 -1
- package/dist/window-host.d.cts +1 -0
- package/dist/window-host.d.ts +1 -0
- package/dist/window-host.js +11 -2
- package/dist/window-host.js.map +1 -1
- package/dist/window.cjs +2 -1
- package/dist/window.cjs.map +1 -1
- package/dist/window.d.cts +3 -2
- package/dist/window.d.ts +3 -2
- package/dist/window.js +2 -1
- package/dist/window.js.map +1 -1
- package/package.json +28 -5
package/dist/root.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/root/SummonRoot.tsx
|
|
4
|
+
import { lazy, Suspense, useEffect, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { Provider } from "react-redux";
|
|
6
|
+
import { isSummonInstalled, subscribeSummonInstalled } from "@ai-matrx/summon/open";
|
|
7
|
+
|
|
8
|
+
// src/root/context.ts
|
|
9
|
+
import { createContext } from "react";
|
|
10
|
+
var SummonStoreContext = createContext(null);
|
|
11
|
+
SummonStoreContext.displayName = "SummonStoreContext";
|
|
12
|
+
var SummonPortsContext = createContext(null);
|
|
13
|
+
SummonPortsContext.displayName = "SummonPortsContext";
|
|
14
|
+
|
|
15
|
+
// src/root/host.ts
|
|
16
|
+
import { globalSlot } from "@ai-matrx/kit/global-slot";
|
|
17
|
+
import { resolvePorts } from "@ai-matrx/summon/open";
|
|
18
|
+
var slot = () => globalSlot("ai-matrx.summon.host", () => ({ config: {}, ports: null, version: 0 }));
|
|
19
|
+
function configureSummonHost(config) {
|
|
20
|
+
const s = slot();
|
|
21
|
+
if (s.config === config) return;
|
|
22
|
+
s.config = config;
|
|
23
|
+
s.ports = null;
|
|
24
|
+
s.version += 1;
|
|
25
|
+
}
|
|
26
|
+
function getSummonHostConfig() {
|
|
27
|
+
return slot().config;
|
|
28
|
+
}
|
|
29
|
+
function getSummonHostPorts() {
|
|
30
|
+
const s = slot();
|
|
31
|
+
return s.ports ??= resolvePorts(s.config.ports);
|
|
32
|
+
}
|
|
33
|
+
function getSummonHostVersion() {
|
|
34
|
+
return slot().version;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/root/SummonRoot.tsx
|
|
38
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
39
|
+
var serverSnapshot = () => false;
|
|
40
|
+
function SummonRoot({ store, runtime, children, ports, renderWrapper, errorReportExtras, windows }) {
|
|
41
|
+
const [config] = useState(() => {
|
|
42
|
+
const initial = { ports, renderWrapper, errorReportExtras, windows };
|
|
43
|
+
configureSummonHost(initial);
|
|
44
|
+
return initial;
|
|
45
|
+
});
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (config.ports === ports && config.renderWrapper === renderWrapper && config.errorReportExtras === errorReportExtras && config.windows === windows) return;
|
|
48
|
+
configureSummonHost({ ports, renderWrapper, errorReportExtras, windows });
|
|
49
|
+
}, [config, ports, renderWrapper, errorReportExtras, windows]);
|
|
50
|
+
const installed = useSyncExternalStore(subscribeSummonInstalled, isSummonInstalled, serverSnapshot);
|
|
51
|
+
const [Runtime] = useState(() => runtime ? lazy(runtime) : null);
|
|
52
|
+
return /* @__PURE__ */ jsxs(Provider, { store, context: SummonStoreContext, children: [
|
|
53
|
+
children,
|
|
54
|
+
installed && Runtime ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Runtime, {}) }) : null
|
|
55
|
+
] });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/root/storeHooks.ts
|
|
59
|
+
import { useContext } from "react";
|
|
60
|
+
import {
|
|
61
|
+
createDispatchHook,
|
|
62
|
+
createSelectorHook,
|
|
63
|
+
createStoreHook
|
|
64
|
+
} from "react-redux";
|
|
65
|
+
var useSummonSelector = createSelectorHook(SummonStoreContext).withTypes();
|
|
66
|
+
var useSummonDispatch = createDispatchHook(SummonStoreContext).withTypes();
|
|
67
|
+
var useSummonStore = createStoreHook(SummonStoreContext).withTypes();
|
|
68
|
+
function useSummonPorts() {
|
|
69
|
+
return useContext(SummonPortsContext) ?? getSummonHostPorts();
|
|
70
|
+
}
|
|
71
|
+
function useOptionalSummonPorts() {
|
|
72
|
+
return useContext(SummonPortsContext) ?? getSummonHostPorts();
|
|
73
|
+
}
|
|
74
|
+
function useOptionalSummonStore() {
|
|
75
|
+
const context = useContext(SummonStoreContext);
|
|
76
|
+
return context ? context.store : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/root/defineOverlay.tsx
|
|
80
|
+
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
81
|
+
import {
|
|
82
|
+
DEFAULT_INSTANCE_ID as DEFAULT_INSTANCE_ID2,
|
|
83
|
+
instanceKey as toInstanceKey,
|
|
84
|
+
openPendingEventChannel,
|
|
85
|
+
selectIsOverlayOpen
|
|
86
|
+
} from "@ai-matrx/summon/open";
|
|
87
|
+
|
|
88
|
+
// src/root/dispatchers.ts
|
|
89
|
+
import { closeOverlay, openOverlay, DEFAULT_INSTANCE_ID } from "@ai-matrx/summon/open";
|
|
90
|
+
function newInstanceId(overlayId) {
|
|
91
|
+
return `${overlayId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
92
|
+
}
|
|
93
|
+
function dispatchOpen(dispatch, overlayId, instanceId, data) {
|
|
94
|
+
dispatch(
|
|
95
|
+
openOverlay({
|
|
96
|
+
overlayId,
|
|
97
|
+
instanceId,
|
|
98
|
+
data: data ?? null
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
function dispatchClose(dispatch, overlayId, instanceId = DEFAULT_INSTANCE_ID) {
|
|
103
|
+
dispatch(closeOverlay({ overlayId, instanceId }));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/root/emit.ts
|
|
107
|
+
import { globalSlot as globalSlot2 } from "@ai-matrx/kit/global-slot";
|
|
108
|
+
import { materializePendingChannels } from "@ai-matrx/summon/open";
|
|
109
|
+
var slot2 = () => globalSlot2("ai-matrx.summon.emitter", () => ({ emit: null }));
|
|
110
|
+
function registerOverlayEmitter(emit) {
|
|
111
|
+
slot2().emit = emit;
|
|
112
|
+
}
|
|
113
|
+
function emitOverlayEventLight(channelId, type, payload) {
|
|
114
|
+
const emit = slot2().emit;
|
|
115
|
+
if (emit) {
|
|
116
|
+
emit(channelId, type, payload);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
void import("@ai-matrx/summon/handles").then(({ createEventChannel, getEventChannel }) => {
|
|
120
|
+
materializePendingChannels(createEventChannel);
|
|
121
|
+
getEventChannel(channelId)?.emit({ type, payload });
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/root/instance.ts
|
|
126
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
127
|
+
var OverlayInstanceContext = createContext2(null);
|
|
128
|
+
OverlayInstanceContext.displayName = "OverlayInstanceContext";
|
|
129
|
+
function useOverlayInstance() {
|
|
130
|
+
const value = useContext2(OverlayInstanceContext);
|
|
131
|
+
if (!value) throw new Error("[summon] useOverlayInstance() was called outside a rendered overlay.");
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
function useOptionalOverlayInstance() {
|
|
135
|
+
return useContext2(OverlayInstanceContext);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/root/defineOverlay.tsx
|
|
139
|
+
function openOverlayEventChannel(owner, getHandlers) {
|
|
140
|
+
return openPendingEventChannel(owner, getHandlers);
|
|
141
|
+
}
|
|
142
|
+
var CALLBACK_GROUP_KEY = "callbackGroupId";
|
|
143
|
+
function openWith(dispatch, id, multi, options) {
|
|
144
|
+
const instanceId = options?.instanceId ?? (multi ? newInstanceId(id) : DEFAULT_INSTANCE_ID2);
|
|
145
|
+
const key = toInstanceKey(id, instanceId);
|
|
146
|
+
const data = {};
|
|
147
|
+
const given = options?.data;
|
|
148
|
+
if (typeof given === "object" && given !== null) Object.assign(data, given);
|
|
149
|
+
const handlers = options?.handlers;
|
|
150
|
+
if (handlers) data[CALLBACK_GROUP_KEY] = openOverlayEventChannel(key, () => handlers);
|
|
151
|
+
dispatchOpen(dispatch, id, instanceId, data);
|
|
152
|
+
return { overlayId: id, instanceId, close: () => dispatchClose(dispatch, id, instanceId) };
|
|
153
|
+
}
|
|
154
|
+
function dataSignature(data) {
|
|
155
|
+
try {
|
|
156
|
+
return JSON.stringify(data, (_key, value) => typeof value === "function" ? void 0 : value);
|
|
157
|
+
} catch {
|
|
158
|
+
return String(Date.now());
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function defineOverlay() {
|
|
162
|
+
return function define(spec) {
|
|
163
|
+
const id = spec.id;
|
|
164
|
+
const multi = spec.instance === "multi";
|
|
165
|
+
const meta = spec;
|
|
166
|
+
function useSummon() {
|
|
167
|
+
const dispatch = useSummonDispatch();
|
|
168
|
+
return useCallback((options) => openWith(dispatch, id, multi, options), [dispatch]);
|
|
169
|
+
}
|
|
170
|
+
function useIsOpen(instanceId = DEFAULT_INSTANCE_ID2) {
|
|
171
|
+
return useSummonSelector((state) => selectIsOverlayOpen(state, id, instanceId));
|
|
172
|
+
}
|
|
173
|
+
function useClose() {
|
|
174
|
+
const dispatch = useSummonDispatch();
|
|
175
|
+
return useCallback((instanceId) => dispatchClose(dispatch, id, instanceId ?? DEFAULT_INSTANCE_ID2), [dispatch]);
|
|
176
|
+
}
|
|
177
|
+
function useEmit() {
|
|
178
|
+
const instance = useOptionalOverlayInstance();
|
|
179
|
+
const channelId = instance?.overlayId === id ? instance.rawData?.[CALLBACK_GROUP_KEY] : void 0;
|
|
180
|
+
return useCallback(
|
|
181
|
+
(event, payload) => {
|
|
182
|
+
if (typeof channelId === "string") emitOverlayEventLight(channelId, event, payload);
|
|
183
|
+
},
|
|
184
|
+
[channelId]
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
function Controller(props) {
|
|
188
|
+
const dispatch = useSummonDispatch();
|
|
189
|
+
const [ownInstanceId] = useState2(() => multi ? newInstanceId(id) : DEFAULT_INSTANCE_ID2);
|
|
190
|
+
const instanceId = props.instanceId ?? ownInstanceId;
|
|
191
|
+
const handlersRef = useRef(props.handlers);
|
|
192
|
+
handlersRef.current = props.handlers;
|
|
193
|
+
const hasHandlers = props.handlers !== void 0;
|
|
194
|
+
const data = useMemo(() => {
|
|
195
|
+
const out = {};
|
|
196
|
+
for (const [key, value] of Object.entries(props)) {
|
|
197
|
+
if (key !== "instanceId" && key !== "handlers") out[key] = value;
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}, [props]);
|
|
201
|
+
const signature = dataSignature(data);
|
|
202
|
+
const dataRef = useRef(data);
|
|
203
|
+
dataRef.current = data;
|
|
204
|
+
useEffect2(() => {
|
|
205
|
+
const key = toInstanceKey(id, instanceId);
|
|
206
|
+
const open = { ...dataRef.current };
|
|
207
|
+
if (hasHandlers) open[CALLBACK_GROUP_KEY] = openOverlayEventChannel(key, () => handlersRef.current);
|
|
208
|
+
dispatchOpen(dispatch, id, instanceId, open);
|
|
209
|
+
return () => dispatchClose(dispatch, id, instanceId);
|
|
210
|
+
}, [dispatch, instanceId, signature, hasHandlers]);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
Controller.displayName = `${id}Controller`;
|
|
214
|
+
return {
|
|
215
|
+
...spec,
|
|
216
|
+
id,
|
|
217
|
+
label: spec.label,
|
|
218
|
+
instance: multi ? "multi" : "single",
|
|
219
|
+
keepAlive: spec.keepAlive ?? "none",
|
|
220
|
+
meta,
|
|
221
|
+
useSummon,
|
|
222
|
+
Controller,
|
|
223
|
+
useIsOpen,
|
|
224
|
+
useClose,
|
|
225
|
+
useEmit
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
CALLBACK_GROUP_KEY,
|
|
231
|
+
OverlayInstanceContext,
|
|
232
|
+
SummonPortsContext,
|
|
233
|
+
SummonRoot,
|
|
234
|
+
SummonStoreContext,
|
|
235
|
+
configureSummonHost,
|
|
236
|
+
defineOverlay,
|
|
237
|
+
dispatchClose,
|
|
238
|
+
dispatchOpen,
|
|
239
|
+
emitOverlayEventLight,
|
|
240
|
+
getSummonHostConfig,
|
|
241
|
+
getSummonHostPorts,
|
|
242
|
+
getSummonHostVersion,
|
|
243
|
+
newInstanceId,
|
|
244
|
+
registerOverlayEmitter,
|
|
245
|
+
useOptionalOverlayInstance,
|
|
246
|
+
useOptionalSummonPorts,
|
|
247
|
+
useOptionalSummonStore,
|
|
248
|
+
useOverlayInstance,
|
|
249
|
+
useSummonDispatch,
|
|
250
|
+
useSummonPorts,
|
|
251
|
+
useSummonSelector,
|
|
252
|
+
useSummonStore
|
|
253
|
+
};
|
|
254
|
+
//# sourceMappingURL=root.js.map
|
package/dist/root.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/root/SummonRoot.tsx","../src/root/context.ts","../src/root/host.ts","../src/root/storeHooks.ts","../src/root/defineOverlay.tsx","../src/root/dispatchers.ts","../src/root/emit.ts","../src/root/instance.ts"],"sourcesContent":["// `<SummonRoot>` — the only summon component a host mounts at its root, and it does next to\n// nothing: it hands the host's store to the summon context (so every opener works at once) and\n// records the host's settings. Nothing heavy loads here. The host's runtime component (tray sync,\n// router bridge — whatever only matters once windows exist) is fetched and mounted the moment the\n// summon core installs: on the first open, a page's own WindowPanel, a link, or saved windows.\n\nimport { lazy, Suspense, useEffect, useState, useSyncExternalStore, type ComponentType, type ReactNode } from \"react\";\nimport { Provider } from \"react-redux\";\nimport type { Store } from \"@reduxjs/toolkit\";\nimport { isSummonInstalled, subscribeSummonInstalled } from \"@ai-matrx/summon/open\";\nimport { SummonStoreContext } from \"./context\";\nimport { configureSummonHost, type SummonHostConfig } from \"./host\";\n\nexport interface SummonRootProps extends SummonHostConfig {\n /** The host store, with the gate (`createSummonGate`) in its middleware chain. */\n readonly store: Store<unknown>;\n /** Mounted once the summon core is installed; one import() of the host's whole runtime set. */\n readonly runtime?: () => Promise<{ default: ComponentType }>;\n readonly children?: ReactNode;\n}\n\nconst serverSnapshot = () => false;\n\nexport function SummonRoot({ store, runtime, children, ports, renderWrapper, errorReportExtras, windows }: SummonRootProps) {\n // Settings are recorded before the first child renders (a page's WindowPanel may read them).\n const [config] = useState<SummonHostConfig>(() => {\n const initial = { ports, renderWrapper, errorReportExtras, windows };\n configureSummonHost(initial);\n return initial;\n });\n useEffect(() => {\n if (config.ports === ports && config.renderWrapper === renderWrapper && config.errorReportExtras === errorReportExtras && config.windows === windows) return;\n configureSummonHost({ ports, renderWrapper, errorReportExtras, windows });\n }, [config, ports, renderWrapper, errorReportExtras, windows]);\n\n const installed = useSyncExternalStore(subscribeSummonInstalled, isSummonInstalled, serverSnapshot);\n const [Runtime] = useState(() => (runtime ? lazy(runtime) : null));\n\n return (\n <Provider store={store} context={SummonStoreContext}>\n {children}\n {installed && Runtime ? (\n <Suspense fallback={null}>\n <Runtime />\n </Suspense>\n ) : null}\n </Provider>\n );\n}\n","// The contexts every summon section reads, light enough for the root: the overlays store rides\n// its OWN react-redux context (chat-package pattern), so a private overlays store never collides\n// with a host's store and a host that hands its one store to `<SummonRoot>` simply passes it in.\nimport { createContext } from \"react\";\nimport type { ReactReduxContextValue } from \"react-redux\";\nimport type { SummonPorts } from \"@ai-matrx/summon/open\";\n\nexport const SummonStoreContext = createContext<ReactReduxContextValue | null>(null);\nSummonStoreContext.displayName = \"SummonStoreContext\";\n\nexport const SummonPortsContext = createContext<SummonPorts | null>(null);\nSummonPortsContext.displayName = \"SummonPortsContext\";\n","// The host's settings, configured ONCE at the root (`<SummonRoot>`) and read wherever a provider\n// is absent: a WindowPanel rendered by a page, the lazily loaded host, links and persistence. A\n// heavy `<SummonProvider>` still overrides them for its subtree (a package's own bare host).\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport { resolvePorts, type SummonPorts } from \"@ai-matrx/summon/open\";\nimport type { ComponentType, ReactNode } from \"react\";\n\n/** One rendered overlay instance, as the host wrapper and error extras see it. */\nexport interface OverlayRenderContext {\n readonly overlayId: string;\n readonly instanceId: string;\n readonly instanceKey: string;\n readonly label: string;\n}\n\nexport interface OverlayErrorExtrasContext extends OverlayRenderContext {\n readonly error: unknown;\n readonly componentStack: string | null;\n}\n\nexport interface SummonHostWindows {\n readonly escapeYieldSelector?: string | undefined;\n readonly mobileTopOffset?: string | undefined;\n readonly selectionSurface?: ComponentType<{ readonly children: ReactNode }> | undefined;\n}\n\nexport interface SummonHostConfig {\n readonly ports?: Partial<SummonPorts> | undefined;\n /** Wrap every rendered overlay (AI Matrx: its surface layer boundary). */\n readonly renderWrapper?: ((node: ReactNode, context: OverlayRenderContext) => ReactNode) | undefined;\n /** Extra controls in the default error card's footer (AI Matrx: \"Copy for AI\"). */\n readonly errorReportExtras?: ((context: OverlayErrorExtrasContext) => ReactNode) | undefined;\n /** Window host options (the window section reads them when no WindowsProvider is above). */\n readonly windows?: SummonHostWindows | undefined;\n}\n\ninterface HostSlot {\n config: SummonHostConfig;\n ports: SummonPorts | null;\n version: number;\n}\n\nconst slot = (): HostSlot => globalSlot<HostSlot>(\"ai-matrx.summon.host\", () => ({ config: {}, ports: null, version: 0 }));\n\nexport function configureSummonHost(config: SummonHostConfig): void {\n const s = slot();\n if (s.config === config) return;\n s.config = config;\n s.ports = null;\n s.version += 1;\n}\n\nexport function getSummonHostConfig(): SummonHostConfig {\n return slot().config;\n}\n\n/** The configured ports, resolved over the working defaults (stable until reconfigured). */\nexport function getSummonHostPorts(): SummonPorts {\n const s = slot();\n return (s.ports ??= resolvePorts(s.config.ports));\n}\n\n/** Bumps on every reconfigure; memo keys for values derived from the host config. */\nexport function getSummonHostVersion(): number {\n return slot().version;\n}\n","// Typed hooks over the package's own store context. Every package component reads the overlays\n// store through these, never react-redux's default context, so a host's own <Provider> and the\n// overlays store never collide.\n\nimport { useContext } from \"react\";\nimport {\n createDispatchHook,\n createSelectorHook,\n createStoreHook,\n type UseDispatch,\n type UseSelector,\n type UseStore,\n} from \"react-redux\";\nimport type { Dispatch, Store, UnknownAction } from \"@reduxjs/toolkit\";\nimport type { SummonRootState } from \"@ai-matrx/summon\";\nimport type { SummonPorts } from \"@ai-matrx/summon/open\";\nimport { SummonPortsContext, SummonStoreContext } from \"./context\";\nimport { getSummonHostPorts } from \"./host\";\n\n/** Dispatch as the React section uses it: plain actions (thunks are the host's business). */\nexport type SummonReactDispatch = Dispatch<UnknownAction>;\n\n/** The store as the React section sees it: the overlays slices at their root keys (a host store has more). */\nexport type SummonReactStore = Store<SummonRootState>;\n\n/** Select from the overlays store. */\nexport const useSummonSelector: UseSelector<SummonRootState> = createSelectorHook(SummonStoreContext).withTypes<SummonRootState>();\n\n/** The overlays store's dispatch. */\nexport const useSummonDispatch: UseDispatch<SummonReactDispatch> = createDispatchHook(SummonStoreContext).withTypes<SummonReactDispatch>();\n\n/** The overlays store itself (for `getState()` inside callbacks). */\nexport const useSummonStore: UseStore<SummonReactStore> = createStoreHook(SummonStoreContext).withTypes<SummonReactStore>();\n\n/** The nearest provider's ports, else the ports configured at the root, else the working defaults. */\nexport function useSummonPorts(): SummonPorts {\n return useContext(SummonPortsContext) ?? getSummonHostPorts();\n}\n\n/** Same as `useSummonPorts` (kept for components written to work page-local). */\nexport function useOptionalSummonPorts(): SummonPorts {\n return useContext(SummonPortsContext) ?? getSummonHostPorts();\n}\n\n/**\n * The nearest provider's store, or `null` when nothing above provides one — for packages that\n * mount their own provider only when the host has none (chat's windows on a bare host).\n */\nexport function useOptionalSummonStore(): SummonReactStore | null {\n const context = useContext(SummonStoreContext);\n return context ? (context.store as unknown as SummonReactStore) : null;\n}\n","// `defineOverlay` — the declare-once API (C38 R2). One light definition per overlay carries its\n// metadata and (optionally) a lazy `render` import; the definition generates the opener hook, the declarative\n// Controller, the open/close hooks and a typed event emitter. The host renders definitions; the\n// render module is the old controller block, moved verbatim, and is fetched only when opened.\n//\n// Light by construction (the root section): no slice, no host, no handles registry. Opening goes\n// through the light creators (a gated host store loads the core on the first one); a callback-aware\n// open reserves its event channel and the handles section arrives with that first open.\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n DEFAULT_INSTANCE_ID,\n instanceKey as toInstanceKey,\n openPendingEventChannel,\n selectIsOverlayOpen,\n type InstanceKey,\n type OverlayDefinitionMeta,\n type OverlayRawData,\n type AnyOverlayMeta,\n} from \"@ai-matrx/summon/open\";\nimport type { OverlayRenderProps } from \"../react/instanceBody\";\nimport type { RenderModule } from \"../react/loaderCache\";\nimport { dispatchClose, dispatchOpen, newInstanceId } from \"./dispatchers\";\nimport { emitOverlayEventLight } from \"./emit\";\nimport { useOptionalOverlayInstance } from \"./instance\";\nimport { useSummonDispatch, useSummonSelector, type SummonReactDispatch } from \"./storeHooks\";\n\n/** Event name → payload, declared once per overlay: `defineOverlay<Data, { uploaded: Result }>()`. */\nexport type OverlayEventMap = Record<string, unknown>;\n\nexport type OverlayHandlers<Events extends OverlayEventMap> = {\n readonly [K in keyof Events]?: (payload: Events[K]) => void;\n};\n\nfunction openOverlayEventChannel<Events extends OverlayEventMap>(\n owner: InstanceKey,\n getHandlers: () => OverlayHandlers<Events> | undefined,\n): string {\n return openPendingEventChannel(owner, getHandlers as () => Readonly<Record<string, ((payload: unknown) => void) | undefined>> | undefined);\n}\n\n/** The open-data key that carries an overlay's event channel id (the old callback-group key). */\nexport const CALLBACK_GROUP_KEY = \"callbackGroupId\";\n\nexport interface OverlayHandle {\n readonly overlayId: string;\n readonly instanceId: string;\n close(): void;\n}\n\nexport interface OverlayOpenOptions<Data, Events extends OverlayEventMap> {\n readonly data?: Data;\n /** Single-instance overlays ignore it unless given; multi-instance generate one when omitted. */\n readonly instanceId?: string;\n /** Plain functions; they reach the caller through an event channel owned by the instance. */\n readonly handlers?: OverlayHandlers<Events>;\n}\n\nexport type OverlayOpener<Data, Events extends OverlayEventMap> = (options?: OverlayOpenOptions<Data, Events>) => OverlayHandle;\n\nexport type OverlayControllerProps<Data, Events extends OverlayEventMap> = Data & {\n readonly instanceId?: string;\n readonly handlers?: OverlayHandlers<Events>;\n};\n\nexport type OverlayEmitter<Events extends OverlayEventMap> = <K extends keyof Events & string>(event: K, payload: Events[K]) => void;\n\n/** What a definition file passes to `defineOverlay<Data>()(…)`. */\nexport interface OverlaySpec<Id extends string, Data, Parsed> extends OverlayDefinitionMeta<Id, Data> {\n /**\n * The lazily loaded render module. Its default export receives `OverlayRenderProps<Parsed>`.\n * Optional: a large app leaves it out and hands `<SummonHost renders={…}>` one id → import map,\n * so files that import a definition for its opener never gain an import() edge to the window\n * (the host-build law). When present it wins over the host map.\n */\n readonly render?: () => Promise<RenderModule<OverlayRenderProps<NoInfer<Parsed>>>>;\n /** Narrow raw open data once. `null` = refuse: the instance is closed with a warning. */\n readonly parse?: (raw: OverlayRawData) => Parsed | null;\n}\n\n/** The type-erased half the host renders; no data types cross this line. */\nexport interface HostableOverlay {\n readonly id: string;\n readonly label: string;\n readonly instance: \"single\" | \"multi\";\n readonly keepAlive: \"none\" | \"hidden\" | \"restore\";\n /** The metadata the core registry stores (links, window options, defaults…). */\n readonly meta: AnyOverlayMeta;\n /** The definition's own render loader (the host's `renders` map is used when absent). */\n readonly render?: (() => Promise<unknown>) | undefined;\n /** Narrows raw open data; the host builds each instance body from it on first render. */\n readonly parse?: ((raw: OverlayRawData) => unknown) | undefined;\n}\n\nexport interface OverlayDefinition<Id extends string, Data, Parsed, Events extends OverlayEventMap>\n extends Omit<OverlayDefinitionMeta<Id, Data>, \"id\" | \"instance\" | \"keepAlive\">,\n HostableOverlay {\n readonly id: Id;\n readonly render?: () => Promise<RenderModule<OverlayRenderProps<Parsed>>>;\n /** Imperative opener: `const open = def.useSummon(); open({ data })` → handle. */\n useSummon(): OverlayOpener<Data, Events>;\n /** Declarative form: opens on mount, closes on unmount, re-opens when its data changes. */\n readonly Controller: (props: OverlayControllerProps<Data, Events>) => null;\n useIsOpen(instanceId?: string): boolean;\n /** `const close = def.useClose(); close(instanceId?)`. */\n useClose(): (instanceId?: string) => void;\n /** Inside the render tree: emit a declared event to the opener's handlers. */\n useEmit(): OverlayEmitter<Events>;\n}\n\nfunction openWith<Data, Events extends OverlayEventMap>(\n dispatch: SummonReactDispatch,\n id: string,\n multi: boolean,\n options: OverlayOpenOptions<Data, Events> | undefined,\n): OverlayHandle {\n const instanceId = options?.instanceId ?? (multi ? newInstanceId(id) : DEFAULT_INSTANCE_ID);\n const key = toInstanceKey(id, instanceId);\n const data: Record<string, unknown> = {};\n const given: unknown = options?.data;\n if (typeof given === \"object\" && given !== null) Object.assign(data, given);\n const handlers = options?.handlers;\n if (handlers) data[CALLBACK_GROUP_KEY] = openOverlayEventChannel<Events>(key, () => handlers);\n dispatchOpen(dispatch, id, instanceId, data);\n return { overlayId: id, instanceId, close: () => dispatchClose(dispatch, id, instanceId) };\n}\n\n/** Stable identity for the serialisable part of Controller props (functions never reopen). */\nfunction dataSignature(data: Readonly<Record<string, unknown>>): string {\n try {\n return JSON.stringify(data, (_key, value: unknown) => (typeof value === \"function\" ? undefined : value));\n } catch {\n return String(Date.now());\n }\n}\n\n/**\n * Declare an overlay once. Curried so `Data` (and `Events`) are named while the id and the\n * parsed-data type are inferred:\n *\n * export const notesWindow = defineOverlay<NotesWindowData>()({\n * id: \"notesWindow\", label: \"Notes\", render: () => import(\"./notesWindow.render\"),\n * });\n */\nexport function defineOverlay<Data extends object = Record<string, never>, Events extends OverlayEventMap = Record<never, never>>() {\n return function define<const Id extends string, Parsed = OverlayRawData>(\n spec: OverlaySpec<Id, Data, Parsed>,\n ): OverlayDefinition<Id, Data, Parsed, Events> {\n const id = spec.id;\n const multi = spec.instance === \"multi\";\n // The registry stores every definition's metadata under one erased type (AnyOverlayMeta).\n const meta: AnyOverlayMeta = spec;\n\n function useSummon(): OverlayOpener<Data, Events> {\n const dispatch = useSummonDispatch();\n return useCallback((options) => openWith(dispatch, id, multi, options), [dispatch]);\n }\n\n function useIsOpen(instanceId: string = DEFAULT_INSTANCE_ID): boolean {\n return useSummonSelector((state) => selectIsOverlayOpen(state, id, instanceId));\n }\n\n function useClose(): (instanceId?: string) => void {\n const dispatch = useSummonDispatch();\n return useCallback((instanceId?: string) => dispatchClose(dispatch, id, instanceId ?? DEFAULT_INSTANCE_ID), [dispatch]);\n }\n\n function useEmit(): OverlayEmitter<Events> {\n const instance = useOptionalOverlayInstance();\n const channelId = instance?.overlayId === id ? instance.rawData?.[CALLBACK_GROUP_KEY] : undefined;\n return useCallback<OverlayEmitter<Events>>(\n (event, payload) => {\n if (typeof channelId === \"string\") emitOverlayEventLight(channelId, event, payload);\n },\n [channelId],\n );\n }\n\n function Controller(props: OverlayControllerProps<Data, Events>): null {\n const dispatch = useSummonDispatch();\n const [ownInstanceId] = useState(() => (multi ? newInstanceId(id) : DEFAULT_INSTANCE_ID));\n const instanceId = props.instanceId ?? ownInstanceId;\n // Latest handlers, read at emit time: a new function identity never reopens the overlay.\n const handlersRef = useRef(props.handlers);\n handlersRef.current = props.handlers;\n const hasHandlers = props.handlers !== undefined;\n\n const data = useMemo(() => {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(props)) {\n if (key !== \"instanceId\" && key !== \"handlers\") out[key] = value;\n }\n return out;\n }, [props]);\n const signature = dataSignature(data);\n const dataRef = useRef(data);\n dataRef.current = data;\n\n useEffect(() => {\n const key: InstanceKey = toInstanceKey(id, instanceId);\n const open: Record<string, unknown> = { ...dataRef.current };\n if (hasHandlers) open[CALLBACK_GROUP_KEY] = openOverlayEventChannel<Events>(key, () => handlersRef.current);\n dispatchOpen(dispatch, id, instanceId, open);\n return () => dispatchClose(dispatch, id, instanceId);\n }, [dispatch, instanceId, signature, hasHandlers]);\n\n return null;\n }\n Controller.displayName = `${id}Controller`;\n\n return {\n ...spec,\n id,\n label: spec.label,\n instance: multi ? \"multi\" : \"single\",\n keepAlive: spec.keepAlive ?? \"none\",\n meta,\n useSummon,\n Controller,\n useIsOpen,\n useClose,\n useEmit,\n };\n };\n}\n","// The one place a definition turns an untyped (string) overlay id into the typed action creators.\n// Inside the package `OverlayId` is `string`; a host that augments the registry narrows it to its\n// ids, and these keep compiling there because definitions carry their own id.\n\nimport { closeOverlay, openOverlay, DEFAULT_INSTANCE_ID, type OverlayDataOf, type OverlayId } from \"@ai-matrx/summon/open\";\nimport type { SummonReactDispatch } from \"./storeHooks\";\n\nexport function newInstanceId(overlayId: string): string {\n return `${overlayId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport function dispatchOpen(dispatch: SummonReactDispatch, overlayId: string, instanceId: string, data: unknown): void {\n dispatch(\n openOverlay({\n overlayId: overlayId as OverlayId,\n instanceId,\n data: (data ?? null) as OverlayDataOf<OverlayId> | null,\n }),\n );\n}\n\nexport function dispatchClose(dispatch: SummonReactDispatch, overlayId: string, instanceId: string = DEFAULT_INSTANCE_ID): void {\n dispatch(closeOverlay({ overlayId: overlayId as OverlayId, instanceId }));\n}\n","// Emitting an overlay event from inside its render tree. The render tree has the handles registry\n// loaded (the react section registers its synchronous emitter on evaluation); before that, the\n// emit waits for the one handles import.\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport { materializePendingChannels } from \"@ai-matrx/summon/open\";\n\ntype Emitter = (channelId: string, type: string, payload: unknown) => void;\n\nconst slot = (): { emit: Emitter | null } => globalSlot(\"ai-matrx.summon.emitter\", () => ({ emit: null as Emitter | null }));\n\n/** The react section installs the synchronous emitter (it holds handles statically). */\nexport function registerOverlayEmitter(emit: Emitter): void {\n slot().emit = emit;\n}\n\nexport function emitOverlayEventLight(channelId: string, type: string, payload: unknown): void {\n const emit = slot().emit;\n if (emit) {\n emit(channelId, type, payload);\n return;\n }\n void import(\"@ai-matrx/summon/handles\").then(({ createEventChannel, getEventChannel }) => {\n materializePendingChannels(createEventChannel);\n getEventChannel<{ type: string; payload: unknown }>(channelId)?.emit({ type, payload });\n });\n}\n","// The context every rendered overlay instance provides to its subtree, and the hooks that read who\n// it is. Light: a definition's `useEmit` reads it; the saved-state hook stays in `./react`.\nimport { createContext, useContext } from \"react\";\nimport type { InstanceKey, OverlayRawData } from \"@ai-matrx/summon/open\";\n\nexport interface OverlayInstanceContextValue {\n readonly overlayId: string;\n readonly instanceId: string;\n readonly instanceKey: InstanceKey;\n readonly close: () => void;\n readonly hidden: boolean;\n /** The open data as it arrived (pre-parse); event emitters read their channel id from it. */\n readonly rawData: OverlayRawData;\n}\n\nexport const OverlayInstanceContext = createContext<OverlayInstanceContextValue | null>(null);\nOverlayInstanceContext.displayName = \"OverlayInstanceContext\";\n\n/** The open instance this component renders inside. Throws outside an overlay. */\nexport function useOverlayInstance(): OverlayInstanceContextValue {\n const value = useContext(OverlayInstanceContext);\n if (!value) throw new Error(\"[summon] useOverlayInstance() was called outside a rendered overlay.\");\n return value;\n}\n\n/** Same as `useOverlayInstance`, but `null` outside an overlay. */\nexport function useOptionalOverlayInstance(): OverlayInstanceContextValue | null {\n return useContext(OverlayInstanceContext);\n}\n"],"mappings":"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","names":["useEffect","useState","DEFAULT_INSTANCE_ID","globalSlot","slot","createContext","useContext","DEFAULT_INSTANCE_ID","useState","useEffect"]}
|
package/dist/window-host.cjs
CHANGED
|
@@ -40,6 +40,7 @@ module.exports = __toCommonJS(window_host_exports);
|
|
|
40
40
|
|
|
41
41
|
// src/window/config.tsx
|
|
42
42
|
var import_react = require("react");
|
|
43
|
+
var import_root = require("@ai-matrx/summon/root");
|
|
43
44
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
44
45
|
function PassthroughSurface({ children }) {
|
|
45
46
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
@@ -51,7 +52,7 @@ var DEFAULT_WINDOW_CONFIG = {
|
|
|
51
52
|
mobileTopOffset: DEFAULT_MOBILE_TOP_OFFSET,
|
|
52
53
|
selectionSurface: PassthroughSurface
|
|
53
54
|
};
|
|
54
|
-
var WindowConfigContext = (0, import_react.createContext)(
|
|
55
|
+
var WindowConfigContext = (0, import_react.createContext)(null);
|
|
55
56
|
WindowConfigContext.displayName = "WindowConfigContext";
|
|
56
57
|
function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }) {
|
|
57
58
|
const value = (0, import_react.useMemo)(
|
|
@@ -65,7 +66,15 @@ function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurfac
|
|
|
65
66
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WindowConfigContext.Provider, { value, children });
|
|
66
67
|
}
|
|
67
68
|
function useWindowConfig() {
|
|
68
|
-
|
|
69
|
+
const fromContext = (0, import_react.useContext)(WindowConfigContext);
|
|
70
|
+
if (fromContext) return fromContext;
|
|
71
|
+
const host = (0, import_root.getSummonHostConfig)().windows;
|
|
72
|
+
if (!host) return DEFAULT_WINDOW_CONFIG;
|
|
73
|
+
return {
|
|
74
|
+
escapeYieldSelector: host.escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,
|
|
75
|
+
mobileTopOffset: host.mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,
|
|
76
|
+
selectionSurface: host.selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface
|
|
77
|
+
};
|
|
69
78
|
}
|
|
70
79
|
function WindowSelectionSurface({ children }) {
|
|
71
80
|
const Surface = useWindowConfig().selectionSurface;
|
package/dist/window-host.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/window-host.ts","../src/window/config.tsx","../src/window/WindowTraySync.tsx","../src/window/popout/PopoutContext.tsx","../src/window/popout/usePopoutContainer.ts","../src/window/useOverlaySurfaceRenderAck.ts","../src/window/tray/trayPreviewRegistry.ts"],"sourcesContent":["// @ai-matrx/summon/window-host — the light half of the window section, safe to mount at the app\n// root: window configuration (WindowsProvider), the viewport/tray sync, and tray-preview\n// registration. Importing it never loads WindowPanel; the window section reads this section's\n// context through the package name, so both share one context.\nexport {\n WindowsProvider,\n useWindowConfig,\n WindowSelectionSurface,\n DEFAULT_WINDOW_CONFIG,\n DEFAULT_ESCAPE_YIELD_SELECTOR,\n DEFAULT_MOBILE_TOP_OFFSET,\n type WindowConfig,\n type WindowsProviderProps,\n type WindowSelectionSurfaceProps,\n} from \"./window/config\";\nexport { WindowTraySync } from \"./window/WindowTraySync\";\n// Hooks a render module calls inside a window, light enough for the boot graph. The popout context\n// lives HERE (one identity): the window section imports it through the package name.\nexport { usePopout, PopoutContext, type PopoutContextValue, type PopoutMode } from \"./window/popout/PopoutContext\";\nexport { usePopoutContainer } from \"./window/popout/usePopoutContainer\";\nexport { useOverlaySurfaceRenderAck } from \"./window/useOverlaySurfaceRenderAck\";\nexport {\n registerTrayPreview,\n getTrayPreviewEntry,\n resetTrayPreviews,\n type TrayPreviewContext,\n type TrayPreviewEntry,\n type TraySnapshotCapture,\n} from \"./window/tray/trayPreviewRegistry\";\n","\"use client\";\n\n/**\n * WindowsProvider — the window section's host options, read by every WindowPanel through context.\n * Everything has a working default, so no provider is needed at all; a host passes only what it\n * has (AI Matrx: its right-click selection menu and its header offset).\n */\nimport { createContext, useContext, useMemo, type ComponentType, type ReactNode } from \"react\";\n\nexport interface WindowSelectionSurfaceProps {\n readonly children: ReactNode;\n}\n\nexport interface WindowConfig {\n /**\n * Visible elements matching this selector own the Escape key: a window with `closeOnEscape`\n * yields to them (menus, popovers, listboxes and open dialogs always do). Default\n * `[data-overlay-escape-yield]` — mark any host surface that must take Escape first.\n */\n readonly escapeYieldSelector: string;\n /** Top offset of a phone fullscreen window (below the host's header). Default `var(--header-height, 0px)`. */\n readonly mobileTopOffset: string;\n /**\n * Wraps every window body (desktop, popped out, and all three phone presentations). Default:\n * passthrough. AI Matrx injects its selection toolbar + right-click menu here.\n */\n readonly selectionSurface: ComponentType<WindowSelectionSurfaceProps>;\n}\n\nfunction PassthroughSurface({ children }: WindowSelectionSurfaceProps) {\n return <>{children}</>;\n}\n\nexport const DEFAULT_ESCAPE_YIELD_SELECTOR = \"[data-overlay-escape-yield]\";\nexport const DEFAULT_MOBILE_TOP_OFFSET = \"var(--header-height, 0px)\";\n\nexport const DEFAULT_WINDOW_CONFIG: WindowConfig = {\n escapeYieldSelector: DEFAULT_ESCAPE_YIELD_SELECTOR,\n mobileTopOffset: DEFAULT_MOBILE_TOP_OFFSET,\n selectionSurface: PassthroughSurface,\n};\n\nconst WindowConfigContext = createContext<WindowConfig>(DEFAULT_WINDOW_CONFIG);\nWindowConfigContext.displayName = \"WindowConfigContext\";\n\nexport interface WindowsProviderProps extends Partial<WindowConfig> {\n readonly children?: ReactNode;\n}\n\nexport function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }: WindowsProviderProps) {\n const value = useMemo<WindowConfig>(\n () => ({\n escapeYieldSelector: escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,\n mobileTopOffset: mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,\n selectionSurface: selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface,\n }),\n [escapeYieldSelector, mobileTopOffset, selectionSurface],\n );\n return <WindowConfigContext.Provider value={value}>{children}</WindowConfigContext.Provider>;\n}\n\nexport function useWindowConfig(): WindowConfig {\n return useContext(WindowConfigContext);\n}\n\n/** The configured selection surface, as a component. */\nexport function WindowSelectionSurface({ children }: WindowSelectionSurfaceProps) {\n const Surface = useWindowConfig().selectionSurface;\n return <Surface>{children}</Surface>;\n}\n","\"use client\";\n\n/**\n * WindowTraySync\n *\n * Mounts ONCE in the root layout. Does nothing — no listener, no dispatch — until a window\n * exists (one boolean selector), then attaches a single debounced resize listener that:\n * 1. Recomputes the positions of all minimized windows (tray slots can change\n * when chips-per-row shifts on viewport resize).\n * 2. Clamps every docked windowed window into the new viewport so windows\n * positioned for a larger screen stay reachable after a downsize.\n *\n * Zero re-renders — fires and forgets.\n *\n * Debounce: 500ms — a continuous window-drag generates exactly one dispatch\n * when the user stops moving. Short bursts (e.g. snapping) are ignored.\n *\n * Usage (in both authenticated and SSR root layouts):\n * <WindowTraySync />\n */\n\nimport { useEffect } from \"react\";\nimport { useSummonDispatch, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport {\n recomputeTrayPositions,\n clampAllWindowRects,\n} from \"@ai-matrx/summon\";\nimport { safeViewportDims, type WindowManagerState } from \"@ai-matrx/summon\";\n\nconst DEBOUNCE_MS = 500;\n\n/** Any window registered or waiting to restore: until then there is no geometry to keep in view. */\nexport function selectAnyWindowGeometry(state: { windowManager?: WindowManagerState }): boolean {\n const wm = state.windowManager;\n if (!wm) return false;\n for (const _ in wm.windows) return true;\n for (const _ in wm.pendingRestores) return true;\n return false;\n}\n\nexport function WindowTraySync() {\n const dispatch = useSummonDispatch();\n const active = useSummonSelector(selectAnyWindowGeometry);\n\n useEffect(() => {\n if (!active) return;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const syncWindowGeometry = () => {\n // A resize that measures 0×0 is a hidden/undisplayed tab or a\n // display:none ancestor, never a real screen. Bail WITHOUT dispatching:\n // reacting to it would rewrite every window rect and every tray slot\n // against invented dimensions, and the next real measurement would\n // then have to undo it. No measurement, no write.\n const { vw, vh, degenerate } = safeViewportDims();\n if (degenerate) return;\n dispatch(\n recomputeTrayPositions({ viewportWidth: vw, viewportHeight: vh }),\n );\n dispatch(clampAllWindowRects({ viewportWidth: vw, viewportHeight: vh }));\n };\n\n // This island may load after the viewport already changed. Synchronising\n // once on mount closes that event-listener gap for persisted windows.\n syncWindowGeometry();\n\n const handleResize = () => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n syncWindowGeometry();\n }, DEBOUNCE_MS);\n };\n\n window.addEventListener(\"resize\", handleResize, { passive: true });\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n if (timer !== null) clearTimeout(timer);\n };\n }, [dispatch, active]);\n\n return null;\n}\n\nexport default WindowTraySync;\n","\"use client\";\n\n/**\n * PopoutContext — exposes the popout `Document` / `Window` / portal target\n * to descendants of a popped-out subtree.\n *\n * The default value (`popoutDocument: null`, etc.) means \"I'm rendered in\n * the parent document.\" Descendants that need to behave differently inside\n * a popout (e.g. Radix portals retargeting their content) call\n * `usePopout()` and branch on the values.\n *\n * **Architectural note:** because `createPortal` preserves the parent\n * React tree, every ancestor `Provider` cascades into the popout — Redux,\n * Theme, Tooltip, Router, all work without re-wrapping. This context is\n * for the *opposite* direction: telling popped-out content \"you're inside\n * a popout window, here's the document/window/container you should use\n * instead of the global `document`/`window`.\"\n */\nimport { createContext, useContext } from \"react\";\n\nexport type PopoutMode = \"pip\" | \"popup\";\n\nexport interface PopoutContextValue {\n /**\n * The popout `Document`. `null` when this subtree is rendered in the\n * parent (the default).\n */\n popoutDocument: Document | null;\n /**\n * The popout `Window`. `null` when this subtree is rendered in the parent.\n * Use for `addEventListener`, `matchMedia`, `innerWidth`, etc., when the\n * popout-scoped answer is what you need.\n */\n popoutWindow: Window | null;\n /**\n * Element to use as the Radix `Portal.container` prop so floating UI\n * (tooltips, popovers, dropdowns) renders inside the popout instead of\n * the parent `document.body`. Returns `null` outside a popout —\n * `usePopoutContainer` coerces this to `undefined` so Radix falls back\n * to its default container automatically.\n */\n popoutContainer: HTMLElement | null;\n /**\n * Which popout mechanism is in use. `null` outside a popout.\n * Currently only used for diagnostics; downstream components may branch\n * on it later (e.g. show different chrome for `popup` vs `pip`).\n */\n mode: PopoutMode | null;\n}\n\nconst DEFAULT_VALUE: PopoutContextValue = {\n popoutDocument: null,\n popoutWindow: null,\n popoutContainer: null,\n mode: null,\n};\n\nexport const PopoutContext =\n createContext<PopoutContextValue>(DEFAULT_VALUE);\n\n/** Read the current popout context. Always defined — never throws. */\nexport function usePopout(): PopoutContextValue {\n return useContext(PopoutContext);\n}\n","\"use client\";\n\n/**\n * usePopoutContainer — return the element Radix portals (Tooltip, Popover,\n * Dropdown, Dialog, Select, AlertDialog, HoverCard) should mount into.\n *\n * **The problem this solves:** by default every Radix `Portal` mounts to\n * `document.body`. When a tooltip-trigger lives inside a popped-out window\n * but the popover content portals to the *parent* document, the tooltip\n * appears in the wrong window — typically invisible to the user.\n *\n * **The fix:** wrappers around Radix primitives call this hook and pass\n * the result as `<Primitive.Portal container={...} />`. Inside a popout,\n * the result is the popout's `<body>`. Outside a popout, the result is\n * `undefined`, which Radix interprets as \"use the default\" — meaning no\n * behavior change for any of the thousands of existing usages.\n *\n * **Why `undefined` instead of `null`?** Radix's `container` prop accepts\n * `HTMLElement | null | undefined`. Passing `null` actively tries to\n * portal into a `null` target and crashes. Passing `undefined` falls\n * through to the default (`document.body`). The `?? undefined` coerces\n * the `null` we get from `PopoutContext` outside a popout into the\n * Radix-friendly default-fallthrough value.\n */\nimport { usePopout } from \"./PopoutContext\";\n\nexport function usePopoutContainer(): HTMLElement | undefined {\n return usePopout().popoutContainer ?? undefined;\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { ackOverlaySurfaceRender, clearOverlaySurfaceRender } from \"@ai-matrx/summon\";\n\n/**\n * Acknowledge a visible presentation that intentionally replaces WindowPanel.\n *\n * Registered singleton window overlays normally prove they are on screen by mounting a\n * WindowPanel and registering geometry. Mobile drawers, sheets, and full-screen\n * viewers have no window-manager geometry, so their composition root must use\n * this hook while that alternate surface is active.\n */\nexport function useOverlaySurfaceRenderAck(\n overlayId: string,\n active: boolean,\n): void {\n useEffect(() => {\n if (!active) return undefined;\n ackOverlaySurfaceRender(overlayId);\n return () => clearOverlaySurfaceRender(overlayId);\n }, [active, overlayId]);\n}\n","/**\n * Tray previews — overlay id → a semantic minimized preview and/or a custom snapshot capture.\n *\n * The host registers its previews once (AI Matrx keeps its own preview components in the app):\n *\n * registerTrayPreview(\"notesWindow\", NotesTrayPreview);\n * registerTrayPreview(\"canvasWindow\", { captureTraySnapshot: captureCanvas });\n *\n * A window with a preview component skips snapshot capture entirely (cheaper, higher fidelity).\n * A window with neither gets the default capture (`defaultTraySnapshotCapture`, html-to-image when\n * the host installed it). State lives in a global slot (kit globalSlot, D9).\n */\nimport type { ComponentType } from \"react\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\nexport interface TrayPreviewContext {\n /** The window's allowlisted semantic data (open data merged with what `onCollectData` returned). */\n readonly data: Readonly<Record<string, unknown>>;\n /** Stable overlay id (the registry key). */\n readonly overlayId: string;\n /** Instance id — `\"default\"` for singleton windows. */\n readonly instanceId: string;\n /** The display title. */\n readonly title: string;\n}\n\nexport type TraySnapshotCapture = (bodyEl: HTMLElement) => Promise<Blob | null>;\n\nexport interface TrayPreviewEntry {\n readonly Preview?: ComponentType<TrayPreviewContext>;\n readonly captureTraySnapshot?: TraySnapshotCapture;\n}\n\nconst EMPTY: TrayPreviewEntry = {};\n\nconst registry = (): Map<string, TrayPreviewEntry> =>\n globalSlot<Map<string, TrayPreviewEntry>>(\"ai-matrx.summon.window.trayPreviews\", () => new Map());\n\n/**\n * Register the minimized preview for an overlay (or any window id). Pass a component, or an entry\n * with `Preview` and/or `captureTraySnapshot`. Re-registering replaces. Returns an unregister.\n */\nexport function registerTrayPreview(\n overlayId: string,\n preview: ComponentType<TrayPreviewContext> | TrayPreviewEntry,\n): () => void {\n const entry: TrayPreviewEntry = typeof preview === \"function\" ? { Preview: preview } : preview;\n registry().set(overlayId, entry);\n return () => {\n if (registry().get(overlayId) === entry) registry().delete(overlayId);\n };\n}\n\nexport function getTrayPreviewEntry(overlayId: string): TrayPreviewEntry {\n return registry().get(overlayId) ?? EMPTY;\n}\n\n/** Test-only. */\nexport function resetTrayPreviews(): void {\n registry().clear();\n}\n"],"mappings":"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","names":["import_react","import_summon","import_react","import_react","import_summon"]}
|
|
1
|
+
{"version":3,"sources":["../src/window-host.ts","../src/window/config.tsx","../src/window/WindowTraySync.tsx","../src/window/popout/PopoutContext.tsx","../src/window/popout/usePopoutContainer.ts","../src/window/useOverlaySurfaceRenderAck.ts","../src/window/tray/trayPreviewRegistry.ts"],"sourcesContent":["// @ai-matrx/summon/window-host — the light half of the window section, safe to mount at the app\n// root: window configuration (WindowsProvider), the viewport/tray sync, and tray-preview\n// registration. Importing it never loads WindowPanel; the window section reads this section's\n// context through the package name, so both share one context.\nexport {\n WindowsProvider,\n useWindowConfig,\n WindowSelectionSurface,\n DEFAULT_WINDOW_CONFIG,\n DEFAULT_ESCAPE_YIELD_SELECTOR,\n DEFAULT_MOBILE_TOP_OFFSET,\n type WindowConfig,\n type WindowsProviderProps,\n type WindowSelectionSurfaceProps,\n} from \"./window/config\";\nexport { WindowTraySync } from \"./window/WindowTraySync\";\n// Hooks a render module calls inside a window, light enough for the boot graph. The popout context\n// lives HERE (one identity): the window section imports it through the package name.\nexport { usePopout, PopoutContext, type PopoutContextValue, type PopoutMode } from \"./window/popout/PopoutContext\";\nexport { usePopoutContainer } from \"./window/popout/usePopoutContainer\";\nexport { useOverlaySurfaceRenderAck } from \"./window/useOverlaySurfaceRenderAck\";\nexport {\n registerTrayPreview,\n getTrayPreviewEntry,\n resetTrayPreviews,\n type TrayPreviewContext,\n type TrayPreviewEntry,\n type TraySnapshotCapture,\n} from \"./window/tray/trayPreviewRegistry\";\n","\"use client\";\n\n/**\n * WindowsProvider — the window section's host options, read by every WindowPanel through context.\n * Everything has a working default, so no provider is needed at all; a host passes only what it\n * has (AI Matrx: its right-click selection menu and its header offset).\n */\nimport { createContext, useContext, useMemo, type ComponentType, type ReactNode } from \"react\";\nimport { getSummonHostConfig } from \"@ai-matrx/summon/root\";\n\nexport interface WindowSelectionSurfaceProps {\n readonly children: ReactNode;\n}\n\nexport interface WindowConfig {\n /**\n * Visible elements matching this selector own the Escape key: a window with `closeOnEscape`\n * yields to them (menus, popovers, listboxes and open dialogs always do). Default\n * `[data-overlay-escape-yield]` — mark any host surface that must take Escape first.\n */\n readonly escapeYieldSelector: string;\n /** Top offset of a phone fullscreen window (below the host's header). Default `var(--header-height, 0px)`. */\n readonly mobileTopOffset: string;\n /**\n * Wraps every window body (desktop, popped out, and all three phone presentations). Default:\n * passthrough. AI Matrx injects its selection toolbar + right-click menu here.\n */\n readonly selectionSurface: ComponentType<WindowSelectionSurfaceProps>;\n}\n\nfunction PassthroughSurface({ children }: WindowSelectionSurfaceProps) {\n return <>{children}</>;\n}\n\nexport const DEFAULT_ESCAPE_YIELD_SELECTOR = \"[data-overlay-escape-yield]\";\nexport const DEFAULT_MOBILE_TOP_OFFSET = \"var(--header-height, 0px)\";\n\nexport const DEFAULT_WINDOW_CONFIG: WindowConfig = {\n escapeYieldSelector: DEFAULT_ESCAPE_YIELD_SELECTOR,\n mobileTopOffset: DEFAULT_MOBILE_TOP_OFFSET,\n selectionSurface: PassthroughSurface,\n};\n\nconst WindowConfigContext = createContext<WindowConfig | null>(null);\nWindowConfigContext.displayName = \"WindowConfigContext\";\n\nexport interface WindowsProviderProps extends Partial<WindowConfig> {\n readonly children?: ReactNode;\n}\n\nexport function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }: WindowsProviderProps) {\n const value = useMemo<WindowConfig>(\n () => ({\n escapeYieldSelector: escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,\n mobileTopOffset: mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,\n selectionSurface: selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface,\n }),\n [escapeYieldSelector, mobileTopOffset, selectionSurface],\n );\n return <WindowConfigContext.Provider value={value}>{children}</WindowConfigContext.Provider>;\n}\n\n/** The nearest WindowsProvider's options, else the ones configured at `<SummonRoot windows>`, else defaults. */\nexport function useWindowConfig(): WindowConfig {\n const fromContext = useContext(WindowConfigContext);\n if (fromContext) return fromContext;\n const host = getSummonHostConfig().windows;\n if (!host) return DEFAULT_WINDOW_CONFIG;\n return {\n escapeYieldSelector: host.escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,\n mobileTopOffset: host.mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,\n selectionSurface: host.selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface,\n };\n}\n\n/** The configured selection surface, as a component. */\nexport function WindowSelectionSurface({ children }: WindowSelectionSurfaceProps) {\n const Surface = useWindowConfig().selectionSurface;\n return <Surface>{children}</Surface>;\n}\n","\"use client\";\n\n/**\n * WindowTraySync\n *\n * Mounts ONCE in the root layout. Does nothing — no listener, no dispatch — until a window\n * exists (one boolean selector), then attaches a single debounced resize listener that:\n * 1. Recomputes the positions of all minimized windows (tray slots can change\n * when chips-per-row shifts on viewport resize).\n * 2. Clamps every docked windowed window into the new viewport so windows\n * positioned for a larger screen stay reachable after a downsize.\n *\n * Zero re-renders — fires and forgets.\n *\n * Debounce: 500ms — a continuous window-drag generates exactly one dispatch\n * when the user stops moving. Short bursts (e.g. snapping) are ignored.\n *\n * Usage (in both authenticated and SSR root layouts):\n * <WindowTraySync />\n */\n\nimport { useEffect } from \"react\";\nimport { useSummonDispatch, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport {\n recomputeTrayPositions,\n clampAllWindowRects,\n} from \"@ai-matrx/summon\";\nimport { safeViewportDims, type WindowManagerState } from \"@ai-matrx/summon\";\n\nconst DEBOUNCE_MS = 500;\n\n/** Any window registered or waiting to restore: until then there is no geometry to keep in view. */\nexport function selectAnyWindowGeometry(state: { windowManager?: WindowManagerState }): boolean {\n const wm = state.windowManager;\n if (!wm) return false;\n for (const _ in wm.windows) return true;\n for (const _ in wm.pendingRestores) return true;\n return false;\n}\n\nexport function WindowTraySync() {\n const dispatch = useSummonDispatch();\n const active = useSummonSelector(selectAnyWindowGeometry);\n\n useEffect(() => {\n if (!active) return;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const syncWindowGeometry = () => {\n // A resize that measures 0×0 is a hidden/undisplayed tab or a\n // display:none ancestor, never a real screen. Bail WITHOUT dispatching:\n // reacting to it would rewrite every window rect and every tray slot\n // against invented dimensions, and the next real measurement would\n // then have to undo it. No measurement, no write.\n const { vw, vh, degenerate } = safeViewportDims();\n if (degenerate) return;\n dispatch(\n recomputeTrayPositions({ viewportWidth: vw, viewportHeight: vh }),\n );\n dispatch(clampAllWindowRects({ viewportWidth: vw, viewportHeight: vh }));\n };\n\n // This island may load after the viewport already changed. Synchronising\n // once on mount closes that event-listener gap for persisted windows.\n syncWindowGeometry();\n\n const handleResize = () => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n syncWindowGeometry();\n }, DEBOUNCE_MS);\n };\n\n window.addEventListener(\"resize\", handleResize, { passive: true });\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n if (timer !== null) clearTimeout(timer);\n };\n }, [dispatch, active]);\n\n return null;\n}\n\nexport default WindowTraySync;\n","\"use client\";\n\n/**\n * PopoutContext — exposes the popout `Document` / `Window` / portal target\n * to descendants of a popped-out subtree.\n *\n * The default value (`popoutDocument: null`, etc.) means \"I'm rendered in\n * the parent document.\" Descendants that need to behave differently inside\n * a popout (e.g. Radix portals retargeting their content) call\n * `usePopout()` and branch on the values.\n *\n * **Architectural note:** because `createPortal` preserves the parent\n * React tree, every ancestor `Provider` cascades into the popout — Redux,\n * Theme, Tooltip, Router, all work without re-wrapping. This context is\n * for the *opposite* direction: telling popped-out content \"you're inside\n * a popout window, here's the document/window/container you should use\n * instead of the global `document`/`window`.\"\n */\nimport { createContext, useContext } from \"react\";\n\nexport type PopoutMode = \"pip\" | \"popup\";\n\nexport interface PopoutContextValue {\n /**\n * The popout `Document`. `null` when this subtree is rendered in the\n * parent (the default).\n */\n popoutDocument: Document | null;\n /**\n * The popout `Window`. `null` when this subtree is rendered in the parent.\n * Use for `addEventListener`, `matchMedia`, `innerWidth`, etc., when the\n * popout-scoped answer is what you need.\n */\n popoutWindow: Window | null;\n /**\n * Element to use as the Radix `Portal.container` prop so floating UI\n * (tooltips, popovers, dropdowns) renders inside the popout instead of\n * the parent `document.body`. Returns `null` outside a popout —\n * `usePopoutContainer` coerces this to `undefined` so Radix falls back\n * to its default container automatically.\n */\n popoutContainer: HTMLElement | null;\n /**\n * Which popout mechanism is in use. `null` outside a popout.\n * Currently only used for diagnostics; downstream components may branch\n * on it later (e.g. show different chrome for `popup` vs `pip`).\n */\n mode: PopoutMode | null;\n}\n\nconst DEFAULT_VALUE: PopoutContextValue = {\n popoutDocument: null,\n popoutWindow: null,\n popoutContainer: null,\n mode: null,\n};\n\nexport const PopoutContext =\n createContext<PopoutContextValue>(DEFAULT_VALUE);\n\n/** Read the current popout context. Always defined — never throws. */\nexport function usePopout(): PopoutContextValue {\n return useContext(PopoutContext);\n}\n","\"use client\";\n\n/**\n * usePopoutContainer — return the element Radix portals (Tooltip, Popover,\n * Dropdown, Dialog, Select, AlertDialog, HoverCard) should mount into.\n *\n * **The problem this solves:** by default every Radix `Portal` mounts to\n * `document.body`. When a tooltip-trigger lives inside a popped-out window\n * but the popover content portals to the *parent* document, the tooltip\n * appears in the wrong window — typically invisible to the user.\n *\n * **The fix:** wrappers around Radix primitives call this hook and pass\n * the result as `<Primitive.Portal container={...} />`. Inside a popout,\n * the result is the popout's `<body>`. Outside a popout, the result is\n * `undefined`, which Radix interprets as \"use the default\" — meaning no\n * behavior change for any of the thousands of existing usages.\n *\n * **Why `undefined` instead of `null`?** Radix's `container` prop accepts\n * `HTMLElement | null | undefined`. Passing `null` actively tries to\n * portal into a `null` target and crashes. Passing `undefined` falls\n * through to the default (`document.body`). The `?? undefined` coerces\n * the `null` we get from `PopoutContext` outside a popout into the\n * Radix-friendly default-fallthrough value.\n */\nimport { usePopout } from \"./PopoutContext\";\n\nexport function usePopoutContainer(): HTMLElement | undefined {\n return usePopout().popoutContainer ?? undefined;\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { ackOverlaySurfaceRender, clearOverlaySurfaceRender } from \"@ai-matrx/summon\";\n\n/**\n * Acknowledge a visible presentation that intentionally replaces WindowPanel.\n *\n * Registered singleton window overlays normally prove they are on screen by mounting a\n * WindowPanel and registering geometry. Mobile drawers, sheets, and full-screen\n * viewers have no window-manager geometry, so their composition root must use\n * this hook while that alternate surface is active.\n */\nexport function useOverlaySurfaceRenderAck(\n overlayId: string,\n active: boolean,\n): void {\n useEffect(() => {\n if (!active) return undefined;\n ackOverlaySurfaceRender(overlayId);\n return () => clearOverlaySurfaceRender(overlayId);\n }, [active, overlayId]);\n}\n","/**\n * Tray previews — overlay id → a semantic minimized preview and/or a custom snapshot capture.\n *\n * The host registers its previews once (AI Matrx keeps its own preview components in the app):\n *\n * registerTrayPreview(\"notesWindow\", NotesTrayPreview);\n * registerTrayPreview(\"canvasWindow\", { captureTraySnapshot: captureCanvas });\n *\n * A window with a preview component skips snapshot capture entirely (cheaper, higher fidelity).\n * A window with neither gets the default capture (`defaultTraySnapshotCapture`, html-to-image when\n * the host installed it). State lives in a global slot (kit globalSlot, D9).\n */\nimport type { ComponentType } from \"react\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\nexport interface TrayPreviewContext {\n /** The window's allowlisted semantic data (open data merged with what `onCollectData` returned). */\n readonly data: Readonly<Record<string, unknown>>;\n /** Stable overlay id (the registry key). */\n readonly overlayId: string;\n /** Instance id — `\"default\"` for singleton windows. */\n readonly instanceId: string;\n /** The display title. */\n readonly title: string;\n}\n\nexport type TraySnapshotCapture = (bodyEl: HTMLElement) => Promise<Blob | null>;\n\nexport interface TrayPreviewEntry {\n readonly Preview?: ComponentType<TrayPreviewContext>;\n readonly captureTraySnapshot?: TraySnapshotCapture;\n}\n\nconst EMPTY: TrayPreviewEntry = {};\n\nconst registry = (): Map<string, TrayPreviewEntry> =>\n globalSlot<Map<string, TrayPreviewEntry>>(\"ai-matrx.summon.window.trayPreviews\", () => new Map());\n\n/**\n * Register the minimized preview for an overlay (or any window id). Pass a component, or an entry\n * with `Preview` and/or `captureTraySnapshot`. Re-registering replaces. Returns an unregister.\n */\nexport function registerTrayPreview(\n overlayId: string,\n preview: ComponentType<TrayPreviewContext> | TrayPreviewEntry,\n): () => void {\n const entry: TrayPreviewEntry = typeof preview === \"function\" ? { Preview: preview } : preview;\n registry().set(overlayId, entry);\n return () => {\n if (registry().get(overlayId) === entry) registry().delete(overlayId);\n };\n}\n\nexport function getTrayPreviewEntry(overlayId: string): TrayPreviewEntry {\n return registry().get(overlayId) ?? EMPTY;\n}\n\n/** Test-only. */\nexport function resetTrayPreviews(): void {\n registry().clear();\n}\n"],"mappings":"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","names":["import_react","import_summon","import_react","import_react","import_summon"]}
|
package/dist/window-host.d.cts
CHANGED
|
@@ -26,6 +26,7 @@ interface WindowsProviderProps extends Partial<WindowConfig> {
|
|
|
26
26
|
readonly children?: ReactNode;
|
|
27
27
|
}
|
|
28
28
|
declare function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }: WindowsProviderProps): react.JSX.Element;
|
|
29
|
+
/** The nearest WindowsProvider's options, else the ones configured at `<SummonRoot windows>`, else defaults. */
|
|
29
30
|
declare function useWindowConfig(): WindowConfig;
|
|
30
31
|
/** The configured selection surface, as a component. */
|
|
31
32
|
declare function WindowSelectionSurface({ children }: WindowSelectionSurfaceProps): react.JSX.Element;
|
package/dist/window-host.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ interface WindowsProviderProps extends Partial<WindowConfig> {
|
|
|
26
26
|
readonly children?: ReactNode;
|
|
27
27
|
}
|
|
28
28
|
declare function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }: WindowsProviderProps): react.JSX.Element;
|
|
29
|
+
/** The nearest WindowsProvider's options, else the ones configured at `<SummonRoot windows>`, else defaults. */
|
|
29
30
|
declare function useWindowConfig(): WindowConfig;
|
|
30
31
|
/** The configured selection surface, as a component. */
|
|
31
32
|
declare function WindowSelectionSurface({ children }: WindowSelectionSurfaceProps): react.JSX.Element;
|
package/dist/window-host.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/window/config.tsx
|
|
4
4
|
import { createContext, useContext, useMemo } from "react";
|
|
5
|
+
import { getSummonHostConfig } from "@ai-matrx/summon/root";
|
|
5
6
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
6
7
|
function PassthroughSurface({ children }) {
|
|
7
8
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
@@ -13,7 +14,7 @@ var DEFAULT_WINDOW_CONFIG = {
|
|
|
13
14
|
mobileTopOffset: DEFAULT_MOBILE_TOP_OFFSET,
|
|
14
15
|
selectionSurface: PassthroughSurface
|
|
15
16
|
};
|
|
16
|
-
var WindowConfigContext = createContext(
|
|
17
|
+
var WindowConfigContext = createContext(null);
|
|
17
18
|
WindowConfigContext.displayName = "WindowConfigContext";
|
|
18
19
|
function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }) {
|
|
19
20
|
const value = useMemo(
|
|
@@ -27,7 +28,15 @@ function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurfac
|
|
|
27
28
|
return /* @__PURE__ */ jsx(WindowConfigContext.Provider, { value, children });
|
|
28
29
|
}
|
|
29
30
|
function useWindowConfig() {
|
|
30
|
-
|
|
31
|
+
const fromContext = useContext(WindowConfigContext);
|
|
32
|
+
if (fromContext) return fromContext;
|
|
33
|
+
const host = getSummonHostConfig().windows;
|
|
34
|
+
if (!host) return DEFAULT_WINDOW_CONFIG;
|
|
35
|
+
return {
|
|
36
|
+
escapeYieldSelector: host.escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,
|
|
37
|
+
mobileTopOffset: host.mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,
|
|
38
|
+
selectionSurface: host.selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface
|
|
39
|
+
};
|
|
31
40
|
}
|
|
32
41
|
function WindowSelectionSurface({ children }) {
|
|
33
42
|
const Surface = useWindowConfig().selectionSurface;
|
package/dist/window-host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/window/config.tsx","../src/window/WindowTraySync.tsx","../src/window/popout/PopoutContext.tsx","../src/window/popout/usePopoutContainer.ts","../src/window/useOverlaySurfaceRenderAck.ts","../src/window/tray/trayPreviewRegistry.ts"],"sourcesContent":["\"use client\";\n\n/**\n * WindowsProvider — the window section's host options, read by every WindowPanel through context.\n * Everything has a working default, so no provider is needed at all; a host passes only what it\n * has (AI Matrx: its right-click selection menu and its header offset).\n */\nimport { createContext, useContext, useMemo, type ComponentType, type ReactNode } from \"react\";\n\nexport interface WindowSelectionSurfaceProps {\n readonly children: ReactNode;\n}\n\nexport interface WindowConfig {\n /**\n * Visible elements matching this selector own the Escape key: a window with `closeOnEscape`\n * yields to them (menus, popovers, listboxes and open dialogs always do). Default\n * `[data-overlay-escape-yield]` — mark any host surface that must take Escape first.\n */\n readonly escapeYieldSelector: string;\n /** Top offset of a phone fullscreen window (below the host's header). Default `var(--header-height, 0px)`. */\n readonly mobileTopOffset: string;\n /**\n * Wraps every window body (desktop, popped out, and all three phone presentations). Default:\n * passthrough. AI Matrx injects its selection toolbar + right-click menu here.\n */\n readonly selectionSurface: ComponentType<WindowSelectionSurfaceProps>;\n}\n\nfunction PassthroughSurface({ children }: WindowSelectionSurfaceProps) {\n return <>{children}</>;\n}\n\nexport const DEFAULT_ESCAPE_YIELD_SELECTOR = \"[data-overlay-escape-yield]\";\nexport const DEFAULT_MOBILE_TOP_OFFSET = \"var(--header-height, 0px)\";\n\nexport const DEFAULT_WINDOW_CONFIG: WindowConfig = {\n escapeYieldSelector: DEFAULT_ESCAPE_YIELD_SELECTOR,\n mobileTopOffset: DEFAULT_MOBILE_TOP_OFFSET,\n selectionSurface: PassthroughSurface,\n};\n\nconst WindowConfigContext = createContext<WindowConfig>(DEFAULT_WINDOW_CONFIG);\nWindowConfigContext.displayName = \"WindowConfigContext\";\n\nexport interface WindowsProviderProps extends Partial<WindowConfig> {\n readonly children?: ReactNode;\n}\n\nexport function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }: WindowsProviderProps) {\n const value = useMemo<WindowConfig>(\n () => ({\n escapeYieldSelector: escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,\n mobileTopOffset: mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,\n selectionSurface: selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface,\n }),\n [escapeYieldSelector, mobileTopOffset, selectionSurface],\n );\n return <WindowConfigContext.Provider value={value}>{children}</WindowConfigContext.Provider>;\n}\n\nexport function useWindowConfig(): WindowConfig {\n return useContext(WindowConfigContext);\n}\n\n/** The configured selection surface, as a component. */\nexport function WindowSelectionSurface({ children }: WindowSelectionSurfaceProps) {\n const Surface = useWindowConfig().selectionSurface;\n return <Surface>{children}</Surface>;\n}\n","\"use client\";\n\n/**\n * WindowTraySync\n *\n * Mounts ONCE in the root layout. Does nothing — no listener, no dispatch — until a window\n * exists (one boolean selector), then attaches a single debounced resize listener that:\n * 1. Recomputes the positions of all minimized windows (tray slots can change\n * when chips-per-row shifts on viewport resize).\n * 2. Clamps every docked windowed window into the new viewport so windows\n * positioned for a larger screen stay reachable after a downsize.\n *\n * Zero re-renders — fires and forgets.\n *\n * Debounce: 500ms — a continuous window-drag generates exactly one dispatch\n * when the user stops moving. Short bursts (e.g. snapping) are ignored.\n *\n * Usage (in both authenticated and SSR root layouts):\n * <WindowTraySync />\n */\n\nimport { useEffect } from \"react\";\nimport { useSummonDispatch, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport {\n recomputeTrayPositions,\n clampAllWindowRects,\n} from \"@ai-matrx/summon\";\nimport { safeViewportDims, type WindowManagerState } from \"@ai-matrx/summon\";\n\nconst DEBOUNCE_MS = 500;\n\n/** Any window registered or waiting to restore: until then there is no geometry to keep in view. */\nexport function selectAnyWindowGeometry(state: { windowManager?: WindowManagerState }): boolean {\n const wm = state.windowManager;\n if (!wm) return false;\n for (const _ in wm.windows) return true;\n for (const _ in wm.pendingRestores) return true;\n return false;\n}\n\nexport function WindowTraySync() {\n const dispatch = useSummonDispatch();\n const active = useSummonSelector(selectAnyWindowGeometry);\n\n useEffect(() => {\n if (!active) return;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const syncWindowGeometry = () => {\n // A resize that measures 0×0 is a hidden/undisplayed tab or a\n // display:none ancestor, never a real screen. Bail WITHOUT dispatching:\n // reacting to it would rewrite every window rect and every tray slot\n // against invented dimensions, and the next real measurement would\n // then have to undo it. No measurement, no write.\n const { vw, vh, degenerate } = safeViewportDims();\n if (degenerate) return;\n dispatch(\n recomputeTrayPositions({ viewportWidth: vw, viewportHeight: vh }),\n );\n dispatch(clampAllWindowRects({ viewportWidth: vw, viewportHeight: vh }));\n };\n\n // This island may load after the viewport already changed. Synchronising\n // once on mount closes that event-listener gap for persisted windows.\n syncWindowGeometry();\n\n const handleResize = () => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n syncWindowGeometry();\n }, DEBOUNCE_MS);\n };\n\n window.addEventListener(\"resize\", handleResize, { passive: true });\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n if (timer !== null) clearTimeout(timer);\n };\n }, [dispatch, active]);\n\n return null;\n}\n\nexport default WindowTraySync;\n","\"use client\";\n\n/**\n * PopoutContext — exposes the popout `Document` / `Window` / portal target\n * to descendants of a popped-out subtree.\n *\n * The default value (`popoutDocument: null`, etc.) means \"I'm rendered in\n * the parent document.\" Descendants that need to behave differently inside\n * a popout (e.g. Radix portals retargeting their content) call\n * `usePopout()` and branch on the values.\n *\n * **Architectural note:** because `createPortal` preserves the parent\n * React tree, every ancestor `Provider` cascades into the popout — Redux,\n * Theme, Tooltip, Router, all work without re-wrapping. This context is\n * for the *opposite* direction: telling popped-out content \"you're inside\n * a popout window, here's the document/window/container you should use\n * instead of the global `document`/`window`.\"\n */\nimport { createContext, useContext } from \"react\";\n\nexport type PopoutMode = \"pip\" | \"popup\";\n\nexport interface PopoutContextValue {\n /**\n * The popout `Document`. `null` when this subtree is rendered in the\n * parent (the default).\n */\n popoutDocument: Document | null;\n /**\n * The popout `Window`. `null` when this subtree is rendered in the parent.\n * Use for `addEventListener`, `matchMedia`, `innerWidth`, etc., when the\n * popout-scoped answer is what you need.\n */\n popoutWindow: Window | null;\n /**\n * Element to use as the Radix `Portal.container` prop so floating UI\n * (tooltips, popovers, dropdowns) renders inside the popout instead of\n * the parent `document.body`. Returns `null` outside a popout —\n * `usePopoutContainer` coerces this to `undefined` so Radix falls back\n * to its default container automatically.\n */\n popoutContainer: HTMLElement | null;\n /**\n * Which popout mechanism is in use. `null` outside a popout.\n * Currently only used for diagnostics; downstream components may branch\n * on it later (e.g. show different chrome for `popup` vs `pip`).\n */\n mode: PopoutMode | null;\n}\n\nconst DEFAULT_VALUE: PopoutContextValue = {\n popoutDocument: null,\n popoutWindow: null,\n popoutContainer: null,\n mode: null,\n};\n\nexport const PopoutContext =\n createContext<PopoutContextValue>(DEFAULT_VALUE);\n\n/** Read the current popout context. Always defined — never throws. */\nexport function usePopout(): PopoutContextValue {\n return useContext(PopoutContext);\n}\n","\"use client\";\n\n/**\n * usePopoutContainer — return the element Radix portals (Tooltip, Popover,\n * Dropdown, Dialog, Select, AlertDialog, HoverCard) should mount into.\n *\n * **The problem this solves:** by default every Radix `Portal` mounts to\n * `document.body`. When a tooltip-trigger lives inside a popped-out window\n * but the popover content portals to the *parent* document, the tooltip\n * appears in the wrong window — typically invisible to the user.\n *\n * **The fix:** wrappers around Radix primitives call this hook and pass\n * the result as `<Primitive.Portal container={...} />`. Inside a popout,\n * the result is the popout's `<body>`. Outside a popout, the result is\n * `undefined`, which Radix interprets as \"use the default\" — meaning no\n * behavior change for any of the thousands of existing usages.\n *\n * **Why `undefined` instead of `null`?** Radix's `container` prop accepts\n * `HTMLElement | null | undefined`. Passing `null` actively tries to\n * portal into a `null` target and crashes. Passing `undefined` falls\n * through to the default (`document.body`). The `?? undefined` coerces\n * the `null` we get from `PopoutContext` outside a popout into the\n * Radix-friendly default-fallthrough value.\n */\nimport { usePopout } from \"./PopoutContext\";\n\nexport function usePopoutContainer(): HTMLElement | undefined {\n return usePopout().popoutContainer ?? undefined;\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { ackOverlaySurfaceRender, clearOverlaySurfaceRender } from \"@ai-matrx/summon\";\n\n/**\n * Acknowledge a visible presentation that intentionally replaces WindowPanel.\n *\n * Registered singleton window overlays normally prove they are on screen by mounting a\n * WindowPanel and registering geometry. Mobile drawers, sheets, and full-screen\n * viewers have no window-manager geometry, so their composition root must use\n * this hook while that alternate surface is active.\n */\nexport function useOverlaySurfaceRenderAck(\n overlayId: string,\n active: boolean,\n): void {\n useEffect(() => {\n if (!active) return undefined;\n ackOverlaySurfaceRender(overlayId);\n return () => clearOverlaySurfaceRender(overlayId);\n }, [active, overlayId]);\n}\n","/**\n * Tray previews — overlay id → a semantic minimized preview and/or a custom snapshot capture.\n *\n * The host registers its previews once (AI Matrx keeps its own preview components in the app):\n *\n * registerTrayPreview(\"notesWindow\", NotesTrayPreview);\n * registerTrayPreview(\"canvasWindow\", { captureTraySnapshot: captureCanvas });\n *\n * A window with a preview component skips snapshot capture entirely (cheaper, higher fidelity).\n * A window with neither gets the default capture (`defaultTraySnapshotCapture`, html-to-image when\n * the host installed it). State lives in a global slot (kit globalSlot, D9).\n */\nimport type { ComponentType } from \"react\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\nexport interface TrayPreviewContext {\n /** The window's allowlisted semantic data (open data merged with what `onCollectData` returned). */\n readonly data: Readonly<Record<string, unknown>>;\n /** Stable overlay id (the registry key). */\n readonly overlayId: string;\n /** Instance id — `\"default\"` for singleton windows. */\n readonly instanceId: string;\n /** The display title. */\n readonly title: string;\n}\n\nexport type TraySnapshotCapture = (bodyEl: HTMLElement) => Promise<Blob | null>;\n\nexport interface TrayPreviewEntry {\n readonly Preview?: ComponentType<TrayPreviewContext>;\n readonly captureTraySnapshot?: TraySnapshotCapture;\n}\n\nconst EMPTY: TrayPreviewEntry = {};\n\nconst registry = (): Map<string, TrayPreviewEntry> =>\n globalSlot<Map<string, TrayPreviewEntry>>(\"ai-matrx.summon.window.trayPreviews\", () => new Map());\n\n/**\n * Register the minimized preview for an overlay (or any window id). Pass a component, or an entry\n * with `Preview` and/or `captureTraySnapshot`. Re-registering replaces. Returns an unregister.\n */\nexport function registerTrayPreview(\n overlayId: string,\n preview: ComponentType<TrayPreviewContext> | TrayPreviewEntry,\n): () => void {\n const entry: TrayPreviewEntry = typeof preview === \"function\" ? { Preview: preview } : preview;\n registry().set(overlayId, entry);\n return () => {\n if (registry().get(overlayId) === entry) registry().delete(overlayId);\n };\n}\n\nexport function getTrayPreviewEntry(overlayId: string): TrayPreviewEntry {\n return registry().get(overlayId) ?? EMPTY;\n}\n\n/** Test-only. */\nexport function resetTrayPreviews(): void {\n registry().clear();\n}\n"],"mappings":"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","names":["createContext","useContext","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/window/config.tsx","../src/window/WindowTraySync.tsx","../src/window/popout/PopoutContext.tsx","../src/window/popout/usePopoutContainer.ts","../src/window/useOverlaySurfaceRenderAck.ts","../src/window/tray/trayPreviewRegistry.ts"],"sourcesContent":["\"use client\";\n\n/**\n * WindowsProvider — the window section's host options, read by every WindowPanel through context.\n * Everything has a working default, so no provider is needed at all; a host passes only what it\n * has (AI Matrx: its right-click selection menu and its header offset).\n */\nimport { createContext, useContext, useMemo, type ComponentType, type ReactNode } from \"react\";\nimport { getSummonHostConfig } from \"@ai-matrx/summon/root\";\n\nexport interface WindowSelectionSurfaceProps {\n readonly children: ReactNode;\n}\n\nexport interface WindowConfig {\n /**\n * Visible elements matching this selector own the Escape key: a window with `closeOnEscape`\n * yields to them (menus, popovers, listboxes and open dialogs always do). Default\n * `[data-overlay-escape-yield]` — mark any host surface that must take Escape first.\n */\n readonly escapeYieldSelector: string;\n /** Top offset of a phone fullscreen window (below the host's header). Default `var(--header-height, 0px)`. */\n readonly mobileTopOffset: string;\n /**\n * Wraps every window body (desktop, popped out, and all three phone presentations). Default:\n * passthrough. AI Matrx injects its selection toolbar + right-click menu here.\n */\n readonly selectionSurface: ComponentType<WindowSelectionSurfaceProps>;\n}\n\nfunction PassthroughSurface({ children }: WindowSelectionSurfaceProps) {\n return <>{children}</>;\n}\n\nexport const DEFAULT_ESCAPE_YIELD_SELECTOR = \"[data-overlay-escape-yield]\";\nexport const DEFAULT_MOBILE_TOP_OFFSET = \"var(--header-height, 0px)\";\n\nexport const DEFAULT_WINDOW_CONFIG: WindowConfig = {\n escapeYieldSelector: DEFAULT_ESCAPE_YIELD_SELECTOR,\n mobileTopOffset: DEFAULT_MOBILE_TOP_OFFSET,\n selectionSurface: PassthroughSurface,\n};\n\nconst WindowConfigContext = createContext<WindowConfig | null>(null);\nWindowConfigContext.displayName = \"WindowConfigContext\";\n\nexport interface WindowsProviderProps extends Partial<WindowConfig> {\n readonly children?: ReactNode;\n}\n\nexport function WindowsProvider({ escapeYieldSelector, mobileTopOffset, selectionSurface, children }: WindowsProviderProps) {\n const value = useMemo<WindowConfig>(\n () => ({\n escapeYieldSelector: escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,\n mobileTopOffset: mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,\n selectionSurface: selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface,\n }),\n [escapeYieldSelector, mobileTopOffset, selectionSurface],\n );\n return <WindowConfigContext.Provider value={value}>{children}</WindowConfigContext.Provider>;\n}\n\n/** The nearest WindowsProvider's options, else the ones configured at `<SummonRoot windows>`, else defaults. */\nexport function useWindowConfig(): WindowConfig {\n const fromContext = useContext(WindowConfigContext);\n if (fromContext) return fromContext;\n const host = getSummonHostConfig().windows;\n if (!host) return DEFAULT_WINDOW_CONFIG;\n return {\n escapeYieldSelector: host.escapeYieldSelector ?? DEFAULT_WINDOW_CONFIG.escapeYieldSelector,\n mobileTopOffset: host.mobileTopOffset ?? DEFAULT_WINDOW_CONFIG.mobileTopOffset,\n selectionSurface: host.selectionSurface ?? DEFAULT_WINDOW_CONFIG.selectionSurface,\n };\n}\n\n/** The configured selection surface, as a component. */\nexport function WindowSelectionSurface({ children }: WindowSelectionSurfaceProps) {\n const Surface = useWindowConfig().selectionSurface;\n return <Surface>{children}</Surface>;\n}\n","\"use client\";\n\n/**\n * WindowTraySync\n *\n * Mounts ONCE in the root layout. Does nothing — no listener, no dispatch — until a window\n * exists (one boolean selector), then attaches a single debounced resize listener that:\n * 1. Recomputes the positions of all minimized windows (tray slots can change\n * when chips-per-row shifts on viewport resize).\n * 2. Clamps every docked windowed window into the new viewport so windows\n * positioned for a larger screen stay reachable after a downsize.\n *\n * Zero re-renders — fires and forgets.\n *\n * Debounce: 500ms — a continuous window-drag generates exactly one dispatch\n * when the user stops moving. Short bursts (e.g. snapping) are ignored.\n *\n * Usage (in both authenticated and SSR root layouts):\n * <WindowTraySync />\n */\n\nimport { useEffect } from \"react\";\nimport { useSummonDispatch, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport {\n recomputeTrayPositions,\n clampAllWindowRects,\n} from \"@ai-matrx/summon\";\nimport { safeViewportDims, type WindowManagerState } from \"@ai-matrx/summon\";\n\nconst DEBOUNCE_MS = 500;\n\n/** Any window registered or waiting to restore: until then there is no geometry to keep in view. */\nexport function selectAnyWindowGeometry(state: { windowManager?: WindowManagerState }): boolean {\n const wm = state.windowManager;\n if (!wm) return false;\n for (const _ in wm.windows) return true;\n for (const _ in wm.pendingRestores) return true;\n return false;\n}\n\nexport function WindowTraySync() {\n const dispatch = useSummonDispatch();\n const active = useSummonSelector(selectAnyWindowGeometry);\n\n useEffect(() => {\n if (!active) return;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const syncWindowGeometry = () => {\n // A resize that measures 0×0 is a hidden/undisplayed tab or a\n // display:none ancestor, never a real screen. Bail WITHOUT dispatching:\n // reacting to it would rewrite every window rect and every tray slot\n // against invented dimensions, and the next real measurement would\n // then have to undo it. No measurement, no write.\n const { vw, vh, degenerate } = safeViewportDims();\n if (degenerate) return;\n dispatch(\n recomputeTrayPositions({ viewportWidth: vw, viewportHeight: vh }),\n );\n dispatch(clampAllWindowRects({ viewportWidth: vw, viewportHeight: vh }));\n };\n\n // This island may load after the viewport already changed. Synchronising\n // once on mount closes that event-listener gap for persisted windows.\n syncWindowGeometry();\n\n const handleResize = () => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n syncWindowGeometry();\n }, DEBOUNCE_MS);\n };\n\n window.addEventListener(\"resize\", handleResize, { passive: true });\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n if (timer !== null) clearTimeout(timer);\n };\n }, [dispatch, active]);\n\n return null;\n}\n\nexport default WindowTraySync;\n","\"use client\";\n\n/**\n * PopoutContext — exposes the popout `Document` / `Window` / portal target\n * to descendants of a popped-out subtree.\n *\n * The default value (`popoutDocument: null`, etc.) means \"I'm rendered in\n * the parent document.\" Descendants that need to behave differently inside\n * a popout (e.g. Radix portals retargeting their content) call\n * `usePopout()` and branch on the values.\n *\n * **Architectural note:** because `createPortal` preserves the parent\n * React tree, every ancestor `Provider` cascades into the popout — Redux,\n * Theme, Tooltip, Router, all work without re-wrapping. This context is\n * for the *opposite* direction: telling popped-out content \"you're inside\n * a popout window, here's the document/window/container you should use\n * instead of the global `document`/`window`.\"\n */\nimport { createContext, useContext } from \"react\";\n\nexport type PopoutMode = \"pip\" | \"popup\";\n\nexport interface PopoutContextValue {\n /**\n * The popout `Document`. `null` when this subtree is rendered in the\n * parent (the default).\n */\n popoutDocument: Document | null;\n /**\n * The popout `Window`. `null` when this subtree is rendered in the parent.\n * Use for `addEventListener`, `matchMedia`, `innerWidth`, etc., when the\n * popout-scoped answer is what you need.\n */\n popoutWindow: Window | null;\n /**\n * Element to use as the Radix `Portal.container` prop so floating UI\n * (tooltips, popovers, dropdowns) renders inside the popout instead of\n * the parent `document.body`. Returns `null` outside a popout —\n * `usePopoutContainer` coerces this to `undefined` so Radix falls back\n * to its default container automatically.\n */\n popoutContainer: HTMLElement | null;\n /**\n * Which popout mechanism is in use. `null` outside a popout.\n * Currently only used for diagnostics; downstream components may branch\n * on it later (e.g. show different chrome for `popup` vs `pip`).\n */\n mode: PopoutMode | null;\n}\n\nconst DEFAULT_VALUE: PopoutContextValue = {\n popoutDocument: null,\n popoutWindow: null,\n popoutContainer: null,\n mode: null,\n};\n\nexport const PopoutContext =\n createContext<PopoutContextValue>(DEFAULT_VALUE);\n\n/** Read the current popout context. Always defined — never throws. */\nexport function usePopout(): PopoutContextValue {\n return useContext(PopoutContext);\n}\n","\"use client\";\n\n/**\n * usePopoutContainer — return the element Radix portals (Tooltip, Popover,\n * Dropdown, Dialog, Select, AlertDialog, HoverCard) should mount into.\n *\n * **The problem this solves:** by default every Radix `Portal` mounts to\n * `document.body`. When a tooltip-trigger lives inside a popped-out window\n * but the popover content portals to the *parent* document, the tooltip\n * appears in the wrong window — typically invisible to the user.\n *\n * **The fix:** wrappers around Radix primitives call this hook and pass\n * the result as `<Primitive.Portal container={...} />`. Inside a popout,\n * the result is the popout's `<body>`. Outside a popout, the result is\n * `undefined`, which Radix interprets as \"use the default\" — meaning no\n * behavior change for any of the thousands of existing usages.\n *\n * **Why `undefined` instead of `null`?** Radix's `container` prop accepts\n * `HTMLElement | null | undefined`. Passing `null` actively tries to\n * portal into a `null` target and crashes. Passing `undefined` falls\n * through to the default (`document.body`). The `?? undefined` coerces\n * the `null` we get from `PopoutContext` outside a popout into the\n * Radix-friendly default-fallthrough value.\n */\nimport { usePopout } from \"./PopoutContext\";\n\nexport function usePopoutContainer(): HTMLElement | undefined {\n return usePopout().popoutContainer ?? undefined;\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { ackOverlaySurfaceRender, clearOverlaySurfaceRender } from \"@ai-matrx/summon\";\n\n/**\n * Acknowledge a visible presentation that intentionally replaces WindowPanel.\n *\n * Registered singleton window overlays normally prove they are on screen by mounting a\n * WindowPanel and registering geometry. Mobile drawers, sheets, and full-screen\n * viewers have no window-manager geometry, so their composition root must use\n * this hook while that alternate surface is active.\n */\nexport function useOverlaySurfaceRenderAck(\n overlayId: string,\n active: boolean,\n): void {\n useEffect(() => {\n if (!active) return undefined;\n ackOverlaySurfaceRender(overlayId);\n return () => clearOverlaySurfaceRender(overlayId);\n }, [active, overlayId]);\n}\n","/**\n * Tray previews — overlay id → a semantic minimized preview and/or a custom snapshot capture.\n *\n * The host registers its previews once (AI Matrx keeps its own preview components in the app):\n *\n * registerTrayPreview(\"notesWindow\", NotesTrayPreview);\n * registerTrayPreview(\"canvasWindow\", { captureTraySnapshot: captureCanvas });\n *\n * A window with a preview component skips snapshot capture entirely (cheaper, higher fidelity).\n * A window with neither gets the default capture (`defaultTraySnapshotCapture`, html-to-image when\n * the host installed it). State lives in a global slot (kit globalSlot, D9).\n */\nimport type { ComponentType } from \"react\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\n\nexport interface TrayPreviewContext {\n /** The window's allowlisted semantic data (open data merged with what `onCollectData` returned). */\n readonly data: Readonly<Record<string, unknown>>;\n /** Stable overlay id (the registry key). */\n readonly overlayId: string;\n /** Instance id — `\"default\"` for singleton windows. */\n readonly instanceId: string;\n /** The display title. */\n readonly title: string;\n}\n\nexport type TraySnapshotCapture = (bodyEl: HTMLElement) => Promise<Blob | null>;\n\nexport interface TrayPreviewEntry {\n readonly Preview?: ComponentType<TrayPreviewContext>;\n readonly captureTraySnapshot?: TraySnapshotCapture;\n}\n\nconst EMPTY: TrayPreviewEntry = {};\n\nconst registry = (): Map<string, TrayPreviewEntry> =>\n globalSlot<Map<string, TrayPreviewEntry>>(\"ai-matrx.summon.window.trayPreviews\", () => new Map());\n\n/**\n * Register the minimized preview for an overlay (or any window id). Pass a component, or an entry\n * with `Preview` and/or `captureTraySnapshot`. Re-registering replaces. Returns an unregister.\n */\nexport function registerTrayPreview(\n overlayId: string,\n preview: ComponentType<TrayPreviewContext> | TrayPreviewEntry,\n): () => void {\n const entry: TrayPreviewEntry = typeof preview === \"function\" ? { Preview: preview } : preview;\n registry().set(overlayId, entry);\n return () => {\n if (registry().get(overlayId) === entry) registry().delete(overlayId);\n };\n}\n\nexport function getTrayPreviewEntry(overlayId: string): TrayPreviewEntry {\n return registry().get(overlayId) ?? EMPTY;\n}\n\n/** Test-only. */\nexport function resetTrayPreviews(): void {\n registry().clear();\n}\n"],"mappings":"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","names":["createContext","useContext","useEffect"]}
|
package/dist/window.cjs
CHANGED
|
@@ -562,7 +562,8 @@ function useWindowPanel(opts = {}) {
|
|
|
562
562
|
const h = opts.initialRect?.height ?? resolveSize(opts.height, DEFAULT_HEIGHT);
|
|
563
563
|
const { vw, vh } = (0, import_summon2.safeViewportDims)();
|
|
564
564
|
let autoPlaced = null;
|
|
565
|
-
|
|
565
|
+
const explicitlyPlaced = opts.initialRect?.x !== void 0 || opts.initialRect?.y !== void 0;
|
|
566
|
+
if (opts.autoOpened && !explicitlyPlaced && vw >= NARROW_VIEWPORT) {
|
|
566
567
|
{
|
|
567
568
|
const ap = autoPlacement();
|
|
568
569
|
if (!ap.order.has(id)) ap.order.set(id, ap.next++);
|