@matterfact/embed 0.7.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 +978 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -31
- package/dist/index.d.ts +133 -31
- package/dist/index.js +534 -130
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1004 -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 +553 -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/README.md +37 -0
- package/examples/embed-demo/src/App.tsx +55 -21
- package/examples/embed-demo/src/HoistDemo.tsx +390 -0
- 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/react.cjs
CHANGED
|
@@ -22,6 +22,401 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
22
22
|
};
|
|
23
23
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
24
|
|
|
25
|
+
// src/redact.ts
|
|
26
|
+
function redact(text) {
|
|
27
|
+
let out = text;
|
|
28
|
+
for (const re of PII) out = out.replace(re, "[redacted]");
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
var PII;
|
|
32
|
+
var init_redact = __esm({
|
|
33
|
+
"src/redact.ts"() {
|
|
34
|
+
"use strict";
|
|
35
|
+
PII = [
|
|
36
|
+
/\b[\w.+-]+@[\w-]+\.[\w.]{2,}\b/g,
|
|
37
|
+
// email
|
|
38
|
+
/\b(?:\d[ -]?){13,19}\b/g,
|
|
39
|
+
// card-ish
|
|
40
|
+
/\b\d{3}[- ]?\d{2}[- ]?\d{4}\b/g,
|
|
41
|
+
// ssn — dashed, spaced, OR bare 9 digits
|
|
42
|
+
/\b\d{9,}\b/g,
|
|
43
|
+
// long bare digit runs: account / MRN / routing numbers
|
|
44
|
+
/(?:\+?\d{1,2}[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b/g,
|
|
45
|
+
// us phone
|
|
46
|
+
/\b(?:sk|pk|rk|ak)_(?:live|test)_[A-Za-z0-9]{8,}\b/g,
|
|
47
|
+
// stripe-style api keys
|
|
48
|
+
/\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g
|
|
49
|
+
// jwt
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// src/adapters/hoist.ts
|
|
55
|
+
function detectHoist(read) {
|
|
56
|
+
let rt;
|
|
57
|
+
try {
|
|
58
|
+
rt = read();
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (!rt) return null;
|
|
63
|
+
const worthwhile = !!rt.route.name || rt.grids.length > 0 || rt.charts.length > 0 || rt.nav.length > 0;
|
|
64
|
+
return worthwhile ? rt : null;
|
|
65
|
+
}
|
|
66
|
+
function quote(s, max = 120) {
|
|
67
|
+
const clean = redact(s.replace(/\s+/g, " ").trim());
|
|
68
|
+
const capped = clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
69
|
+
return `"${capped.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
70
|
+
}
|
|
71
|
+
function cell(value) {
|
|
72
|
+
const s = value == null ? "" : String(value);
|
|
73
|
+
return `- cell ${quote(s)}`;
|
|
74
|
+
}
|
|
75
|
+
function pushRow(row, cols, out, pad) {
|
|
76
|
+
out.push(`${pad}- row:`);
|
|
77
|
+
for (const c of cols) out.push(`${pad} ${cell(row[c.field])}`);
|
|
78
|
+
}
|
|
79
|
+
function renderGrid(grid, rowCap, out, depth) {
|
|
80
|
+
const pad = " ".repeat(depth);
|
|
81
|
+
out.push(`${pad}- table ${quote(grid.title || "grid")}:`);
|
|
82
|
+
out.push(`${pad} - rowgroup "columns":`);
|
|
83
|
+
for (const c of grid.columns)
|
|
84
|
+
out.push(`${pad} - columnheader ${quote(c.header || c.field)}`);
|
|
85
|
+
if (grid.selected.length) {
|
|
86
|
+
out.push(`${pad} - rowgroup "selected":`);
|
|
87
|
+
for (const r of grid.selected) pushRow(r, grid.columns, out, `${pad} `);
|
|
88
|
+
}
|
|
89
|
+
const shown = grid.rows.slice(0, Math.max(0, rowCap));
|
|
90
|
+
out.push(`${pad} - rowgroup "rows":`);
|
|
91
|
+
for (const r of shown) pushRow(r, grid.columns, out, `${pad} `);
|
|
92
|
+
out.push(
|
|
93
|
+
`${pad} - text: ${quote(`showing ${shown.length} of ${grid.totalCount} rows`)}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function seriesSummary(name, data) {
|
|
97
|
+
if (!data.length) return `series ${name}: 0 points`;
|
|
98
|
+
let min = data[0];
|
|
99
|
+
let max = data[0];
|
|
100
|
+
for (const n of data) {
|
|
101
|
+
if (n < min) min = n;
|
|
102
|
+
if (n > max) max = n;
|
|
103
|
+
}
|
|
104
|
+
const last = data[data.length - 1];
|
|
105
|
+
return `series ${name}: ${data.length} points, min ${min}, max ${max}, last ${last}`;
|
|
106
|
+
}
|
|
107
|
+
function renderChart(chart, out, depth) {
|
|
108
|
+
const pad = " ".repeat(depth);
|
|
109
|
+
out.push(`${pad}- figure ${quote(chart.title || "chart")}:`);
|
|
110
|
+
for (const s of chart.series) {
|
|
111
|
+
out.push(`${pad} - text: ${quote(seriesSummary(s.name, s.data))}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function renderNav(groups, out, depth) {
|
|
115
|
+
const pad = " ".repeat(depth);
|
|
116
|
+
for (const g of groups) {
|
|
117
|
+
if (!g.items.length) continue;
|
|
118
|
+
out.push(`${pad}- navigation ${quote(g.label)}:`);
|
|
119
|
+
for (const d of g.items) {
|
|
120
|
+
const mark = d.current ? " [current]" : "";
|
|
121
|
+
out.push(`${pad} - link ${quote(d.label)}${mark} \u2192 ${redact(d.path)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function flattenDestinations(rt) {
|
|
126
|
+
return rt.nav.flatMap((g) => g.items);
|
|
127
|
+
}
|
|
128
|
+
function buildNavigateTool(rt, policy) {
|
|
129
|
+
const destinations = flattenDestinations(rt);
|
|
130
|
+
const bySection = rt.nav.filter((g) => g.items.length).map((g) => `${g.label} (${g.items.map((d) => d.label).join(", ")})`).join("; ");
|
|
131
|
+
return {
|
|
132
|
+
name: "hoist.navigate",
|
|
133
|
+
description: redact(
|
|
134
|
+
`Navigate the user to a destination in the app. "destination" must be one of the destinations currently available, grouped by section: ${bySection || "(none available)"}.`
|
|
135
|
+
),
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
destination: {
|
|
140
|
+
type: "string",
|
|
141
|
+
enum: destinations.map((d) => d.label)
|
|
142
|
+
},
|
|
143
|
+
params: { type: "object" }
|
|
144
|
+
},
|
|
145
|
+
required: ["destination"]
|
|
146
|
+
},
|
|
147
|
+
readOnly: false,
|
|
148
|
+
// 'confirm' policy ⇒ the widget must show a confirm card every call; 'auto' ⇒ it
|
|
149
|
+
// may call without asking. See the confirm field's doc comment on HostTool.
|
|
150
|
+
confirm: policy === "auto" ? "auto" : "required"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function buildHoistContext(rt) {
|
|
154
|
+
const selected = rt.grids.reduce((n, g) => n + g.selected.length, 0);
|
|
155
|
+
const view = rt.route.name || rt.route.path || "this view";
|
|
156
|
+
const bits = [`Viewing ${view}`];
|
|
157
|
+
if (selected) bits.push(`${selected} selected`);
|
|
158
|
+
if (rt.grids.length) bits.push(`${rt.grids.length} grid(s)`);
|
|
159
|
+
if (rt.charts.length) bits.push(`${rt.charts.length} chart(s)`);
|
|
160
|
+
return {
|
|
161
|
+
route: rt.route.name ? redact(rt.route.name) : void 0,
|
|
162
|
+
description: redact(`${bits.join(", ")}.`)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function renderHoistSnapshot(rt, rowCap) {
|
|
166
|
+
const out = [];
|
|
167
|
+
renderNav(rt.nav, out, 0);
|
|
168
|
+
let truncated = false;
|
|
169
|
+
for (const g of rt.grids) {
|
|
170
|
+
if (g.rows.length > rowCap) truncated = true;
|
|
171
|
+
renderGrid(g, rowCap, out, 0);
|
|
172
|
+
}
|
|
173
|
+
for (const c of rt.charts) renderChart(c, out, 0);
|
|
174
|
+
return { yaml: out.join("\n"), truncated };
|
|
175
|
+
}
|
|
176
|
+
var init_hoist = __esm({
|
|
177
|
+
"src/adapters/hoist.ts"() {
|
|
178
|
+
"use strict";
|
|
179
|
+
init_redact();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// src/adapters/hoist-runtime.ts
|
|
184
|
+
function readHoistConfig(win) {
|
|
185
|
+
const cfg = win?.matterfact?.hoist;
|
|
186
|
+
const ex = cfg?.excludeModels;
|
|
187
|
+
return {
|
|
188
|
+
rows: typeof cfg?.rows === "number" && cfg.rows >= 0 ? cfg.rows : 50,
|
|
189
|
+
excludeModels: Array.isArray(ex) ? ex : []
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function getModelsByClass(XH, name) {
|
|
193
|
+
const models = typeof XH?.getModels === "function" ? XH.getModels(name) : [];
|
|
194
|
+
return Array.isArray(models) ? models : [];
|
|
195
|
+
}
|
|
196
|
+
function className(m) {
|
|
197
|
+
return m?.constructor?.name ?? "";
|
|
198
|
+
}
|
|
199
|
+
function modelId(m) {
|
|
200
|
+
return String(m?.xhId ?? m?.id ?? m?.modelId ?? "");
|
|
201
|
+
}
|
|
202
|
+
function headerOf(c) {
|
|
203
|
+
return String(c?.headerName ?? c?.displayName ?? c?.field ?? c?.colId ?? "");
|
|
204
|
+
}
|
|
205
|
+
function collectHeaders(cols, prefix, out) {
|
|
206
|
+
for (const c of Array.isArray(cols) ? cols : []) {
|
|
207
|
+
const children = c?.children;
|
|
208
|
+
if (Array.isArray(children) && children.length) {
|
|
209
|
+
collectHeaders(children, [...prefix, headerOf(c)], out);
|
|
210
|
+
} else {
|
|
211
|
+
const id = String(c?.colId ?? c?.field ?? "");
|
|
212
|
+
if (id) out.set(id, [...prefix, headerOf(c)].filter(Boolean).join(" "));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function readColumns(g) {
|
|
217
|
+
const leaves = typeof g?.getVisibleLeafColumns === "function" ? g.getVisibleLeafColumns() : Array.isArray(g?.columns) ? g.columns : [];
|
|
218
|
+
const headers = /* @__PURE__ */ new Map();
|
|
219
|
+
collectHeaders(Array.isArray(g?.columns) ? g.columns : [], [], headers);
|
|
220
|
+
return (Array.isArray(leaves) ? leaves : []).map((c) => {
|
|
221
|
+
const field = String(c?.colId ?? c?.field ?? "");
|
|
222
|
+
return { field, header: headers.get(field) || headerOf(c) };
|
|
223
|
+
}).filter((c) => c.field);
|
|
224
|
+
}
|
|
225
|
+
function readRow(rec, cols) {
|
|
226
|
+
const row = {};
|
|
227
|
+
for (const c of cols)
|
|
228
|
+
row[c.field] = typeof rec?.get === "function" ? rec.get(c.field) : rec?.data?.[c.field];
|
|
229
|
+
return row;
|
|
230
|
+
}
|
|
231
|
+
function readGrid(g) {
|
|
232
|
+
const columns = readColumns(g);
|
|
233
|
+
const records = Array.isArray(g?.store?.records) ? g.store.records : [];
|
|
234
|
+
return {
|
|
235
|
+
id: modelId(g),
|
|
236
|
+
title: String(g?.title ?? ""),
|
|
237
|
+
columns,
|
|
238
|
+
selected: (Array.isArray(g?.selectedRecords) ? g.selectedRecords : []).map(
|
|
239
|
+
(r) => readRow(r, columns)
|
|
240
|
+
),
|
|
241
|
+
rows: records.map((r) => readRow(r, columns)),
|
|
242
|
+
totalCount: typeof g?.store?.allCount === "number" ? g.store.allCount : records.length
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function hasContent(g) {
|
|
246
|
+
return g.totalCount > 0 || g.selected.length > 0;
|
|
247
|
+
}
|
|
248
|
+
function readChart(c) {
|
|
249
|
+
const series = c?.series ?? c?.highchartsConfig?.series ?? [];
|
|
250
|
+
return {
|
|
251
|
+
id: modelId(c),
|
|
252
|
+
title: String(c?.title ?? c?.highchartsConfig?.title?.text ?? ""),
|
|
253
|
+
series: (Array.isArray(series) ? series : []).map((s) => ({
|
|
254
|
+
name: String(s?.name ?? ""),
|
|
255
|
+
data: (Array.isArray(s?.data) ? s.data : []).map((d) => typeof d === "number" ? d : Number(d?.y ?? d?.[1])).filter((n) => Number.isFinite(n))
|
|
256
|
+
}))
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function humanize(seg) {
|
|
260
|
+
return seg.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()).trim();
|
|
261
|
+
}
|
|
262
|
+
function childrenOf(node) {
|
|
263
|
+
const k = node?.children;
|
|
264
|
+
return k ? Array.isArray(k) ? k : Array.from(k) : [];
|
|
265
|
+
}
|
|
266
|
+
function routeIndex(XH, params) {
|
|
267
|
+
const router = XH?.router;
|
|
268
|
+
const root = router?.rootNode ?? router?.config?.rootNode;
|
|
269
|
+
const out = /* @__PURE__ */ new Map();
|
|
270
|
+
if (!router || typeof router.buildPath !== "function" || !root) return out;
|
|
271
|
+
const walk2 = (node, prefix) => {
|
|
272
|
+
for (const c of childrenOf(node)) {
|
|
273
|
+
const seg = String(c?.name ?? "");
|
|
274
|
+
if (!seg) continue;
|
|
275
|
+
const name = prefix ? `${prefix}.${seg}` : seg;
|
|
276
|
+
let path = "";
|
|
277
|
+
try {
|
|
278
|
+
path = router.buildPath(name, params);
|
|
279
|
+
} catch {
|
|
280
|
+
path = "";
|
|
281
|
+
}
|
|
282
|
+
if (path && !out.has(seg)) out.set(seg, { path, name });
|
|
283
|
+
walk2(c, name);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
walk2(root, "");
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
function readSitemap(XH, excluded, origin) {
|
|
290
|
+
const params = XH?.routerState?.params ?? {};
|
|
291
|
+
const currentName = String(XH?.routerState?.name ?? "");
|
|
292
|
+
const routes = routeIndex(XH, params);
|
|
293
|
+
if (!routes.size) return [];
|
|
294
|
+
const groups = [];
|
|
295
|
+
for (const tc of getModelsByClass(XH, "TabContainerModel")) {
|
|
296
|
+
if (excluded(tc)) continue;
|
|
297
|
+
const tabs = Array.isArray(tc?.tabs) ? tc.tabs : [];
|
|
298
|
+
const activeTabId = String(tc?.activeTabId ?? "");
|
|
299
|
+
const items = [];
|
|
300
|
+
let companyScoped = false;
|
|
301
|
+
for (const t of tabs) {
|
|
302
|
+
const id = String(t?.id ?? "");
|
|
303
|
+
const r = routes.get(id);
|
|
304
|
+
if (!id || !r) continue;
|
|
305
|
+
if (r.name.includes(".company.ticker.")) companyScoped = true;
|
|
306
|
+
items.push({
|
|
307
|
+
// Redact HERE, at the source — not in a downstream renderer. This is the
|
|
308
|
+
// one place a Hoist tab TITLE (host-page text, so potentially PII-shaped)
|
|
309
|
+
// becomes a `HoistDestination.label`, and that label is what EVERY
|
|
310
|
+
// consumer uses verbatim: renderNav's YAML, buildNavigateTool's
|
|
311
|
+
// description AND its inputSchema.enum, and executeHostTool's
|
|
312
|
+
// `.find(d => d.label === destination)` lookup. Redacting once here keeps
|
|
313
|
+
// all of them consistent by construction — an enum built from an
|
|
314
|
+
// ALREADY-redacted label can never leak PII a sibling renderer scrubbed
|
|
315
|
+
// (see Important #1), and a destination arg matching the redacted string
|
|
316
|
+
// (the only thing the agent ever saw) still resolves.
|
|
317
|
+
label: redact(String(t?.title ?? "") || humanize(id)),
|
|
318
|
+
// ABSOLUTE, against the HOST page's origin — not the router's bare path.
|
|
319
|
+
// This path's only job is to be a link the agent can hand the user, and
|
|
320
|
+
// the agent's answer renders inside our widget iframe, which is on OUR
|
|
321
|
+
// origin: a bare "/research" there resolves to app.matterfact.com/research
|
|
322
|
+
// (a dead link to the wrong app), which is exactly what shipped before.
|
|
323
|
+
// The adapter runs in the host page, so its own origin is the right one.
|
|
324
|
+
path: origin && r.path.startsWith("/") ? origin + r.path : r.path,
|
|
325
|
+
// The fully-qualified route name — what hoist.navigate hands XH.navigate.
|
|
326
|
+
// Always present: `r` only exists here because routes.get(id) resolved.
|
|
327
|
+
name: r.name,
|
|
328
|
+
current: id === activeTabId || r.name === currentName
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (!items.length) continue;
|
|
332
|
+
const ticker = String(params.ticker ?? "");
|
|
333
|
+
const label = companyScoped ? ticker ? `Company \xB7 ${ticker}` : "Company" : "Site";
|
|
334
|
+
groups.push({ label, items });
|
|
335
|
+
}
|
|
336
|
+
return groups;
|
|
337
|
+
}
|
|
338
|
+
function readHoistRuntime(win = typeof window !== "undefined" ? window : void 0) {
|
|
339
|
+
const XH = win?.XH;
|
|
340
|
+
if (!XH) return null;
|
|
341
|
+
const { excludeModels } = readHoistConfig(win);
|
|
342
|
+
const excluded = (m) => excludeModels.includes(className(m)) || excludeModels.includes(modelId(m));
|
|
343
|
+
const rs = XH.routerState ?? {};
|
|
344
|
+
return {
|
|
345
|
+
route: {
|
|
346
|
+
name: String(rs.name ?? ""),
|
|
347
|
+
path: String(rs.path ?? ""),
|
|
348
|
+
params: rs.params ?? {}
|
|
349
|
+
},
|
|
350
|
+
grids: getModelsByClass(XH, "GridModel").filter((m) => !excluded(m)).map(readGrid).filter(hasContent),
|
|
351
|
+
charts: getModelsByClass(XH, "ChartModel").filter((m) => !excluded(m)).map(readChart).filter((c) => c.series.length > 0),
|
|
352
|
+
nav: readSitemap(
|
|
353
|
+
XH,
|
|
354
|
+
excluded,
|
|
355
|
+
String(
|
|
356
|
+
win?.location?.origin ?? ""
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function readActionsConfig(win) {
|
|
362
|
+
const navigate = win?.matterfact?.hoist?.actions?.navigate;
|
|
363
|
+
return {
|
|
364
|
+
navigate: navigate === "confirm" || navigate === "auto" ? navigate : "off"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function advertiseTools(win = typeof window !== "undefined" ? window : void 0) {
|
|
368
|
+
try {
|
|
369
|
+
const XH = win?.XH;
|
|
370
|
+
if (!XH) return [];
|
|
371
|
+
const { navigate } = readActionsConfig(win);
|
|
372
|
+
if (navigate === "off") return [];
|
|
373
|
+
const rt = readHoistRuntime(win);
|
|
374
|
+
if (!rt) return [];
|
|
375
|
+
return [buildNavigateTool(rt, navigate)];
|
|
376
|
+
} catch {
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function executeHostTool(call, win = typeof window !== "undefined" ? window : void 0) {
|
|
381
|
+
try {
|
|
382
|
+
if (call.name !== "hoist.navigate") {
|
|
383
|
+
return { ok: false, error: "unknown tool" };
|
|
384
|
+
}
|
|
385
|
+
if (readActionsConfig(win).navigate === "off") {
|
|
386
|
+
return { ok: false, error: "navigation is disabled" };
|
|
387
|
+
}
|
|
388
|
+
const destination = String(call.args?.destination ?? "");
|
|
389
|
+
const rt = readHoistRuntime(win);
|
|
390
|
+
const item = rt ? flattenDestinations(rt).find((d) => d.label === destination) : void 0;
|
|
391
|
+
if (!item) return { ok: false, error: "unknown destination" };
|
|
392
|
+
const XH = win?.XH;
|
|
393
|
+
const params = {
|
|
394
|
+
...XH?.routerState?.params ?? {},
|
|
395
|
+
...call.args?.params ?? {}
|
|
396
|
+
};
|
|
397
|
+
if (typeof XH?.navigate === "function") {
|
|
398
|
+
XH.navigate(item.name, params);
|
|
399
|
+
} else if (typeof XH?.router?.navigate === "function") {
|
|
400
|
+
XH.router.navigate(item.name, params);
|
|
401
|
+
} else {
|
|
402
|
+
return { ok: false, error: "navigation unavailable" };
|
|
403
|
+
}
|
|
404
|
+
return { ok: true, result: redact(`navigated to ${destination}`) };
|
|
405
|
+
} catch (e) {
|
|
406
|
+
return {
|
|
407
|
+
ok: false,
|
|
408
|
+
error: redact(e instanceof Error ? e.message : "navigate failed")
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
var init_hoist_runtime = __esm({
|
|
413
|
+
"src/adapters/hoist-runtime.ts"() {
|
|
414
|
+
"use strict";
|
|
415
|
+
init_hoist();
|
|
416
|
+
init_redact();
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
25
420
|
// src/dom/a11y.ts
|
|
26
421
|
function createStyleCache() {
|
|
27
422
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
@@ -275,7 +670,7 @@ function isHidden(el, style, layout) {
|
|
|
275
670
|
if (el.hasAttribute("hidden") || el.hasAttribute("inert")) return true;
|
|
276
671
|
return !isVisible(el, style, layout);
|
|
277
672
|
}
|
|
278
|
-
function
|
|
673
|
+
function childrenOf2(el) {
|
|
279
674
|
if (el.shadowRoot) return el.shadowRoot.childNodes;
|
|
280
675
|
if (el.tagName === "SLOT") {
|
|
281
676
|
const assigned = el.assignedNodes({ flatten: true });
|
|
@@ -284,7 +679,7 @@ function childrenOf(el) {
|
|
|
284
679
|
return el.childNodes;
|
|
285
680
|
}
|
|
286
681
|
function visitChildren(el, parent, style, w) {
|
|
287
|
-
const children =
|
|
682
|
+
const children = childrenOf2(el);
|
|
288
683
|
for (let i = 0; i < children.length; i++) {
|
|
289
684
|
const child = children[i];
|
|
290
685
|
if (child) visitNode(child, parent, style, w);
|
|
@@ -405,17 +800,17 @@ function sanitize(text, max) {
|
|
|
405
800
|
const clean = redact(collapsed);
|
|
406
801
|
return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
407
802
|
}
|
|
408
|
-
function
|
|
803
|
+
function quote2(text) {
|
|
409
804
|
return `"${text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
410
805
|
}
|
|
411
806
|
function render(children, depth, out) {
|
|
412
807
|
const pad = " ".repeat(depth);
|
|
413
808
|
for (const child of children) {
|
|
414
809
|
if (typeof child === "string") {
|
|
415
|
-
out.push(`${pad}- text: ${
|
|
810
|
+
out.push(`${pad}- text: ${quote2(sanitize(child, MAX_TEXT))}`);
|
|
416
811
|
continue;
|
|
417
812
|
}
|
|
418
|
-
const name = child.name ? ` ${
|
|
813
|
+
const name = child.name ? ` ${quote2(sanitize(child.name, MAX_NAME))}` : "";
|
|
419
814
|
const head = `${pad}- ${child.role}${name}${child.props}`;
|
|
420
815
|
if (child.children.length) {
|
|
421
816
|
out.push(`${head}:`);
|
|
@@ -511,11 +906,6 @@ __export(context_exports, {
|
|
|
511
906
|
start: () => start,
|
|
512
907
|
stop: () => stop
|
|
513
908
|
});
|
|
514
|
-
function redact(text) {
|
|
515
|
-
let out = text;
|
|
516
|
-
for (const re of PII) out = out.replace(re, "[redacted]");
|
|
517
|
-
return out;
|
|
518
|
-
}
|
|
519
909
|
function isPrivate(el) {
|
|
520
910
|
if (el.closest("[data-mf-private]")) return true;
|
|
521
911
|
const tag = el.tagName;
|
|
@@ -597,6 +987,8 @@ async function resolveDeclaredContext() {
|
|
|
597
987
|
const c = await mf.getPageContext();
|
|
598
988
|
if (c) return c;
|
|
599
989
|
}
|
|
990
|
+
const hoist = detectHoist(() => readHoistRuntime());
|
|
991
|
+
if (hoist) return buildHoistContext(hoist);
|
|
600
992
|
return mf?.context ?? {};
|
|
601
993
|
}
|
|
602
994
|
async function publishContext() {
|
|
@@ -699,6 +1091,10 @@ function watchNavigation() {
|
|
|
699
1091
|
pushActivity({ type: "nav", summary: `navigated to ${url}` });
|
|
700
1092
|
void publishContext();
|
|
701
1093
|
publishArtifactGrants();
|
|
1094
|
+
send?.({
|
|
1095
|
+
type: "host.tools",
|
|
1096
|
+
tools: advertiseTools(typeof window !== "undefined" ? window : void 0)
|
|
1097
|
+
});
|
|
702
1098
|
};
|
|
703
1099
|
navFire = fire;
|
|
704
1100
|
for (const name of ["pushState", "replaceState"]) {
|
|
@@ -755,6 +1151,20 @@ function scheduleFocus() {
|
|
|
755
1151
|
}
|
|
756
1152
|
async function sendSnapshot(emit) {
|
|
757
1153
|
if (!pageContextOn) return;
|
|
1154
|
+
const hoist = detectHoist(() => readHoistRuntime());
|
|
1155
|
+
if (hoist) {
|
|
1156
|
+
const { rows } = readHoistConfig(
|
|
1157
|
+
typeof window !== "undefined" ? window : void 0
|
|
1158
|
+
);
|
|
1159
|
+
const { yaml: yaml2, truncated: truncated2 } = renderHoistSnapshot(hoist, rows);
|
|
1160
|
+
emit({
|
|
1161
|
+
type: "host.snapshot",
|
|
1162
|
+
snapshot: { yaml: redact(yaml2), seq: ++snapshotSeq, truncated: truncated2 }
|
|
1163
|
+
});
|
|
1164
|
+
lastFocus = { ...lastFocus, visibleRefs: [] };
|
|
1165
|
+
emit({ type: "host.focus", focus: lastFocus });
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
758
1168
|
const { snapshot: snapshot2 } = await loadSnapshotModule();
|
|
759
1169
|
const { yaml, truncated, visibleRefs } = snapshot2();
|
|
760
1170
|
emit({
|
|
@@ -780,11 +1190,25 @@ async function sendRegion(ref, emit) {
|
|
|
780
1190
|
emit({ type: "host.region", ref, yaml: redact(yaml) });
|
|
781
1191
|
}
|
|
782
1192
|
async function callTool(call, emit) {
|
|
1193
|
+
if (!pageContextOn) {
|
|
1194
|
+
emit({
|
|
1195
|
+
type: "host.toolResult",
|
|
1196
|
+
callId: call.callId,
|
|
1197
|
+
ok: false,
|
|
1198
|
+
error: "host tools are disabled"
|
|
1199
|
+
});
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
const r = executeHostTool(
|
|
1203
|
+
call,
|
|
1204
|
+
typeof window !== "undefined" ? window : void 0
|
|
1205
|
+
);
|
|
783
1206
|
emit({
|
|
784
1207
|
type: "host.toolResult",
|
|
785
1208
|
callId: call.callId,
|
|
786
|
-
ok:
|
|
787
|
-
|
|
1209
|
+
ok: r.ok,
|
|
1210
|
+
result: r.result,
|
|
1211
|
+
error: r.error
|
|
788
1212
|
});
|
|
789
1213
|
}
|
|
790
1214
|
function stop() {
|
|
@@ -854,11 +1278,21 @@ function start(emit, origin, pageContext = true, provider) {
|
|
|
854
1278
|
}
|
|
855
1279
|
const theme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
856
1280
|
emit({ type: "host.theme", mode: theme });
|
|
1281
|
+
if (pageContextOn) {
|
|
1282
|
+
const tools = advertiseTools(
|
|
1283
|
+
typeof window !== "undefined" ? window : void 0
|
|
1284
|
+
);
|
|
1285
|
+
if (tools.length) emit({ type: "host.tools", tools });
|
|
1286
|
+
}
|
|
857
1287
|
}
|
|
858
|
-
var widgetOrigin, MAX_ACTIVITY, send, activitySeq, activity, lastUrl, pageContextOn, contextProvider, navFire, origPushState, origReplaceState, snapshotModule,
|
|
1288
|
+
var widgetOrigin, MAX_ACTIVITY, send, activitySeq, activity, lastUrl, pageContextOn, contextProvider, navFire, origPushState, origReplaceState, snapshotModule, FORM_CONTROLS, ACTIONABLE, lastFocus, focusTimer, snapshotSeq;
|
|
859
1289
|
var init_context = __esm({
|
|
860
1290
|
"src/context.ts"() {
|
|
861
1291
|
"use strict";
|
|
1292
|
+
init_redact();
|
|
1293
|
+
init_hoist();
|
|
1294
|
+
init_hoist_runtime();
|
|
1295
|
+
init_redact();
|
|
862
1296
|
widgetOrigin = "";
|
|
863
1297
|
MAX_ACTIVITY = 40;
|
|
864
1298
|
send = null;
|
|
@@ -870,22 +1304,6 @@ var init_context = __esm({
|
|
|
870
1304
|
origPushState = null;
|
|
871
1305
|
origReplaceState = null;
|
|
872
1306
|
snapshotModule = null;
|
|
873
|
-
PII = [
|
|
874
|
-
/\b[\w.+-]+@[\w-]+\.[\w.]{2,}\b/g,
|
|
875
|
-
// email
|
|
876
|
-
/\b(?:\d[ -]?){13,19}\b/g,
|
|
877
|
-
// card-ish
|
|
878
|
-
/\b\d{3}[- ]?\d{2}[- ]?\d{4}\b/g,
|
|
879
|
-
// ssn — dashed, spaced, OR bare 9 digits
|
|
880
|
-
/\b\d{9,}\b/g,
|
|
881
|
-
// long bare digit runs: account / MRN / routing numbers
|
|
882
|
-
/(?:\+?\d{1,2}[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b/g,
|
|
883
|
-
// us phone
|
|
884
|
-
/\b(?:sk|pk|rk|ak)_(?:live|test)_[A-Za-z0-9]{8,}\b/g,
|
|
885
|
-
// stripe-style api keys
|
|
886
|
-
/\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g
|
|
887
|
-
// jwt
|
|
888
|
-
];
|
|
889
1307
|
FORM_CONTROLS = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
|
|
890
1308
|
ACTIONABLE = 'a[href],button,input,select,textarea,summary,[role="button"],[role="link"],[role="menuitem"],[role="tab"],[role="option"],[role="checkbox"],[role="switch"],[role="radio"],[onclick],[tabindex]';
|
|
891
1309
|
lastFocus = {};
|
|
@@ -904,7 +1322,7 @@ module.exports = __toCommonJS(react_exports);
|
|
|
904
1322
|
var import_react = require("react");
|
|
905
1323
|
|
|
906
1324
|
// src/protocol.ts
|
|
907
|
-
var PROTOCOL_VERSION =
|
|
1325
|
+
var PROTOCOL_VERSION = 2;
|
|
908
1326
|
var CHANNEL = "mf-embed";
|
|
909
1327
|
function envelope(payload, id) {
|
|
910
1328
|
return {
|
|
@@ -918,6 +1336,213 @@ function isEnvelope(data) {
|
|
|
918
1336
|
return typeof data === "object" && data !== null && data.channel === CHANNEL;
|
|
919
1337
|
}
|
|
920
1338
|
|
|
1339
|
+
// src/geometry.ts
|
|
1340
|
+
var MARGIN = 20;
|
|
1341
|
+
var LAUNCHER = 56;
|
|
1342
|
+
var SNAP_RADIUS = 96;
|
|
1343
|
+
var TAB_W = 44;
|
|
1344
|
+
var TAB_H = 132;
|
|
1345
|
+
var EDGE_DOCK = 64;
|
|
1346
|
+
var DEFAULT_FLOAT_W = 420;
|
|
1347
|
+
var DEFAULT_FLOAT_H = 640;
|
|
1348
|
+
var MIN_FLOAT_W = 320;
|
|
1349
|
+
var MIN_FLOAT_H = 420;
|
|
1350
|
+
var DEFAULT_DOCK_W = 600;
|
|
1351
|
+
var MIN_DOCK_W = 320;
|
|
1352
|
+
var CORNERS = ["tl", "tr", "bl", "br"];
|
|
1353
|
+
function defaultGeometry() {
|
|
1354
|
+
return {
|
|
1355
|
+
// Dock is the default: a side panel reads as part of the host app.
|
|
1356
|
+
mode: "dock",
|
|
1357
|
+
corner: "br",
|
|
1358
|
+
x: 0,
|
|
1359
|
+
y: 0,
|
|
1360
|
+
floatW: DEFAULT_FLOAT_W,
|
|
1361
|
+
floatH: DEFAULT_FLOAT_H,
|
|
1362
|
+
dockSide: "right",
|
|
1363
|
+
tabY: 0,
|
|
1364
|
+
// 0 => centre it (see tabTop)
|
|
1365
|
+
dockW: DEFAULT_DOCK_W
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
function clamp(n, lo, hi) {
|
|
1369
|
+
return Math.min(Math.max(n, lo), hi);
|
|
1370
|
+
}
|
|
1371
|
+
function cornerOrigin(corner, w, h, vw, vh) {
|
|
1372
|
+
return {
|
|
1373
|
+
x: corner[1] === "l" ? MARGIN : vw - MARGIN - w,
|
|
1374
|
+
y: corner[0] === "t" ? MARGIN : vh - MARGIN - h
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
function launcherOrigin(g, vw, vh) {
|
|
1378
|
+
if (g.corner) return cornerOrigin(g.corner, LAUNCHER, LAUNCHER, vw, vh);
|
|
1379
|
+
return {
|
|
1380
|
+
x: clamp(g.x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER)),
|
|
1381
|
+
y: clamp(g.y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER))
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function growthCorner(g, vw, vh) {
|
|
1385
|
+
if (g.corner) return g.corner;
|
|
1386
|
+
const o = launcherOrigin(g, vw, vh);
|
|
1387
|
+
return nearestCorner(o.x + LAUNCHER / 2, o.y + LAUNCHER / 2, vw, vh);
|
|
1388
|
+
}
|
|
1389
|
+
function nearestCorner(cx, cy, vw, vh) {
|
|
1390
|
+
return `${cy < vh / 2 ? "t" : "b"}${cx < vw / 2 ? "l" : "r"}`;
|
|
1391
|
+
}
|
|
1392
|
+
function snapOrFree(x, y, vw, vh) {
|
|
1393
|
+
const cx = clamp(x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER));
|
|
1394
|
+
const cy = clamp(y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER));
|
|
1395
|
+
for (const corner of CORNERS) {
|
|
1396
|
+
const o = cornerOrigin(corner, LAUNCHER, LAUNCHER, vw, vh);
|
|
1397
|
+
if (Math.hypot(cx - o.x, cy - o.y) <= SNAP_RADIUS) {
|
|
1398
|
+
return { corner, x: cx, y: cy };
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return { corner: null, x: cx, y: cy };
|
|
1402
|
+
}
|
|
1403
|
+
function edgeDock(x, w, vw) {
|
|
1404
|
+
if (x <= EDGE_DOCK) return "left";
|
|
1405
|
+
if (x + w >= vw - EDGE_DOCK) return "right";
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
function dragAnchor(g, vw, vh) {
|
|
1409
|
+
if (g.mode === "dock") {
|
|
1410
|
+
return { x: g.dockSide === "left" ? 0 : vw - TAB_W, y: tabTop(g, vh) };
|
|
1411
|
+
}
|
|
1412
|
+
return launcherOrigin(g, vw, vh);
|
|
1413
|
+
}
|
|
1414
|
+
function visibleBox(g, open, vw, vh) {
|
|
1415
|
+
if (g.mode === "dock") {
|
|
1416
|
+
const w2 = open ? Math.min(Math.max(g.dockW, MIN_DOCK_W), Math.max(0, vw - 2 * MARGIN)) : TAB_W;
|
|
1417
|
+
return { x: g.dockSide === "left" ? 0 : vw - w2, w: w2 };
|
|
1418
|
+
}
|
|
1419
|
+
const o = launcherOrigin(g, vw, vh);
|
|
1420
|
+
if (!open) return { x: o.x, w: LAUNCHER };
|
|
1421
|
+
const { w } = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);
|
|
1422
|
+
return {
|
|
1423
|
+
x: growthCorner(g, vw, vh)[1] === "r" ? o.x + LAUNCHER - w : o.x,
|
|
1424
|
+
w
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
function placeDrop(anchorX, anchorY, boxLeft, boxW, vw, vh) {
|
|
1428
|
+
const snapped = snapOrFree(anchorX, anchorY, vw, vh);
|
|
1429
|
+
if (snapped.corner) return { mode: "float", ...snapped };
|
|
1430
|
+
const side = edgeDock(boxLeft, boxW, vw);
|
|
1431
|
+
if (side) {
|
|
1432
|
+
return {
|
|
1433
|
+
mode: "dock",
|
|
1434
|
+
dockSide: side,
|
|
1435
|
+
tabY: clamp(anchorY, 0, Math.max(0, vh - TAB_H))
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
return { mode: "float", ...snapped };
|
|
1439
|
+
}
|
|
1440
|
+
function clampSize(w, h, vw, vh, minW, minH) {
|
|
1441
|
+
return {
|
|
1442
|
+
w: Math.min(Math.max(w, minW), Math.max(0, vw - 2 * MARGIN)),
|
|
1443
|
+
h: Math.min(Math.max(h, minH), Math.max(0, vh - 2 * MARGIN))
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
function applyResizeDelta(corner, startW, startH, dx, dy) {
|
|
1447
|
+
const wSign = corner[1] === "r" ? -1 : 1;
|
|
1448
|
+
const hSign = corner[0] === "b" ? -1 : 1;
|
|
1449
|
+
return { w: startW + wSign * dx, h: startH + hSign * dy };
|
|
1450
|
+
}
|
|
1451
|
+
function applyDockResizeDelta(side, startW, dx) {
|
|
1452
|
+
return side === "right" ? startW - dx : startW + dx;
|
|
1453
|
+
}
|
|
1454
|
+
function parseGeometry(raw) {
|
|
1455
|
+
if (!raw) return null;
|
|
1456
|
+
try {
|
|
1457
|
+
const p = JSON.parse(raw);
|
|
1458
|
+
if (!p || p.mode !== "float" && p.mode !== "dock") return null;
|
|
1459
|
+
const d = defaultGeometry();
|
|
1460
|
+
const num = (v, dv) => typeof v === "number" ? v : dv;
|
|
1461
|
+
return {
|
|
1462
|
+
mode: p.mode,
|
|
1463
|
+
corner: CORNERS.includes(p.corner) ? p.corner : null,
|
|
1464
|
+
x: num(p.x, d.x),
|
|
1465
|
+
y: num(p.y, d.y),
|
|
1466
|
+
floatW: num(p.floatW, d.floatW),
|
|
1467
|
+
floatH: num(p.floatH, d.floatH),
|
|
1468
|
+
dockSide: p.dockSide === "left" ? "left" : "right",
|
|
1469
|
+
tabY: num(p.tabY, d.tabY),
|
|
1470
|
+
dockW: num(p.dockW, d.dockW)
|
|
1471
|
+
};
|
|
1472
|
+
} catch {
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
function serializeGeometry(g) {
|
|
1477
|
+
return JSON.stringify(g);
|
|
1478
|
+
}
|
|
1479
|
+
var px = (n) => `${n}px`;
|
|
1480
|
+
function boxAt(ox, oy, w, h, corner, vw, vh) {
|
|
1481
|
+
const right = corner[1] === "r";
|
|
1482
|
+
const bottom = corner[0] === "b";
|
|
1483
|
+
const rawX = right ? vw - (ox + LAUNCHER) : ox;
|
|
1484
|
+
const rawY = bottom ? vh - (oy + LAUNCHER) : oy;
|
|
1485
|
+
const x = clamp(rawX, MARGIN, Math.max(MARGIN, vw - MARGIN - w));
|
|
1486
|
+
const y = clamp(rawY, MARGIN, Math.max(MARGIN, vh - MARGIN - h));
|
|
1487
|
+
return {
|
|
1488
|
+
top: bottom ? "auto" : px(y),
|
|
1489
|
+
bottom: bottom ? px(y) : "auto",
|
|
1490
|
+
left: right ? "auto" : px(x),
|
|
1491
|
+
right: right ? px(x) : "auto",
|
|
1492
|
+
width: px(w),
|
|
1493
|
+
height: px(h)
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
function tabTop(g, vh) {
|
|
1497
|
+
const max = Math.max(0, vh - TAB_H);
|
|
1498
|
+
return clamp(g.tabY || Math.round((vh - TAB_H) / 2), 0, max);
|
|
1499
|
+
}
|
|
1500
|
+
function tabBox(g, vw, vh) {
|
|
1501
|
+
const left = g.dockSide === "left";
|
|
1502
|
+
return {
|
|
1503
|
+
top: px(tabTop(g, vh)),
|
|
1504
|
+
bottom: "auto",
|
|
1505
|
+
left: left ? "0px" : "auto",
|
|
1506
|
+
right: left ? "auto" : "0px",
|
|
1507
|
+
width: px(TAB_W),
|
|
1508
|
+
height: px(TAB_H)
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
function floatChrome(g, w, h, vw, vh, growth) {
|
|
1512
|
+
const o = launcherOrigin(g, vw, vh);
|
|
1513
|
+
return boxAt(o.x, o.y, w, h, growth ?? growthCorner(g, vw, vh), vw, vh);
|
|
1514
|
+
}
|
|
1515
|
+
function dockChrome(g, w, h, vw, vh) {
|
|
1516
|
+
const left = g.dockSide === "left";
|
|
1517
|
+
return {
|
|
1518
|
+
top: px(tabTop(g, vh)),
|
|
1519
|
+
bottom: "auto",
|
|
1520
|
+
left: left ? "0px" : "auto",
|
|
1521
|
+
right: left ? "auto" : "0px",
|
|
1522
|
+
width: px(w),
|
|
1523
|
+
height: px(h)
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
function floatBox(g, vw, vh, growth) {
|
|
1527
|
+
const f = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);
|
|
1528
|
+
return floatChrome(g, f.w, f.h, vw, vh, growth);
|
|
1529
|
+
}
|
|
1530
|
+
function dockBox(g, vw, _vh) {
|
|
1531
|
+
const w = Math.min(
|
|
1532
|
+
Math.max(g.dockW, MIN_DOCK_W),
|
|
1533
|
+
Math.max(0, vw - 2 * MARGIN)
|
|
1534
|
+
);
|
|
1535
|
+
const left = g.dockSide === "left";
|
|
1536
|
+
return {
|
|
1537
|
+
top: "0px",
|
|
1538
|
+
bottom: "0px",
|
|
1539
|
+
height: "auto",
|
|
1540
|
+
left: left ? "0px" : "auto",
|
|
1541
|
+
right: left ? "auto" : "0px",
|
|
1542
|
+
width: px(w)
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
|
|
921
1546
|
// src/loader.ts
|
|
922
1547
|
function devRequested() {
|
|
923
1548
|
try {
|
|
@@ -936,11 +1561,31 @@ var EmbedHost = class {
|
|
|
936
1561
|
this.queue = [];
|
|
937
1562
|
this.ready = false;
|
|
938
1563
|
this.open = false;
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1564
|
+
/** The persisted box: launcher corner, float size, dock state/side/width. */
|
|
1565
|
+
this.geo = defaultGeometry();
|
|
1566
|
+
/** Float size at resizeStart; resizeMove deltas apply against it (they're cumulative). */
|
|
1567
|
+
this.resizeBase = null;
|
|
1568
|
+
/** Docked width at resizeStart — the docked panel resizes in one axis only. */
|
|
1569
|
+
this.dockBase = DEFAULT_DOCK_W;
|
|
1570
|
+
/** The widget's logical anchor when the drag began, and where it is now. Deltas arrive
|
|
1571
|
+
* cumulative from the press, so the current spot is simply origin + delta — no need to
|
|
1572
|
+
* read the box back, which stops being the thing we're moving the moment it docks. */
|
|
1573
|
+
this.dragFrom = null;
|
|
1574
|
+
this.dragAt = { x: 0, y: 0 };
|
|
1575
|
+
/** The painted box when the drag began — the edge test measures what you can SEE
|
|
1576
|
+
* touching the screen edge, which is not the anchor once the panel is open. */
|
|
1577
|
+
this.dragBox = { x: 0, w: 0 };
|
|
1578
|
+
/** Growth direction frozen for the drag — see floatChrome's `growth` param. */
|
|
1579
|
+
this.dragGrowth = null;
|
|
1580
|
+
/**
|
|
1581
|
+
* Has this drag been clear of the dock band yet?
|
|
1582
|
+
*
|
|
1583
|
+
* The resting position is INSIDE the band — a corner-anchored widget sits MARGIN
|
|
1584
|
+
* (20px) from the edge, well within EDGE_DOCK (64px) — so without this, picking one
|
|
1585
|
+
* up docked it instantly, before it had moved anywhere. Docking now requires
|
|
1586
|
+
* ENTERING the band rather than merely starting in it.
|
|
1587
|
+
*/
|
|
1588
|
+
this.dragLeftBand = false;
|
|
944
1589
|
/** Loaded on first open. Holds everything that touches the customer's DOM. */
|
|
945
1590
|
this.context = null;
|
|
946
1591
|
/** The host element; kept so `destroy()` can remove it (React lifecycle). */
|
|
@@ -963,87 +1608,10 @@ var EmbedHost = class {
|
|
|
963
1608
|
if (event.origin !== this.config.origin) return;
|
|
964
1609
|
if (event.source !== this.iframe?.contentWindow) return;
|
|
965
1610
|
if (!isEnvelope(event.data)) return;
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
this.send({
|
|
971
|
-
type: "host.ready",
|
|
972
|
-
protocol: PROTOCOL_VERSION,
|
|
973
|
-
origin: location.origin
|
|
974
|
-
});
|
|
975
|
-
this.flush();
|
|
976
|
-
if (this.inline) void this.loadContext();
|
|
977
|
-
break;
|
|
978
|
-
case "widget.setOpen":
|
|
979
|
-
if (this.inline) break;
|
|
980
|
-
this.open = msg.open;
|
|
981
|
-
this.iframe?.setAttribute("data-open", String(msg.open));
|
|
982
|
-
if (!msg.open) {
|
|
983
|
-
this.expanded = false;
|
|
984
|
-
this.iframe?.setAttribute("data-expanded", "false");
|
|
985
|
-
}
|
|
986
|
-
if (msg.open) void this.loadContext();
|
|
987
|
-
break;
|
|
988
|
-
case "widget.setExpanded":
|
|
989
|
-
if (this.inline) break;
|
|
990
|
-
this.expanded = msg.expanded;
|
|
991
|
-
this.iframe?.setAttribute("data-expanded", String(msg.expanded));
|
|
992
|
-
if (this.iframe) this.iframe.style.height = "";
|
|
993
|
-
break;
|
|
994
|
-
case "widget.resize":
|
|
995
|
-
if (this.inline) break;
|
|
996
|
-
if (this.iframe && this.open && !this.expanded) {
|
|
997
|
-
this.iframe.style.height = `${Math.min(msg.height, window.innerHeight - 40)}px`;
|
|
998
|
-
}
|
|
999
|
-
break;
|
|
1000
|
-
case "widget.requestContext":
|
|
1001
|
-
void this.loadContext().then((m) => m.provideContext());
|
|
1002
|
-
break;
|
|
1003
|
-
case "widget.requestSnapshot":
|
|
1004
|
-
void this.loadContext().then((m) => m.sendSnapshot(this.send));
|
|
1005
|
-
break;
|
|
1006
|
-
case "widget.readRegion":
|
|
1007
|
-
void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));
|
|
1008
|
-
break;
|
|
1009
|
-
case "widget.callTool":
|
|
1010
|
-
void this.loadContext().then((m) => m.callTool(msg.call, this.send));
|
|
1011
|
-
break;
|
|
1012
|
-
// Everything below is LAUNCHER chrome: there is no launcher inline (the widget
|
|
1013
|
-
// doesn't draw one), and moving or hiding the host's own panel from inside it
|
|
1014
|
-
// would be us redecorating their app. An older cached loader could still be told
|
|
1015
|
-
// any of these by a newer widget, so they're guarded rather than assumed absent.
|
|
1016
|
-
case "widget.dragStart":
|
|
1017
|
-
if (this.inline) break;
|
|
1018
|
-
this.dragBase = this.cornerOffset();
|
|
1019
|
-
break;
|
|
1020
|
-
case "widget.dragMove":
|
|
1021
|
-
if (this.inline) break;
|
|
1022
|
-
this.dragTo(msg.dx, msg.dy);
|
|
1023
|
-
break;
|
|
1024
|
-
case "widget.dragEnd":
|
|
1025
|
-
if (this.inline) break;
|
|
1026
|
-
this.dragBase = null;
|
|
1027
|
-
this.writePos(this.pos);
|
|
1028
|
-
break;
|
|
1029
|
-
case "widget.setMenu":
|
|
1030
|
-
if (this.inline) break;
|
|
1031
|
-
if (this.iframe && !this.open) {
|
|
1032
|
-
this.iframe.style.height = msg.open ? `${64 + msg.height}px` : "";
|
|
1033
|
-
}
|
|
1034
|
-
break;
|
|
1035
|
-
case "widget.resetPos":
|
|
1036
|
-
if (this.inline) break;
|
|
1037
|
-
this.resetPos();
|
|
1038
|
-
break;
|
|
1039
|
-
case "widget.hide":
|
|
1040
|
-
if (this.inline) break;
|
|
1041
|
-
if (this.hostEl) this.hostEl.style.display = "none";
|
|
1042
|
-
break;
|
|
1043
|
-
case "widget.needsAuth":
|
|
1044
|
-
void this.provideAuth();
|
|
1045
|
-
break;
|
|
1046
|
-
}
|
|
1611
|
+
this.handle(event.data.payload);
|
|
1612
|
+
};
|
|
1613
|
+
this.onViewportResize = () => {
|
|
1614
|
+
this.place();
|
|
1047
1615
|
};
|
|
1048
1616
|
this.send = (msg) => {
|
|
1049
1617
|
if (!this.ready) {
|
|
@@ -1069,34 +1637,36 @@ var EmbedHost = class {
|
|
|
1069
1637
|
].join(";");
|
|
1070
1638
|
this.config.container.appendChild(host);
|
|
1071
1639
|
} else {
|
|
1072
|
-
this.
|
|
1640
|
+
this.geo = this.readGeometry();
|
|
1073
1641
|
host.style.cssText = [
|
|
1074
1642
|
"all: initial",
|
|
1075
1643
|
"position: fixed",
|
|
1076
|
-
`right: ${this.pos ? this.pos.right : 20}px`,
|
|
1077
|
-
`bottom: ${this.pos ? this.pos.bottom : 20}px`,
|
|
1078
1644
|
// Below the max so a host that genuinely needs to cover us (a modal, a cookie
|
|
1079
1645
|
// banner they are legally obliged to show) still can.
|
|
1080
1646
|
"z-index: 2147483000",
|
|
1081
|
-
"contain: layout style"
|
|
1647
|
+
"contain: layout style",
|
|
1648
|
+
// The host element owns the BOX now (position AND size); the iframe fills it.
|
|
1649
|
+
// Transitioning the box is what makes the corner snap and the open/dock read as
|
|
1650
|
+
// motion rather than a jump. Suppressed during drag/resize (see those handlers).
|
|
1651
|
+
"transition: width .18s ease, height .18s ease, top .18s ease, right .18s ease, bottom .18s ease, left .18s ease"
|
|
1082
1652
|
].join(";");
|
|
1083
1653
|
document.body.appendChild(host);
|
|
1084
1654
|
}
|
|
1085
1655
|
this.shadow = host.attachShadow({ mode: "closed" });
|
|
1086
1656
|
const style = document.createElement("style");
|
|
1087
|
-
style.textContent = ":host{all:initial}iframe{border:0;display:block;
|
|
1088
|
-
//
|
|
1089
|
-
//
|
|
1090
|
-
'iframe{border-radius:12px;box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029
|
|
1657
|
+
style.textContent = ":host{all:initial}iframe{border:0;display:block;width:100%;height:100%;background:transparent}" + (this.inline ? "" : (
|
|
1658
|
+
// color-scheme only on the OPEN panel — on a collapsed launcher it makes the
|
|
1659
|
+
// UA paint an opaque canvas base behind our shapes.
|
|
1660
|
+
':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}'
|
|
1091
1661
|
));
|
|
1092
1662
|
this.shadow.appendChild(style);
|
|
1093
1663
|
const iframe = document.createElement("iframe");
|
|
1094
1664
|
iframe.title = "matterfact assistant";
|
|
1095
1665
|
iframe.setAttribute(
|
|
1096
1666
|
"sandbox",
|
|
1097
|
-
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
1667
|
+
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
|
|
1098
1668
|
);
|
|
1099
|
-
iframe.setAttribute("allow", "microphone");
|
|
1669
|
+
iframe.setAttribute("allow", "microphone; clipboard-write");
|
|
1100
1670
|
iframe.src = `${this.config.origin}/embed/chat?k=${encodeURIComponent(
|
|
1101
1671
|
this.config.publishableKey
|
|
1102
1672
|
)}&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
|
|
@@ -1105,6 +1675,224 @@ var EmbedHost = class {
|
|
|
1105
1675
|
this.iframe = iframe;
|
|
1106
1676
|
this.shadow.appendChild(iframe);
|
|
1107
1677
|
window.addEventListener("message", this.onMessage);
|
|
1678
|
+
if (!this.inline) {
|
|
1679
|
+
window.addEventListener("resize", this.onViewportResize);
|
|
1680
|
+
this.place();
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a
|
|
1685
|
+
* source-valid MessageEvent — this routes a message through the identical logic
|
|
1686
|
+
* without weakening the origin/source checks above, which stay the only real door.
|
|
1687
|
+
*/
|
|
1688
|
+
__testHandle(msg) {
|
|
1689
|
+
this.handle(msg);
|
|
1690
|
+
}
|
|
1691
|
+
handle(msg) {
|
|
1692
|
+
switch (msg.type) {
|
|
1693
|
+
case "widget.ready":
|
|
1694
|
+
this.ready = true;
|
|
1695
|
+
this.send({
|
|
1696
|
+
type: "host.ready",
|
|
1697
|
+
protocol: PROTOCOL_VERSION,
|
|
1698
|
+
origin: location.origin
|
|
1699
|
+
});
|
|
1700
|
+
this.send({ type: "host.theme", mode: this.themeMode() });
|
|
1701
|
+
this.flush();
|
|
1702
|
+
if (this.inline) void this.loadContext();
|
|
1703
|
+
break;
|
|
1704
|
+
case "widget.setOpen":
|
|
1705
|
+
if (this.inline) break;
|
|
1706
|
+
this.open = msg.open;
|
|
1707
|
+
this.place();
|
|
1708
|
+
if (msg.open) void this.loadContext();
|
|
1709
|
+
break;
|
|
1710
|
+
case "widget.setMode":
|
|
1711
|
+
if (this.inline) break;
|
|
1712
|
+
this.geo.mode = msg.mode;
|
|
1713
|
+
this.writeGeometry(this.geo);
|
|
1714
|
+
this.place();
|
|
1715
|
+
break;
|
|
1716
|
+
case "widget.setDockSide":
|
|
1717
|
+
if (this.inline) break;
|
|
1718
|
+
this.geo.dockSide = msg.side;
|
|
1719
|
+
this.writeGeometry(this.geo);
|
|
1720
|
+
this.place();
|
|
1721
|
+
break;
|
|
1722
|
+
case "widget.snapCorner":
|
|
1723
|
+
if (this.inline) break;
|
|
1724
|
+
this.geo.corner = msg.corner;
|
|
1725
|
+
this.writeGeometry(this.geo);
|
|
1726
|
+
this.place();
|
|
1727
|
+
break;
|
|
1728
|
+
case "widget.setLauncherRegion": {
|
|
1729
|
+
if (this.inline || this.open) break;
|
|
1730
|
+
const vw = window.innerWidth;
|
|
1731
|
+
const vh = window.innerHeight;
|
|
1732
|
+
if (msg.w <= LAUNCHER && msg.h <= LAUNCHER) {
|
|
1733
|
+
this.place();
|
|
1734
|
+
} else {
|
|
1735
|
+
const region = this.geo.mode === "dock" ? dockChrome(this.geo, msg.w, msg.h, vw, vh) : floatChrome(this.geo, msg.w, msg.h, vw, vh);
|
|
1736
|
+
this.applyBox(region, msg.h > LAUNCHER ? "menu" : "pill");
|
|
1737
|
+
}
|
|
1738
|
+
break;
|
|
1739
|
+
}
|
|
1740
|
+
case "widget.resize":
|
|
1741
|
+
if (this.inline) break;
|
|
1742
|
+
if (this.hostEl && this.open && this.geo.mode === "float") {
|
|
1743
|
+
this.hostEl.style.height = `${Math.min(
|
|
1744
|
+
msg.height,
|
|
1745
|
+
Math.max(0, window.innerHeight - 2 * MARGIN)
|
|
1746
|
+
)}px`;
|
|
1747
|
+
}
|
|
1748
|
+
break;
|
|
1749
|
+
case "widget.resizeStart":
|
|
1750
|
+
if (this.inline) break;
|
|
1751
|
+
this.resizeBase = { w: this.geo.floatW, h: this.geo.floatH };
|
|
1752
|
+
this.dockBase = this.geo.dockW;
|
|
1753
|
+
if (this.hostEl) this.hostEl.style.transition = "none";
|
|
1754
|
+
break;
|
|
1755
|
+
case "widget.resizeMove": {
|
|
1756
|
+
if (this.inline || !this.resizeBase) break;
|
|
1757
|
+
if (this.geo.mode === "dock") {
|
|
1758
|
+
const w = applyDockResizeDelta(
|
|
1759
|
+
this.geo.dockSide,
|
|
1760
|
+
this.dockBase,
|
|
1761
|
+
msg.dx
|
|
1762
|
+
);
|
|
1763
|
+
this.geo.dockW = Math.min(
|
|
1764
|
+
Math.max(w, MIN_DOCK_W),
|
|
1765
|
+
Math.max(0, window.innerWidth - 2 * MARGIN)
|
|
1766
|
+
);
|
|
1767
|
+
} else {
|
|
1768
|
+
const next = applyResizeDelta(
|
|
1769
|
+
growthCorner(this.geo, window.innerWidth, window.innerHeight),
|
|
1770
|
+
this.resizeBase.w,
|
|
1771
|
+
this.resizeBase.h,
|
|
1772
|
+
msg.dx,
|
|
1773
|
+
msg.dy
|
|
1774
|
+
);
|
|
1775
|
+
const fit = clampSize(
|
|
1776
|
+
next.w,
|
|
1777
|
+
next.h,
|
|
1778
|
+
window.innerWidth,
|
|
1779
|
+
window.innerHeight,
|
|
1780
|
+
MIN_FLOAT_W,
|
|
1781
|
+
MIN_FLOAT_H
|
|
1782
|
+
);
|
|
1783
|
+
this.geo.floatW = fit.w;
|
|
1784
|
+
this.geo.floatH = fit.h;
|
|
1785
|
+
}
|
|
1786
|
+
this.place();
|
|
1787
|
+
break;
|
|
1788
|
+
}
|
|
1789
|
+
case "widget.resizeEnd":
|
|
1790
|
+
if (this.inline) break;
|
|
1791
|
+
this.resizeBase = null;
|
|
1792
|
+
if (this.hostEl) this.hostEl.style.transition = "";
|
|
1793
|
+
this.writeGeometry(this.geo);
|
|
1794
|
+
break;
|
|
1795
|
+
case "widget.requestContext":
|
|
1796
|
+
void this.loadContext().then((m) => m.provideContext());
|
|
1797
|
+
break;
|
|
1798
|
+
case "widget.requestSnapshot":
|
|
1799
|
+
void this.loadContext().then((m) => m.sendSnapshot(this.send));
|
|
1800
|
+
break;
|
|
1801
|
+
case "widget.readRegion":
|
|
1802
|
+
void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));
|
|
1803
|
+
break;
|
|
1804
|
+
case "widget.callTool":
|
|
1805
|
+
void this.loadContext().then((m) => m.callTool(msg.call, this.send));
|
|
1806
|
+
break;
|
|
1807
|
+
// Everything below is LAUNCHER chrome: there is no launcher inline (the widget
|
|
1808
|
+
// doesn't draw one), and moving or hiding the host's own panel from inside it
|
|
1809
|
+
// would be us redecorating their app. An older cached loader could still be told
|
|
1810
|
+
// any of these by a newer widget, so they're guarded rather than assumed absent.
|
|
1811
|
+
case "widget.dragStart":
|
|
1812
|
+
if (this.inline) break;
|
|
1813
|
+
this.dragFrom = dragAnchor(
|
|
1814
|
+
this.geo,
|
|
1815
|
+
window.innerWidth,
|
|
1816
|
+
window.innerHeight
|
|
1817
|
+
);
|
|
1818
|
+
this.dragAt = { ...this.dragFrom };
|
|
1819
|
+
this.dragLeftBand = false;
|
|
1820
|
+
this.dragGrowth = growthCorner(
|
|
1821
|
+
this.geo,
|
|
1822
|
+
window.innerWidth,
|
|
1823
|
+
window.innerHeight
|
|
1824
|
+
);
|
|
1825
|
+
this.dragBox = visibleBox(
|
|
1826
|
+
this.geo,
|
|
1827
|
+
this.open,
|
|
1828
|
+
window.innerWidth,
|
|
1829
|
+
window.innerHeight
|
|
1830
|
+
);
|
|
1831
|
+
if (this.hostEl) this.hostEl.style.transition = "none";
|
|
1832
|
+
break;
|
|
1833
|
+
case "widget.dragMove": {
|
|
1834
|
+
if (this.inline || !this.dragFrom) break;
|
|
1835
|
+
const vw = window.innerWidth;
|
|
1836
|
+
const vh = window.innerHeight;
|
|
1837
|
+
this.dragAt = {
|
|
1838
|
+
x: this.dragFrom.x + msg.dx,
|
|
1839
|
+
y: this.dragFrom.y + msg.dy
|
|
1840
|
+
};
|
|
1841
|
+
const side = edgeDock(this.dragBox.x + msg.dx, this.dragBox.w, vw);
|
|
1842
|
+
if (!side) this.dragLeftBand = true;
|
|
1843
|
+
if (side && (this.dragLeftBand || this.geo.mode === "dock")) {
|
|
1844
|
+
this.geo.mode = "dock";
|
|
1845
|
+
this.geo.dockSide = side;
|
|
1846
|
+
this.geo.tabY = Math.max(0, this.dragAt.y);
|
|
1847
|
+
} else {
|
|
1848
|
+
this.geo.mode = "float";
|
|
1849
|
+
this.geo.corner = null;
|
|
1850
|
+
this.geo.x = this.dragAt.x;
|
|
1851
|
+
this.geo.y = this.dragAt.y;
|
|
1852
|
+
}
|
|
1853
|
+
this.place();
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
case "widget.dragEnd": {
|
|
1857
|
+
if (this.inline || !this.dragFrom) break;
|
|
1858
|
+
const at = placeDrop(
|
|
1859
|
+
this.dragAt.x,
|
|
1860
|
+
this.dragAt.y,
|
|
1861
|
+
this.dragBox.x + (this.dragAt.x - this.dragFrom.x),
|
|
1862
|
+
this.dragBox.w,
|
|
1863
|
+
window.innerWidth,
|
|
1864
|
+
window.innerHeight
|
|
1865
|
+
);
|
|
1866
|
+
this.geo.mode = at.mode;
|
|
1867
|
+
if (at.mode === "dock") {
|
|
1868
|
+
this.geo.dockSide = at.dockSide;
|
|
1869
|
+
this.geo.tabY = at.tabY;
|
|
1870
|
+
} else {
|
|
1871
|
+
this.geo.corner = at.corner;
|
|
1872
|
+
this.geo.x = at.x;
|
|
1873
|
+
this.geo.y = at.y;
|
|
1874
|
+
}
|
|
1875
|
+
this.dragFrom = null;
|
|
1876
|
+
this.dragGrowth = null;
|
|
1877
|
+
if (this.hostEl) this.hostEl.style.transition = "";
|
|
1878
|
+
this.place();
|
|
1879
|
+
this.writeGeometry(this.geo);
|
|
1880
|
+
break;
|
|
1881
|
+
}
|
|
1882
|
+
case "widget.resetPos":
|
|
1883
|
+
if (this.inline) break;
|
|
1884
|
+
this.geo = defaultGeometry();
|
|
1885
|
+
this.writeGeometry(this.geo);
|
|
1886
|
+
this.place();
|
|
1887
|
+
break;
|
|
1888
|
+
case "widget.hide":
|
|
1889
|
+
if (this.inline) break;
|
|
1890
|
+
if (this.hostEl) this.hostEl.style.display = "none";
|
|
1891
|
+
break;
|
|
1892
|
+
case "widget.needsAuth":
|
|
1893
|
+
void this.provideAuth();
|
|
1894
|
+
break;
|
|
1895
|
+
}
|
|
1108
1896
|
}
|
|
1109
1897
|
/**
|
|
1110
1898
|
* Own a drag for its lifetime.
|
|
@@ -1118,47 +1906,77 @@ var EmbedHost = class {
|
|
|
1118
1906
|
* We are the dumb half on purpose — see `widget.dragMove` in the protocol for why the
|
|
1119
1907
|
* widget has to own the gesture. All we do is take a delta and place the element.
|
|
1120
1908
|
*/
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1909
|
+
/** Write a box onto the host element and stamp the mode the shadow CSS keys off. */
|
|
1910
|
+
applyBox(box, mode) {
|
|
1911
|
+
if (!this.hostEl || this.inline) return;
|
|
1912
|
+
const s = this.hostEl.style;
|
|
1913
|
+
s.top = box.top;
|
|
1914
|
+
s.right = box.right;
|
|
1915
|
+
s.bottom = box.bottom;
|
|
1916
|
+
s.left = box.left;
|
|
1917
|
+
s.width = box.width;
|
|
1918
|
+
s.height = box.height;
|
|
1919
|
+
this.hostEl.dataset.mode = mode;
|
|
1920
|
+
if (mode === "tab" || mode === "dock")
|
|
1921
|
+
this.hostEl.dataset.flush = this.geo.dockSide;
|
|
1922
|
+
else delete this.hostEl.dataset.flush;
|
|
1127
1923
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
this.
|
|
1924
|
+
/** Re-apply the box for whatever state we're in: collapsed launcher, float, or dock. */
|
|
1925
|
+
/**
|
|
1926
|
+
* The growth direction to use RIGHT NOW — frozen while a drag is in flight.
|
|
1927
|
+
*
|
|
1928
|
+
* Every consumer has to agree on this, which is the bug that made the widget
|
|
1929
|
+
* flip-flop under small movements near the viewport centre: the box was frozen but
|
|
1930
|
+
* `host.geometry` still published a freshly-derived growth, so the WIDGET kept
|
|
1931
|
+
* swapping which corner it drew against (and it animates that), twitching back and
|
|
1932
|
+
* forth while the loader held perfectly still.
|
|
1933
|
+
*/
|
|
1934
|
+
growthNow() {
|
|
1935
|
+
return this.dragGrowth ?? growthCorner(this.geo, window.innerWidth, window.innerHeight);
|
|
1936
|
+
}
|
|
1937
|
+
place() {
|
|
1938
|
+
const vw = window.innerWidth;
|
|
1939
|
+
const vh = window.innerHeight;
|
|
1940
|
+
if (!this.open) {
|
|
1941
|
+
const dock = this.geo.mode === "dock";
|
|
1942
|
+
this.applyBox(
|
|
1943
|
+
dock ? tabBox(this.geo, vw, vh) : floatChrome(this.geo, LAUNCHER, LAUNCHER, vw, vh, this.growthNow()),
|
|
1944
|
+
dock ? "tab" : "launcher"
|
|
1945
|
+
);
|
|
1946
|
+
} else if (this.geo.mode === "dock") {
|
|
1947
|
+
this.applyBox(dockBox(this.geo, vw, vh), "dock");
|
|
1948
|
+
} else {
|
|
1949
|
+
this.applyBox(floatBox(this.geo, vw, vh, this.growthNow()), "float");
|
|
1146
1950
|
}
|
|
1951
|
+
this.publishGeometry();
|
|
1147
1952
|
}
|
|
1148
|
-
|
|
1953
|
+
/**
|
|
1954
|
+
* Tell the widget where it is. It can't see the viewport and doesn't own the box, so
|
|
1955
|
+
* without this it draws its launcher against a stale guess — which is what made the
|
|
1956
|
+
* circle jump when the launcher sat on the left (see `host.geometry` in the protocol).
|
|
1957
|
+
*/
|
|
1958
|
+
publishGeometry() {
|
|
1959
|
+
this.send({
|
|
1960
|
+
type: "host.geometry",
|
|
1961
|
+
mode: this.geo.mode,
|
|
1962
|
+
dockSide: this.geo.dockSide,
|
|
1963
|
+
growth: this.growthNow()
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
/** The theme to publish: an explicit data-theme wins, else the OS preference. */
|
|
1967
|
+
themeMode() {
|
|
1968
|
+
return this.config.theme === "auto" ? matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this.config.theme;
|
|
1969
|
+
}
|
|
1970
|
+
readGeometry() {
|
|
1149
1971
|
try {
|
|
1150
|
-
|
|
1151
|
-
if (!raw) return null;
|
|
1152
|
-
const p = JSON.parse(raw);
|
|
1153
|
-
return typeof p?.right === "number" && typeof p?.bottom === "number" ? { right: p.right, bottom: p.bottom } : null;
|
|
1972
|
+
return parseGeometry(localStorage.getItem(POS_KEY)) ?? defaultGeometry();
|
|
1154
1973
|
} catch {
|
|
1155
|
-
return
|
|
1974
|
+
return defaultGeometry();
|
|
1156
1975
|
}
|
|
1157
1976
|
}
|
|
1158
|
-
|
|
1977
|
+
writeGeometry(g) {
|
|
1159
1978
|
try {
|
|
1160
|
-
|
|
1161
|
-
else localStorage.removeItem(POS_KEY);
|
|
1979
|
+
localStorage.setItem(POS_KEY, serializeGeometry(g));
|
|
1162
1980
|
} catch {
|
|
1163
1981
|
}
|
|
1164
1982
|
}
|
|
@@ -1214,6 +2032,7 @@ var EmbedHost = class {
|
|
|
1214
2032
|
* calls this. */
|
|
1215
2033
|
destroy() {
|
|
1216
2034
|
window.removeEventListener("message", this.onMessage);
|
|
2035
|
+
window.removeEventListener("resize", this.onViewportResize);
|
|
1217
2036
|
this.hostEl?.remove();
|
|
1218
2037
|
this.hostEl = null;
|
|
1219
2038
|
this.iframe = null;
|
|
@@ -1231,6 +2050,12 @@ function mount(config) {
|
|
|
1231
2050
|
// src/react.tsx
|
|
1232
2051
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1233
2052
|
var DEFAULT_ORIGIN = "https://app.matterfact.com";
|
|
2053
|
+
function writeActionsGlobal(actions) {
|
|
2054
|
+
if (typeof window === "undefined") return;
|
|
2055
|
+
const w = window;
|
|
2056
|
+
const mf = w.matterfact ?? (w.matterfact = {});
|
|
2057
|
+
mf.hoist = { ...mf.hoist, actions: actions ?? { navigate: "off" } };
|
|
2058
|
+
}
|
|
1234
2059
|
function MatterfactAgent({
|
|
1235
2060
|
publishableKey,
|
|
1236
2061
|
widgetOrigin: widgetOrigin2,
|
|
@@ -1239,19 +2064,24 @@ function MatterfactAgent({
|
|
|
1239
2064
|
getAuthToken,
|
|
1240
2065
|
getPageContext,
|
|
1241
2066
|
inline = false,
|
|
1242
|
-
className,
|
|
2067
|
+
className: className2,
|
|
1243
2068
|
style,
|
|
1244
2069
|
pageContext,
|
|
1245
|
-
dev
|
|
2070
|
+
dev,
|
|
2071
|
+
actions
|
|
1246
2072
|
}) {
|
|
1247
2073
|
const authRef = (0, import_react.useRef)(getAuthToken);
|
|
1248
2074
|
authRef.current = getAuthToken;
|
|
1249
2075
|
const pageContextRef = (0, import_react.useRef)(getPageContext);
|
|
1250
2076
|
pageContextRef.current = getPageContext;
|
|
2077
|
+
const actionsRef = (0, import_react.useRef)(actions);
|
|
2078
|
+
actionsRef.current = actions;
|
|
2079
|
+
const actionsKey = JSON.stringify(actions ?? null);
|
|
1251
2080
|
const slot = (0, import_react.useRef)(null);
|
|
1252
2081
|
(0, import_react.useEffect)(() => {
|
|
1253
2082
|
if (typeof window === "undefined") return;
|
|
1254
2083
|
if (inline && !slot.current) return;
|
|
2084
|
+
writeActionsGlobal(actionsRef.current);
|
|
1255
2085
|
const config = {
|
|
1256
2086
|
publishableKey,
|
|
1257
2087
|
origin: widgetOrigin2 || DEFAULT_ORIGIN,
|
|
@@ -1274,13 +2104,22 @@ function MatterfactAgent({
|
|
|
1274
2104
|
console.error("[matterfact] failed to mount the embed widget", e);
|
|
1275
2105
|
}
|
|
1276
2106
|
return () => host?.destroy();
|
|
1277
|
-
}, [
|
|
2107
|
+
}, [
|
|
2108
|
+
publishableKey,
|
|
2109
|
+
widgetOrigin2,
|
|
2110
|
+
surface,
|
|
2111
|
+
theme,
|
|
2112
|
+
inline,
|
|
2113
|
+
pageContext,
|
|
2114
|
+
dev,
|
|
2115
|
+
actionsKey
|
|
2116
|
+
]);
|
|
1278
2117
|
if (!inline) return null;
|
|
1279
2118
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1280
2119
|
"div",
|
|
1281
2120
|
{
|
|
1282
2121
|
ref: slot,
|
|
1283
|
-
className,
|
|
2122
|
+
className: className2,
|
|
1284
2123
|
style: { width: "100%", height: "100%", ...style }
|
|
1285
2124
|
}
|
|
1286
2125
|
);
|
|
@@ -1292,7 +2131,7 @@ function MatterfactArtifact({
|
|
|
1292
2131
|
token,
|
|
1293
2132
|
widgetOrigin: widgetOrigin2,
|
|
1294
2133
|
theme = "auto",
|
|
1295
|
-
className,
|
|
2134
|
+
className: className2,
|
|
1296
2135
|
style
|
|
1297
2136
|
}) {
|
|
1298
2137
|
const origin = widgetOrigin2 || DEFAULT_ARTIFACT_ORIGIN;
|
|
@@ -1302,7 +2141,7 @@ function MatterfactArtifact({
|
|
|
1302
2141
|
{
|
|
1303
2142
|
src,
|
|
1304
2143
|
title: `matterfact artifact ${slug}`,
|
|
1305
|
-
className,
|
|
2144
|
+
className: className2,
|
|
1306
2145
|
style: { width: "100%", height: 600, border: 0, ...style },
|
|
1307
2146
|
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
1308
2147
|
}
|