@ai-matrx/summon 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +380 -2
  4. package/dist/closeBinding-C6raC3qJ.d.cts +20 -0
  5. package/dist/closeBinding-C6raC3qJ.d.ts +20 -0
  6. package/dist/dialog.cjs +60 -0
  7. package/dist/dialog.cjs.map +1 -0
  8. package/dist/dialog.d.cts +16 -0
  9. package/dist/dialog.d.ts +16 -0
  10. package/dist/dialog.js +38 -0
  11. package/dist/dialog.js.map +1 -0
  12. package/dist/errors-eJrKXq33.d.cts +30 -0
  13. package/dist/errors-eJrKXq33.d.ts +30 -0
  14. package/dist/handles.cjs +411 -0
  15. package/dist/handles.cjs.map +1 -0
  16. package/dist/handles.d.cts +166 -0
  17. package/dist/handles.d.ts +166 -0
  18. package/dist/handles.js +388 -0
  19. package/dist/handles.js.map +1 -0
  20. package/dist/index.cjs +2383 -0
  21. package/dist/index.cjs.map +1 -0
  22. package/dist/index.d.cts +1259 -0
  23. package/dist/index.d.ts +1259 -0
  24. package/dist/index.js +2363 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/lite.cjs +460 -0
  27. package/dist/lite.cjs.map +1 -0
  28. package/dist/lite.d.cts +56 -0
  29. package/dist/lite.d.ts +56 -0
  30. package/dist/lite.js +444 -0
  31. package/dist/lite.js.map +1 -0
  32. package/dist/next.cjs +61 -0
  33. package/dist/next.cjs.map +1 -0
  34. package/dist/next.d.cts +5 -0
  35. package/dist/next.d.ts +5 -0
  36. package/dist/next.js +39 -0
  37. package/dist/next.js.map +1 -0
  38. package/dist/persistence-data.cjs +143 -0
  39. package/dist/persistence-data.cjs.map +1 -0
  40. package/dist/persistence-data.d.cts +72 -0
  41. package/dist/persistence-data.d.ts +72 -0
  42. package/dist/persistence-data.js +120 -0
  43. package/dist/persistence-data.js.map +1 -0
  44. package/dist/persistence.cjs +1073 -0
  45. package/dist/persistence.cjs.map +1 -0
  46. package/dist/persistence.d.cts +236 -0
  47. package/dist/persistence.d.ts +236 -0
  48. package/dist/persistence.js +1077 -0
  49. package/dist/persistence.js.map +1 -0
  50. package/dist/react.cjs +733 -0
  51. package/dist/react.cjs.map +1 -0
  52. package/dist/react.d.cts +261 -0
  53. package/dist/react.d.ts +261 -0
  54. package/dist/react.js +718 -0
  55. package/dist/react.js.map +1 -0
  56. package/dist/sheet.cjs +71 -0
  57. package/dist/sheet.cjs.map +1 -0
  58. package/dist/sheet.d.cts +17 -0
  59. package/dist/sheet.d.ts +17 -0
  60. package/dist/sheet.js +49 -0
  61. package/dist/sheet.js.map +1 -0
  62. package/dist/styles.css +78 -0
  63. package/dist/testing.cjs +132 -0
  64. package/dist/testing.cjs.map +1 -0
  65. package/dist/testing.d.cts +44 -0
  66. package/dist/testing.d.ts +44 -0
  67. package/dist/testing.js +115 -0
  68. package/dist/testing.js.map +1 -0
  69. package/dist/url.cjs +362 -0
  70. package/dist/url.cjs.map +1 -0
  71. package/dist/url.d.cts +153 -0
  72. package/dist/url.d.ts +153 -0
  73. package/dist/url.js +352 -0
  74. package/dist/url.js.map +1 -0
  75. package/dist/window-host.cjs +123 -0
  76. package/dist/window-host.cjs.map +1 -0
  77. package/dist/window-host.d.cts +72 -0
  78. package/dist/window-host.d.ts +72 -0
  79. package/dist/window-host.js +104 -0
  80. package/dist/window-host.js.map +1 -0
  81. package/dist/window.cjs +3978 -0
  82. package/dist/window.cjs.map +1 -0
  83. package/dist/window.d.cts +796 -0
  84. package/dist/window.d.ts +796 -0
  85. package/dist/window.js +4003 -0
  86. package/dist/window.js.map +1 -0
  87. package/package.json +237 -4
