@matterfact/embed 0.7.0 → 0.9.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 +171 -1
- package/dist/chunk-6EM7T2JV.js +816 -0
- package/dist/chunk-6EM7T2JV.js.map +1 -0
- package/dist/{chunk-4Q2ROXLR.js → chunk-BKKXHSYU.js} +2 -2
- package/dist/chunk-FEXG4LQJ.js +3 -0
- package/dist/chunk-FEXG4LQJ.js.map +7 -0
- package/dist/chunk-NWNMS34P.js +2 -0
- package/dist/chunk-URGQBG4I.js +800 -0
- package/dist/chunk-URGQBG4I.js.map +1 -0
- package/dist/context-ACFBWIFH.js +3 -0
- package/dist/{context-YX2KXFLI.js.map → context-ACFBWIFH.js.map} +1 -1
- package/dist/{context-WU2F5CBC.js → context-U2HJJN2S.js} +4 -2
- package/dist/embed.js +1 -1
- package/dist/embed.js.map +4 -4
- package/dist/index.cjs +1006 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +192 -32
- package/dist/index.d.ts +192 -32
- package/dist/index.js +537 -130
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1042 -165
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +81 -1
- package/dist/react.d.ts +81 -1
- package/dist/react.js +566 -132
- package/dist/react.js.map +1 -1
- package/dist/{snapshot-JOGZWESK.js → snapshot-4GXT6PKZ.js} +2 -2
- package/dist/{snapshot-MUXE7KXX.js → snapshot-Y75SCGCM.js} +3 -3
- package/dist/{snapshot-MUXE7KXX.js.map → snapshot-Y75SCGCM.js.map} +1 -1
- 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-7I37ZFAJ.js +0 -2
- 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-BKKXHSYU.js.map} +0 -0
- /package/dist/{chunk-7I37ZFAJ.js.map → chunk-NWNMS34P.js.map} +0 -0
- /package/dist/{context-WU2F5CBC.js.map → context-U2HJJN2S.js.map} +0 -0
- /package/dist/{snapshot-JOGZWESK.js.map → snapshot-4GXT6PKZ.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -11,6 +11,398 @@ var __export = (target, all) => {
|
|
|
11
11
|
for (var name in all)
|
|
12
12
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
13
|
};
|
|
14
|
+
|
|
15
|
+
// src/redact.ts
|
|
16
|
+
function redact(text) {
|
|
17
|
+
let out = text;
|
|
18
|
+
for (const re of PII) out = out.replace(re, "[redacted]");
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
var PII;
|
|
22
|
+
var init_redact = __esm({
|
|
23
|
+
"src/redact.ts"() {
|
|
24
|
+
PII = [
|
|
25
|
+
/\b[\w.+-]+@[\w-]+\.[\w.]{2,}\b/g,
|
|
26
|
+
// email
|
|
27
|
+
/\b(?:\d[ -]?){13,19}\b/g,
|
|
28
|
+
// card-ish
|
|
29
|
+
/\b\d{3}[- ]?\d{2}[- ]?\d{4}\b/g,
|
|
30
|
+
// ssn — dashed, spaced, OR bare 9 digits
|
|
31
|
+
/\b\d{9,}\b/g,
|
|
32
|
+
// long bare digit runs: account / MRN / routing numbers
|
|
33
|
+
/(?:\+?\d{1,2}[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b/g,
|
|
34
|
+
// us phone
|
|
35
|
+
/\b(?:sk|pk|rk|ak)_(?:live|test)_[A-Za-z0-9]{8,}\b/g,
|
|
36
|
+
// stripe-style api keys
|
|
37
|
+
/\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g
|
|
38
|
+
// jwt
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// src/adapters/hoist.ts
|
|
44
|
+
function detectHoist(read) {
|
|
45
|
+
let rt;
|
|
46
|
+
try {
|
|
47
|
+
rt = read();
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (!rt) return null;
|
|
52
|
+
const worthwhile = !!rt.route.name || rt.grids.length > 0 || rt.charts.length > 0 || rt.nav.length > 0;
|
|
53
|
+
return worthwhile ? rt : null;
|
|
54
|
+
}
|
|
55
|
+
function quote(s, max = 120) {
|
|
56
|
+
const clean = redact(s.replace(/\s+/g, " ").trim());
|
|
57
|
+
const capped = clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
58
|
+
return `"${capped.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
59
|
+
}
|
|
60
|
+
function cell(value) {
|
|
61
|
+
const s = value == null ? "" : String(value);
|
|
62
|
+
return `- cell ${quote(s)}`;
|
|
63
|
+
}
|
|
64
|
+
function pushRow(row, cols, out, pad) {
|
|
65
|
+
out.push(`${pad}- row:`);
|
|
66
|
+
for (const c of cols) out.push(`${pad} ${cell(row[c.field])}`);
|
|
67
|
+
}
|
|
68
|
+
function renderGrid(grid, rowCap, out, depth) {
|
|
69
|
+
const pad = " ".repeat(depth);
|
|
70
|
+
out.push(`${pad}- table ${quote(grid.title || "grid")}:`);
|
|
71
|
+
out.push(`${pad} - rowgroup "columns":`);
|
|
72
|
+
for (const c of grid.columns)
|
|
73
|
+
out.push(`${pad} - columnheader ${quote(c.header || c.field)}`);
|
|
74
|
+
if (grid.selected.length) {
|
|
75
|
+
out.push(`${pad} - rowgroup "selected":`);
|
|
76
|
+
for (const r of grid.selected) pushRow(r, grid.columns, out, `${pad} `);
|
|
77
|
+
}
|
|
78
|
+
const shown = grid.rows.slice(0, Math.max(0, rowCap));
|
|
79
|
+
out.push(`${pad} - rowgroup "rows":`);
|
|
80
|
+
for (const r of shown) pushRow(r, grid.columns, out, `${pad} `);
|
|
81
|
+
out.push(
|
|
82
|
+
`${pad} - text: ${quote(`showing ${shown.length} of ${grid.totalCount} rows`)}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
function seriesSummary(name, data) {
|
|
86
|
+
if (!data.length) return `series ${name}: 0 points`;
|
|
87
|
+
let min = data[0];
|
|
88
|
+
let max = data[0];
|
|
89
|
+
for (const n of data) {
|
|
90
|
+
if (n < min) min = n;
|
|
91
|
+
if (n > max) max = n;
|
|
92
|
+
}
|
|
93
|
+
const last = data[data.length - 1];
|
|
94
|
+
return `series ${name}: ${data.length} points, min ${min}, max ${max}, last ${last}`;
|
|
95
|
+
}
|
|
96
|
+
function renderChart(chart, out, depth) {
|
|
97
|
+
const pad = " ".repeat(depth);
|
|
98
|
+
out.push(`${pad}- figure ${quote(chart.title || "chart")}:`);
|
|
99
|
+
for (const s of chart.series) {
|
|
100
|
+
out.push(`${pad} - text: ${quote(seriesSummary(s.name, s.data))}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function renderNav(groups, out, depth) {
|
|
104
|
+
const pad = " ".repeat(depth);
|
|
105
|
+
for (const g of groups) {
|
|
106
|
+
if (!g.items.length) continue;
|
|
107
|
+
out.push(`${pad}- navigation ${quote(g.label)}:`);
|
|
108
|
+
for (const d of g.items) {
|
|
109
|
+
const mark = d.current ? " [current]" : "";
|
|
110
|
+
out.push(`${pad} - link ${quote(d.label)}${mark} \u2192 ${redact(d.path)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function flattenDestinations(rt) {
|
|
115
|
+
return rt.nav.flatMap((g) => g.items);
|
|
116
|
+
}
|
|
117
|
+
function buildNavigateTool(rt, policy) {
|
|
118
|
+
const destinations = flattenDestinations(rt);
|
|
119
|
+
const bySection = rt.nav.filter((g) => g.items.length).map((g) => `${g.label} (${g.items.map((d) => d.label).join(", ")})`).join("; ");
|
|
120
|
+
return {
|
|
121
|
+
name: "hoist.navigate",
|
|
122
|
+
description: redact(
|
|
123
|
+
`Navigate the user to a destination in the app. "destination" must be one of the destinations currently available, grouped by section: ${bySection || "(none available)"}.`
|
|
124
|
+
),
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
destination: {
|
|
129
|
+
type: "string",
|
|
130
|
+
enum: destinations.map((d) => d.label)
|
|
131
|
+
},
|
|
132
|
+
params: { type: "object" }
|
|
133
|
+
},
|
|
134
|
+
required: ["destination"]
|
|
135
|
+
},
|
|
136
|
+
readOnly: false,
|
|
137
|
+
// 'confirm' policy ⇒ the widget must show a confirm card every call; 'auto' ⇒ it
|
|
138
|
+
// may call without asking. See the confirm field's doc comment on HostTool.
|
|
139
|
+
confirm: policy === "auto" ? "auto" : "required"
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function buildHoistContext(rt) {
|
|
143
|
+
const selected = rt.grids.reduce((n, g) => n + g.selected.length, 0);
|
|
144
|
+
const view = rt.route.name || rt.route.path || "this view";
|
|
145
|
+
const bits = [`Viewing ${view}`];
|
|
146
|
+
if (selected) bits.push(`${selected} selected`);
|
|
147
|
+
if (rt.grids.length) bits.push(`${rt.grids.length} grid(s)`);
|
|
148
|
+
if (rt.charts.length) bits.push(`${rt.charts.length} chart(s)`);
|
|
149
|
+
return {
|
|
150
|
+
route: rt.route.name ? redact(rt.route.name) : void 0,
|
|
151
|
+
description: redact(`${bits.join(", ")}.`)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function renderHoistSnapshot(rt, rowCap) {
|
|
155
|
+
const out = [];
|
|
156
|
+
renderNav(rt.nav, out, 0);
|
|
157
|
+
let truncated = false;
|
|
158
|
+
for (const g of rt.grids) {
|
|
159
|
+
if (g.rows.length > rowCap) truncated = true;
|
|
160
|
+
renderGrid(g, rowCap, out, 0);
|
|
161
|
+
}
|
|
162
|
+
for (const c of rt.charts) renderChart(c, out, 0);
|
|
163
|
+
return { yaml: out.join("\n"), truncated };
|
|
164
|
+
}
|
|
165
|
+
var init_hoist = __esm({
|
|
166
|
+
"src/adapters/hoist.ts"() {
|
|
167
|
+
init_redact();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// src/adapters/hoist-runtime.ts
|
|
172
|
+
function readHoistConfig(win) {
|
|
173
|
+
const cfg = win?.matterfact?.hoist;
|
|
174
|
+
const ex = cfg?.excludeModels;
|
|
175
|
+
return {
|
|
176
|
+
rows: typeof cfg?.rows === "number" && cfg.rows >= 0 ? cfg.rows : 50,
|
|
177
|
+
excludeModels: Array.isArray(ex) ? ex : []
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function getModelsByClass(XH, name) {
|
|
181
|
+
const models = typeof XH?.getModels === "function" ? XH.getModels(name) : [];
|
|
182
|
+
return Array.isArray(models) ? models : [];
|
|
183
|
+
}
|
|
184
|
+
function className(m) {
|
|
185
|
+
return m?.constructor?.name ?? "";
|
|
186
|
+
}
|
|
187
|
+
function modelId(m) {
|
|
188
|
+
return String(m?.xhId ?? m?.id ?? m?.modelId ?? "");
|
|
189
|
+
}
|
|
190
|
+
function headerOf(c) {
|
|
191
|
+
return String(c?.headerName ?? c?.displayName ?? c?.field ?? c?.colId ?? "");
|
|
192
|
+
}
|
|
193
|
+
function collectHeaders(cols, prefix, out) {
|
|
194
|
+
for (const c of Array.isArray(cols) ? cols : []) {
|
|
195
|
+
const children = c?.children;
|
|
196
|
+
if (Array.isArray(children) && children.length) {
|
|
197
|
+
collectHeaders(children, [...prefix, headerOf(c)], out);
|
|
198
|
+
} else {
|
|
199
|
+
const id = String(c?.colId ?? c?.field ?? "");
|
|
200
|
+
if (id) out.set(id, [...prefix, headerOf(c)].filter(Boolean).join(" "));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function readColumns(g) {
|
|
205
|
+
const leaves = typeof g?.getVisibleLeafColumns === "function" ? g.getVisibleLeafColumns() : Array.isArray(g?.columns) ? g.columns : [];
|
|
206
|
+
const headers = /* @__PURE__ */ new Map();
|
|
207
|
+
collectHeaders(Array.isArray(g?.columns) ? g.columns : [], [], headers);
|
|
208
|
+
return (Array.isArray(leaves) ? leaves : []).map((c) => {
|
|
209
|
+
const field = String(c?.colId ?? c?.field ?? "");
|
|
210
|
+
return { field, header: headers.get(field) || headerOf(c) };
|
|
211
|
+
}).filter((c) => c.field);
|
|
212
|
+
}
|
|
213
|
+
function readRow(rec, cols) {
|
|
214
|
+
const row = {};
|
|
215
|
+
for (const c of cols)
|
|
216
|
+
row[c.field] = typeof rec?.get === "function" ? rec.get(c.field) : rec?.data?.[c.field];
|
|
217
|
+
return row;
|
|
218
|
+
}
|
|
219
|
+
function readGrid(g) {
|
|
220
|
+
const columns = readColumns(g);
|
|
221
|
+
const records = Array.isArray(g?.store?.records) ? g.store.records : [];
|
|
222
|
+
return {
|
|
223
|
+
id: modelId(g),
|
|
224
|
+
title: String(g?.title ?? ""),
|
|
225
|
+
columns,
|
|
226
|
+
selected: (Array.isArray(g?.selectedRecords) ? g.selectedRecords : []).map(
|
|
227
|
+
(r) => readRow(r, columns)
|
|
228
|
+
),
|
|
229
|
+
rows: records.map((r) => readRow(r, columns)),
|
|
230
|
+
totalCount: typeof g?.store?.allCount === "number" ? g.store.allCount : records.length
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function hasContent(g) {
|
|
234
|
+
return g.totalCount > 0 || g.selected.length > 0;
|
|
235
|
+
}
|
|
236
|
+
function readChart(c) {
|
|
237
|
+
const series = c?.series ?? c?.highchartsConfig?.series ?? [];
|
|
238
|
+
return {
|
|
239
|
+
id: modelId(c),
|
|
240
|
+
title: String(c?.title ?? c?.highchartsConfig?.title?.text ?? ""),
|
|
241
|
+
series: (Array.isArray(series) ? series : []).map((s) => ({
|
|
242
|
+
name: String(s?.name ?? ""),
|
|
243
|
+
data: (Array.isArray(s?.data) ? s.data : []).map((d) => typeof d === "number" ? d : Number(d?.y ?? d?.[1])).filter((n) => Number.isFinite(n))
|
|
244
|
+
}))
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function humanize(seg) {
|
|
248
|
+
return seg.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()).trim();
|
|
249
|
+
}
|
|
250
|
+
function childrenOf(node) {
|
|
251
|
+
const k = node?.children;
|
|
252
|
+
return k ? Array.isArray(k) ? k : Array.from(k) : [];
|
|
253
|
+
}
|
|
254
|
+
function routeIndex(XH, params) {
|
|
255
|
+
const router = XH?.router;
|
|
256
|
+
const root = router?.rootNode ?? router?.config?.rootNode;
|
|
257
|
+
const out = /* @__PURE__ */ new Map();
|
|
258
|
+
if (!router || typeof router.buildPath !== "function" || !root) return out;
|
|
259
|
+
const walk2 = (node, prefix) => {
|
|
260
|
+
for (const c of childrenOf(node)) {
|
|
261
|
+
const seg = String(c?.name ?? "");
|
|
262
|
+
if (!seg) continue;
|
|
263
|
+
const name = prefix ? `${prefix}.${seg}` : seg;
|
|
264
|
+
let path = "";
|
|
265
|
+
try {
|
|
266
|
+
path = router.buildPath(name, params);
|
|
267
|
+
} catch {
|
|
268
|
+
path = "";
|
|
269
|
+
}
|
|
270
|
+
if (path && !out.has(seg)) out.set(seg, { path, name });
|
|
271
|
+
walk2(c, name);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
walk2(root, "");
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
function readSitemap(XH, excluded, origin) {
|
|
278
|
+
const params = XH?.routerState?.params ?? {};
|
|
279
|
+
const currentName = String(XH?.routerState?.name ?? "");
|
|
280
|
+
const routes = routeIndex(XH, params);
|
|
281
|
+
if (!routes.size) return [];
|
|
282
|
+
const groups = [];
|
|
283
|
+
for (const tc of getModelsByClass(XH, "TabContainerModel")) {
|
|
284
|
+
if (excluded(tc)) continue;
|
|
285
|
+
const tabs = Array.isArray(tc?.tabs) ? tc.tabs : [];
|
|
286
|
+
const activeTabId = String(tc?.activeTabId ?? "");
|
|
287
|
+
const items = [];
|
|
288
|
+
let companyScoped = false;
|
|
289
|
+
for (const t of tabs) {
|
|
290
|
+
const id = String(t?.id ?? "");
|
|
291
|
+
const r = routes.get(id);
|
|
292
|
+
if (!id || !r) continue;
|
|
293
|
+
if (r.name.includes(".company.ticker.")) companyScoped = true;
|
|
294
|
+
items.push({
|
|
295
|
+
// Redact HERE, at the source — not in a downstream renderer. This is the
|
|
296
|
+
// one place a Hoist tab TITLE (host-page text, so potentially PII-shaped)
|
|
297
|
+
// becomes a `HoistDestination.label`, and that label is what EVERY
|
|
298
|
+
// consumer uses verbatim: renderNav's YAML, buildNavigateTool's
|
|
299
|
+
// description AND its inputSchema.enum, and executeHostTool's
|
|
300
|
+
// `.find(d => d.label === destination)` lookup. Redacting once here keeps
|
|
301
|
+
// all of them consistent by construction — an enum built from an
|
|
302
|
+
// ALREADY-redacted label can never leak PII a sibling renderer scrubbed
|
|
303
|
+
// (see Important #1), and a destination arg matching the redacted string
|
|
304
|
+
// (the only thing the agent ever saw) still resolves.
|
|
305
|
+
label: redact(String(t?.title ?? "") || humanize(id)),
|
|
306
|
+
// ABSOLUTE, against the HOST page's origin — not the router's bare path.
|
|
307
|
+
// This path's only job is to be a link the agent can hand the user, and
|
|
308
|
+
// the agent's answer renders inside our widget iframe, which is on OUR
|
|
309
|
+
// origin: a bare "/research" there resolves to app.matterfact.com/research
|
|
310
|
+
// (a dead link to the wrong app), which is exactly what shipped before.
|
|
311
|
+
// The adapter runs in the host page, so its own origin is the right one.
|
|
312
|
+
path: origin && r.path.startsWith("/") ? origin + r.path : r.path,
|
|
313
|
+
// The fully-qualified route name — what hoist.navigate hands XH.navigate.
|
|
314
|
+
// Always present: `r` only exists here because routes.get(id) resolved.
|
|
315
|
+
name: r.name,
|
|
316
|
+
current: id === activeTabId || r.name === currentName
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (!items.length) continue;
|
|
320
|
+
const ticker = String(params.ticker ?? "");
|
|
321
|
+
const label = companyScoped ? ticker ? `Company \xB7 ${ticker}` : "Company" : "Site";
|
|
322
|
+
groups.push({ label, items });
|
|
323
|
+
}
|
|
324
|
+
return groups;
|
|
325
|
+
}
|
|
326
|
+
function readHoistRuntime(win = typeof window !== "undefined" ? window : void 0) {
|
|
327
|
+
const XH = win?.XH;
|
|
328
|
+
if (!XH) return null;
|
|
329
|
+
const { excludeModels } = readHoistConfig(win);
|
|
330
|
+
const excluded = (m) => excludeModels.includes(className(m)) || excludeModels.includes(modelId(m));
|
|
331
|
+
const rs = XH.routerState ?? {};
|
|
332
|
+
return {
|
|
333
|
+
route: {
|
|
334
|
+
name: String(rs.name ?? ""),
|
|
335
|
+
path: String(rs.path ?? ""),
|
|
336
|
+
params: rs.params ?? {}
|
|
337
|
+
},
|
|
338
|
+
grids: getModelsByClass(XH, "GridModel").filter((m) => !excluded(m)).map(readGrid).filter(hasContent),
|
|
339
|
+
charts: getModelsByClass(XH, "ChartModel").filter((m) => !excluded(m)).map(readChart).filter((c) => c.series.length > 0),
|
|
340
|
+
nav: readSitemap(
|
|
341
|
+
XH,
|
|
342
|
+
excluded,
|
|
343
|
+
String(
|
|
344
|
+
win?.location?.origin ?? ""
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function readActionsConfig(win) {
|
|
350
|
+
const navigate = win?.matterfact?.hoist?.actions?.navigate;
|
|
351
|
+
return {
|
|
352
|
+
navigate: navigate === "confirm" || navigate === "auto" ? navigate : "off"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function advertiseTools(win = typeof window !== "undefined" ? window : void 0) {
|
|
356
|
+
try {
|
|
357
|
+
const XH = win?.XH;
|
|
358
|
+
if (!XH) return [];
|
|
359
|
+
const { navigate } = readActionsConfig(win);
|
|
360
|
+
if (navigate === "off") return [];
|
|
361
|
+
const rt = readHoistRuntime(win);
|
|
362
|
+
if (!rt) return [];
|
|
363
|
+
return [buildNavigateTool(rt, navigate)];
|
|
364
|
+
} catch {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function executeHostTool(call, win = typeof window !== "undefined" ? window : void 0) {
|
|
369
|
+
try {
|
|
370
|
+
if (call.name !== "hoist.navigate") {
|
|
371
|
+
return { ok: false, error: "unknown tool" };
|
|
372
|
+
}
|
|
373
|
+
if (readActionsConfig(win).navigate === "off") {
|
|
374
|
+
return { ok: false, error: "navigation is disabled" };
|
|
375
|
+
}
|
|
376
|
+
const destination = String(call.args?.destination ?? "");
|
|
377
|
+
const rt = readHoistRuntime(win);
|
|
378
|
+
const item = rt ? flattenDestinations(rt).find((d) => d.label === destination) : void 0;
|
|
379
|
+
if (!item) return { ok: false, error: "unknown destination" };
|
|
380
|
+
const XH = win?.XH;
|
|
381
|
+
const params = {
|
|
382
|
+
...XH?.routerState?.params ?? {},
|
|
383
|
+
...call.args?.params ?? {}
|
|
384
|
+
};
|
|
385
|
+
if (typeof XH?.navigate === "function") {
|
|
386
|
+
XH.navigate(item.name, params);
|
|
387
|
+
} else if (typeof XH?.router?.navigate === "function") {
|
|
388
|
+
XH.router.navigate(item.name, params);
|
|
389
|
+
} else {
|
|
390
|
+
return { ok: false, error: "navigation unavailable" };
|
|
391
|
+
}
|
|
392
|
+
return { ok: true, result: redact(`navigated to ${destination}`) };
|
|
393
|
+
} catch (e) {
|
|
394
|
+
return {
|
|
395
|
+
ok: false,
|
|
396
|
+
error: redact(e instanceof Error ? e.message : "navigate failed")
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
var init_hoist_runtime = __esm({
|
|
401
|
+
"src/adapters/hoist-runtime.ts"() {
|
|
402
|
+
init_hoist();
|
|
403
|
+
init_redact();
|
|
404
|
+
}
|
|
405
|
+
});
|
|
14
406
|
function createStyleCache() {
|
|
15
407
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
16
408
|
return ((el, pseudo) => {
|
|
@@ -260,7 +652,7 @@ function isHidden(el, style, layout) {
|
|
|
260
652
|
if (el.hasAttribute("hidden") || el.hasAttribute("inert")) return true;
|
|
261
653
|
return !isVisible(el, style, layout);
|
|
262
654
|
}
|
|
263
|
-
function
|
|
655
|
+
function childrenOf2(el) {
|
|
264
656
|
if (el.shadowRoot) return el.shadowRoot.childNodes;
|
|
265
657
|
if (el.tagName === "SLOT") {
|
|
266
658
|
const assigned = el.assignedNodes({ flatten: true });
|
|
@@ -269,7 +661,7 @@ function childrenOf(el) {
|
|
|
269
661
|
return el.childNodes;
|
|
270
662
|
}
|
|
271
663
|
function visitChildren(el, parent, style, w) {
|
|
272
|
-
const children =
|
|
664
|
+
const children = childrenOf2(el);
|
|
273
665
|
for (let i = 0; i < children.length; i++) {
|
|
274
666
|
const child = children[i];
|
|
275
667
|
if (child) visitNode(child, parent, style, w);
|
|
@@ -390,17 +782,17 @@ function sanitize(text, max) {
|
|
|
390
782
|
const clean = redact(collapsed);
|
|
391
783
|
return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
392
784
|
}
|
|
393
|
-
function
|
|
785
|
+
function quote2(text) {
|
|
394
786
|
return `"${text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
395
787
|
}
|
|
396
788
|
function render(children, depth, out) {
|
|
397
789
|
const pad = " ".repeat(depth);
|
|
398
790
|
for (const child of children) {
|
|
399
791
|
if (typeof child === "string") {
|
|
400
|
-
out.push(`${pad}- text: ${
|
|
792
|
+
out.push(`${pad}- text: ${quote2(sanitize(child, MAX_TEXT))}`);
|
|
401
793
|
continue;
|
|
402
794
|
}
|
|
403
|
-
const name = child.name ? ` ${
|
|
795
|
+
const name = child.name ? ` ${quote2(sanitize(child.name, MAX_NAME))}` : "";
|
|
404
796
|
const head = `${pad}- ${child.role}${name}${child.props}`;
|
|
405
797
|
if (child.children.length) {
|
|
406
798
|
out.push(`${head}:`);
|
|
@@ -486,6 +878,7 @@ __export(context_exports, {
|
|
|
486
878
|
callTool: () => callTool,
|
|
487
879
|
grantsFromIframeSrcs: () => grantsFromIframeSrcs,
|
|
488
880
|
isPrivate: () => isPrivate,
|
|
881
|
+
navigateHost: () => navigateHost,
|
|
489
882
|
provideContext: () => provideContext,
|
|
490
883
|
readPageContext: () => readPageContext,
|
|
491
884
|
redact: () => redact,
|
|
@@ -494,11 +887,6 @@ __export(context_exports, {
|
|
|
494
887
|
start: () => start,
|
|
495
888
|
stop: () => stop
|
|
496
889
|
});
|
|
497
|
-
function redact(text) {
|
|
498
|
-
let out = text;
|
|
499
|
-
for (const re of PII) out = out.replace(re, "[redacted]");
|
|
500
|
-
return out;
|
|
501
|
-
}
|
|
502
890
|
function isPrivate(el) {
|
|
503
891
|
if (el.closest("[data-mf-private]")) return true;
|
|
504
892
|
const tag = el.tagName;
|
|
@@ -580,6 +968,8 @@ async function resolveDeclaredContext() {
|
|
|
580
968
|
const c = await mf.getPageContext();
|
|
581
969
|
if (c) return c;
|
|
582
970
|
}
|
|
971
|
+
const hoist = detectHoist(() => readHoistRuntime());
|
|
972
|
+
if (hoist) return buildHoistContext(hoist);
|
|
583
973
|
return mf?.context ?? {};
|
|
584
974
|
}
|
|
585
975
|
async function publishContext() {
|
|
@@ -589,6 +979,7 @@ async function publishContext() {
|
|
|
589
979
|
}
|
|
590
980
|
function provideContext() {
|
|
591
981
|
void publishContext();
|
|
982
|
+
publishSitemap();
|
|
592
983
|
}
|
|
593
984
|
function grantsFromIframeSrcs(srcs, origin) {
|
|
594
985
|
const out = [];
|
|
@@ -620,6 +1011,27 @@ function readArtifactGrants() {
|
|
|
620
1011
|
function publishArtifactGrants() {
|
|
621
1012
|
send?.({ type: "host.artifactGrants", grants: readArtifactGrants() });
|
|
622
1013
|
}
|
|
1014
|
+
function readSitemap2() {
|
|
1015
|
+
const s = window.matterfact?.sitemap;
|
|
1016
|
+
return Array.isArray(s) ? s : [];
|
|
1017
|
+
}
|
|
1018
|
+
function publishSitemap() {
|
|
1019
|
+
if (!pageContextOn) return;
|
|
1020
|
+
const sitemap = readSitemap2();
|
|
1021
|
+
if (sitemap.length) send?.({ type: "host.sitemap", sitemap });
|
|
1022
|
+
}
|
|
1023
|
+
function navigateHost(href) {
|
|
1024
|
+
if (typeof href !== "string" || !href) return;
|
|
1025
|
+
let target;
|
|
1026
|
+
try {
|
|
1027
|
+
target = new URL(href, location.href);
|
|
1028
|
+
} catch {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") return;
|
|
1032
|
+
if (target.origin !== location.origin) return;
|
|
1033
|
+
location.assign(target.href);
|
|
1034
|
+
}
|
|
623
1035
|
function pushActivity(e) {
|
|
624
1036
|
activity.push({ ...e, seq: ++activitySeq, ts: Date.now() });
|
|
625
1037
|
if (activity.length > MAX_ACTIVITY) activity.shift();
|
|
@@ -682,6 +1094,11 @@ function watchNavigation() {
|
|
|
682
1094
|
pushActivity({ type: "nav", summary: `navigated to ${url}` });
|
|
683
1095
|
void publishContext();
|
|
684
1096
|
publishArtifactGrants();
|
|
1097
|
+
publishSitemap();
|
|
1098
|
+
send?.({
|
|
1099
|
+
type: "host.tools",
|
|
1100
|
+
tools: advertiseTools(typeof window !== "undefined" ? window : void 0)
|
|
1101
|
+
});
|
|
685
1102
|
};
|
|
686
1103
|
navFire = fire;
|
|
687
1104
|
for (const name of ["pushState", "replaceState"]) {
|
|
@@ -737,6 +1154,20 @@ function scheduleFocus() {
|
|
|
737
1154
|
}
|
|
738
1155
|
async function sendSnapshot(emit) {
|
|
739
1156
|
if (!pageContextOn) return;
|
|
1157
|
+
const hoist = detectHoist(() => readHoistRuntime());
|
|
1158
|
+
if (hoist) {
|
|
1159
|
+
const { rows } = readHoistConfig(
|
|
1160
|
+
typeof window !== "undefined" ? window : void 0
|
|
1161
|
+
);
|
|
1162
|
+
const { yaml: yaml2, truncated: truncated2 } = renderHoistSnapshot(hoist, rows);
|
|
1163
|
+
emit({
|
|
1164
|
+
type: "host.snapshot",
|
|
1165
|
+
snapshot: { yaml: redact(yaml2), seq: ++snapshotSeq, truncated: truncated2 }
|
|
1166
|
+
});
|
|
1167
|
+
lastFocus = { ...lastFocus, visibleRefs: [] };
|
|
1168
|
+
emit({ type: "host.focus", focus: lastFocus });
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
740
1171
|
const { snapshot: snapshot2 } = await loadSnapshotModule();
|
|
741
1172
|
const { yaml, truncated, visibleRefs } = snapshot2();
|
|
742
1173
|
emit({
|
|
@@ -762,11 +1193,25 @@ async function sendRegion(ref, emit) {
|
|
|
762
1193
|
emit({ type: "host.region", ref, yaml: redact(yaml) });
|
|
763
1194
|
}
|
|
764
1195
|
async function callTool(call, emit) {
|
|
1196
|
+
if (!pageContextOn) {
|
|
1197
|
+
emit({
|
|
1198
|
+
type: "host.toolResult",
|
|
1199
|
+
callId: call.callId,
|
|
1200
|
+
ok: false,
|
|
1201
|
+
error: "host tools are disabled"
|
|
1202
|
+
});
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const r = executeHostTool(
|
|
1206
|
+
call,
|
|
1207
|
+
typeof window !== "undefined" ? window : void 0
|
|
1208
|
+
);
|
|
765
1209
|
emit({
|
|
766
1210
|
type: "host.toolResult",
|
|
767
1211
|
callId: call.callId,
|
|
768
|
-
ok:
|
|
769
|
-
|
|
1212
|
+
ok: r.ok,
|
|
1213
|
+
result: r.result,
|
|
1214
|
+
error: r.error
|
|
770
1215
|
});
|
|
771
1216
|
}
|
|
772
1217
|
function stop() {
|
|
@@ -809,6 +1254,7 @@ function start(emit, origin, pageContext = true, provider) {
|
|
|
809
1254
|
if (pageContextOn) {
|
|
810
1255
|
void publishContext();
|
|
811
1256
|
publishArtifactGrants();
|
|
1257
|
+
publishSitemap();
|
|
812
1258
|
watchNavigation();
|
|
813
1259
|
document.addEventListener("click", onClick, {
|
|
814
1260
|
capture: true,
|
|
@@ -836,10 +1282,20 @@ function start(emit, origin, pageContext = true, provider) {
|
|
|
836
1282
|
}
|
|
837
1283
|
const theme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
838
1284
|
emit({ type: "host.theme", mode: theme });
|
|
1285
|
+
if (pageContextOn) {
|
|
1286
|
+
const tools = advertiseTools(
|
|
1287
|
+
typeof window !== "undefined" ? window : void 0
|
|
1288
|
+
);
|
|
1289
|
+
if (tools.length) emit({ type: "host.tools", tools });
|
|
1290
|
+
}
|
|
839
1291
|
}
|
|
840
|
-
var widgetOrigin, MAX_ACTIVITY, send, activitySeq, activity, lastUrl, pageContextOn, contextProvider, navFire, origPushState, origReplaceState, snapshotModule,
|
|
1292
|
+
var widgetOrigin, MAX_ACTIVITY, send, activitySeq, activity, lastUrl, pageContextOn, contextProvider, navFire, origPushState, origReplaceState, snapshotModule, FORM_CONTROLS, ACTIONABLE, lastFocus, focusTimer, snapshotSeq;
|
|
841
1293
|
var init_context = __esm({
|
|
842
1294
|
"src/context.ts"() {
|
|
1295
|
+
init_redact();
|
|
1296
|
+
init_hoist();
|
|
1297
|
+
init_hoist_runtime();
|
|
1298
|
+
init_redact();
|
|
843
1299
|
widgetOrigin = "";
|
|
844
1300
|
MAX_ACTIVITY = 40;
|
|
845
1301
|
send = null;
|
|
@@ -851,22 +1307,6 @@ var init_context = __esm({
|
|
|
851
1307
|
origPushState = null;
|
|
852
1308
|
origReplaceState = null;
|
|
853
1309
|
snapshotModule = null;
|
|
854
|
-
PII = [
|
|
855
|
-
/\b[\w.+-]+@[\w-]+\.[\w.]{2,}\b/g,
|
|
856
|
-
// email
|
|
857
|
-
/\b(?:\d[ -]?){13,19}\b/g,
|
|
858
|
-
// card-ish
|
|
859
|
-
/\b\d{3}[- ]?\d{2}[- ]?\d{4}\b/g,
|
|
860
|
-
// ssn — dashed, spaced, OR bare 9 digits
|
|
861
|
-
/\b\d{9,}\b/g,
|
|
862
|
-
// long bare digit runs: account / MRN / routing numbers
|
|
863
|
-
/(?:\+?\d{1,2}[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b/g,
|
|
864
|
-
// us phone
|
|
865
|
-
/\b(?:sk|pk|rk|ak)_(?:live|test)_[A-Za-z0-9]{8,}\b/g,
|
|
866
|
-
// stripe-style api keys
|
|
867
|
-
/\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g
|
|
868
|
-
// jwt
|
|
869
|
-
];
|
|
870
1310
|
FORM_CONTROLS = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
|
|
871
1311
|
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]';
|
|
872
1312
|
lastFocus = {};
|
|
@@ -876,7 +1316,7 @@ var init_context = __esm({
|
|
|
876
1316
|
});
|
|
877
1317
|
|
|
878
1318
|
// src/protocol.ts
|
|
879
|
-
var PROTOCOL_VERSION =
|
|
1319
|
+
var PROTOCOL_VERSION = 3;
|
|
880
1320
|
var CHANNEL = "mf-embed";
|
|
881
1321
|
function envelope(payload, id) {
|
|
882
1322
|
return {
|
|
@@ -890,6 +1330,213 @@ function isEnvelope(data) {
|
|
|
890
1330
|
return typeof data === "object" && data !== null && data.channel === CHANNEL;
|
|
891
1331
|
}
|
|
892
1332
|
|
|
1333
|
+
// src/geometry.ts
|
|
1334
|
+
var MARGIN = 20;
|
|
1335
|
+
var LAUNCHER = 56;
|
|
1336
|
+
var SNAP_RADIUS = 96;
|
|
1337
|
+
var TAB_W = 44;
|
|
1338
|
+
var TAB_H = 132;
|
|
1339
|
+
var EDGE_DOCK = 64;
|
|
1340
|
+
var DEFAULT_FLOAT_W = 420;
|
|
1341
|
+
var DEFAULT_FLOAT_H = 640;
|
|
1342
|
+
var MIN_FLOAT_W = 320;
|
|
1343
|
+
var MIN_FLOAT_H = 420;
|
|
1344
|
+
var DEFAULT_DOCK_W = 600;
|
|
1345
|
+
var MIN_DOCK_W = 320;
|
|
1346
|
+
var CORNERS = ["tl", "tr", "bl", "br"];
|
|
1347
|
+
function defaultGeometry() {
|
|
1348
|
+
return {
|
|
1349
|
+
// Dock is the default: a side panel reads as part of the host app.
|
|
1350
|
+
mode: "dock",
|
|
1351
|
+
corner: "br",
|
|
1352
|
+
x: 0,
|
|
1353
|
+
y: 0,
|
|
1354
|
+
floatW: DEFAULT_FLOAT_W,
|
|
1355
|
+
floatH: DEFAULT_FLOAT_H,
|
|
1356
|
+
dockSide: "right",
|
|
1357
|
+
tabY: 0,
|
|
1358
|
+
// 0 => centre it (see tabTop)
|
|
1359
|
+
dockW: DEFAULT_DOCK_W
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
function clamp(n, lo, hi) {
|
|
1363
|
+
return Math.min(Math.max(n, lo), hi);
|
|
1364
|
+
}
|
|
1365
|
+
function cornerOrigin(corner, w, h, vw, vh) {
|
|
1366
|
+
return {
|
|
1367
|
+
x: corner[1] === "l" ? MARGIN : vw - MARGIN - w,
|
|
1368
|
+
y: corner[0] === "t" ? MARGIN : vh - MARGIN - h
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
function launcherOrigin(g, vw, vh) {
|
|
1372
|
+
if (g.corner) return cornerOrigin(g.corner, LAUNCHER, LAUNCHER, vw, vh);
|
|
1373
|
+
return {
|
|
1374
|
+
x: clamp(g.x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER)),
|
|
1375
|
+
y: clamp(g.y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER))
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function growthCorner(g, vw, vh) {
|
|
1379
|
+
if (g.corner) return g.corner;
|
|
1380
|
+
const o = launcherOrigin(g, vw, vh);
|
|
1381
|
+
return nearestCorner(o.x + LAUNCHER / 2, o.y + LAUNCHER / 2, vw, vh);
|
|
1382
|
+
}
|
|
1383
|
+
function nearestCorner(cx, cy, vw, vh) {
|
|
1384
|
+
return `${cy < vh / 2 ? "t" : "b"}${cx < vw / 2 ? "l" : "r"}`;
|
|
1385
|
+
}
|
|
1386
|
+
function snapOrFree(x, y, vw, vh) {
|
|
1387
|
+
const cx = clamp(x, MARGIN, Math.max(MARGIN, vw - MARGIN - LAUNCHER));
|
|
1388
|
+
const cy = clamp(y, MARGIN, Math.max(MARGIN, vh - MARGIN - LAUNCHER));
|
|
1389
|
+
for (const corner of CORNERS) {
|
|
1390
|
+
const o = cornerOrigin(corner, LAUNCHER, LAUNCHER, vw, vh);
|
|
1391
|
+
if (Math.hypot(cx - o.x, cy - o.y) <= SNAP_RADIUS) {
|
|
1392
|
+
return { corner, x: cx, y: cy };
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return { corner: null, x: cx, y: cy };
|
|
1396
|
+
}
|
|
1397
|
+
function edgeDock(x, w, vw) {
|
|
1398
|
+
if (x <= EDGE_DOCK) return "left";
|
|
1399
|
+
if (x + w >= vw - EDGE_DOCK) return "right";
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
function dragAnchor(g, vw, vh) {
|
|
1403
|
+
if (g.mode === "dock") {
|
|
1404
|
+
return { x: g.dockSide === "left" ? 0 : vw - TAB_W, y: tabTop(g, vh) };
|
|
1405
|
+
}
|
|
1406
|
+
return launcherOrigin(g, vw, vh);
|
|
1407
|
+
}
|
|
1408
|
+
function visibleBox(g, open, vw, vh) {
|
|
1409
|
+
if (g.mode === "dock") {
|
|
1410
|
+
const w2 = open ? Math.min(Math.max(g.dockW, MIN_DOCK_W), Math.max(0, vw - 2 * MARGIN)) : TAB_W;
|
|
1411
|
+
return { x: g.dockSide === "left" ? 0 : vw - w2, w: w2 };
|
|
1412
|
+
}
|
|
1413
|
+
const o = launcherOrigin(g, vw, vh);
|
|
1414
|
+
if (!open) return { x: o.x, w: LAUNCHER };
|
|
1415
|
+
const { w } = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);
|
|
1416
|
+
return {
|
|
1417
|
+
x: growthCorner(g, vw, vh)[1] === "r" ? o.x + LAUNCHER - w : o.x,
|
|
1418
|
+
w
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
function placeDrop(anchorX, anchorY, boxLeft, boxW, vw, vh) {
|
|
1422
|
+
const snapped = snapOrFree(anchorX, anchorY, vw, vh);
|
|
1423
|
+
if (snapped.corner) return { mode: "float", ...snapped };
|
|
1424
|
+
const side = edgeDock(boxLeft, boxW, vw);
|
|
1425
|
+
if (side) {
|
|
1426
|
+
return {
|
|
1427
|
+
mode: "dock",
|
|
1428
|
+
dockSide: side,
|
|
1429
|
+
tabY: clamp(anchorY, 0, Math.max(0, vh - TAB_H))
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
return { mode: "float", ...snapped };
|
|
1433
|
+
}
|
|
1434
|
+
function clampSize(w, h, vw, vh, minW, minH) {
|
|
1435
|
+
return {
|
|
1436
|
+
w: Math.min(Math.max(w, minW), Math.max(0, vw - 2 * MARGIN)),
|
|
1437
|
+
h: Math.min(Math.max(h, minH), Math.max(0, vh - 2 * MARGIN))
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
function applyResizeDelta(corner, startW, startH, dx, dy) {
|
|
1441
|
+
const wSign = corner[1] === "r" ? -1 : 1;
|
|
1442
|
+
const hSign = corner[0] === "b" ? -1 : 1;
|
|
1443
|
+
return { w: startW + wSign * dx, h: startH + hSign * dy };
|
|
1444
|
+
}
|
|
1445
|
+
function applyDockResizeDelta(side, startW, dx) {
|
|
1446
|
+
return side === "right" ? startW - dx : startW + dx;
|
|
1447
|
+
}
|
|
1448
|
+
function parseGeometry(raw) {
|
|
1449
|
+
if (!raw) return null;
|
|
1450
|
+
try {
|
|
1451
|
+
const p = JSON.parse(raw);
|
|
1452
|
+
if (!p || p.mode !== "float" && p.mode !== "dock") return null;
|
|
1453
|
+
const d = defaultGeometry();
|
|
1454
|
+
const num = (v, dv) => typeof v === "number" ? v : dv;
|
|
1455
|
+
return {
|
|
1456
|
+
mode: p.mode,
|
|
1457
|
+
corner: CORNERS.includes(p.corner) ? p.corner : null,
|
|
1458
|
+
x: num(p.x, d.x),
|
|
1459
|
+
y: num(p.y, d.y),
|
|
1460
|
+
floatW: num(p.floatW, d.floatW),
|
|
1461
|
+
floatH: num(p.floatH, d.floatH),
|
|
1462
|
+
dockSide: p.dockSide === "left" ? "left" : "right",
|
|
1463
|
+
tabY: num(p.tabY, d.tabY),
|
|
1464
|
+
dockW: num(p.dockW, d.dockW)
|
|
1465
|
+
};
|
|
1466
|
+
} catch {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function serializeGeometry(g) {
|
|
1471
|
+
return JSON.stringify(g);
|
|
1472
|
+
}
|
|
1473
|
+
var px = (n) => `${n}px`;
|
|
1474
|
+
function boxAt(ox, oy, w, h, corner, vw, vh) {
|
|
1475
|
+
const right = corner[1] === "r";
|
|
1476
|
+
const bottom = corner[0] === "b";
|
|
1477
|
+
const rawX = right ? vw - (ox + LAUNCHER) : ox;
|
|
1478
|
+
const rawY = bottom ? vh - (oy + LAUNCHER) : oy;
|
|
1479
|
+
const x = clamp(rawX, MARGIN, Math.max(MARGIN, vw - MARGIN - w));
|
|
1480
|
+
const y = clamp(rawY, MARGIN, Math.max(MARGIN, vh - MARGIN - h));
|
|
1481
|
+
return {
|
|
1482
|
+
top: bottom ? "auto" : px(y),
|
|
1483
|
+
bottom: bottom ? px(y) : "auto",
|
|
1484
|
+
left: right ? "auto" : px(x),
|
|
1485
|
+
right: right ? px(x) : "auto",
|
|
1486
|
+
width: px(w),
|
|
1487
|
+
height: px(h)
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function tabTop(g, vh) {
|
|
1491
|
+
const max = Math.max(0, vh - TAB_H);
|
|
1492
|
+
return clamp(g.tabY || Math.round((vh - TAB_H) / 2), 0, max);
|
|
1493
|
+
}
|
|
1494
|
+
function tabBox(g, vw, vh) {
|
|
1495
|
+
const left = g.dockSide === "left";
|
|
1496
|
+
return {
|
|
1497
|
+
top: px(tabTop(g, vh)),
|
|
1498
|
+
bottom: "auto",
|
|
1499
|
+
left: left ? "0px" : "auto",
|
|
1500
|
+
right: left ? "auto" : "0px",
|
|
1501
|
+
width: px(TAB_W),
|
|
1502
|
+
height: px(TAB_H)
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function floatChrome(g, w, h, vw, vh, growth) {
|
|
1506
|
+
const o = launcherOrigin(g, vw, vh);
|
|
1507
|
+
return boxAt(o.x, o.y, w, h, growth ?? growthCorner(g, vw, vh), vw, vh);
|
|
1508
|
+
}
|
|
1509
|
+
function dockChrome(g, w, h, vw, vh) {
|
|
1510
|
+
const left = g.dockSide === "left";
|
|
1511
|
+
return {
|
|
1512
|
+
top: px(tabTop(g, vh)),
|
|
1513
|
+
bottom: "auto",
|
|
1514
|
+
left: left ? "0px" : "auto",
|
|
1515
|
+
right: left ? "auto" : "0px",
|
|
1516
|
+
width: px(w),
|
|
1517
|
+
height: px(h)
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
function floatBox(g, vw, vh, growth) {
|
|
1521
|
+
const f = clampSize(g.floatW, g.floatH, vw, vh, MIN_FLOAT_W, MIN_FLOAT_H);
|
|
1522
|
+
return floatChrome(g, f.w, f.h, vw, vh, growth);
|
|
1523
|
+
}
|
|
1524
|
+
function dockBox(g, vw, _vh) {
|
|
1525
|
+
const w = Math.min(
|
|
1526
|
+
Math.max(g.dockW, MIN_DOCK_W),
|
|
1527
|
+
Math.max(0, vw - 2 * MARGIN)
|
|
1528
|
+
);
|
|
1529
|
+
const left = g.dockSide === "left";
|
|
1530
|
+
return {
|
|
1531
|
+
top: "0px",
|
|
1532
|
+
bottom: "0px",
|
|
1533
|
+
height: "auto",
|
|
1534
|
+
left: left ? "0px" : "auto",
|
|
1535
|
+
right: left ? "auto" : "0px",
|
|
1536
|
+
width: px(w)
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
|
|
893
1540
|
// src/loader.ts
|
|
894
1541
|
var DEFAULT_ORIGIN = "https://app.matterfact.com";
|
|
895
1542
|
function devRequested() {
|
|
@@ -923,6 +1570,10 @@ function readConfig() {
|
|
|
923
1570
|
pageContext: pageContextAttr !== "off" && pageContextAttr !== "false" && pageContextAttr !== "0"
|
|
924
1571
|
// No `data-dev` — see the `dev` field's doc comment: the URL trigger is the
|
|
925
1572
|
// point for the script-tag path, so there is deliberately no script-tag knob here.
|
|
1573
|
+
// No `data-actions` either: the action policy lives entirely in the lazy chunk,
|
|
1574
|
+
// read straight off `window.matterfact.hoist.actions` by hoist-runtime.ts's
|
|
1575
|
+
// readActionsConfig() — see LoaderConfig's class doc and MatterfactAgentProps.actions
|
|
1576
|
+
// (react.tsx) for the one remaining way to SET that global programmatically.
|
|
926
1577
|
};
|
|
927
1578
|
}
|
|
928
1579
|
var POS_KEY = "mf.embed.pos";
|
|
@@ -935,11 +1586,31 @@ var EmbedHost = class {
|
|
|
935
1586
|
this.queue = [];
|
|
936
1587
|
this.ready = false;
|
|
937
1588
|
this.open = false;
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1589
|
+
/** The persisted box: launcher corner, float size, dock state/side/width. */
|
|
1590
|
+
this.geo = defaultGeometry();
|
|
1591
|
+
/** Float size at resizeStart; resizeMove deltas apply against it (they're cumulative). */
|
|
1592
|
+
this.resizeBase = null;
|
|
1593
|
+
/** Docked width at resizeStart — the docked panel resizes in one axis only. */
|
|
1594
|
+
this.dockBase = DEFAULT_DOCK_W;
|
|
1595
|
+
/** The widget's logical anchor when the drag began, and where it is now. Deltas arrive
|
|
1596
|
+
* cumulative from the press, so the current spot is simply origin + delta — no need to
|
|
1597
|
+
* read the box back, which stops being the thing we're moving the moment it docks. */
|
|
1598
|
+
this.dragFrom = null;
|
|
1599
|
+
this.dragAt = { x: 0, y: 0 };
|
|
1600
|
+
/** The painted box when the drag began — the edge test measures what you can SEE
|
|
1601
|
+
* touching the screen edge, which is not the anchor once the panel is open. */
|
|
1602
|
+
this.dragBox = { x: 0, w: 0 };
|
|
1603
|
+
/** Growth direction frozen for the drag — see floatChrome's `growth` param. */
|
|
1604
|
+
this.dragGrowth = null;
|
|
1605
|
+
/**
|
|
1606
|
+
* Has this drag been clear of the dock band yet?
|
|
1607
|
+
*
|
|
1608
|
+
* The resting position is INSIDE the band — a corner-anchored widget sits MARGIN
|
|
1609
|
+
* (20px) from the edge, well within EDGE_DOCK (64px) — so without this, picking one
|
|
1610
|
+
* up docked it instantly, before it had moved anywhere. Docking now requires
|
|
1611
|
+
* ENTERING the band rather than merely starting in it.
|
|
1612
|
+
*/
|
|
1613
|
+
this.dragLeftBand = false;
|
|
943
1614
|
/** Loaded on first open. Holds everything that touches the customer's DOM. */
|
|
944
1615
|
this.context = null;
|
|
945
1616
|
/** The host element; kept so `destroy()` can remove it (React lifecycle). */
|
|
@@ -962,87 +1633,10 @@ var EmbedHost = class {
|
|
|
962
1633
|
if (event.origin !== this.config.origin) return;
|
|
963
1634
|
if (event.source !== this.iframe?.contentWindow) return;
|
|
964
1635
|
if (!isEnvelope(event.data)) return;
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
this.send({
|
|
970
|
-
type: "host.ready",
|
|
971
|
-
protocol: PROTOCOL_VERSION,
|
|
972
|
-
origin: location.origin
|
|
973
|
-
});
|
|
974
|
-
this.flush();
|
|
975
|
-
if (this.inline) void this.loadContext();
|
|
976
|
-
break;
|
|
977
|
-
case "widget.setOpen":
|
|
978
|
-
if (this.inline) break;
|
|
979
|
-
this.open = msg.open;
|
|
980
|
-
this.iframe?.setAttribute("data-open", String(msg.open));
|
|
981
|
-
if (!msg.open) {
|
|
982
|
-
this.expanded = false;
|
|
983
|
-
this.iframe?.setAttribute("data-expanded", "false");
|
|
984
|
-
}
|
|
985
|
-
if (msg.open) void this.loadContext();
|
|
986
|
-
break;
|
|
987
|
-
case "widget.setExpanded":
|
|
988
|
-
if (this.inline) break;
|
|
989
|
-
this.expanded = msg.expanded;
|
|
990
|
-
this.iframe?.setAttribute("data-expanded", String(msg.expanded));
|
|
991
|
-
if (this.iframe) this.iframe.style.height = "";
|
|
992
|
-
break;
|
|
993
|
-
case "widget.resize":
|
|
994
|
-
if (this.inline) break;
|
|
995
|
-
if (this.iframe && this.open && !this.expanded) {
|
|
996
|
-
this.iframe.style.height = `${Math.min(msg.height, window.innerHeight - 40)}px`;
|
|
997
|
-
}
|
|
998
|
-
break;
|
|
999
|
-
case "widget.requestContext":
|
|
1000
|
-
void this.loadContext().then((m) => m.provideContext());
|
|
1001
|
-
break;
|
|
1002
|
-
case "widget.requestSnapshot":
|
|
1003
|
-
void this.loadContext().then((m) => m.sendSnapshot(this.send));
|
|
1004
|
-
break;
|
|
1005
|
-
case "widget.readRegion":
|
|
1006
|
-
void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));
|
|
1007
|
-
break;
|
|
1008
|
-
case "widget.callTool":
|
|
1009
|
-
void this.loadContext().then((m) => m.callTool(msg.call, this.send));
|
|
1010
|
-
break;
|
|
1011
|
-
// Everything below is LAUNCHER chrome: there is no launcher inline (the widget
|
|
1012
|
-
// doesn't draw one), and moving or hiding the host's own panel from inside it
|
|
1013
|
-
// would be us redecorating their app. An older cached loader could still be told
|
|
1014
|
-
// any of these by a newer widget, so they're guarded rather than assumed absent.
|
|
1015
|
-
case "widget.dragStart":
|
|
1016
|
-
if (this.inline) break;
|
|
1017
|
-
this.dragBase = this.cornerOffset();
|
|
1018
|
-
break;
|
|
1019
|
-
case "widget.dragMove":
|
|
1020
|
-
if (this.inline) break;
|
|
1021
|
-
this.dragTo(msg.dx, msg.dy);
|
|
1022
|
-
break;
|
|
1023
|
-
case "widget.dragEnd":
|
|
1024
|
-
if (this.inline) break;
|
|
1025
|
-
this.dragBase = null;
|
|
1026
|
-
this.writePos(this.pos);
|
|
1027
|
-
break;
|
|
1028
|
-
case "widget.setMenu":
|
|
1029
|
-
if (this.inline) break;
|
|
1030
|
-
if (this.iframe && !this.open) {
|
|
1031
|
-
this.iframe.style.height = msg.open ? `${64 + msg.height}px` : "";
|
|
1032
|
-
}
|
|
1033
|
-
break;
|
|
1034
|
-
case "widget.resetPos":
|
|
1035
|
-
if (this.inline) break;
|
|
1036
|
-
this.resetPos();
|
|
1037
|
-
break;
|
|
1038
|
-
case "widget.hide":
|
|
1039
|
-
if (this.inline) break;
|
|
1040
|
-
if (this.hostEl) this.hostEl.style.display = "none";
|
|
1041
|
-
break;
|
|
1042
|
-
case "widget.needsAuth":
|
|
1043
|
-
void this.provideAuth();
|
|
1044
|
-
break;
|
|
1045
|
-
}
|
|
1636
|
+
this.handle(event.data.payload);
|
|
1637
|
+
};
|
|
1638
|
+
this.onViewportResize = () => {
|
|
1639
|
+
this.place();
|
|
1046
1640
|
};
|
|
1047
1641
|
this.send = (msg) => {
|
|
1048
1642
|
if (!this.ready) {
|
|
@@ -1068,34 +1662,36 @@ var EmbedHost = class {
|
|
|
1068
1662
|
].join(";");
|
|
1069
1663
|
this.config.container.appendChild(host);
|
|
1070
1664
|
} else {
|
|
1071
|
-
this.
|
|
1665
|
+
this.geo = this.readGeometry();
|
|
1072
1666
|
host.style.cssText = [
|
|
1073
1667
|
"all: initial",
|
|
1074
1668
|
"position: fixed",
|
|
1075
|
-
`right: ${this.pos ? this.pos.right : 20}px`,
|
|
1076
|
-
`bottom: ${this.pos ? this.pos.bottom : 20}px`,
|
|
1077
1669
|
// Below the max so a host that genuinely needs to cover us (a modal, a cookie
|
|
1078
1670
|
// banner they are legally obliged to show) still can.
|
|
1079
1671
|
"z-index: 2147483000",
|
|
1080
|
-
"contain: layout style"
|
|
1672
|
+
"contain: layout style",
|
|
1673
|
+
// The host element owns the BOX now (position AND size); the iframe fills it.
|
|
1674
|
+
// Transitioning the box is what makes the corner snap and the open/dock read as
|
|
1675
|
+
// motion rather than a jump. Suppressed during drag/resize (see those handlers).
|
|
1676
|
+
"transition: width .18s ease, height .18s ease, top .18s ease, right .18s ease, bottom .18s ease, left .18s ease"
|
|
1081
1677
|
].join(";");
|
|
1082
1678
|
document.body.appendChild(host);
|
|
1083
1679
|
}
|
|
1084
1680
|
this.shadow = host.attachShadow({ mode: "closed" });
|
|
1085
1681
|
const style = document.createElement("style");
|
|
1086
|
-
style.textContent = ":host{all:initial}iframe{border:0;display:block;
|
|
1087
|
-
//
|
|
1088
|
-
//
|
|
1089
|
-
'iframe{border-radius:12px;box-shadow:0 0 0 1px #00000014,0 8px 40px #00000029
|
|
1682
|
+
style.textContent = ":host{all:initial}iframe{border:0;display:block;width:100%;height:100%;background:transparent}" + (this.inline ? "" : (
|
|
1683
|
+
// color-scheme only on the OPEN panel — on a collapsed launcher it makes the
|
|
1684
|
+
// UA paint an opaque canvas base behind our shapes.
|
|
1685
|
+
':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}'
|
|
1090
1686
|
));
|
|
1091
1687
|
this.shadow.appendChild(style);
|
|
1092
1688
|
const iframe = document.createElement("iframe");
|
|
1093
1689
|
iframe.title = "matterfact assistant";
|
|
1094
1690
|
iframe.setAttribute(
|
|
1095
1691
|
"sandbox",
|
|
1096
|
-
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
1692
|
+
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
|
|
1097
1693
|
);
|
|
1098
|
-
iframe.setAttribute("allow", "microphone");
|
|
1694
|
+
iframe.setAttribute("allow", "microphone; clipboard-write");
|
|
1099
1695
|
iframe.src = `${this.config.origin}/embed/chat?k=${encodeURIComponent(
|
|
1100
1696
|
this.config.publishableKey
|
|
1101
1697
|
)}&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
|
|
@@ -1104,6 +1700,226 @@ var EmbedHost = class {
|
|
|
1104
1700
|
this.iframe = iframe;
|
|
1105
1701
|
this.shadow.appendChild(iframe);
|
|
1106
1702
|
window.addEventListener("message", this.onMessage);
|
|
1703
|
+
if (!this.inline) {
|
|
1704
|
+
window.addEventListener("resize", this.onViewportResize);
|
|
1705
|
+
this.place();
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Test seam. The shadow root is CLOSED, so a test cannot reach the iframe to forge a
|
|
1710
|
+
* source-valid MessageEvent — this routes a message through the identical logic
|
|
1711
|
+
* without weakening the origin/source checks above, which stay the only real door.
|
|
1712
|
+
*/
|
|
1713
|
+
__testHandle(msg) {
|
|
1714
|
+
this.handle(msg);
|
|
1715
|
+
}
|
|
1716
|
+
handle(msg) {
|
|
1717
|
+
switch (msg.type) {
|
|
1718
|
+
case "widget.ready":
|
|
1719
|
+
this.ready = true;
|
|
1720
|
+
this.send({
|
|
1721
|
+
type: "host.ready",
|
|
1722
|
+
protocol: PROTOCOL_VERSION,
|
|
1723
|
+
origin: location.origin
|
|
1724
|
+
});
|
|
1725
|
+
this.send({ type: "host.theme", mode: this.themeMode() });
|
|
1726
|
+
this.flush();
|
|
1727
|
+
if (this.inline) void this.loadContext();
|
|
1728
|
+
break;
|
|
1729
|
+
case "widget.setOpen":
|
|
1730
|
+
if (this.inline) break;
|
|
1731
|
+
this.open = msg.open;
|
|
1732
|
+
this.place();
|
|
1733
|
+
if (msg.open) void this.loadContext();
|
|
1734
|
+
break;
|
|
1735
|
+
case "widget.setMode":
|
|
1736
|
+
if (this.inline) break;
|
|
1737
|
+
this.geo.mode = msg.mode;
|
|
1738
|
+
this.writeGeometry(this.geo);
|
|
1739
|
+
this.place();
|
|
1740
|
+
break;
|
|
1741
|
+
case "widget.setDockSide":
|
|
1742
|
+
if (this.inline) break;
|
|
1743
|
+
this.geo.dockSide = msg.side;
|
|
1744
|
+
this.writeGeometry(this.geo);
|
|
1745
|
+
this.place();
|
|
1746
|
+
break;
|
|
1747
|
+
case "widget.snapCorner":
|
|
1748
|
+
if (this.inline) break;
|
|
1749
|
+
this.geo.corner = msg.corner;
|
|
1750
|
+
this.writeGeometry(this.geo);
|
|
1751
|
+
this.place();
|
|
1752
|
+
break;
|
|
1753
|
+
case "widget.setLauncherRegion": {
|
|
1754
|
+
if (this.inline || this.open) break;
|
|
1755
|
+
const vw = window.innerWidth;
|
|
1756
|
+
const vh = window.innerHeight;
|
|
1757
|
+
if (msg.w <= LAUNCHER && msg.h <= LAUNCHER) {
|
|
1758
|
+
this.place();
|
|
1759
|
+
} else {
|
|
1760
|
+
const region = this.geo.mode === "dock" ? dockChrome(this.geo, msg.w, msg.h, vw, vh) : floatChrome(this.geo, msg.w, msg.h, vw, vh);
|
|
1761
|
+
this.applyBox(region, msg.h > LAUNCHER ? "menu" : "pill");
|
|
1762
|
+
}
|
|
1763
|
+
break;
|
|
1764
|
+
}
|
|
1765
|
+
case "widget.resize":
|
|
1766
|
+
if (this.inline) break;
|
|
1767
|
+
if (this.hostEl && this.open && this.geo.mode === "float") {
|
|
1768
|
+
this.hostEl.style.height = `${Math.min(
|
|
1769
|
+
msg.height,
|
|
1770
|
+
Math.max(0, window.innerHeight - 2 * MARGIN)
|
|
1771
|
+
)}px`;
|
|
1772
|
+
}
|
|
1773
|
+
break;
|
|
1774
|
+
case "widget.resizeStart":
|
|
1775
|
+
if (this.inline) break;
|
|
1776
|
+
this.resizeBase = { w: this.geo.floatW, h: this.geo.floatH };
|
|
1777
|
+
this.dockBase = this.geo.dockW;
|
|
1778
|
+
if (this.hostEl) this.hostEl.style.transition = "none";
|
|
1779
|
+
break;
|
|
1780
|
+
case "widget.resizeMove": {
|
|
1781
|
+
if (this.inline || !this.resizeBase) break;
|
|
1782
|
+
if (this.geo.mode === "dock") {
|
|
1783
|
+
const w = applyDockResizeDelta(
|
|
1784
|
+
this.geo.dockSide,
|
|
1785
|
+
this.dockBase,
|
|
1786
|
+
msg.dx
|
|
1787
|
+
);
|
|
1788
|
+
this.geo.dockW = Math.min(
|
|
1789
|
+
Math.max(w, MIN_DOCK_W),
|
|
1790
|
+
Math.max(0, window.innerWidth - 2 * MARGIN)
|
|
1791
|
+
);
|
|
1792
|
+
} else {
|
|
1793
|
+
const next = applyResizeDelta(
|
|
1794
|
+
growthCorner(this.geo, window.innerWidth, window.innerHeight),
|
|
1795
|
+
this.resizeBase.w,
|
|
1796
|
+
this.resizeBase.h,
|
|
1797
|
+
msg.dx,
|
|
1798
|
+
msg.dy
|
|
1799
|
+
);
|
|
1800
|
+
const fit = clampSize(
|
|
1801
|
+
next.w,
|
|
1802
|
+
next.h,
|
|
1803
|
+
window.innerWidth,
|
|
1804
|
+
window.innerHeight,
|
|
1805
|
+
MIN_FLOAT_W,
|
|
1806
|
+
MIN_FLOAT_H
|
|
1807
|
+
);
|
|
1808
|
+
this.geo.floatW = fit.w;
|
|
1809
|
+
this.geo.floatH = fit.h;
|
|
1810
|
+
}
|
|
1811
|
+
this.place();
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
case "widget.resizeEnd":
|
|
1815
|
+
if (this.inline) break;
|
|
1816
|
+
this.resizeBase = null;
|
|
1817
|
+
if (this.hostEl) this.hostEl.style.transition = "";
|
|
1818
|
+
this.writeGeometry(this.geo);
|
|
1819
|
+
break;
|
|
1820
|
+
case "widget.requestContext":
|
|
1821
|
+
void this.loadContext().then((m) => m.provideContext());
|
|
1822
|
+
break;
|
|
1823
|
+
case "widget.requestSnapshot":
|
|
1824
|
+
void this.loadContext().then((m) => m.sendSnapshot(this.send));
|
|
1825
|
+
break;
|
|
1826
|
+
case "widget.readRegion":
|
|
1827
|
+
void this.loadContext().then((m) => m.sendRegion(msg.ref, this.send));
|
|
1828
|
+
break;
|
|
1829
|
+
case "widget.callTool":
|
|
1830
|
+
void this.loadContext().then((m) => m.callTool(msg.call, this.send));
|
|
1831
|
+
break;
|
|
1832
|
+
// Everything below is LAUNCHER chrome: there is no launcher inline (the widget
|
|
1833
|
+
// doesn't draw one), and moving or hiding the host's own panel from inside it
|
|
1834
|
+
// would be us redecorating their app. An older cached loader could still be told
|
|
1835
|
+
// any of these by a newer widget, so they're guarded rather than assumed absent.
|
|
1836
|
+
case "widget.dragStart":
|
|
1837
|
+
if (this.inline) break;
|
|
1838
|
+
this.dragFrom = dragAnchor(
|
|
1839
|
+
this.geo,
|
|
1840
|
+
window.innerWidth,
|
|
1841
|
+
window.innerHeight
|
|
1842
|
+
);
|
|
1843
|
+
this.dragAt = { ...this.dragFrom };
|
|
1844
|
+
this.dragLeftBand = false;
|
|
1845
|
+
this.dragGrowth = growthCorner(
|
|
1846
|
+
this.geo,
|
|
1847
|
+
window.innerWidth,
|
|
1848
|
+
window.innerHeight
|
|
1849
|
+
);
|
|
1850
|
+
this.dragBox = visibleBox(
|
|
1851
|
+
this.geo,
|
|
1852
|
+
this.open,
|
|
1853
|
+
window.innerWidth,
|
|
1854
|
+
window.innerHeight
|
|
1855
|
+
);
|
|
1856
|
+
if (this.hostEl) this.hostEl.style.transition = "none";
|
|
1857
|
+
break;
|
|
1858
|
+
case "widget.dragMove": {
|
|
1859
|
+
if (this.inline || !this.dragFrom) break;
|
|
1860
|
+
const vw = window.innerWidth;
|
|
1861
|
+
this.dragAt = {
|
|
1862
|
+
x: this.dragFrom.x + msg.dx,
|
|
1863
|
+
y: this.dragFrom.y + msg.dy
|
|
1864
|
+
};
|
|
1865
|
+
const side = edgeDock(this.dragBox.x + msg.dx, this.dragBox.w, vw);
|
|
1866
|
+
if (!side) this.dragLeftBand = true;
|
|
1867
|
+
if (side && (this.dragLeftBand || this.geo.mode === "dock")) {
|
|
1868
|
+
this.geo.mode = "dock";
|
|
1869
|
+
this.geo.dockSide = side;
|
|
1870
|
+
this.geo.tabY = Math.max(0, this.dragAt.y);
|
|
1871
|
+
} else {
|
|
1872
|
+
this.geo.mode = "float";
|
|
1873
|
+
this.geo.corner = null;
|
|
1874
|
+
this.geo.x = this.dragAt.x;
|
|
1875
|
+
this.geo.y = this.dragAt.y;
|
|
1876
|
+
}
|
|
1877
|
+
this.place();
|
|
1878
|
+
break;
|
|
1879
|
+
}
|
|
1880
|
+
case "widget.dragEnd": {
|
|
1881
|
+
if (this.inline || !this.dragFrom) break;
|
|
1882
|
+
const at = placeDrop(
|
|
1883
|
+
this.dragAt.x,
|
|
1884
|
+
this.dragAt.y,
|
|
1885
|
+
this.dragBox.x + (this.dragAt.x - this.dragFrom.x),
|
|
1886
|
+
this.dragBox.w,
|
|
1887
|
+
window.innerWidth,
|
|
1888
|
+
window.innerHeight
|
|
1889
|
+
);
|
|
1890
|
+
this.geo.mode = at.mode;
|
|
1891
|
+
if (at.mode === "dock") {
|
|
1892
|
+
this.geo.dockSide = at.dockSide;
|
|
1893
|
+
this.geo.tabY = at.tabY;
|
|
1894
|
+
} else {
|
|
1895
|
+
this.geo.corner = at.corner;
|
|
1896
|
+
this.geo.x = at.x;
|
|
1897
|
+
this.geo.y = at.y;
|
|
1898
|
+
}
|
|
1899
|
+
this.dragFrom = null;
|
|
1900
|
+
this.dragGrowth = null;
|
|
1901
|
+
if (this.hostEl) this.hostEl.style.transition = "";
|
|
1902
|
+
this.place();
|
|
1903
|
+
this.writeGeometry(this.geo);
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
case "widget.resetPos":
|
|
1907
|
+
if (this.inline) break;
|
|
1908
|
+
this.geo = defaultGeometry();
|
|
1909
|
+
this.writeGeometry(this.geo);
|
|
1910
|
+
this.place();
|
|
1911
|
+
break;
|
|
1912
|
+
case "widget.hide":
|
|
1913
|
+
if (this.inline) break;
|
|
1914
|
+
if (this.hostEl) this.hostEl.style.display = "none";
|
|
1915
|
+
break;
|
|
1916
|
+
case "widget.needsAuth":
|
|
1917
|
+
void this.provideAuth();
|
|
1918
|
+
break;
|
|
1919
|
+
case "widget.navigate":
|
|
1920
|
+
void this.loadContext().then((m) => m.navigateHost(msg.href));
|
|
1921
|
+
break;
|
|
1922
|
+
}
|
|
1107
1923
|
}
|
|
1108
1924
|
/**
|
|
1109
1925
|
* Own a drag for its lifetime.
|
|
@@ -1117,47 +1933,77 @@ var EmbedHost = class {
|
|
|
1117
1933
|
* We are the dumb half on purpose — see `widget.dragMove` in the protocol for why the
|
|
1118
1934
|
* widget has to own the gesture. All we do is take a delta and place the element.
|
|
1119
1935
|
*/
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
return
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1936
|
+
/** Write a box onto the host element and stamp the mode the shadow CSS keys off. */
|
|
1937
|
+
applyBox(box, mode) {
|
|
1938
|
+
if (!this.hostEl || this.inline) return;
|
|
1939
|
+
const s = this.hostEl.style;
|
|
1940
|
+
s.top = box.top;
|
|
1941
|
+
s.right = box.right;
|
|
1942
|
+
s.bottom = box.bottom;
|
|
1943
|
+
s.left = box.left;
|
|
1944
|
+
s.width = box.width;
|
|
1945
|
+
s.height = box.height;
|
|
1946
|
+
this.hostEl.dataset.mode = mode;
|
|
1947
|
+
if (mode === "tab" || mode === "dock")
|
|
1948
|
+
this.hostEl.dataset.flush = this.geo.dockSide;
|
|
1949
|
+
else delete this.hostEl.dataset.flush;
|
|
1950
|
+
}
|
|
1951
|
+
/** Re-apply the box for whatever state we're in: collapsed launcher, float, or dock. */
|
|
1952
|
+
/**
|
|
1953
|
+
* The growth direction to use RIGHT NOW — frozen while a drag is in flight.
|
|
1954
|
+
*
|
|
1955
|
+
* Every consumer has to agree on this, which is the bug that made the widget
|
|
1956
|
+
* flip-flop under small movements near the viewport centre: the box was frozen but
|
|
1957
|
+
* `host.geometry` still published a freshly-derived growth, so the WIDGET kept
|
|
1958
|
+
* swapping which corner it drew against (and it animates that), twitching back and
|
|
1959
|
+
* forth while the loader held perfectly still.
|
|
1960
|
+
*/
|
|
1961
|
+
growthNow() {
|
|
1962
|
+
return this.dragGrowth ?? growthCorner(this.geo, window.innerWidth, window.innerHeight);
|
|
1126
1963
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
this.
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
this.pos = null;
|
|
1141
|
-
this.writePos(null);
|
|
1142
|
-
if (this.hostEl) {
|
|
1143
|
-
this.hostEl.style.right = "20px";
|
|
1144
|
-
this.hostEl.style.bottom = "20px";
|
|
1964
|
+
place() {
|
|
1965
|
+
const vw = window.innerWidth;
|
|
1966
|
+
const vh = window.innerHeight;
|
|
1967
|
+
if (!this.open) {
|
|
1968
|
+
const dock = this.geo.mode === "dock";
|
|
1969
|
+
this.applyBox(
|
|
1970
|
+
dock ? tabBox(this.geo, vw, vh) : floatChrome(this.geo, LAUNCHER, LAUNCHER, vw, vh, this.growthNow()),
|
|
1971
|
+
dock ? "tab" : "launcher"
|
|
1972
|
+
);
|
|
1973
|
+
} else if (this.geo.mode === "dock") {
|
|
1974
|
+
this.applyBox(dockBox(this.geo, vw), "dock");
|
|
1975
|
+
} else {
|
|
1976
|
+
this.applyBox(floatBox(this.geo, vw, vh, this.growthNow()), "float");
|
|
1145
1977
|
}
|
|
1978
|
+
this.publishGeometry();
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Tell the widget where it is. It can't see the viewport and doesn't own the box, so
|
|
1982
|
+
* without this it draws its launcher against a stale guess — which is what made the
|
|
1983
|
+
* circle jump when the launcher sat on the left (see `host.geometry` in the protocol).
|
|
1984
|
+
*/
|
|
1985
|
+
publishGeometry() {
|
|
1986
|
+
this.send({
|
|
1987
|
+
type: "host.geometry",
|
|
1988
|
+
mode: this.geo.mode,
|
|
1989
|
+
dockSide: this.geo.dockSide,
|
|
1990
|
+
growth: this.growthNow()
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
/** The theme to publish: an explicit data-theme wins, else the OS preference. */
|
|
1994
|
+
themeMode() {
|
|
1995
|
+
return this.config.theme === "auto" ? matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this.config.theme;
|
|
1146
1996
|
}
|
|
1147
|
-
|
|
1997
|
+
readGeometry() {
|
|
1148
1998
|
try {
|
|
1149
|
-
|
|
1150
|
-
if (!raw) return null;
|
|
1151
|
-
const p = JSON.parse(raw);
|
|
1152
|
-
return typeof p?.right === "number" && typeof p?.bottom === "number" ? { right: p.right, bottom: p.bottom } : null;
|
|
1999
|
+
return parseGeometry(localStorage.getItem(POS_KEY)) ?? defaultGeometry();
|
|
1153
2000
|
} catch {
|
|
1154
|
-
return
|
|
2001
|
+
return defaultGeometry();
|
|
1155
2002
|
}
|
|
1156
2003
|
}
|
|
1157
|
-
|
|
2004
|
+
writeGeometry(g) {
|
|
1158
2005
|
try {
|
|
1159
|
-
|
|
1160
|
-
else localStorage.removeItem(POS_KEY);
|
|
2006
|
+
localStorage.setItem(POS_KEY, serializeGeometry(g));
|
|
1161
2007
|
} catch {
|
|
1162
2008
|
}
|
|
1163
2009
|
}
|
|
@@ -1213,6 +2059,7 @@ var EmbedHost = class {
|
|
|
1213
2059
|
* calls this. */
|
|
1214
2060
|
destroy() {
|
|
1215
2061
|
window.removeEventListener("message", this.onMessage);
|
|
2062
|
+
window.removeEventListener("resize", this.onViewportResize);
|
|
1216
2063
|
this.hostEl?.remove();
|
|
1217
2064
|
this.hostEl = null;
|
|
1218
2065
|
this.iframe = null;
|