@matterfact/embed 0.6.0 → 0.8.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.
- package/README.md +99 -1
- package/dist/chunk-PNSYFXXU.js +791 -0
- package/dist/chunk-PNSYFXXU.js.map +1 -0
- package/dist/chunk-R2ZEJARX.js +776 -0
- package/dist/chunk-R2ZEJARX.js.map +1 -0
- package/dist/{chunk-4Q2ROXLR.js → chunk-UD7CAQXV.js} +2 -2
- package/dist/{chunk-7I37ZFAJ.js → chunk-UQCETVRF.js} +2 -2
- package/dist/chunk-W52Q7G4J.js +3 -0
- package/dist/chunk-W52Q7G4J.js.map +7 -0
- package/dist/context-ARBB2XD6.js +3 -0
- package/dist/{context-YX2KXFLI.js.map → context-ARBB2XD6.js.map} +1 -1
- package/dist/{context-WU2F5CBC.js → context-MVGSYIMB.js} +2 -2
- package/dist/embed.js +1 -1
- package/dist/embed.js.map +4 -4
- package/dist/index.cjs +986 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +140 -31
- package/dist/index.d.ts +140 -31
- package/dist/index.js +542 -130
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1012 -165
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +33 -1
- package/dist/react.d.ts +33 -1
- package/dist/react.js +561 -132
- package/dist/react.js.map +1 -1
- package/dist/{snapshot-MUXE7KXX.js → snapshot-GL4YBMXD.js} +3 -3
- package/dist/{snapshot-MUXE7KXX.js.map → snapshot-GL4YBMXD.js.map} +1 -1
- package/dist/{snapshot-JOGZWESK.js → snapshot-UGTXZVB6.js} +2 -2
- package/examples/embed-demo/.env.example +5 -1
- package/examples/embed-demo/README.md +37 -0
- package/examples/embed-demo/package.json +1 -1
- package/examples/embed-demo/src/App.tsx +82 -44
- package/examples/embed-demo/src/HoistDemo.tsx +390 -0
- package/examples/embed-demo/src/config.ts +34 -4
- package/examples/embed-demo/src/main.tsx +7 -0
- package/examples/embed-demo/src/mockXH.ts +492 -0
- package/examples/embed-demo/src/placement.tsx +43 -0
- package/examples/embed-demo/src/styles.css +147 -2
- package/examples/embed-demo/vite.config.ts +2 -2
- package/package.json +1 -1
- package/dist/chunk-AXKXNYRT.js +0 -381
- package/dist/chunk-AXKXNYRT.js.map +0 -1
- package/dist/chunk-D7R6WVHG.js +0 -2
- package/dist/chunk-D7R6WVHG.js.map +0 -7
- package/dist/chunk-RS6RMZ77.js +0 -396
- package/dist/chunk-RS6RMZ77.js.map +0 -1
- package/dist/context-YX2KXFLI.js +0 -3
- /package/dist/{chunk-4Q2ROXLR.js.map → chunk-UD7CAQXV.js.map} +0 -0
- /package/dist/{chunk-7I37ZFAJ.js.map → chunk-UQCETVRF.js.map} +0 -0
- /package/dist/{context-WU2F5CBC.js.map → context-MVGSYIMB.js.map} +0 -0
- /package/dist/{snapshot-JOGZWESK.js.map → snapshot-UGTXZVB6.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/protocol.ts
|
|
2
|
-
var PROTOCOL_VERSION =
|
|
2
|
+
var PROTOCOL_VERSION = 2;
|
|
3
3
|
var CHANNEL = "mf-embed";
|
|
4
4
|
function envelope(payload, id) {
|
|
5
5
|
return {
|
|
@@ -13,6 +13,213 @@ function isEnvelope(data) {
|
|
|
13
13
|
return typeof data === "object" && data !== null && data.channel === CHANNEL;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// src/geometry.ts
|
|
17
|
+
var MARGIN = 20;
|
|
18
|
+
var LAUNCHER = 56;
|
|
19
|
+
var SNAP_RADIUS = 96;
|
|
20
|
+
var TAB_W = 44;
|
|
21
|
+
var TAB_H = 132;
|
|
22
|
+
var EDGE_DOCK = 64;
|
|
23
|
+
var DEFAULT_FLOAT_W = 420;
|
|
24
|
+
var DEFAULT_FLOAT_H = 640;
|
|
25
|
+
var MIN_FLOAT_W = 320;
|
|
26
|
+
var MIN_FLOAT_H = 420;
|
|
27
|
+
var DEFAULT_DOCK_W = 600;
|
|
28
|
+
var MIN_DOCK_W = 320;
|
|
29
|
+
var CORNERS = ["tl", "tr", "bl", "br"];
|
|
30
|
+
function defaultGeometry() {
|
|
31
|
+
return {
|
|
32
|
+
// Dock is the default: a side panel reads as part of the host app.
|
|
33
|
+
mode: "dock",
|
|
34
|
+
corner: "br",
|
|
35
|
+
x: 0,
|
|
36
|
+
y: 0,
|
|
37
|
+
floatW: DEFAULT_FLOAT_W,
|
|
38
|
+
floatH: DEFAULT_FLOAT_H,
|
|
39
|
+
dockSide: "right",
|
|
40
|
+
tabY: 0,
|
|
41
|
+
// 0 => centre it (see tabTop)
|
|
42
|
+
dockW: DEFAULT_DOCK_W
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function clamp(n, lo, hi) {
|
|
46
|
+
return Math.min(Math.max(n, lo), hi);
|
|
47
|
+
}
|
|
48
|
+
function cornerOrigin(corner, w, h, vw, vh) {
|
|
49
|
+
return {
|
|
50
|
+
x: corner[1] === "l" ? MARGIN : vw - MARGIN - w,
|
|
51
|
+
y: corner[0] === "t" ? MARGIN : vh - MARGIN - h
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function launcherOrigin(g, vw, vh) {
|
|
55
|
+
if (g.corner) return cornerOrigin(g.corner, LAUNCHER, LAUNCHER, vw, vh);
|
|
56
|
+
return {
|
|
57
|
+
x: clamp(g.x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER)),
|
|
58
|
+
y: clamp(g.y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER))
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function growthCorner(g, vw, vh) {
|
|
62
|
+
if (g.corner) return g.corner;
|
|
63
|
+
const o = launcherOrigin(g, vw, vh);
|
|
64
|
+
return nearestCorner(o.x + LAUNCHER / 2, o.y + LAUNCHER / 2, vw, vh);
|
|
65
|
+
}
|
|
66
|
+
function nearestCorner(cx, cy, vw, vh) {
|
|
67
|
+
return `${cy < vh / 2 ? "t" : "b"}${cx < vw / 2 ? "l" : "r"}`;
|
|
68
|
+
}
|
|
69
|
+
function snapOrFree(x, y, vw, vh) {
|
|
70
|
+
const cx = clamp(x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER));
|
|
71
|
+
const cy = clamp(y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER));
|
|
72
|
+
for (const corner of CORNERS) {
|
|
73
|
+
const o = cornerOrigin(corner, LAUNCHER, LAUNCHER, vw, vh);
|
|
74
|
+
if (Math.hypot(cx - o.x, cy - o.y) <= SNAP_RADIUS) {
|
|
75
|
+
return { corner, x: cx, y: cy };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { corner: null, x: cx, y: cy };
|
|
79
|
+
}
|
|
80
|
+
function edgeDock(x, w, vw) {
|
|
81
|
+
if (x <= EDGE_DOCK) return "left";
|
|
82
|
+
if (x + w >= vw - EDGE_DOCK) return "right";
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function dragAnchor(g, vw, vh) {
|
|
86
|
+
if (g.mode === "dock") {
|
|
87
|
+
return { x: g.dockSide === "left" ? 0 : vw - TAB_W, y: tabTop(g, vh) };
|
|
88
|
+
}
|
|
89
|
+
return launcherOrigin(g, vw, vh);
|
|
90
|
+
}
|
|
91
|
+
function visibleBox(g, open, vw, vh) {
|
|
92
|
+
if (g.mode === "dock") {
|
|
93
|
+
const w2 = open ? Math.min(Math.max(g.dockW, MIN_DOCK_W), Math.max(0, vw - 2 * MARGIN)) : TAB_W;
|
|
94
|
+
return { x: g.dockSide === "left" ? 0 : vw - w2, w: w2 };
|
|
95
|
+
}
|
|
96
|
+
const o = launcherOrigin(g, vw, vh);
|
|
97
|
+
if (!open) return { x: o.x, w: LAUNCHER };
|
|
98
|
+
const { w } = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);
|
|
99
|
+
return {
|
|
100
|
+
x: growthCorner(g, vw, vh)[1] === "r" ? o.x + LAUNCHER - w : o.x,
|
|
101
|
+
w
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function placeDrop(anchorX, anchorY, boxLeft, boxW, vw, vh) {
|
|
105
|
+
const snapped = snapOrFree(anchorX, anchorY, vw, vh);
|
|
106
|
+
if (snapped.corner) return { mode: "float", ...snapped };
|
|
107
|
+
const side = edgeDock(boxLeft, boxW, vw);
|
|
108
|
+
if (side) {
|
|
109
|
+
return {
|
|
110
|
+
mode: "dock",
|
|
111
|
+
dockSide: side,
|
|
112
|
+
tabY: clamp(anchorY, 0, Math.max(0, vh - TAB_H))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { mode: "float", ...snapped };
|
|
116
|
+
}
|
|
117
|
+
function clampSize(w, h, vw, vh, minW, minH) {
|
|
118
|
+
return {
|
|
119
|
+
w: Math.min(Math.max(w, minW), Math.max(0, vw - 2 * MARGIN)),
|
|
120
|
+
h: Math.min(Math.max(h, minH), Math.max(0, vh - 2 * MARGIN))
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function applyResizeDelta(corner, startW, startH, dx, dy) {
|
|
124
|
+
const wSign = corner[1] === "r" ? -1 : 1;
|
|
125
|
+
const hSign = corner[0] === "b" ? -1 : 1;
|
|
126
|
+
return { w: startW + wSign * dx, h: startH + hSign * dy };
|
|
127
|
+
}
|
|
128
|
+
function applyDockResizeDelta(side, startW, dx) {
|
|
129
|
+
return side === "right" ? startW - dx : startW + dx;
|
|
130
|
+
}
|
|
131
|
+
function parseGeometry(raw) {
|
|
132
|
+
if (!raw) return null;
|
|
133
|
+
try {
|
|
134
|
+
const p = JSON.parse(raw);
|
|
135
|
+
if (!p || p.mode !== "float" && p.mode !== "dock") return null;
|
|
136
|
+
const d = defaultGeometry();
|
|
137
|
+
const num = (v, dv) => typeof v === "number" ? v : dv;
|
|
138
|
+
return {
|
|
139
|
+
mode: p.mode,
|
|
140
|
+
corner: CORNERS.includes(p.corner) ? p.corner : null,
|
|
141
|
+
x: num(p.x, d.x),
|
|
142
|
+
y: num(p.y, d.y),
|
|
143
|
+
floatW: num(p.floatW, d.floatW),
|
|
144
|
+
floatH: num(p.floatH, d.floatH),
|
|
145
|
+
dockSide: p.dockSide === "left" ? "left" : "right",
|
|
146
|
+
tabY: num(p.tabY, d.tabY),
|
|
147
|
+
dockW: num(p.dockW, d.dockW)
|
|
148
|
+
};
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function serializeGeometry(g) {
|
|
154
|
+
return JSON.stringify(g);
|
|
155
|
+
}
|
|
156
|
+
var px = (n) => `${n}px`;
|
|
157
|
+
function boxAt(ox, oy, w, h, corner, vw, vh) {
|
|
158
|
+
const right = corner[1] === "r";
|
|
159
|
+
const bottom = corner[0] === "b";
|
|
160
|
+
const rawX = right ? vw - (ox + LAUNCHER) : ox;
|
|
161
|
+
const rawY = bottom ? vh - (oy + LAUNCHER) : oy;
|
|
162
|
+
const x = clamp(rawX, MARGIN, Math.max(MARGIN, vw - MARGIN - w));
|
|
163
|
+
const y = clamp(rawY, MARGIN, Math.max(MARGIN, vh - MARGIN - h));
|
|
164
|
+
return {
|
|
165
|
+
top: bottom ? "auto" : px(y),
|
|
166
|
+
bottom: bottom ? px(y) : "auto",
|
|
167
|
+
left: right ? "auto" : px(x),
|
|
168
|
+
right: right ? px(x) : "auto",
|
|
169
|
+
width: px(w),
|
|
170
|
+
height: px(h)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function tabTop(g, vh) {
|
|
174
|
+
const max = Math.max(0, vh - TAB_H);
|
|
175
|
+
return clamp(g.tabY || Math.round((vh - TAB_H) / 2), 0, max);
|
|
176
|
+
}
|
|
177
|
+
function tabBox(g, vw, vh) {
|
|
178
|
+
const left = g.dockSide === "left";
|
|
179
|
+
return {
|
|
180
|
+
top: px(tabTop(g, vh)),
|
|
181
|
+
bottom: "auto",
|
|
182
|
+
left: left ? "0px" : "auto",
|
|
183
|
+
right: left ? "auto" : "0px",
|
|
184
|
+
width: px(TAB_W),
|
|
185
|
+
height: px(TAB_H)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function floatChrome(g, w, h, vw, vh, growth) {
|
|
189
|
+
const o = launcherOrigin(g, vw, vh);
|
|
190
|
+
return boxAt(o.x, o.y, w, h, growth ?? growthCorner(g, vw, vh), vw, vh);
|
|
191
|
+
}
|
|
192
|
+
function dockChrome(g, w, h, vw, vh) {
|
|
193
|
+
const left = g.dockSide === "left";
|
|
194
|
+
return {
|
|
195
|
+
top: px(tabTop(g, vh)),
|
|
196
|
+
bottom: "auto",
|
|
197
|
+
left: left ? "0px" : "auto",
|
|
198
|
+
right: left ? "auto" : "0px",
|
|
199
|
+
width: px(w),
|
|
200
|
+
height: px(h)
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function floatBox(g, vw, vh, growth) {
|
|
204
|
+
const f = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);
|
|
205
|
+
return floatChrome(g, f.w, f.h, vw, vh, growth);
|
|
206
|
+
}
|
|
207
|
+
function dockBox(g, vw, _vh) {
|
|
208
|
+
const w = Math.min(
|
|
209
|
+
Math.max(g.dockW, MIN_DOCK_W),
|
|
210
|
+
Math.max(0, vw - 2 * MARGIN)
|
|
211
|
+
);
|
|
212
|
+
const left = g.dockSide === "left";
|
|
213
|
+
return {
|
|
214
|
+
top: "0px",
|
|
215
|
+
bottom: "0px",
|
|
216
|
+
height: "auto",
|
|
217
|
+
left: left ? "0px" : "auto",
|
|
218
|
+
right: left ? "auto" : "0px",
|
|
219
|
+
width: px(w)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
16
223
|
// src/loader.ts
|
|
17
224
|
var DEFAULT_ORIGIN = "https://app.matterfact.com";
|
|
18
225
|
function devRequested() {
|
|
@@ -46,6 +253,10 @@ function readConfig() {
|
|
|
46
253
|
pageContext: pageContextAttr !== "off" && pageContextAttr !== "false" && pageContextAttr !== "0"
|
|
47
254
|
// No `data-dev` — see the `dev` field's doc comment: the URL trigger is the
|
|
48
255
|
// point for the script-tag path, so there is deliberately no script-tag knob here.
|
|
256
|
+
// No `data-actions` either: the action policy lives entirely in the lazy chunk,
|
|
257
|
+
// read straight off `window.matterfact.hoist.actions` by hoist-runtime.ts's
|
|
258
|
+
// readActionsConfig() — see LoaderConfig's class doc and MatterfactAgentProps.actions
|
|
259
|
+
// (react.tsx) for the one remaining way to SET that global programmatically.
|
|
49
260
|
};
|
|
50
261
|
}
|
|
51
262
|
var POS_KEY = "mf.embed.pos";
|
|
@@ -58,15 +269,42 @@ var EmbedHost = class {
|
|
|
58
269
|
this.queue = [];
|
|
59
270
|
this.ready = false;
|
|
60
271
|
this.open = false;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
272
|
+
/** The persisted box: launcher corner, float size, dock state/side/width. */
|
|
273
|
+
this.geo = defaultGeometry();
|
|
274
|
+
/** Float size at resizeStart; resizeMove deltas apply against it (they're cumulative). */
|
|
275
|
+
this.resizeBase = null;
|
|
276
|
+
/** Docked width at resizeStart — the docked panel resizes in one axis only. */
|
|
277
|
+
this.dockBase = DEFAULT_DOCK_W;
|
|
278
|
+
/** The widget's logical anchor when the drag began, and where it is now. Deltas arrive
|
|
279
|
+
* cumulative from the press, so the current spot is simply origin + delta — no need to
|
|
280
|
+
* read the box back, which stops being the thing we're moving the moment it docks. */
|
|
281
|
+
this.dragFrom = null;
|
|
282
|
+
this.dragAt = { x: 0, y: 0 };
|
|
283
|
+
/** The painted box when the drag began — the edge test measures what you can SEE
|
|
284
|
+
* touching the screen edge, which is not the anchor once the panel is open. */
|
|
285
|
+
this.dragBox = { x: 0, w: 0 };
|
|
286
|
+
/** Growth direction frozen for the drag — see floatChrome's `growth` param. */
|
|
287
|
+
this.dragGrowth = null;
|
|
288
|
+
/**
|
|
289
|
+
* Has this drag been clear of the dock band yet?
|
|
290
|
+
*
|
|
291
|
+
* The resting position is INSIDE the band — a corner-anchored widget sits MARGIN
|
|
292
|
+
* (20px) from the edge, well within EDGE_DOCK (64px) — so without this, picking one
|
|
293
|
+
* up docked it instantly, before it had moved anywhere. Docking now requires
|
|
294
|
+
* ENTERING the band rather than merely starting in it.
|
|
295
|
+
*/
|
|
296
|
+
this.dragLeftBand = false;
|
|
66
297
|
/** Loaded on first open. Holds everything that touches the customer's DOM. */
|
|
67
298
|
this.context = null;
|
|
68
299
|
/** The host element; kept so `destroy()` can remove it (React lifecycle). */
|
|
69
300
|
this.hostEl = null;
|
|
301
|
+
/** Host-auth storm guard (see provideAuth). Counts getAuthToken calls; past a cap we stop
|
|
302
|
+
* calling the provider, rather than joining the storm. This host instance OUTLIVES iframe
|
|
303
|
+
* reloads, so — unlike anything in the widget — the counter survives the very reload loop
|
|
304
|
+
* that drives the storm. (Kept tiny on purpose: this file has a hard per-page size budget;
|
|
305
|
+
* the widget side carries the windowed/retry-after-settle half.) Terse name = fewer bytes
|
|
306
|
+
* in the stub; it is the host-auth request count. */
|
|
307
|
+
this.ac = 0;
|
|
70
308
|
/**
|
|
71
309
|
* Every message is checked twice, on every single message — not once at setup.
|
|
72
310
|
*
|
|
@@ -78,87 +316,10 @@ var EmbedHost = class {
|
|
|
78
316
|
if (event.origin !== this.config.origin) return;
|
|
79
317
|
if (event.source !== this.iframe?.contentWindow) return;
|
|
80
318
|
if (!isEnvelope(event.data)) return;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.send({
|
|
86
|
-
type: "host.ready",
|
|
87
|
-
protocol: PROTOCOL_VERSION,
|
|
88
|
-
origin: location.origin
|
|
89
|
-
});
|
|
90
|
-
this.flush();
|
|
91
|
-
if (this.inline) void this.loadContext();
|
|
92
|
-
break;
|
|
93
|
-
case "widget.setOpen":
|
|
94
|
-
if (this.inline) break;
|
|
95
|
-
this.open = msg.open;
|
|
96
|
-
this.iframe?.setAttribute("data-open", String(msg.open));
|
|
97
|
-
if (!msg.open) {
|
|
98
|
-
this.expanded = false;
|
|
99
|
-
this.iframe?.setAttribute("data-expanded", "false");
|
|
100
|
-
}
|
|
101
|
-
if (msg.open) void this.loadContext();
|
|
102
|
-
break;
|
|
103
|
-
case "widget.setExpanded":
|
|
104
|
-
if (this.inline) break;
|
|
105
|
-
this.expanded = msg.expanded;
|
|
106
|
-
this.iframe?.setAttribute("data-expanded", String(msg.expanded));
|
|
107
|
-
if (this.iframe) this.iframe.style.height = "";
|
|
108
|
-
break;
|
|
109
|
-
case "widget.resize":
|
|
110
|
-
if (this.inline) break;
|
|
111
|
-
if (this.iframe && this.open && !this.expanded) {
|
|
112
|
-
this.iframe.style.height = `${Math.min(msg.height, window.innerHeight - 40)}px`;
|
|
113
|
-
}
|
|
114
|
-
break;
|
|
115
|
-
case "widget.requestContext":
|
|
116
|
-
void this.loadContext().then((m) => m.provideContext());
|
|
117
|
-
break;
|
|
118
|
-
case "widget.requestSnapshot":
|
|
119
|
-
void this.loadContext().then((m) => m.sendSnapshot(this.send));
|
|
120
|
-
break;
|
|
121
|
-
case "widget.readRegion":
|
|
122
|
-
void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));
|
|
123
|
-
break;
|
|
124
|
-
case "widget.callTool":
|
|
125
|
-
void this.loadContext().then((m) => m.callTool(msg.call, this.send));
|
|
126
|
-
break;
|
|
127
|
-
// Everything below is LAUNCHER chrome: there is no launcher inline (the widget
|
|
128
|
-
// doesn't draw one), and moving or hiding the host's own panel from inside it
|
|
129
|
-
// would be us redecorating their app. An older cached loader could still be told
|
|
130
|
-
// any of these by a newer widget, so they're guarded rather than assumed absent.
|
|
131
|
-
case "widget.dragStart":
|
|
132
|
-
if (this.inline) break;
|
|
133
|
-
this.dragBase = this.cornerOffset();
|
|
134
|
-
break;
|
|
135
|
-
case "widget.dragMove":
|
|
136
|
-
if (this.inline) break;
|
|
137
|
-
this.dragTo(msg.dx, msg.dy);
|
|
138
|
-
break;
|
|
139
|
-
case "widget.dragEnd":
|
|
140
|
-
if (this.inline) break;
|
|
141
|
-
this.dragBase = null;
|
|
142
|
-
this.writePos(this.pos);
|
|
143
|
-
break;
|
|
144
|
-
case "widget.setMenu":
|
|
145
|
-
if (this.inline) break;
|
|
146
|
-
if (this.iframe && !this.open) {
|
|
147
|
-
this.iframe.style.height = msg.open ? `${64 + msg.height}px` : "";
|
|
148
|
-
}
|
|
149
|
-
break;
|
|
150
|
-
case "widget.resetPos":
|
|
151
|
-
if (this.inline) break;
|
|
152
|
-
this.resetPos();
|
|
153
|
-
break;
|
|
154
|
-
case "widget.hide":
|
|
155
|
-
if (this.inline) break;
|
|
156
|
-
if (this.hostEl) this.hostEl.style.display = "none";
|
|
157
|
-
break;
|
|
158
|
-
case "widget.needsAuth":
|
|
159
|
-
void this.provideAuth();
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
319
|
+
this.handle(event.data.payload);
|
|
320
|
+
};
|
|
321
|
+
this.onViewportResize = () => {
|
|
322
|
+
this.place();
|
|
162
323
|
};
|
|
163
324
|
this.send = (msg) => {
|
|
164
325
|
if (!this.ready) {
|
|
@@ -184,34 +345,36 @@ var EmbedHost = class {
|
|
|
184
345
|
].join(";");
|
|
185
346
|
this.config.container.appendChild(host);
|
|
186
347
|
} else {
|
|
187
|
-
this.
|
|
348
|
+
this.geo = this.readGeometry();
|
|
188
349
|
host.style.cssText = [
|
|
189
350
|
"all: initial",
|
|
190
351
|
"position: fixed",
|
|
191
|
-
`right: ${this.pos ? this.pos.right : 20}px`,
|
|
192
|
-
`bottom: ${this.pos ? this.pos.bottom : 20}px`,
|
|
193
352
|
// Below the max so a host that genuinely needs to cover us (a modal, a cookie
|
|
194
353
|
// banner they are legally obliged to show) still can.
|
|
195
354
|
"z-index: 2147483000",
|
|
196
|
-
"contain: layout style"
|
|
355
|
+
"contain: layout style",
|
|
356
|
+
// The host element owns the BOX now (position AND size); the iframe fills it.
|
|
357
|
+
// Transitioning the box is what makes the corner snap and the open/dock read as
|
|
358
|
+
// motion rather than a jump. Suppressed during drag/resize (see those handlers).
|
|
359
|
+
"transition: width .18s ease, height .18s ease, top .18s ease, right .18s ease, bottom .18s ease, left .18s ease"
|
|
197
360
|
].join(";");
|
|
198
361
|
document.body.appendChild(host);
|
|
199
362
|
}
|
|
200
363
|
this.shadow = host.attachShadow({ mode: "closed" });
|
|
201
364
|
const style = document.createElement("style");
|
|
202
|
-
style.textContent = ":host{all:initial}iframe{border:0;display:block;
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
'iframe{border-radius:12px;box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029
|
|
365
|
+
style.textContent = ":host{all:initial}iframe{border:0;display:block;width:100%;height:100%;background:transparent}" + (this.inline ? "" : (
|
|
366
|
+
// color-scheme only on the OPEN panel — on a collapsed launcher it makes the
|
|
367
|
+
// UA paint an opaque canvas base behind our shapes.
|
|
368
|
+
':host([data-mode="float"]) iframe,:host([data-mode="dock"]) iframe{color-scheme:light dark}:host([data-mode="float"]) iframe{border-radius:12px;box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}:host([data-mode="dock"]) iframe{box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029}:host([data-mode="dock"][data-flush="right"]) iframe{border-radius:12px 0 0 12px}:host([data-mode="dock"][data-flush="left"]) iframe{border-radius:0 12px 12px 0}'
|
|
206
369
|
));
|
|
207
370
|
this.shadow.appendChild(style);
|
|
208
371
|
const iframe = document.createElement("iframe");
|
|
209
372
|
iframe.title = "matterfact assistant";
|
|
210
373
|
iframe.setAttribute(
|
|
211
374
|
"sandbox",
|
|
212
|
-
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
375
|
+
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
|
|
213
376
|
);
|
|
214
|
-
iframe.setAttribute("allow", "microphone");
|
|
377
|
+
iframe.setAttribute("allow", "microphone; clipboard-write");
|
|
215
378
|
iframe.src = `${this.config.origin}/embed/chat?k=${encodeURIComponent(
|
|
216
379
|
this.config.publishableKey
|
|
217
380
|
)}&o=${encodeURIComponent(location.origin)}` + (this.config.surface ? `&s=${encodeURIComponent(this.config.surface)}` : "") + // Either trigger works: the URL param (no redeploy needed) OR the config's
|
|
@@ -220,6 +383,223 @@ var EmbedHost = class {
|
|
|
220
383
|
this.iframe = iframe;
|
|
221
384
|
this.shadow.appendChild(iframe);
|
|
222
385
|
window.addEventListener("message", this.onMessage);
|
|
386
|
+
if (!this.inline) {
|
|
387
|
+
window.addEventListener("resize", this.onViewportResize);
|
|
388
|
+
this.place();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a
|
|
393
|
+
* source-valid MessageEvent — this routes a message through the identical logic
|
|
394
|
+
* without weakening the origin/source checks above, which stay the only real door.
|
|
395
|
+
*/
|
|
396
|
+
__testHandle(msg) {
|
|
397
|
+
this.handle(msg);
|
|
398
|
+
}
|
|
399
|
+
handle(msg) {
|
|
400
|
+
switch (msg.type) {
|
|
401
|
+
case "widget.ready":
|
|
402
|
+
this.ready = true;
|
|
403
|
+
this.send({
|
|
404
|
+
type: "host.ready",
|
|
405
|
+
protocol: PROTOCOL_VERSION,
|
|
406
|
+
origin: location.origin
|
|
407
|
+
});
|
|
408
|
+
this.send({ type: "host.theme", mode: this.themeMode() });
|
|
409
|
+
this.flush();
|
|
410
|
+
if (this.inline) void this.loadContext();
|
|
411
|
+
break;
|
|
412
|
+
case "widget.setOpen":
|
|
413
|
+
if (this.inline) break;
|
|
414
|
+
this.open = msg.open;
|
|
415
|
+
this.place();
|
|
416
|
+
if (msg.open) void this.loadContext();
|
|
417
|
+
break;
|
|
418
|
+
case "widget.setMode":
|
|
419
|
+
if (this.inline) break;
|
|
420
|
+
this.geo.mode = msg.mode;
|
|
421
|
+
this.writeGeometry(this.geo);
|
|
422
|
+
this.place();
|
|
423
|
+
break;
|
|
424
|
+
case "widget.setDockSide":
|
|
425
|
+
if (this.inline) break;
|
|
426
|
+
this.geo.dockSide = msg.side;
|
|
427
|
+
this.writeGeometry(this.geo);
|
|
428
|
+
this.place();
|
|
429
|
+
break;
|
|
430
|
+
case "widget.snapCorner":
|
|
431
|
+
if (this.inline) break;
|
|
432
|
+
this.geo.corner = msg.corner;
|
|
433
|
+
this.writeGeometry(this.geo);
|
|
434
|
+
this.place();
|
|
435
|
+
break;
|
|
436
|
+
case "widget.setLauncherRegion": {
|
|
437
|
+
if (this.inline || this.open) break;
|
|
438
|
+
const vw = window.innerWidth;
|
|
439
|
+
const vh = window.innerHeight;
|
|
440
|
+
if (msg.w <= LAUNCHER && msg.h <= LAUNCHER) {
|
|
441
|
+
this.place();
|
|
442
|
+
} else {
|
|
443
|
+
const region = this.geo.mode === "dock" ? dockChrome(this.geo, msg.w, msg.h, vw, vh) : floatChrome(this.geo, msg.w, msg.h, vw, vh);
|
|
444
|
+
this.applyBox(region, msg.h > LAUNCHER ? "menu" : "pill");
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
case "widget.resize":
|
|
449
|
+
if (this.inline) break;
|
|
450
|
+
if (this.hostEl && this.open && this.geo.mode === "float") {
|
|
451
|
+
this.hostEl.style.height = `${Math.min(
|
|
452
|
+
msg.height,
|
|
453
|
+
Math.max(0, window.innerHeight - 2 * MARGIN)
|
|
454
|
+
)}px`;
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
case "widget.resizeStart":
|
|
458
|
+
if (this.inline) break;
|
|
459
|
+
this.resizeBase = { w: this.geo.floatW, h: this.geo.floatH };
|
|
460
|
+
this.dockBase = this.geo.dockW;
|
|
461
|
+
if (this.hostEl) this.hostEl.style.transition = "none";
|
|
462
|
+
break;
|
|
463
|
+
case "widget.resizeMove": {
|
|
464
|
+
if (this.inline || !this.resizeBase) break;
|
|
465
|
+
if (this.geo.mode === "dock") {
|
|
466
|
+
const w = applyDockResizeDelta(
|
|
467
|
+
this.geo.dockSide,
|
|
468
|
+
this.dockBase,
|
|
469
|
+
msg.dx
|
|
470
|
+
);
|
|
471
|
+
this.geo.dockW = Math.min(
|
|
472
|
+
Math.max(w, MIN_DOCK_W),
|
|
473
|
+
Math.max(0, window.innerWidth - 2 * MARGIN)
|
|
474
|
+
);
|
|
475
|
+
} else {
|
|
476
|
+
const next = applyResizeDelta(
|
|
477
|
+
growthCorner(this.geo, window.innerWidth, window.innerHeight),
|
|
478
|
+
this.resizeBase.w,
|
|
479
|
+
this.resizeBase.h,
|
|
480
|
+
msg.dx,
|
|
481
|
+
msg.dy
|
|
482
|
+
);
|
|
483
|
+
const fit = clampSize(
|
|
484
|
+
next.w,
|
|
485
|
+
next.h,
|
|
486
|
+
window.innerWidth,
|
|
487
|
+
window.innerHeight,
|
|
488
|
+
MIN_FLOAT_W,
|
|
489
|
+
MIN_FLOAT_H
|
|
490
|
+
);
|
|
491
|
+
this.geo.floatW = fit.w;
|
|
492
|
+
this.geo.floatH = fit.h;
|
|
493
|
+
}
|
|
494
|
+
this.place();
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
case "widget.resizeEnd":
|
|
498
|
+
if (this.inline) break;
|
|
499
|
+
this.resizeBase = null;
|
|
500
|
+
if (this.hostEl) this.hostEl.style.transition = "";
|
|
501
|
+
this.writeGeometry(this.geo);
|
|
502
|
+
break;
|
|
503
|
+
case "widget.requestContext":
|
|
504
|
+
void this.loadContext().then((m) => m.provideContext());
|
|
505
|
+
break;
|
|
506
|
+
case "widget.requestSnapshot":
|
|
507
|
+
void this.loadContext().then((m) => m.sendSnapshot(this.send));
|
|
508
|
+
break;
|
|
509
|
+
case "widget.readRegion":
|
|
510
|
+
void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));
|
|
511
|
+
break;
|
|
512
|
+
case "widget.callTool":
|
|
513
|
+
void this.loadContext().then((m) => m.callTool(msg.call, this.send));
|
|
514
|
+
break;
|
|
515
|
+
// Everything below is LAUNCHER chrome: there is no launcher inline (the widget
|
|
516
|
+
// doesn't draw one), and moving or hiding the host's own panel from inside it
|
|
517
|
+
// would be us redecorating their app. An older cached loader could still be told
|
|
518
|
+
// any of these by a newer widget, so they're guarded rather than assumed absent.
|
|
519
|
+
case "widget.dragStart":
|
|
520
|
+
if (this.inline) break;
|
|
521
|
+
this.dragFrom = dragAnchor(
|
|
522
|
+
this.geo,
|
|
523
|
+
window.innerWidth,
|
|
524
|
+
window.innerHeight
|
|
525
|
+
);
|
|
526
|
+
this.dragAt = { ...this.dragFrom };
|
|
527
|
+
this.dragLeftBand = false;
|
|
528
|
+
this.dragGrowth = growthCorner(
|
|
529
|
+
this.geo,
|
|
530
|
+
window.innerWidth,
|
|
531
|
+
window.innerHeight
|
|
532
|
+
);
|
|
533
|
+
this.dragBox = visibleBox(
|
|
534
|
+
this.geo,
|
|
535
|
+
this.open,
|
|
536
|
+
window.innerWidth,
|
|
537
|
+
window.innerHeight
|
|
538
|
+
);
|
|
539
|
+
if (this.hostEl) this.hostEl.style.transition = "none";
|
|
540
|
+
break;
|
|
541
|
+
case "widget.dragMove": {
|
|
542
|
+
if (this.inline || !this.dragFrom) break;
|
|
543
|
+
const vw = window.innerWidth;
|
|
544
|
+
this.dragAt = {
|
|
545
|
+
x: this.dragFrom.x + msg.dx,
|
|
546
|
+
y: this.dragFrom.y + msg.dy
|
|
547
|
+
};
|
|
548
|
+
const side = edgeDock(this.dragBox.x + msg.dx, this.dragBox.w, vw);
|
|
549
|
+
if (!side) this.dragLeftBand = true;
|
|
550
|
+
if (side && (this.dragLeftBand || this.geo.mode === "dock")) {
|
|
551
|
+
this.geo.mode = "dock";
|
|
552
|
+
this.geo.dockSide = side;
|
|
553
|
+
this.geo.tabY = Math.max(0, this.dragAt.y);
|
|
554
|
+
} else {
|
|
555
|
+
this.geo.mode = "float";
|
|
556
|
+
this.geo.corner = null;
|
|
557
|
+
this.geo.x = this.dragAt.x;
|
|
558
|
+
this.geo.y = this.dragAt.y;
|
|
559
|
+
}
|
|
560
|
+
this.place();
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case "widget.dragEnd": {
|
|
564
|
+
if (this.inline || !this.dragFrom) break;
|
|
565
|
+
const at = placeDrop(
|
|
566
|
+
this.dragAt.x,
|
|
567
|
+
this.dragAt.y,
|
|
568
|
+
this.dragBox.x + (this.dragAt.x - this.dragFrom.x),
|
|
569
|
+
this.dragBox.w,
|
|
570
|
+
window.innerWidth,
|
|
571
|
+
window.innerHeight
|
|
572
|
+
);
|
|
573
|
+
this.geo.mode = at.mode;
|
|
574
|
+
if (at.mode === "dock") {
|
|
575
|
+
this.geo.dockSide = at.dockSide;
|
|
576
|
+
this.geo.tabY = at.tabY;
|
|
577
|
+
} else {
|
|
578
|
+
this.geo.corner = at.corner;
|
|
579
|
+
this.geo.x = at.x;
|
|
580
|
+
this.geo.y = at.y;
|
|
581
|
+
}
|
|
582
|
+
this.dragFrom = null;
|
|
583
|
+
this.dragGrowth = null;
|
|
584
|
+
if (this.hostEl) this.hostEl.style.transition = "";
|
|
585
|
+
this.place();
|
|
586
|
+
this.writeGeometry(this.geo);
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
case "widget.resetPos":
|
|
590
|
+
if (this.inline) break;
|
|
591
|
+
this.geo = defaultGeometry();
|
|
592
|
+
this.writeGeometry(this.geo);
|
|
593
|
+
this.place();
|
|
594
|
+
break;
|
|
595
|
+
case "widget.hide":
|
|
596
|
+
if (this.inline) break;
|
|
597
|
+
if (this.hostEl) this.hostEl.style.display = "none";
|
|
598
|
+
break;
|
|
599
|
+
case "widget.needsAuth":
|
|
600
|
+
void this.provideAuth();
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
223
603
|
}
|
|
224
604
|
/**
|
|
225
605
|
* Own a drag for its lifetime.
|
|
@@ -233,47 +613,77 @@ var EmbedHost = class {
|
|
|
233
613
|
* We are the dumb half on purpose — see `widget.dragMove` in the protocol for why the
|
|
234
614
|
* widget has to own the gesture. All we do is take a delta and place the element.
|
|
235
615
|
*/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
616
|
+
/** Write a box onto the host element and stamp the mode the shadow CSS keys off. */
|
|
617
|
+
applyBox(box, mode) {
|
|
618
|
+
if (!this.hostEl || this.inline) return;
|
|
619
|
+
const s = this.hostEl.style;
|
|
620
|
+
s.top = box.top;
|
|
621
|
+
s.right = box.right;
|
|
622
|
+
s.bottom = box.bottom;
|
|
623
|
+
s.left = box.left;
|
|
624
|
+
s.width = box.width;
|
|
625
|
+
s.height = box.height;
|
|
626
|
+
this.hostEl.dataset.mode = mode;
|
|
627
|
+
if (mode === "tab" || mode === "dock")
|
|
628
|
+
this.hostEl.dataset.flush = this.geo.dockSide;
|
|
629
|
+
else delete this.hostEl.dataset.flush;
|
|
242
630
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
631
|
+
/** Re-apply the box for whatever state we're in: collapsed launcher, float, or dock. */
|
|
632
|
+
/**
|
|
633
|
+
* The growth direction to use RIGHT NOW — frozen while a drag is in flight.
|
|
634
|
+
*
|
|
635
|
+
* Every consumer has to agree on this, which is the bug that made the widget
|
|
636
|
+
* flip-flop under small movements near the viewport centre: the box was frozen but
|
|
637
|
+
* `host.geometry` still published a freshly-derived growth, so the WIDGET kept
|
|
638
|
+
* swapping which corner it drew against (and it animates that), twitching back and
|
|
639
|
+
* forth while the loader held perfectly still.
|
|
640
|
+
*/
|
|
641
|
+
growthNow() {
|
|
642
|
+
return this.dragGrowth ?? growthCorner(this.geo, window.innerWidth, window.innerHeight);
|
|
253
643
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.
|
|
258
|
-
|
|
259
|
-
this.
|
|
260
|
-
|
|
644
|
+
place() {
|
|
645
|
+
const vw = window.innerWidth;
|
|
646
|
+
const vh = window.innerHeight;
|
|
647
|
+
if (!this.open) {
|
|
648
|
+
const dock = this.geo.mode === "dock";
|
|
649
|
+
this.applyBox(
|
|
650
|
+
dock ? tabBox(this.geo, vw, vh) : floatChrome(this.geo, LAUNCHER, LAUNCHER, vw, vh, this.growthNow()),
|
|
651
|
+
dock ? "tab" : "launcher"
|
|
652
|
+
);
|
|
653
|
+
} else if (this.geo.mode === "dock") {
|
|
654
|
+
this.applyBox(dockBox(this.geo, vw), "dock");
|
|
655
|
+
} else {
|
|
656
|
+
this.applyBox(floatBox(this.geo, vw, vh, this.growthNow()), "float");
|
|
261
657
|
}
|
|
658
|
+
this.publishGeometry();
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Tell the widget where it is. It can't see the viewport and doesn't own the box, so
|
|
662
|
+
* without this it draws its launcher against a stale guess — which is what made the
|
|
663
|
+
* circle jump when the launcher sat on the left (see `host.geometry` in the protocol).
|
|
664
|
+
*/
|
|
665
|
+
publishGeometry() {
|
|
666
|
+
this.send({
|
|
667
|
+
type: "host.geometry",
|
|
668
|
+
mode: this.geo.mode,
|
|
669
|
+
dockSide: this.geo.dockSide,
|
|
670
|
+
growth: this.growthNow()
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
/** The theme to publish: an explicit data-theme wins, else the OS preference. */
|
|
674
|
+
themeMode() {
|
|
675
|
+
return this.config.theme === "auto" ? matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this.config.theme;
|
|
262
676
|
}
|
|
263
|
-
|
|
677
|
+
readGeometry() {
|
|
264
678
|
try {
|
|
265
|
-
|
|
266
|
-
if (!raw) return null;
|
|
267
|
-
const p = JSON.parse(raw);
|
|
268
|
-
return typeof p?.right === "number" && typeof p?.bottom === "number" ? { right: p.right, bottom: p.bottom } : null;
|
|
679
|
+
return parseGeometry(localStorage.getItem(POS_KEY)) ?? defaultGeometry();
|
|
269
680
|
} catch {
|
|
270
|
-
return
|
|
681
|
+
return defaultGeometry();
|
|
271
682
|
}
|
|
272
683
|
}
|
|
273
|
-
|
|
684
|
+
writeGeometry(g) {
|
|
274
685
|
try {
|
|
275
|
-
|
|
276
|
-
else localStorage.removeItem(POS_KEY);
|
|
686
|
+
localStorage.setItem(POS_KEY, serializeGeometry(g));
|
|
277
687
|
} catch {
|
|
278
688
|
}
|
|
279
689
|
}
|
|
@@ -297,6 +707,7 @@ var EmbedHost = class {
|
|
|
297
707
|
async provideAuth() {
|
|
298
708
|
const provider = this.config.authTokenProvider ?? globalThis.matterfact?.getEmbedAuthToken;
|
|
299
709
|
if (!provider) return;
|
|
710
|
+
if (++this.ac > 5) return;
|
|
300
711
|
try {
|
|
301
712
|
const token = await provider();
|
|
302
713
|
if (token) this.send({ type: "host.auth", token, expiresAt: 0 });
|
|
@@ -304,7 +715,7 @@ var EmbedHost = class {
|
|
|
304
715
|
}
|
|
305
716
|
}
|
|
306
717
|
loadContext() {
|
|
307
|
-
this.context ?? (this.context = import('./context-
|
|
718
|
+
this.context ?? (this.context = import('./context-ARBB2XD6.js').then((m) => {
|
|
308
719
|
m.start(
|
|
309
720
|
this.send,
|
|
310
721
|
this.config.origin,
|
|
@@ -328,6 +739,7 @@ var EmbedHost = class {
|
|
|
328
739
|
* calls this. */
|
|
329
740
|
destroy() {
|
|
330
741
|
window.removeEventListener("message", this.onMessage);
|
|
742
|
+
window.removeEventListener("resize", this.onViewportResize);
|
|
331
743
|
this.hostEl?.remove();
|
|
332
744
|
this.hostEl = null;
|
|
333
745
|
this.iframe = null;
|