package/dist/react.js ADDED
@@ -0,0 +1,718 @@
1
+ "use client";
2
+
3
+ // src/react/SummonProvider.tsx
4
+ import { useEffect as useEffect2, useMemo, useRef, useState } from "react";
5
+ import { Provider } from "react-redux";
6
+ import { resolvePorts } from "@ai-matrx/summon";
7
+ import { createSummonStore } from "@ai-matrx/summon";
8
+
9
+ // src/react/context.ts
10
+ import { createContext } from "react";
11
+ var SummonStoreContext = createContext(null);
12
+ SummonStoreContext.displayName = "SummonStoreContext";
13
+ var SummonPortsContext = createContext(null);
14
+ SummonPortsContext.displayName = "SummonPortsContext";
15
+ var SummonConfigContext = createContext(null);
16
+ SummonConfigContext.displayName = "SummonConfigContext";
17
+
18
+ // src/react/fallbacks.tsx
19
+ import { useEffect } from "react";
20
+ import { Button } from "@ai-matrx/design-system";
21
+
22
+ // src/react/storeHooks.ts
23
+ import { useContext } from "react";
24
+ import {
25
+ createDispatchHook,
26
+ createSelectorHook,
27
+ createStoreHook
28
+ } from "react-redux";
29
+ import { DEFAULT_PORTS } from "@ai-matrx/summon";
30
+ var useSummonSelector = createSelectorHook(SummonStoreContext).withTypes();
31
+ var useSummonDispatch = createDispatchHook(SummonStoreContext).withTypes();
32
+ var useSummonStore = createStoreHook(SummonStoreContext).withTypes();
33
+ var MISSING_PROVIDER = "[summon] No <SummonProvider> above this component. Wrap the app (or the part that opens overlays) in <SummonProvider>.";
34
+ function useSummonPorts() {
35
+ const ports = useContext(SummonPortsContext);
36
+ if (!ports) throw new Error(MISSING_PROVIDER);
37
+ return ports;
38
+ }
39
+ function useOptionalSummonPorts() {
40
+ return useContext(SummonPortsContext) ?? DEFAULT_PORTS;
41
+ }
42
+ function useSummonConfig() {
43
+ const config = useContext(SummonConfigContext);
44
+ if (!config) throw new Error(MISSING_PROVIDER);
45
+ return config;
46
+ }
47
+
48
+ // src/react/fallbacks.tsx
49
+ import { jsx, jsxs } from "react/jsx-runtime";
50
+ function normalizeOverlayError(error) {
51
+ if (error instanceof Error) {
52
+ const isChunkLoadError = error.name === "ChunkLoadError" || /Loading chunk [\w-]+ failed|Failed to fetch dynamically imported module|Importing a module script failed/i.test(
53
+ error.message
54
+ );
55
+ return { name: error.name, message: error.message, stack: error.stack ?? null, isChunkLoadError };
56
+ }
57
+ return { name: "Error", message: typeof error === "string" ? error : String(error), stack: null, isChunkLoadError: false };
58
+ }
59
+ function OverlayLoadingFallback({ overlayId, label }) {
60
+ const ports = useSummonPorts();
61
+ const { stallWarnMs } = useSummonConfig();
62
+ useEffect(() => {
63
+ const timer = setTimeout(() => {
64
+ ports.errors.capture(new Error(`Overlay "${overlayId}" still loading after ${stallWarnMs}ms`), {
65
+ stage: "overlay-load-stall",
66
+ overlayId,
67
+ level: "warning"
68
+ });
69
+ }, stallWarnMs);
70
+ return () => clearTimeout(timer);
71
+ }, [ports, overlayId, stallWarnMs]);
72
+ return /* @__PURE__ */ jsx("div", { className: "pointer-events-none fixed inset-0 z-[2147483000] flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(
73
+ "div",
74
+ {
75
+ role: "status",
76
+ "aria-label": `Loading ${label}`,
77
+ className: "pointer-events-auto flex items-center gap-3 rounded-xl border border-border bg-card px-5 py-4 text-sm text-muted-foreground shadow-[var(--elevation-2)]",
78
+ children: [
79
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "size-4 animate-spin rounded-full border-2 border-border border-t-primary" }),
80
+ /* @__PURE__ */ jsx("span", { children: "Loading\u2026" })
81
+ ]
82
+ }
83
+ ) });
84
+ }
85
+ function OverlayErrorFallback({ label, error, retry, close, extras }) {
86
+ const e = normalizeOverlayError(error);
87
+ return /* @__PURE__ */ jsx("div", { className: "pointer-events-none fixed inset-0 z-[2147483001] flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(
88
+ "div",
89
+ {
90
+ role: "alertdialog",
91
+ "aria-label": `${label} failed to load`,
92
+ className: "pointer-events-auto flex max-h-[85dvh] w-full max-w-lg flex-col overflow-hidden rounded-xl border border-destructive bg-card shadow-[var(--elevation-3)]",
93
+ children: [
94
+ /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-5 py-4", children: [
95
+ /* @__PURE__ */ jsxs("h2", { className: "text-sm font-semibold text-foreground", children: [
96
+ label,
97
+ " failed to load"
98
+ ] }),
99
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: e.isChunkLoadError ? "Part of it did not finish loading." : "It hit an error while rendering." })
100
+ ] }),
101
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-muted px-3 py-2 font-mono text-xs text-foreground", children: [
102
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: e.name }),
103
+ ": ",
104
+ e.message
105
+ ] }) }),
106
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border px-5 py-3", children: [
107
+ /* @__PURE__ */ jsx("div", { children: extras }),
108
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
109
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: close, children: "Close" }),
110
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: retry, children: "Try again" }),
111
+ /* @__PURE__ */ jsx(
112
+ Button,
113
+ {
114
+ variant: "outline",
115
+ size: "sm",
116
+ onClick: () => {
117
+ if (typeof window !== "undefined") window.location.reload();
118
+ },
119
+ children: "Reload page"
120
+ }
121
+ )
122
+ ] })
123
+ ] })
124
+ ]
125
+ }
126
+ ) });
127
+ }
128
+
129
+ // src/react/keepAlive.ts
130
+ var KeepAliveRegistry = class {
131
+ constructor(limit) {
132
+ this.limit = limit;
133
+ }
134
+ limit;
135
+ order = [];
136
+ evictors = /* @__PURE__ */ new Map();
137
+ setLimit(limit) {
138
+ this.limit = Math.max(0, Math.floor(limit));
139
+ this.trim();
140
+ }
141
+ /** Mark `key` as hidden-alive (most recent). `evict` unmounts it when the cap is exceeded. */
142
+ retain(key, evict) {
143
+ this.drop(key);
144
+ this.order.push(key);
145
+ this.evictors.set(key, evict);
146
+ this.trim();
147
+ }
148
+ /** `key` reopened or its slot went away: no longer counted. */
149
+ release(key) {
150
+ this.drop(key);
151
+ }
152
+ size() {
153
+ return this.order.length;
154
+ }
155
+ drop(key) {
156
+ const at = this.order.indexOf(key);
157
+ if (at >= 0) this.order.splice(at, 1);
158
+ this.evictors.delete(key);
159
+ }
160
+ trim() {
161
+ while (this.order.length > this.limit) {
162
+ const oldest = this.order.shift();
163
+ if (oldest === void 0) return;
164
+ const evict = this.evictors.get(oldest);
165
+ this.evictors.delete(oldest);
166
+ evict?.();
167
+ }
168
+ }
169
+ };
170
+
171
+ // src/react/loaderCache.ts
172
+ import { lazy } from "react";
173
+ function chunkTimeoutError(key, timeoutMs) {
174
+ const error = new Error(
175
+ `Overlay "${key}" render module did not load within ${timeoutMs}ms. The import() did not resolve before the recovery timeout.`
176
+ );
177
+ error.name = "ChunkLoadError";
178
+ return error;
179
+ }
180
+ function raceTimeout(key, real, timeoutMs) {
181
+ real.catch(() => void 0);
182
+ let timer;
183
+ const timeout = new Promise((_, reject) => {
184
+ timer = setTimeout(() => reject(chunkTimeoutError(key, timeoutMs)), timeoutMs);
185
+ });
186
+ return Promise.race([real, timeout]).finally(() => {
187
+ if (timer !== void 0) clearTimeout(timer);
188
+ });
189
+ }
190
+ var LoaderCache = class {
191
+ constructor(options) {
192
+ this.options = options;
193
+ }
194
+ options;
195
+ entries = /* @__PURE__ */ new Map();
196
+ create(key, loader, attempt) {
197
+ const Component2 = lazy(() => {
198
+ this.options.onImportStart?.(key, attempt);
199
+ return raceTimeout(key, loader(), this.options.timeoutMs());
200
+ });
201
+ return { attempt, Component: Component2 };
202
+ }
203
+ /** The current lazy component for `key`, created on first request. */
204
+ get(key, loader) {
205
+ const existing = this.entries.get(key);
206
+ if (existing) return existing;
207
+ const created = this.create(key, loader, 0);
208
+ this.entries.set(key, created);
209
+ return created;
210
+ }
211
+ /** Replace `key`'s lazy with a fresh one so the next render re-imports. */
212
+ retry(key, loader, failedAttempt) {
213
+ const existing = this.entries.get(key);
214
+ if (existing && existing.attempt > failedAttempt) return;
215
+ const next = this.create(key, loader, failedAttempt + 1);
216
+ this.entries.set(key, next);
217
+ }
218
+ };
219
+
220
+ // src/react/SummonProvider.tsx
221
+ import { jsx as jsx2 } from "react/jsx-runtime";
222
+ var DEFAULT_KEEP_ALIVE_LIMIT = 8;
223
+ var DEFAULT_IMPORT_TIMEOUT_MS = 12e3;
224
+ var DEFAULT_STALL_WARN_MS = 1e4;
225
+ function SummonProvider({
226
+ store,
227
+ ports,
228
+ fallbacks,
229
+ errorReportExtras,
230
+ renderWrapper,
231
+ keepAliveLimit = DEFAULT_KEEP_ALIVE_LIMIT,
232
+ importTimeoutMs = DEFAULT_IMPORT_TIMEOUT_MS,
233
+ stallWarnMs = DEFAULT_STALL_WARN_MS,
234
+ children
235
+ }) {
236
+ const resolvedPorts = useMemo(() => resolvePorts(ports), [ports]);
237
+ const [privateStore] = useState(() => store ? null : createSummonStore({ ports: resolvedPorts }));
238
+ const activeStore = store ?? privateStore;
239
+ const timeoutRef = useRef(importTimeoutMs);
240
+ timeoutRef.current = importTimeoutMs;
241
+ const [keepAlive] = useState(() => new KeepAliveRegistry(keepAliveLimit));
242
+ const [loaders] = useState(() => new LoaderCache({ timeoutMs: () => timeoutRef.current }));
243
+ useEffect2(() => keepAlive.setLimit(keepAliveLimit), [keepAlive, keepAliveLimit]);
244
+ const loading = fallbacks?.loading ?? OverlayLoadingFallback;
245
+ const error = fallbacks?.error ?? OverlayErrorFallback;
246
+ const config = useMemo(
247
+ () => ({
248
+ loadingFallback: loading,
249
+ errorFallback: error,
250
+ errorReportExtras: errorReportExtras ?? null,
251
+ renderWrapper: renderWrapper ?? null,
252
+ importTimeoutMs,
253
+ stallWarnMs,
254
+ keepAlive,
255
+ loaders
256
+ }),
257
+ [loading, error, errorReportExtras, renderWrapper, importTimeoutMs, stallWarnMs, keepAlive, loaders]
258
+ );
259
+ return /* @__PURE__ */ jsx2(Provider, { store: activeStore, context: SummonStoreContext, children: /* @__PURE__ */ jsx2(SummonPortsContext.Provider, { value: resolvedPorts, children: /* @__PURE__ */ jsx2(SummonConfigContext.Provider, { value: config, children }) }) });
260
+ }
261
+
262
+ // src/react/defineOverlay.tsx
263
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
264
+ import {
265
+ DEFAULT_INSTANCE_ID as DEFAULT_INSTANCE_ID2,
266
+ instanceKey as toInstanceKey
267
+ } from "@ai-matrx/summon";
268
+ import { selectIsOverlayOpen } from "@ai-matrx/summon";
269
+
270
+ // src/react/dispatchers.ts
271
+ import { closeOverlay, openOverlay } from "@ai-matrx/summon";
272
+ import { DEFAULT_INSTANCE_ID } from "@ai-matrx/summon";
273
+ function newInstanceId(overlayId) {
274
+ return `${overlayId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
275
+ }
276
+ function dispatchOpen(dispatch, overlayId, instanceId, data) {
277
+ dispatch(
278
+ openOverlay({
279
+ overlayId,
280
+ instanceId,
281
+ data: data ?? null
282
+ })
283
+ );
284
+ }
285
+ function dispatchClose(dispatch, overlayId, instanceId = DEFAULT_INSTANCE_ID) {
286
+ dispatch(closeOverlay({ overlayId, instanceId }));
287
+ }
288
+
289
+ // src/react/events.ts
290
+ import { createEventChannel, getEventChannel } from "@ai-matrx/summon/handles";
291
+ function openOverlayEventChannel(owner, getHandlers) {
292
+ const channel = createEventChannel({ owner });
293
+ channel.subscribe((message) => {
294
+ const handlers = getHandlers();
295
+ handlers?.[message.type]?.(message.payload);
296
+ });
297
+ return channel.id;
298
+ }
299
+ function emitOverlayEvent(channelId, type, payload) {
300
+ getEventChannel(channelId)?.emit({ type, payload });
301
+ }
302
+
303
+ // src/react/instanceBody.tsx
304
+ import { Suspense, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from "react";
305
+
306
+ // src/react/OverlayErrorBoundary.tsx
307
+ import { Component } from "react";
308
+ import { jsx as jsx3 } from "react/jsx-runtime";
309
+ var CLEAR = { error: null, hasError: false, componentStack: null };
310
+ var OverlayErrorBoundary = class extends Component {
311
+ state = CLEAR;
312
+ static getDerivedStateFromError(error) {
313
+ return { error, hasError: true };
314
+ }
315
+ componentDidCatch(error, info) {
316
+ const componentStack = info.componentStack ?? null;
317
+ this.setState({ componentStack });
318
+ const e = normalizeOverlayError(error);
319
+ this.props.errors.capture(error, {
320
+ boundary: "OverlayErrorBoundary",
321
+ overlayId: this.props.overlayId,
322
+ instanceId: this.props.instanceId,
323
+ errorName: e.name,
324
+ isChunkLoadError: e.isChunkLoadError,
325
+ componentStack
326
+ });
327
+ }
328
+ retry = () => {
329
+ this.props.onRetry();
330
+ this.setState(CLEAR);
331
+ };
332
+ render() {
333
+ const { hasError, error, componentStack } = this.state;
334
+ if (!hasError) return this.props.children;
335
+ const { overlayId, instanceId, instanceKey, label, extras, onClose } = this.props;
336
+ const Fallback = this.props.fallback;
337
+ const renderedExtras = extras ? extras({ overlayId, instanceId, instanceKey, label, error, componentStack }) : null;
338
+ return /* @__PURE__ */ jsx3(
339
+ Fallback,
340
+ {
341
+ overlayId,
342
+ instanceId,
343
+ label,
344
+ error,
345
+ componentStack,
346
+ retry: this.retry,
347
+ close: onClose,
348
+ extras: renderedExtras
349
+ }
350
+ );
351
+ }
352
+ };
353
+
354
+ // src/react/instance.tsx
355
+ import { createContext as createContext2, useCallback, useContext as useContext2 } from "react";
356
+ import { selectOverlayStateValue, setOverlayStateValue } from "@ai-matrx/summon";
357
+ var OverlayInstanceContext = createContext2(null);
358
+ OverlayInstanceContext.displayName = "OverlayInstanceContext";
359
+ function useOverlayInstance() {
360
+ const value = useContext2(OverlayInstanceContext);
361
+ if (!value) {
362
+ throw new Error("[summon] useOverlayInstance() was called outside a rendered overlay.");
363
+ }
364
+ return value;
365
+ }
366
+ function useOptionalOverlayInstance() {
367
+ return useContext2(OverlayInstanceContext);
368
+ }
369
+ function useOverlayState(name, initial) {
370
+ const { instanceKey } = useOverlayInstance();
371
+ const dispatch = useSummonDispatch();
372
+ const store = useSummonStore();
373
+ const stored = useSummonSelector((state) => selectOverlayStateValue(state, instanceKey, name));
374
+ const value = stored === void 0 ? initial : stored;
375
+ const setValue = useCallback(
376
+ (next) => {
377
+ const current = selectOverlayStateValue(store.getState(), instanceKey, name);
378
+ const previous = current === void 0 ? initial : current;
379
+ const resolved = typeof next === "function" ? next(previous) : next;
380
+ dispatch(setOverlayStateValue({ key: instanceKey, name, value: resolved }));
381
+ },
382
+ // `initial` is the first-render default, like useState's; its identity never re-binds.
383
+ // eslint-disable-next-line react-hooks/exhaustive-deps
384
+ [dispatch, store, instanceKey, name]
385
+ );
386
+ return [value, setValue];
387
+ }
388
+
389
+ // src/react/instanceBody.tsx
390
+ import { jsx as jsx4 } from "react/jsx-runtime";
391
+ function LazyRender({ overlayId, instanceId, data, hidden, close, render, attemptRef }) {
392
+ const { loaders } = useSummonConfig();
393
+ const entry = loaders.get(overlayId, render);
394
+ attemptRef.current = entry.attempt;
395
+ const Render = entry.Component;
396
+ return /* @__PURE__ */ jsx4(Render, { overlayId, instanceId, data, isOpen: true, close, hidden });
397
+ }
398
+ function createInstanceBody(overlayId, label, render, parse, refusable) {
399
+ function OverlayInstanceBody({ instanceId, instanceKey, data, hidden, close }) {
400
+ const config = useSummonConfig();
401
+ const ports = useSummonPorts();
402
+ const attemptRef = useRef2(0);
403
+ const parsed = useMemo2(() => parse(data), [data]);
404
+ const refused = refusable && parsed === null;
405
+ useEffect3(() => {
406
+ if (!refused) return;
407
+ if (process.env.NODE_ENV !== "production") {
408
+ ports.notify.warn(`Overlay "${overlayId}" was opened with data its parse() refused; closing it.`, {
409
+ id: `overlays-parse-${instanceKey}`
410
+ });
411
+ }
412
+ close();
413
+ }, [refused, ports, instanceKey, close]);
414
+ const instance = useMemo2(
415
+ () => ({ overlayId, instanceId, instanceKey, close, hidden, rawData: data }),
416
+ [instanceId, instanceKey, close, hidden, data]
417
+ );
418
+ if (refused) return null;
419
+ const context = { overlayId, instanceId, instanceKey, label };
420
+ const body = /* @__PURE__ */ jsx4(
421
+ OverlayErrorBoundary,
422
+ {
423
+ overlayId,
424
+ instanceId,
425
+ instanceKey,
426
+ label,
427
+ errors: ports.errors,
428
+ fallback: config.errorFallback,
429
+ extras: config.errorReportExtras,
430
+ onRetry: () => config.loaders.retry(overlayId, render, attemptRef.current),
431
+ onClose: close,
432
+ children: /* @__PURE__ */ jsx4(Suspense, { fallback: /* @__PURE__ */ jsx4(config.loadingFallback, { overlayId, label }), children: /* @__PURE__ */ jsx4(
433
+ LazyRender,
434
+ {
435
+ overlayId,
436
+ instanceId,
437
+ data: parsed,
438
+ hidden,
439
+ close,
440
+ render,
441
+ attemptRef
442
+ }
443
+ ) })
444
+ }
445
+ );
446
+ return /* @__PURE__ */ jsx4(OverlayInstanceContext.Provider, { value: instance, children: config.renderWrapper ? config.renderWrapper(body, context) : body });
447
+ }
448
+ OverlayInstanceBody.displayName = `Overlay(${overlayId})`;
449
+ return OverlayInstanceBody;
450
+ }
451
+
452
+ // src/react/defineOverlay.tsx
453
+ var CALLBACK_GROUP_KEY = "callbackGroupId";
454
+ function openWith(dispatch, id, multi, options) {
455
+ const instanceId = options?.instanceId ?? (multi ? newInstanceId(id) : DEFAULT_INSTANCE_ID2);
456
+ const key = toInstanceKey(id, instanceId);
457
+ const data = {};
458
+ const given = options?.data;
459
+ if (typeof given === "object" && given !== null) Object.assign(data, given);
460
+ const handlers = options?.handlers;
461
+ if (handlers) data[CALLBACK_GROUP_KEY] = openOverlayEventChannel(key, () => handlers);
462
+ dispatchOpen(dispatch, id, instanceId, data);
463
+ return { overlayId: id, instanceId, close: () => dispatchClose(dispatch, id, instanceId) };
464
+ }
465
+ function dataSignature(data) {
466
+ try {
467
+ return JSON.stringify(data, (_key, value) => typeof value === "function" ? void 0 : value);
468
+ } catch {
469
+ return String(Date.now());
470
+ }
471
+ }
472
+ function defineOverlay() {
473
+ return function define(spec) {
474
+ const id = spec.id;
475
+ const multi = spec.instance === "multi";
476
+ const parse = spec.parse ?? ((raw) => raw);
477
+ const Body = createInstanceBody(id, spec.label, spec.render, parse, spec.parse !== void 0);
478
+ const meta = spec;
479
+ function useSummon() {
480
+ const dispatch = useSummonDispatch();
481
+ return useCallback2((options) => openWith(dispatch, id, multi, options), [dispatch]);
482
+ }
483
+ function useIsOpen(instanceId = DEFAULT_INSTANCE_ID2) {
484
+ return useSummonSelector((state) => selectIsOverlayOpen(state, id, instanceId));
485
+ }
486
+ function useClose() {
487
+ const dispatch = useSummonDispatch();
488
+ return useCallback2((instanceId) => dispatchClose(dispatch, id, instanceId ?? DEFAULT_INSTANCE_ID2), [dispatch]);
489
+ }
490
+ function useEmit() {
491
+ const instance = useOptionalOverlayInstance();
492
+ const channelId = instance?.overlayId === id ? instance.rawData?.[CALLBACK_GROUP_KEY] : void 0;
493
+ return useCallback2(
494
+ (event, payload) => {
495
+ if (typeof channelId === "string") emitOverlayEvent(channelId, event, payload);
496
+ },
497
+ [channelId]
498
+ );
499
+ }
500
+ function Controller(props) {
501
+ const dispatch = useSummonDispatch();
502
+ const [ownInstanceId] = useState2(() => multi ? newInstanceId(id) : DEFAULT_INSTANCE_ID2);
503
+ const instanceId = props.instanceId ?? ownInstanceId;
504
+ const handlersRef = useRef3(props.handlers);
505
+ handlersRef.current = props.handlers;
506
+ const hasHandlers = props.handlers !== void 0;
507
+ const data = useMemo3(() => {
508
+ const out = {};
509
+ for (const [key, value] of Object.entries(props)) {
510
+ if (key !== "instanceId" && key !== "handlers") out[key] = value;
511
+ }
512
+ return out;
513
+ }, [props]);
514
+ const signature = dataSignature(data);
515
+ const dataRef = useRef3(data);
516
+ dataRef.current = data;
517
+ useEffect4(() => {
518
+ const key = toInstanceKey(id, instanceId);
519
+ const open = { ...dataRef.current };
520
+ if (hasHandlers) open[CALLBACK_GROUP_KEY] = openOverlayEventChannel(key, () => handlersRef.current);
521
+ dispatchOpen(dispatch, id, instanceId, open);
522
+ return () => dispatchClose(dispatch, id, instanceId);
523
+ }, [dispatch, instanceId, signature, hasHandlers]);
524
+ return null;
525
+ }
526
+ Controller.displayName = `${id}Controller`;
527
+ return {
528
+ ...spec,
529
+ id,
530
+ label: spec.label,
531
+ instance: multi ? "multi" : "single",
532
+ keepAlive: spec.keepAlive ?? "none",
533
+ meta,
534
+ Body,
535
+ useSummon,
536
+ Controller,
537
+ useIsOpen,
538
+ useClose,
539
+ useEmit
540
+ };
541
+ };
542
+ }
543
+
544
+ // src/react/SummonHost.tsx
545
+ import { Activity, useCallback as useCallback3, useEffect as useEffect5, useRef as useRef4, useState as useState3 } from "react";
546
+ import { releaseOverlayInstance } from "@ai-matrx/summon";
547
+ import { registerOverlayMeta } from "@ai-matrx/summon";
548
+ import { closeOverlaysBoundToPage, overlayIdsBoundToPage, selectOpenInstances } from "@ai-matrx/summon";
549
+ import { instanceKey as toInstanceKey2 } from "@ai-matrx/summon";
550
+ import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
551
+ function asRawData(data) {
552
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? data : null;
553
+ }
554
+ function sameInstances(a, b) {
555
+ if (a === b) return true;
556
+ if (a.length !== b.length) return false;
557
+ for (let i = 0; i < a.length; i += 1) {
558
+ const x = a[i];
559
+ const y = b[i];
560
+ if (!x || !y || x.instanceId !== y.instanceId || x.data !== y.data) return false;
561
+ }
562
+ return true;
563
+ }
564
+ var NO_RETAINED = /* @__PURE__ */ new Map();
565
+ function useCloseOnNavigation() {
566
+ const ports = useSummonPorts();
567
+ const dispatch = useSummonDispatch();
568
+ useEffect5(() => {
569
+ const router = ports.router;
570
+ let previous = router.pathname();
571
+ return router.subscribe(() => {
572
+ const next = router.pathname();
573
+ if (next === previous) return;
574
+ previous = next;
575
+ const overlayIds = overlayIdsBoundToPage();
576
+ if (overlayIds.length > 0) dispatch(closeOverlaysBoundToPage({ overlayIds }));
577
+ });
578
+ }, [ports, dispatch]);
579
+ }
580
+ function InstanceSlot({ definition, instanceId, data, hidden }) {
581
+ const dispatch = useSummonDispatch();
582
+ const close = useCallback3(() => dispatchClose(dispatch, definition.id, instanceId), [dispatch, definition.id, instanceId]);
583
+ const Body = definition.Body;
584
+ return /* @__PURE__ */ jsx5(Body, { instanceId, instanceKey: toInstanceKey2(definition.id, instanceId), data, hidden, close });
585
+ }
586
+ function DefinitionSlot({ definition }) {
587
+ const { keepAlive } = useSummonConfig();
588
+ const dispatch = useSummonDispatch();
589
+ const open = useSummonSelector((state) => selectOpenInstances(state, definition.id), sameInstances);
590
+ const keepHidden = definition.keepAlive === "hidden";
591
+ const [retained, setRetained] = useState3(NO_RETAINED);
592
+ const [previousOpen, setPreviousOpen] = useState3(open);
593
+ if (previousOpen !== open) {
594
+ setPreviousOpen(open);
595
+ if (keepHidden) {
596
+ const openIds = new Set(open.map((instance) => instance.instanceId));
597
+ const next = new Map(retained);
598
+ for (const instance of previousOpen) {
599
+ if (!openIds.has(instance.instanceId)) next.set(instance.instanceId, asRawData(instance.data));
600
+ }
601
+ for (const id of openIds) next.delete(id);
602
+ setRetained(next);
603
+ }
604
+ }
605
+ const registered = useRef4(/* @__PURE__ */ new Set());
606
+ useEffect5(() => {
607
+ const seen = registered.current;
608
+ for (const instanceId of retained.keys()) {
609
+ if (seen.has(instanceId)) continue;
610
+ seen.add(instanceId);
611
+ const key = toInstanceKey2(definition.id, instanceId);
612
+ keepAlive.retain(key, () => {
613
+ seen.delete(instanceId);
614
+ releaseOverlayInstance(dispatch, key);
615
+ setRetained((current) => {
616
+ if (!current.has(instanceId)) return current;
617
+ const next = new Map(current);
618
+ next.delete(instanceId);
619
+ return next;
620
+ });
621
+ });
622
+ }
623
+ for (const instanceId of [...seen]) {
624
+ if (retained.has(instanceId)) continue;
625
+ seen.delete(instanceId);
626
+ keepAlive.release(toInstanceKey2(definition.id, instanceId));
627
+ }
628
+ }, [retained, keepAlive, definition.id, dispatch]);
629
+ useEffect5(() => {
630
+ const seen = registered.current;
631
+ return () => {
632
+ for (const instanceId of seen) {
633
+ const key = toInstanceKey2(definition.id, instanceId);
634
+ keepAlive.release(key);
635
+ releaseOverlayInstance(dispatch, key);
636
+ }
637
+ seen.clear();
638
+ };
639
+ }, [keepAlive, definition.id, dispatch]);
640
+ if (open.length === 0 && retained.size === 0) return null;
641
+ const nodes = [];
642
+ for (const instance of open) {
643
+ const node = /* @__PURE__ */ jsx5(InstanceSlot, { definition, instanceId: instance.instanceId, data: asRawData(instance.data), hidden: false }, instance.instanceId);
644
+ nodes.push(
645
+ keepHidden ? /* @__PURE__ */ jsx5(Activity, { mode: "visible", children: node }, instance.instanceId) : node
646
+ );
647
+ }
648
+ if (keepHidden) {
649
+ for (const [instanceId, data] of retained) {
650
+ nodes.push(
651
+ /* @__PURE__ */ jsx5(Activity, { mode: "hidden", children: /* @__PURE__ */ jsx5(InstanceSlot, { definition, instanceId, data, hidden: true }, instanceId) }, instanceId)
652
+ );
653
+ }
654
+ }
655
+ return /* @__PURE__ */ jsx5(Fragment, { children: nodes });
656
+ }
657
+ function SummonHost({ definitions }) {
658
+ const registeredFor = useRef4(null);
659
+ if (registeredFor.current !== definitions) {
660
+ registerOverlayMeta(definitions.map((definition) => definition.meta));
661
+ registeredFor.current = definitions;
662
+ }
663
+ useCloseOnNavigation();
664
+ return /* @__PURE__ */ jsx5(Fragment, { children: definitions.map((definition) => /* @__PURE__ */ jsx5(DefinitionSlot, { definition }, definition.id)) });
665
+ }
666
+
667
+ // src/react/hooks.ts
668
+ import { useCallback as useCallback4 } from "react";
669
+ import { getOverlayMeta } from "@ai-matrx/summon";
670
+ import { selectIsOverlayOpen as selectIsOverlayOpen2 } from "@ai-matrx/summon";
671
+ import { DEFAULT_INSTANCE_ID as DEFAULT_INSTANCE_ID3 } from "@ai-matrx/summon";
672
+ function useIsOverlayOpen(overlayId, instanceId = DEFAULT_INSTANCE_ID3) {
673
+ return useSummonSelector((state) => selectIsOverlayOpen2(state, overlayId, instanceId));
674
+ }
675
+ function useOpenOverlay(overlayId) {
676
+ const dispatch = useSummonDispatch();
677
+ return useCallback4(
678
+ (options) => {
679
+ const multi = getOverlayMeta(overlayId)?.instance === "multi";
680
+ const instanceId = options?.instanceId ?? (multi ? newInstanceId(overlayId) : DEFAULT_INSTANCE_ID3);
681
+ dispatchOpen(dispatch, overlayId, instanceId, options?.data ?? null);
682
+ return { overlayId, instanceId, close: () => dispatchClose(dispatch, overlayId, instanceId) };
683
+ },
684
+ [dispatch, overlayId]
685
+ );
686
+ }
687
+ function useCloseOverlay(overlayId, instanceId = DEFAULT_INSTANCE_ID3) {
688
+ const dispatch = useSummonDispatch();
689
+ return useCallback4(() => dispatchClose(dispatch, overlayId, instanceId), [dispatch, overlayId, instanceId]);
690
+ }
691
+ export {
692
+ CALLBACK_GROUP_KEY,
693
+ DEFAULT_IMPORT_TIMEOUT_MS,
694
+ DEFAULT_KEEP_ALIVE_LIMIT,
695
+ DEFAULT_STALL_WARN_MS,
696
+ OverlayErrorBoundary,
697
+ OverlayErrorFallback,
698
+ OverlayLoadingFallback,
699
+ SummonHost,
700
+ SummonProvider,
701
+ SummonStoreContext,
702
+ chunkTimeoutError,
703
+ defineOverlay,
704
+ normalizeOverlayError,
705
+ useCloseOnNavigation,
706
+ useCloseOverlay,
707
+ useIsOverlayOpen,
708
+ useOpenOverlay,
709
+ useOptionalOverlayInstance,
710
+ useOptionalSummonPorts,
711
+ useOverlayInstance,
712
+ useOverlayState,
713
+ useSummonDispatch,
714
+ useSummonPorts,
715
+ useSummonSelector,
716
+ useSummonStore
717
+ };
718
+ //# sourceMappingURL=react.js.map