@perspective-ai/sdk 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +333 -0
- package/dist/browser.cjs +1939 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +213 -0
- package/dist/browser.d.ts +213 -0
- package/dist/browser.js +1900 -0
- package/dist/browser.js.map +1 -0
- package/dist/cdn/perspective.global.js +406 -0
- package/dist/cdn/perspective.global.js.map +1 -0
- package/dist/constants.cjs +142 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.d.cts +104 -0
- package/dist/constants.d.ts +104 -0
- package/dist/constants.js +127 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.cjs +1596 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +155 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.js +1579 -0
- package/dist/index.js.map +1 -0
- package/package.json +83 -0
- package/src/browser.test.ts +388 -0
- package/src/browser.ts +509 -0
- package/src/config.test.ts +81 -0
- package/src/config.ts +95 -0
- package/src/constants.ts +214 -0
- package/src/float.test.ts +332 -0
- package/src/float.ts +231 -0
- package/src/fullpage.test.ts +224 -0
- package/src/fullpage.ts +126 -0
- package/src/iframe.test.ts +1037 -0
- package/src/iframe.ts +421 -0
- package/src/index.ts +61 -0
- package/src/loading.ts +90 -0
- package/src/popup.test.ts +344 -0
- package/src/popup.ts +157 -0
- package/src/slider.test.ts +277 -0
- package/src/slider.ts +158 -0
- package/src/styles.ts +395 -0
- package/src/types.ts +148 -0
- package/src/utils.test.ts +162 -0
- package/src/utils.ts +86 -0
- package/src/widget.test.ts +375 -0
- package/src/widget.ts +195 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
|
+
import { openPopup } from "./popup";
|
|
3
|
+
import * as config from "./config";
|
|
4
|
+
|
|
5
|
+
describe("openPopup", () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
// Clean up any popups left in the DOM
|
|
8
|
+
document
|
|
9
|
+
.querySelectorAll(".perspective-overlay")
|
|
10
|
+
.forEach((el) => el.remove());
|
|
11
|
+
vi.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("creates popup overlay in body", () => {
|
|
15
|
+
const handle = openPopup({ researchId: "test-research-id" });
|
|
16
|
+
|
|
17
|
+
expect(handle.researchId).toBe("test-research-id");
|
|
18
|
+
expect(handle.type).toBe("popup");
|
|
19
|
+
expect(document.querySelector(".perspective-overlay")).toBeTruthy();
|
|
20
|
+
expect(document.querySelector(".perspective-modal")).toBeTruthy();
|
|
21
|
+
|
|
22
|
+
handle.destroy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("creates iframe inside modal", () => {
|
|
26
|
+
const handle = openPopup({ researchId: "test-research-id" });
|
|
27
|
+
|
|
28
|
+
const iframe = document.querySelector(
|
|
29
|
+
".perspective-modal iframe[data-perspective]"
|
|
30
|
+
);
|
|
31
|
+
expect(iframe).toBeTruthy();
|
|
32
|
+
|
|
33
|
+
handle.destroy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("creates close button", () => {
|
|
37
|
+
const handle = openPopup({ researchId: "test-research-id" });
|
|
38
|
+
|
|
39
|
+
const closeBtn = document.querySelector(".perspective-close");
|
|
40
|
+
expect(closeBtn).toBeTruthy();
|
|
41
|
+
expect(closeBtn?.getAttribute("aria-label")).toBe("Close");
|
|
42
|
+
|
|
43
|
+
handle.destroy();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("creates loading indicator", () => {
|
|
47
|
+
const handle = openPopup({ researchId: "test-research-id" });
|
|
48
|
+
|
|
49
|
+
const loading = document.querySelector(".perspective-loading");
|
|
50
|
+
expect(loading).toBeTruthy();
|
|
51
|
+
|
|
52
|
+
handle.destroy();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns no-op handle when no DOM", () => {
|
|
56
|
+
vi.spyOn(config, "hasDom").mockReturnValue(false);
|
|
57
|
+
|
|
58
|
+
const handle = openPopup({ researchId: "test-research-id" });
|
|
59
|
+
|
|
60
|
+
expect(handle.iframe).toBeNull();
|
|
61
|
+
expect(handle.container).toBeNull();
|
|
62
|
+
expect(document.querySelector(".perspective-overlay")).toBeFalsy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("destroy removes overlay", () => {
|
|
66
|
+
const handle = openPopup({ researchId: "test-research-id" });
|
|
67
|
+
|
|
68
|
+
expect(document.querySelector(".perspective-overlay")).toBeTruthy();
|
|
69
|
+
|
|
70
|
+
handle.destroy();
|
|
71
|
+
|
|
72
|
+
expect(document.querySelector(".perspective-overlay")).toBeFalsy();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("calls onClose callback when destroyed", () => {
|
|
76
|
+
const onClose = vi.fn();
|
|
77
|
+
const handle = openPopup({
|
|
78
|
+
researchId: "test-research-id",
|
|
79
|
+
onClose,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
handle.destroy();
|
|
83
|
+
|
|
84
|
+
expect(onClose).toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("close button click closes popup", () => {
|
|
88
|
+
const onClose = vi.fn();
|
|
89
|
+
openPopup({
|
|
90
|
+
researchId: "test-research-id",
|
|
91
|
+
onClose,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const closeBtn = document.querySelector(
|
|
95
|
+
".perspective-close"
|
|
96
|
+
) as HTMLElement;
|
|
97
|
+
closeBtn.click();
|
|
98
|
+
|
|
99
|
+
expect(onClose).toHaveBeenCalled();
|
|
100
|
+
expect(document.querySelector(".perspective-overlay")).toBeFalsy();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("clicking overlay background closes popup", () => {
|
|
104
|
+
const onClose = vi.fn();
|
|
105
|
+
openPopup({
|
|
106
|
+
researchId: "test-research-id",
|
|
107
|
+
onClose,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const overlay = document.querySelector(
|
|
111
|
+
".perspective-overlay"
|
|
112
|
+
) as HTMLElement;
|
|
113
|
+
overlay.click();
|
|
114
|
+
|
|
115
|
+
expect(onClose).toHaveBeenCalled();
|
|
116
|
+
expect(document.querySelector(".perspective-overlay")).toBeFalsy();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("clicking modal does not close popup", () => {
|
|
120
|
+
const onClose = vi.fn();
|
|
121
|
+
openPopup({
|
|
122
|
+
researchId: "test-research-id",
|
|
123
|
+
onClose,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const modal = document.querySelector(".perspective-modal") as HTMLElement;
|
|
127
|
+
modal.click();
|
|
128
|
+
|
|
129
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
130
|
+
expect(document.querySelector(".perspective-overlay")).toBeTruthy();
|
|
131
|
+
|
|
132
|
+
// Clean up
|
|
133
|
+
(document.querySelector(".perspective-close") as HTMLElement).click();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("ESC key closes popup", () => {
|
|
137
|
+
const onClose = vi.fn();
|
|
138
|
+
openPopup({
|
|
139
|
+
researchId: "test-research-id",
|
|
140
|
+
onClose,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
|
|
144
|
+
|
|
145
|
+
expect(onClose).toHaveBeenCalled();
|
|
146
|
+
expect(document.querySelector(".perspective-overlay")).toBeFalsy();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("unmount removes overlay", () => {
|
|
150
|
+
const onClose = vi.fn();
|
|
151
|
+
const handle = openPopup({
|
|
152
|
+
researchId: "test-research-id",
|
|
153
|
+
onClose,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
handle.unmount();
|
|
157
|
+
|
|
158
|
+
expect(onClose).toHaveBeenCalled();
|
|
159
|
+
expect(document.querySelector(".perspective-overlay")).toBeFalsy();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("update modifies config", () => {
|
|
163
|
+
const onSubmit = vi.fn();
|
|
164
|
+
const handle = openPopup({
|
|
165
|
+
researchId: "test-research-id",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(() => handle.update({ onSubmit })).not.toThrow();
|
|
169
|
+
|
|
170
|
+
handle.destroy();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("applies theme class", () => {
|
|
174
|
+
const handle = openPopup({
|
|
175
|
+
researchId: "test-research-id",
|
|
176
|
+
theme: "dark",
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const overlay = document.querySelector(".perspective-overlay");
|
|
180
|
+
expect(overlay?.classList.contains("perspective-dark-theme")).toBe(true);
|
|
181
|
+
|
|
182
|
+
handle.destroy();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("uses custom host", () => {
|
|
186
|
+
const handle = openPopup({
|
|
187
|
+
researchId: "test-research-id",
|
|
188
|
+
host: "https://custom.example.com",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const iframe = handle.iframe as HTMLIFrameElement;
|
|
192
|
+
expect(iframe.src).toContain("https://custom.example.com");
|
|
193
|
+
|
|
194
|
+
handle.destroy();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("update() behavior", () => {
|
|
198
|
+
const host = "https://getperspective.ai";
|
|
199
|
+
const researchId = "test-research-id";
|
|
200
|
+
|
|
201
|
+
const sendMessage = (
|
|
202
|
+
iframe: HTMLIFrameElement,
|
|
203
|
+
type: string,
|
|
204
|
+
extra?: Record<string, unknown>
|
|
205
|
+
) => {
|
|
206
|
+
window.dispatchEvent(
|
|
207
|
+
new MessageEvent("message", {
|
|
208
|
+
data: { type, researchId, ...extra },
|
|
209
|
+
origin: host,
|
|
210
|
+
source: iframe.contentWindow,
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
it("update changes which callback is invoked", () => {
|
|
216
|
+
const onSubmit1 = vi.fn();
|
|
217
|
+
const onSubmit2 = vi.fn();
|
|
218
|
+
|
|
219
|
+
const handle = openPopup({
|
|
220
|
+
researchId,
|
|
221
|
+
host,
|
|
222
|
+
onSubmit: onSubmit1,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Update to new callback
|
|
226
|
+
handle.update({ onSubmit: onSubmit2 });
|
|
227
|
+
|
|
228
|
+
// Send submit message
|
|
229
|
+
sendMessage(handle.iframe!, "perspective:submit");
|
|
230
|
+
|
|
231
|
+
// New callback should be called, old one should not
|
|
232
|
+
expect(onSubmit2).toHaveBeenCalledTimes(1);
|
|
233
|
+
expect(onSubmit1).not.toHaveBeenCalled();
|
|
234
|
+
|
|
235
|
+
handle.destroy();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("sequential updates only use latest callback", () => {
|
|
239
|
+
const fn1 = vi.fn();
|
|
240
|
+
const fn2 = vi.fn();
|
|
241
|
+
const fn3 = vi.fn();
|
|
242
|
+
|
|
243
|
+
const handle = openPopup({
|
|
244
|
+
researchId,
|
|
245
|
+
host,
|
|
246
|
+
onSubmit: fn1,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Update twice
|
|
250
|
+
handle.update({ onSubmit: fn2 });
|
|
251
|
+
handle.update({ onSubmit: fn3 });
|
|
252
|
+
|
|
253
|
+
// Send submit message
|
|
254
|
+
sendMessage(handle.iframe!, "perspective:submit");
|
|
255
|
+
|
|
256
|
+
// Only the latest callback should be called
|
|
257
|
+
expect(fn3).toHaveBeenCalledTimes(1);
|
|
258
|
+
expect(fn2).not.toHaveBeenCalled();
|
|
259
|
+
expect(fn1).not.toHaveBeenCalled();
|
|
260
|
+
|
|
261
|
+
handle.destroy();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("destroy prevents further callback invocations", () => {
|
|
265
|
+
const onSubmit = vi.fn();
|
|
266
|
+
const onClose = vi.fn();
|
|
267
|
+
|
|
268
|
+
const handle = openPopup({
|
|
269
|
+
researchId,
|
|
270
|
+
host,
|
|
271
|
+
onSubmit,
|
|
272
|
+
onClose,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const iframe = handle.iframe!;
|
|
276
|
+
|
|
277
|
+
// Destroy calls onClose once
|
|
278
|
+
handle.destroy();
|
|
279
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
280
|
+
|
|
281
|
+
// Subsequent messages should not trigger callbacks
|
|
282
|
+
sendMessage(iframe, "perspective:submit");
|
|
283
|
+
sendMessage(iframe, "perspective:close");
|
|
284
|
+
|
|
285
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
286
|
+
// onClose should still be called only once (from destroy)
|
|
287
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("update after destroy is safe (no-op)", () => {
|
|
291
|
+
const handle = openPopup({
|
|
292
|
+
researchId,
|
|
293
|
+
host,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
handle.destroy();
|
|
297
|
+
|
|
298
|
+
// Should not throw
|
|
299
|
+
expect(() => handle.update({ onSubmit: vi.fn() })).not.toThrow();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("update preserves other config values", () => {
|
|
303
|
+
const onReady = vi.fn();
|
|
304
|
+
const onSubmit1 = vi.fn();
|
|
305
|
+
const onSubmit2 = vi.fn();
|
|
306
|
+
|
|
307
|
+
const handle = openPopup({
|
|
308
|
+
researchId,
|
|
309
|
+
host,
|
|
310
|
+
onReady,
|
|
311
|
+
onSubmit: onSubmit1,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Update only onSubmit
|
|
315
|
+
handle.update({ onSubmit: onSubmit2 });
|
|
316
|
+
|
|
317
|
+
// onReady should still work
|
|
318
|
+
sendMessage(handle.iframe!, "perspective:ready");
|
|
319
|
+
expect(onReady).toHaveBeenCalledTimes(1);
|
|
320
|
+
|
|
321
|
+
// New onSubmit should work
|
|
322
|
+
sendMessage(handle.iframe!, "perspective:submit");
|
|
323
|
+
expect(onSubmit2).toHaveBeenCalledTimes(1);
|
|
324
|
+
expect(onSubmit1).not.toHaveBeenCalled();
|
|
325
|
+
|
|
326
|
+
handle.destroy();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("destroy is idempotent", () => {
|
|
331
|
+
const onClose = vi.fn();
|
|
332
|
+
const handle = openPopup({
|
|
333
|
+
researchId: "test-research-id",
|
|
334
|
+
onClose,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
handle.destroy();
|
|
338
|
+
handle.destroy();
|
|
339
|
+
handle.destroy();
|
|
340
|
+
|
|
341
|
+
// onClose only called once
|
|
342
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
343
|
+
});
|
|
344
|
+
});
|
package/src/popup.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Popup/modal embed - opens in a centered modal overlay
|
|
3
|
+
* SSR-safe - returns no-op handle on server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { EmbedConfig, EmbedHandle } from "./types";
|
|
7
|
+
import { hasDom, getHost } from "./config";
|
|
8
|
+
import {
|
|
9
|
+
createIframe,
|
|
10
|
+
setupMessageListener,
|
|
11
|
+
registerIframe,
|
|
12
|
+
ensureGlobalListeners,
|
|
13
|
+
} from "./iframe";
|
|
14
|
+
import { createLoadingIndicator } from "./loading";
|
|
15
|
+
import { injectStyles, CLOSE_ICON } from "./styles";
|
|
16
|
+
import { cn, getThemeClass } from "./utils";
|
|
17
|
+
|
|
18
|
+
function createNoOpHandle(researchId: string): EmbedHandle {
|
|
19
|
+
return {
|
|
20
|
+
unmount: () => {},
|
|
21
|
+
update: () => {},
|
|
22
|
+
destroy: () => {},
|
|
23
|
+
researchId,
|
|
24
|
+
type: "popup",
|
|
25
|
+
iframe: null,
|
|
26
|
+
container: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function openPopup(config: EmbedConfig): EmbedHandle {
|
|
31
|
+
const { researchId } = config;
|
|
32
|
+
|
|
33
|
+
// SSR safety: return no-op handle
|
|
34
|
+
if (!hasDom()) {
|
|
35
|
+
return createNoOpHandle(researchId);
|
|
36
|
+
}
|
|
37
|
+
const host = getHost(config.host);
|
|
38
|
+
|
|
39
|
+
injectStyles();
|
|
40
|
+
ensureGlobalListeners();
|
|
41
|
+
|
|
42
|
+
// Create overlay
|
|
43
|
+
const overlay = document.createElement("div");
|
|
44
|
+
overlay.className = cn(
|
|
45
|
+
"perspective-overlay perspective-embed-root",
|
|
46
|
+
getThemeClass(config.theme)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Create modal container
|
|
50
|
+
const modal = document.createElement("div");
|
|
51
|
+
modal.className = "perspective-modal";
|
|
52
|
+
|
|
53
|
+
// Create close button
|
|
54
|
+
const closeBtn = document.createElement("button");
|
|
55
|
+
closeBtn.className = "perspective-close";
|
|
56
|
+
closeBtn.innerHTML = CLOSE_ICON;
|
|
57
|
+
closeBtn.setAttribute("aria-label", "Close");
|
|
58
|
+
|
|
59
|
+
// Create loading indicator with theme and brand colors
|
|
60
|
+
const loading = createLoadingIndicator({
|
|
61
|
+
theme: config.theme,
|
|
62
|
+
brand: config.brand,
|
|
63
|
+
});
|
|
64
|
+
loading.style.borderRadius = "16px";
|
|
65
|
+
|
|
66
|
+
// Create iframe (hidden initially)
|
|
67
|
+
const iframe = createIframe(
|
|
68
|
+
researchId,
|
|
69
|
+
"popup",
|
|
70
|
+
host,
|
|
71
|
+
config.params,
|
|
72
|
+
config.brand,
|
|
73
|
+
config.theme
|
|
74
|
+
);
|
|
75
|
+
iframe.style.opacity = "0";
|
|
76
|
+
iframe.style.transition = "opacity 0.3s ease";
|
|
77
|
+
|
|
78
|
+
modal.appendChild(closeBtn);
|
|
79
|
+
modal.appendChild(loading);
|
|
80
|
+
modal.appendChild(iframe);
|
|
81
|
+
overlay.appendChild(modal);
|
|
82
|
+
document.body.appendChild(overlay);
|
|
83
|
+
|
|
84
|
+
// Mutable config reference for updates
|
|
85
|
+
let currentConfig = { ...config };
|
|
86
|
+
let isOpen = true;
|
|
87
|
+
let messageCleanup: (() => void) | null = null;
|
|
88
|
+
|
|
89
|
+
// Register iframe for theme change notifications
|
|
90
|
+
const unregisterIframe = registerIframe(iframe, host);
|
|
91
|
+
|
|
92
|
+
const destroy = () => {
|
|
93
|
+
if (!isOpen) return;
|
|
94
|
+
isOpen = false;
|
|
95
|
+
messageCleanup?.();
|
|
96
|
+
unregisterIframe();
|
|
97
|
+
overlay.remove();
|
|
98
|
+
document.removeEventListener("keydown", escHandler);
|
|
99
|
+
currentConfig.onClose?.();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Set up message listener with loading state handling
|
|
103
|
+
messageCleanup = setupMessageListener(
|
|
104
|
+
researchId,
|
|
105
|
+
{
|
|
106
|
+
get onReady() {
|
|
107
|
+
return () => {
|
|
108
|
+
loading.style.opacity = "0";
|
|
109
|
+
iframe.style.opacity = "1";
|
|
110
|
+
setTimeout(() => loading.remove(), 300);
|
|
111
|
+
currentConfig.onReady?.();
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
get onSubmit() {
|
|
115
|
+
return currentConfig.onSubmit;
|
|
116
|
+
},
|
|
117
|
+
get onNavigate() {
|
|
118
|
+
return currentConfig.onNavigate;
|
|
119
|
+
},
|
|
120
|
+
get onClose() {
|
|
121
|
+
return destroy;
|
|
122
|
+
},
|
|
123
|
+
get onError() {
|
|
124
|
+
return currentConfig.onError;
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
iframe,
|
|
128
|
+
host,
|
|
129
|
+
{ skipResize: true }
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Close handlers
|
|
133
|
+
closeBtn.addEventListener("click", destroy);
|
|
134
|
+
overlay.addEventListener("click", (e) => {
|
|
135
|
+
if (e.target === overlay) destroy();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ESC key closes
|
|
139
|
+
const escHandler = (e: KeyboardEvent) => {
|
|
140
|
+
if (e.key === "Escape") {
|
|
141
|
+
destroy();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
document.addEventListener("keydown", escHandler);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
unmount: destroy,
|
|
148
|
+
update: (options: Parameters<EmbedHandle["update"]>[0]) => {
|
|
149
|
+
currentConfig = { ...currentConfig, ...options };
|
|
150
|
+
},
|
|
151
|
+
destroy,
|
|
152
|
+
researchId,
|
|
153
|
+
type: "popup" as const,
|
|
154
|
+
iframe,
|
|
155
|
+
container: overlay,
|
|
156
|
+
};
|
|
157
|
+
}
|