@ui-context-kit/bridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +909 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +870 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
// src/vite-plugin.ts
|
|
2
|
+
import path2 from "path";
|
|
3
|
+
import fs2 from "fs";
|
|
4
|
+
import { injectSourceAttributes } from "@ui-context-kit/babel-plugin";
|
|
5
|
+
|
|
6
|
+
// src/context-writer.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
async function writeContext(context, projectRoot, outputDir) {
|
|
10
|
+
const outPath = path.resolve(projectRoot, outputDir);
|
|
11
|
+
const capturesDir = path.join(outPath, "captures");
|
|
12
|
+
fs.mkdirSync(outPath, { recursive: true });
|
|
13
|
+
fs.mkdirSync(path.join(outPath, "screenshots"), { recursive: true });
|
|
14
|
+
fs.mkdirSync(capturesDir, { recursive: true });
|
|
15
|
+
const contexts = Array.isArray(context) ? context : [context];
|
|
16
|
+
const screenshotPaths = [];
|
|
17
|
+
for (const ctx of contexts) {
|
|
18
|
+
if (ctx.screenshotDataUrl) {
|
|
19
|
+
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
20
|
+
const filename = `capture-${timestamp2}.png`;
|
|
21
|
+
const relPath = `./screenshots/${filename}`;
|
|
22
|
+
const base64 = ctx.screenshotDataUrl.replace(/^data:image\/\w+;base64,/, "");
|
|
23
|
+
fs.writeFileSync(path.join(outPath, "screenshots", filename), Buffer.from(base64, "base64"));
|
|
24
|
+
screenshotPaths.push(relPath);
|
|
25
|
+
} else {
|
|
26
|
+
screenshotPaths.push(void 0);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const sourceSnippets = [];
|
|
30
|
+
for (const ctx of contexts) {
|
|
31
|
+
if (ctx.source) {
|
|
32
|
+
sourceSnippets.push(readSourceSnippet(
|
|
33
|
+
path.resolve(projectRoot, ctx.source.file),
|
|
34
|
+
ctx.source.line,
|
|
35
|
+
10
|
|
36
|
+
));
|
|
37
|
+
} else {
|
|
38
|
+
sourceSnippets.push("");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const md = contexts.length === 1 ? buildMarkdown(contexts[0], sourceSnippets[0], screenshotPaths[0]) : buildMultiMarkdown(contexts, sourceSnippets, screenshotPaths);
|
|
42
|
+
const latestPath = path.join(outPath, "latest.md");
|
|
43
|
+
fs.writeFileSync(latestPath, md, "utf-8");
|
|
44
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
45
|
+
const capturePath = path.join(capturesDir, `${timestamp}.md`);
|
|
46
|
+
fs.writeFileSync(capturePath, md, "utf-8");
|
|
47
|
+
updateHistory(outPath, contexts, timestamp);
|
|
48
|
+
cleanupOldCaptures(capturesDir, 50);
|
|
49
|
+
console.log(`[ui-context-kit] Context written to ${path.relative(projectRoot, latestPath)}`);
|
|
50
|
+
console.log(`[ui-context-kit] History saved to ${path.relative(projectRoot, capturePath)}`);
|
|
51
|
+
return latestPath;
|
|
52
|
+
}
|
|
53
|
+
function updateHistory(outPath, contexts, timestamp) {
|
|
54
|
+
const historyPath = path.join(outPath, "history.md");
|
|
55
|
+
const lines = [];
|
|
56
|
+
const time = timestamp.replace(/T/, " ").replace(/-(\d{2})-(\d{2})-(\d+)Z?$/, ":$1:$2");
|
|
57
|
+
for (const ctx of contexts) {
|
|
58
|
+
const component = ctx.source?.component || ctx.tagName;
|
|
59
|
+
const file = ctx.source ? `${ctx.source.file}:${ctx.source.line}` : "";
|
|
60
|
+
lines.push(`- **${component}** ${file ? `(\`${file}\`)` : ""} \u2014 ${ctx.url}`);
|
|
61
|
+
}
|
|
62
|
+
const entry = `### ${time}
|
|
63
|
+
${lines.join("\n")}
|
|
64
|
+
|
|
65
|
+
`;
|
|
66
|
+
let existing = "";
|
|
67
|
+
try {
|
|
68
|
+
existing = fs.readFileSync(historyPath, "utf-8");
|
|
69
|
+
} catch {
|
|
70
|
+
existing = "# UI Context Capture History\n\n";
|
|
71
|
+
}
|
|
72
|
+
const headerEnd = existing.indexOf("\n\n");
|
|
73
|
+
if (headerEnd !== -1) {
|
|
74
|
+
const header2 = existing.slice(0, headerEnd + 2);
|
|
75
|
+
const body = existing.slice(headerEnd + 2);
|
|
76
|
+
existing = header2 + entry + body;
|
|
77
|
+
} else {
|
|
78
|
+
existing += entry;
|
|
79
|
+
}
|
|
80
|
+
const entries = existing.split(/(?=### )/);
|
|
81
|
+
const header = entries[0];
|
|
82
|
+
const captureEntries = entries.slice(1);
|
|
83
|
+
if (captureEntries.length > 20) {
|
|
84
|
+
existing = header + captureEntries.slice(0, 20).join("");
|
|
85
|
+
}
|
|
86
|
+
fs.writeFileSync(historyPath, existing, "utf-8");
|
|
87
|
+
}
|
|
88
|
+
function cleanupOldCaptures(capturesDir, maxFiles) {
|
|
89
|
+
try {
|
|
90
|
+
const files = fs.readdirSync(capturesDir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
91
|
+
for (let i = maxFiles; i < files.length; i++) {
|
|
92
|
+
fs.unlinkSync(path.join(capturesDir, files[i]));
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function readSourceSnippet(filePath, targetLine, contextLines) {
|
|
98
|
+
try {
|
|
99
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
100
|
+
const lines = content.split("\n");
|
|
101
|
+
const start = Math.max(0, targetLine - contextLines - 1);
|
|
102
|
+
const end = Math.min(lines.length, targetLine + contextLines);
|
|
103
|
+
return lines.slice(start, end).map((line, i) => {
|
|
104
|
+
const lineNum = start + i + 1;
|
|
105
|
+
const marker = lineNum === targetLine ? " // <-- target" : "";
|
|
106
|
+
return `${line}${marker}`;
|
|
107
|
+
}).join("\n");
|
|
108
|
+
} catch {
|
|
109
|
+
return "// Source file not found";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function buildMarkdown(context, sourceSnippet, screenshotPath) {
|
|
113
|
+
const { source, tagName, classes, computedStyles, dimensions, componentHierarchy, url } = context;
|
|
114
|
+
const lines = ["# UI Context Capture", ""];
|
|
115
|
+
lines.push("## Target Element");
|
|
116
|
+
if (source) {
|
|
117
|
+
if (source.component) {
|
|
118
|
+
lines.push(`- **Component**: \`${source.component}\` in \`${source.file}:${source.line}\``);
|
|
119
|
+
} else {
|
|
120
|
+
lines.push(`- **File**: \`${source.file}:${source.line}\``);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
lines.push(`- **URL**: ${url}`);
|
|
124
|
+
lines.push(`- **Tag**: \`<${tagName}>\``);
|
|
125
|
+
lines.push("");
|
|
126
|
+
if (source && sourceSnippet) {
|
|
127
|
+
const startLine = Math.max(1, source.line - 10);
|
|
128
|
+
const endLine = source.line + 10;
|
|
129
|
+
lines.push("## Source Code");
|
|
130
|
+
lines.push("```tsx");
|
|
131
|
+
lines.push(`// ${source.file}:${startLine}-${endLine}`);
|
|
132
|
+
lines.push(sourceSnippet);
|
|
133
|
+
lines.push("```");
|
|
134
|
+
lines.push("");
|
|
135
|
+
}
|
|
136
|
+
lines.push("## Styling");
|
|
137
|
+
if (classes) {
|
|
138
|
+
lines.push(`- **Classes**: \`${classes}\``);
|
|
139
|
+
}
|
|
140
|
+
const styleEntries = Object.entries(computedStyles);
|
|
141
|
+
if (styleEntries.length > 0) {
|
|
142
|
+
lines.push(`- **Computed**: ${styleEntries.map(([k, v]) => `${camelToKebab(k)}: ${v}`).join(", ")}`);
|
|
143
|
+
}
|
|
144
|
+
lines.push(`- **Dimensions**: ${Math.round(dimensions.width)}x${Math.round(dimensions.height)}px at (${Math.round(dimensions.x)}, ${Math.round(dimensions.y)})`);
|
|
145
|
+
lines.push("");
|
|
146
|
+
if (componentHierarchy.length > 0) {
|
|
147
|
+
lines.push("## Component Hierarchy");
|
|
148
|
+
lines.push(componentHierarchy.join(" > "));
|
|
149
|
+
lines.push("");
|
|
150
|
+
}
|
|
151
|
+
if (screenshotPath) {
|
|
152
|
+
lines.push("## Screenshot");
|
|
153
|
+
lines.push(``);
|
|
154
|
+
lines.push("");
|
|
155
|
+
}
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
|
158
|
+
function buildMultiMarkdown(contexts, sourceSnippets, screenshotPaths) {
|
|
159
|
+
const lines = [
|
|
160
|
+
`# UI Context Capture (${contexts.length} elements)`,
|
|
161
|
+
""
|
|
162
|
+
];
|
|
163
|
+
for (let i = 0; i < contexts.length; i++) {
|
|
164
|
+
const ctx = contexts[i];
|
|
165
|
+
const snippet = sourceSnippets[i];
|
|
166
|
+
const screenshot = screenshotPaths[i];
|
|
167
|
+
lines.push(`---`);
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push(`## Element ${i + 1}: \`<${ctx.tagName}>\`${ctx.source?.component ? ` (${ctx.source.component})` : ""}`);
|
|
170
|
+
lines.push("");
|
|
171
|
+
if (ctx.source) {
|
|
172
|
+
if (ctx.source.component) {
|
|
173
|
+
lines.push(`- **Component**: \`${ctx.source.component}\` in \`${ctx.source.file}:${ctx.source.line}\``);
|
|
174
|
+
} else {
|
|
175
|
+
lines.push(`- **File**: \`${ctx.source.file}:${ctx.source.line}\``);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
lines.push(`- **URL**: ${ctx.url}`);
|
|
179
|
+
lines.push("");
|
|
180
|
+
if (ctx.source && snippet) {
|
|
181
|
+
const startLine = Math.max(1, ctx.source.line - 10);
|
|
182
|
+
const endLine = ctx.source.line + 10;
|
|
183
|
+
lines.push("### Source Code");
|
|
184
|
+
lines.push("```tsx");
|
|
185
|
+
lines.push(`// ${ctx.source.file}:${startLine}-${endLine}`);
|
|
186
|
+
lines.push(snippet);
|
|
187
|
+
lines.push("```");
|
|
188
|
+
lines.push("");
|
|
189
|
+
}
|
|
190
|
+
lines.push("### Styling");
|
|
191
|
+
if (ctx.classes) {
|
|
192
|
+
lines.push(`- **Classes**: \`${ctx.classes}\``);
|
|
193
|
+
}
|
|
194
|
+
const styleEntries = Object.entries(ctx.computedStyles);
|
|
195
|
+
if (styleEntries.length > 0) {
|
|
196
|
+
lines.push(`- **Computed**: ${styleEntries.map(([k, v]) => `${camelToKebab(k)}: ${v}`).join(", ")}`);
|
|
197
|
+
}
|
|
198
|
+
lines.push(`- **Dimensions**: ${Math.round(ctx.dimensions.width)}x${Math.round(ctx.dimensions.height)}px at (${Math.round(ctx.dimensions.x)}, ${Math.round(ctx.dimensions.y)})`);
|
|
199
|
+
lines.push("");
|
|
200
|
+
if (ctx.componentHierarchy.length > 0) {
|
|
201
|
+
lines.push("### Component Hierarchy");
|
|
202
|
+
lines.push(ctx.componentHierarchy.join(" > "));
|
|
203
|
+
lines.push("");
|
|
204
|
+
}
|
|
205
|
+
if (screenshot) {
|
|
206
|
+
lines.push("### Screenshot");
|
|
207
|
+
lines.push(``);
|
|
208
|
+
lines.push("");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return lines.join("\n");
|
|
212
|
+
}
|
|
213
|
+
function camelToKebab(str) {
|
|
214
|
+
return str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/clipboard.ts
|
|
218
|
+
import { exec } from "child_process";
|
|
219
|
+
import { platform } from "os";
|
|
220
|
+
function copyToClipboard(text) {
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
const os = platform();
|
|
223
|
+
let cmd;
|
|
224
|
+
if (os === "darwin") {
|
|
225
|
+
cmd = "pbcopy";
|
|
226
|
+
} else if (os === "linux") {
|
|
227
|
+
cmd = "xclip -selection clipboard";
|
|
228
|
+
} else if (os === "win32") {
|
|
229
|
+
cmd = "clip";
|
|
230
|
+
} else {
|
|
231
|
+
reject(new Error(`Unsupported platform: ${os}`));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const proc = exec(cmd, (err) => {
|
|
235
|
+
if (err) reject(err);
|
|
236
|
+
else resolve();
|
|
237
|
+
});
|
|
238
|
+
proc.stdin?.write(text);
|
|
239
|
+
proc.stdin?.end();
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/ws-server.ts
|
|
244
|
+
function setupWebSocket(server, options) {
|
|
245
|
+
const root = server.config.root;
|
|
246
|
+
const outputDir = options.outputDir || ".ui-context";
|
|
247
|
+
const shouldCopy = options.clipboard !== false;
|
|
248
|
+
server.ws.on("ui-context:capture", async (data, client) => {
|
|
249
|
+
try {
|
|
250
|
+
const count = Array.isArray(data) ? data.length : 1;
|
|
251
|
+
console.log(`[ui-context-kit] Received context capture (${count} element${count > 1 ? "s" : ""})`);
|
|
252
|
+
const filePath = await writeContext(data, root, outputDir);
|
|
253
|
+
if (shouldCopy) {
|
|
254
|
+
try {
|
|
255
|
+
const fs3 = await import("fs");
|
|
256
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
257
|
+
await copyToClipboard(content);
|
|
258
|
+
console.log("[ui-context-kit] Context copied to clipboard");
|
|
259
|
+
} catch (e) {
|
|
260
|
+
console.warn("[ui-context-kit] Could not copy to clipboard:", e);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
client.send("ui-context:capture-result", { success: true });
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error("[ui-context-kit] Error writing context:", err);
|
|
266
|
+
client.send("ui-context:capture-result", { success: false, error: err.message });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/vite-plugin.ts
|
|
272
|
+
var JSX_EXTENSIONS = /\.(jsx|tsx)$/;
|
|
273
|
+
var VIRTUAL_MODULE_ID = "virtual:ui-context-overlay";
|
|
274
|
+
var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
275
|
+
function createVitePlugin(options = {}) {
|
|
276
|
+
const {
|
|
277
|
+
shortcut = "alt+x",
|
|
278
|
+
include,
|
|
279
|
+
exclude = ["node_modules"]
|
|
280
|
+
} = options;
|
|
281
|
+
let projectRoot = "";
|
|
282
|
+
const transformPlugin = {
|
|
283
|
+
name: "ui-context-kit:transform",
|
|
284
|
+
enforce: "pre",
|
|
285
|
+
apply: "serve",
|
|
286
|
+
configResolved(config) {
|
|
287
|
+
projectRoot = config.root;
|
|
288
|
+
},
|
|
289
|
+
transform(code, id) {
|
|
290
|
+
if (!JSX_EXTENSIONS.test(id)) return null;
|
|
291
|
+
const relativePath = path2.relative(projectRoot, id);
|
|
292
|
+
if (exclude?.some((p) => relativePath.includes(p))) return null;
|
|
293
|
+
if (include && !include.some((p) => relativePath.includes(p))) return null;
|
|
294
|
+
if (id.includes("\0") || id.includes("node_modules")) return null;
|
|
295
|
+
const filePath = path2.relative(projectRoot, id);
|
|
296
|
+
let originalCode;
|
|
297
|
+
try {
|
|
298
|
+
originalCode = fs2.readFileSync(id, "utf-8");
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
const result = injectSourceAttributes(code, filePath, originalCode);
|
|
302
|
+
if (!result) return null;
|
|
303
|
+
return { code: result.code, map: result.map };
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const overlayPlugin = {
|
|
307
|
+
name: "ui-context-kit:overlay",
|
|
308
|
+
apply: "serve",
|
|
309
|
+
resolveId(id) {
|
|
310
|
+
if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;
|
|
311
|
+
},
|
|
312
|
+
load(id) {
|
|
313
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
314
|
+
return getOverlayCode(shortcut);
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
configureServer(server) {
|
|
318
|
+
setupWebSocket(server, options);
|
|
319
|
+
},
|
|
320
|
+
transformIndexHtml() {
|
|
321
|
+
return [
|
|
322
|
+
{
|
|
323
|
+
tag: "script",
|
|
324
|
+
attrs: { type: "module", src: `/@id/${VIRTUAL_MODULE_ID}` },
|
|
325
|
+
injectTo: "body"
|
|
326
|
+
}
|
|
327
|
+
];
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
return [transformPlugin, overlayPlugin];
|
|
331
|
+
}
|
|
332
|
+
function getOverlayCode(shortcut) {
|
|
333
|
+
return `
|
|
334
|
+
// UI Context Kit - Overlay (auto-injected in dev mode)
|
|
335
|
+
(function() {
|
|
336
|
+
if (typeof window === 'undefined') return;
|
|
337
|
+
|
|
338
|
+
var isActive = false;
|
|
339
|
+
var highlightEl = null;
|
|
340
|
+
var toolbarEl = null;
|
|
341
|
+
var selectedEl = null;
|
|
342
|
+
var isFrozen = false;
|
|
343
|
+
var frozenObservers = [];
|
|
344
|
+
var multiSelected = new Set();
|
|
345
|
+
|
|
346
|
+
var SHORTCUT = '${shortcut}';
|
|
347
|
+
var ONBOARDING_KEY = 'ui-context-kit-onboarding-dismissed';
|
|
348
|
+
|
|
349
|
+
// ---- Shadow DOM Overlay ----
|
|
350
|
+
function createOverlay() {
|
|
351
|
+
var host = document.createElement('div');
|
|
352
|
+
host.id = 'ui-context-host';
|
|
353
|
+
host.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;';
|
|
354
|
+
document.body.appendChild(host);
|
|
355
|
+
|
|
356
|
+
var shadow = host.attachShadow({ mode: 'open' });
|
|
357
|
+
shadow.innerHTML = [
|
|
358
|
+
'<style>',
|
|
359
|
+
'.highlight { position:fixed; border:2px solid #3b82f6; background:rgba(59,130,246,0.08); pointer-events:none; transition:all 0.15s ease; border-radius:3px; display:none; }',
|
|
360
|
+
'.highlight-label { position:absolute; top:-22px; left:-2px; background:#3b82f6; color:white; font-size:11px; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; padding:2px 8px; border-radius:3px 3px 0 0; white-space:nowrap; }',
|
|
361
|
+
'.toolbar { position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:#0f172a; border:1px solid #334155; border-radius:12px; padding:8px 12px; display:flex; gap:6px; align-items:center; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; box-shadow:0 8px 32px rgba(0,0,0,0.5); pointer-events:auto; }',
|
|
362
|
+
'.toolbar-label { color:#94a3b8; font-size:12px; padding:0 4px; }',
|
|
363
|
+
'.toolbar-btn { color:white; border:none; padding:6px 14px; border-radius:6px; font-size:12px; font-weight:500; cursor:pointer; white-space:nowrap; }',
|
|
364
|
+
'.toolbar-btn:hover { filter:brightness(1.15); }',
|
|
365
|
+
'.toolbar-btn.primary { background:#3b82f6; }',
|
|
366
|
+
'.toolbar-btn.secondary { background:#475569; }',
|
|
367
|
+
'.toolbar-btn.freeze { background:#475569; }',
|
|
368
|
+
'.toolbar-btn.freeze.active { background:#f59e0b; color:#1e293b; }',
|
|
369
|
+
'.badge { display:inline-block; background:#f59e0b; color:#1e293b; font-size:10px; font-weight:700; padding:1px 5px; border-radius:10px; margin-left:4px; }',
|
|
370
|
+
'.divider { width:1px; height:20px; background:#334155; }',
|
|
371
|
+
'.toast { position:fixed; bottom:72px; left:50%; transform:translateX(-50%); background:#0f172a; color:#e2e8f0; padding:10px 20px; border-radius:8px; font-size:13px; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; box-shadow:0 4px 16px rgba(0,0,0,0.4); pointer-events:none; animation:toastIn 0.2s ease; }',
|
|
372
|
+
'@keyframes toastIn { from { opacity:0; transform:translateX(-50%) translateY(8px); } }',
|
|
373
|
+
'.banner { position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:linear-gradient(135deg,#0f172a 0%,#1e293b 100%); border:1px solid #334155; border-radius:12px; padding:12px 20px; display:flex; gap:12px; align-items:center; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; box-shadow:0 8px 32px rgba(0,0,0,0.4); animation:bannerIn 0.4s ease; max-width:480px; pointer-events:auto; }',
|
|
374
|
+
'@keyframes bannerIn { from { opacity:0; transform:translateX(-50%) translateY(20px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }',
|
|
375
|
+
'.banner-text { color:#e2e8f0; font-size:13px; line-height:1.4; }',
|
|
376
|
+
'.banner-text strong { color:#60a5fa; }',
|
|
377
|
+
'.kbd { display:inline-block; background:#334155; color:#e2e8f0; padding:1px 6px; border-radius:4px; font-size:12px; font-family:monospace; border:1px solid #475569; }',
|
|
378
|
+
'.banner-close { background:none; border:none; color:#64748b; cursor:pointer; padding:4px; font-size:16px; flex-shrink:0; }',
|
|
379
|
+
'.banner-close:hover { color:#94a3b8; }',
|
|
380
|
+
'</style>',
|
|
381
|
+
'<div class="highlight"><div class="highlight-label"></div></div>',
|
|
382
|
+
'<div class="toolbar" style="display:none">',
|
|
383
|
+
' <span class="toolbar-label">UI Context</span>',
|
|
384
|
+
' <div class="divider"></div>',
|
|
385
|
+
' <button class="toolbar-btn primary" id="capture-btn">Capture</button>',
|
|
386
|
+
' <button class="toolbar-btn primary" id="capture-all-btn" style="display:none">Capture All <span class="badge" id="multi-badge" style="display:none">0</span></button>',
|
|
387
|
+
' <div class="divider"></div>',
|
|
388
|
+
' <button class="toolbar-btn freeze" id="freeze-btn" title="Freeze DOM (Ctrl+Shift+F)">Freeze</button>',
|
|
389
|
+
' <button class="toolbar-btn secondary" id="close-btn">\\u2715</button>',
|
|
390
|
+
'</div>',
|
|
391
|
+
].join('\\n');
|
|
392
|
+
|
|
393
|
+
highlightEl = shadow.querySelector('.highlight');
|
|
394
|
+
toolbarEl = shadow.querySelector('.toolbar');
|
|
395
|
+
|
|
396
|
+
shadow.getElementById('close-btn').addEventListener('click', deactivate);
|
|
397
|
+
shadow.getElementById('capture-btn').addEventListener('click', function() {
|
|
398
|
+
if (selectedEl) captureAndSend(selectedEl);
|
|
399
|
+
});
|
|
400
|
+
shadow.getElementById('capture-all-btn').addEventListener('click', captureAllSelected);
|
|
401
|
+
shadow.getElementById('freeze-btn').addEventListener('click', toggleFreeze);
|
|
402
|
+
|
|
403
|
+
return { host: host, shadow: shadow };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
var overlayRef = null;
|
|
407
|
+
|
|
408
|
+
function showHighlight(el) {
|
|
409
|
+
if (!highlightEl || !el) { highlightEl && (highlightEl.style.display = 'none'); return; }
|
|
410
|
+
var rect = el.getBoundingClientRect();
|
|
411
|
+
highlightEl.style.display = 'block';
|
|
412
|
+
highlightEl.style.top = rect.top + 'px';
|
|
413
|
+
highlightEl.style.left = rect.left + 'px';
|
|
414
|
+
highlightEl.style.width = rect.width + 'px';
|
|
415
|
+
highlightEl.style.height = rect.height + 'px';
|
|
416
|
+
|
|
417
|
+
var label = highlightEl.querySelector('.highlight-label');
|
|
418
|
+
var src = el.getAttribute('data-source-file');
|
|
419
|
+
var line = el.getAttribute('data-source-line');
|
|
420
|
+
var comp = el.getAttribute('data-source-component');
|
|
421
|
+
label.textContent = src
|
|
422
|
+
? (comp || el.tagName.toLowerCase()) + ' \\u00b7 ' + src + ':' + line
|
|
423
|
+
: el.tagName.toLowerCase();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function showToast(msg, duration) {
|
|
427
|
+
if (!overlayRef) return;
|
|
428
|
+
duration = duration || 3000;
|
|
429
|
+
var t = document.createElement('div');
|
|
430
|
+
t.className = 'toast';
|
|
431
|
+
t.textContent = msg;
|
|
432
|
+
overlayRef.shadow.appendChild(t);
|
|
433
|
+
setTimeout(function() { t.remove(); }, duration);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ---- React Fiber Hierarchy ----
|
|
437
|
+
function getComponentHierarchy(el) {
|
|
438
|
+
var fiberKey = Object.keys(el).find(function(k) {
|
|
439
|
+
return k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$');
|
|
440
|
+
});
|
|
441
|
+
if (!fiberKey) return [];
|
|
442
|
+
var hierarchy = [];
|
|
443
|
+
var fiber = el[fiberKey];
|
|
444
|
+
while (fiber) {
|
|
445
|
+
if (typeof fiber.type === 'function') {
|
|
446
|
+
var name = fiber.type.displayName || fiber.type.name;
|
|
447
|
+
if (name && !name.startsWith('_') && name !== 'Fragment') hierarchy.unshift(name);
|
|
448
|
+
} else if (typeof fiber.type === 'object' && fiber.type) {
|
|
449
|
+
var name = fiber.type.displayName || (fiber.type.render && (fiber.type.render.displayName || fiber.type.render.name));
|
|
450
|
+
if (name && !name.startsWith('_') && name !== 'Fragment') hierarchy.unshift(name);
|
|
451
|
+
}
|
|
452
|
+
fiber = fiber.return;
|
|
453
|
+
}
|
|
454
|
+
return hierarchy;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ---- Computed Styles ----
|
|
458
|
+
var STYLE_PROPS = [
|
|
459
|
+
'padding','padding-top','padding-right','padding-bottom','padding-left',
|
|
460
|
+
'margin','margin-top','margin-right','margin-bottom','margin-left',
|
|
461
|
+
'background-color','color','font-size','font-weight','font-family',
|
|
462
|
+
'border-radius','display','flex-direction','justify-content','align-items',
|
|
463
|
+
'gap','width','height','overflow','position','z-index','opacity',
|
|
464
|
+
'border','box-shadow','line-height','letter-spacing','text-align'
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
function extractContext(el) {
|
|
468
|
+
var computed = getComputedStyle(el);
|
|
469
|
+
var rect = el.getBoundingClientRect();
|
|
470
|
+
var styles = {};
|
|
471
|
+
for (var i = 0; i < STYLE_PROPS.length; i++) {
|
|
472
|
+
var p = STYLE_PROPS[i];
|
|
473
|
+
var v = computed.getPropertyValue(p);
|
|
474
|
+
if (v && v !== 'none' && v !== 'normal' && v !== 'auto' && v !== '0px' && v !== 'rgba(0, 0, 0, 0)') {
|
|
475
|
+
styles[p] = v;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
var file = el.getAttribute('data-source-file');
|
|
480
|
+
var line = el.getAttribute('data-source-line');
|
|
481
|
+
var col = el.getAttribute('data-source-col');
|
|
482
|
+
var component = el.getAttribute('data-source-component');
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
source: file ? { file: file, line: parseInt(line||'0'), col: parseInt(col||'0'), component: component || undefined } : null,
|
|
486
|
+
tagName: el.tagName.toLowerCase(),
|
|
487
|
+
classes: el.className || '',
|
|
488
|
+
computedStyles: styles,
|
|
489
|
+
dimensions: { width: rect.width, height: rect.height, x: rect.x, y: rect.y },
|
|
490
|
+
componentHierarchy: getComponentHierarchy(el),
|
|
491
|
+
url: location.href
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ---- Screenshot ----
|
|
496
|
+
function captureScreenshot(el) {
|
|
497
|
+
return new Promise(function(resolve) {
|
|
498
|
+
try {
|
|
499
|
+
var rect = el.getBoundingClientRect();
|
|
500
|
+
if (rect.width === 0 || rect.height === 0) { resolve(undefined); return; }
|
|
501
|
+
|
|
502
|
+
var canvas = document.createElement('canvas');
|
|
503
|
+
var dpr = window.devicePixelRatio || 1;
|
|
504
|
+
var pad = 8;
|
|
505
|
+
var w = Math.ceil(rect.width + pad * 2);
|
|
506
|
+
var h = Math.ceil(rect.height + pad * 2);
|
|
507
|
+
canvas.width = w * dpr;
|
|
508
|
+
canvas.height = h * dpr;
|
|
509
|
+
|
|
510
|
+
var ctx = canvas.getContext('2d');
|
|
511
|
+
if (!ctx) { resolve(undefined); return; }
|
|
512
|
+
ctx.scale(dpr, dpr);
|
|
513
|
+
|
|
514
|
+
// Try SVG foreignObject approach
|
|
515
|
+
var clone = el.cloneNode(true);
|
|
516
|
+
copyStyles(el, clone);
|
|
517
|
+
|
|
518
|
+
var svgNs = 'http://www.w3.org/2000/svg';
|
|
519
|
+
var svg = document.createElementNS(svgNs, 'svg');
|
|
520
|
+
svg.setAttribute('width', String(w));
|
|
521
|
+
svg.setAttribute('height', String(h));
|
|
522
|
+
svg.setAttribute('xmlns', svgNs);
|
|
523
|
+
|
|
524
|
+
var fo = document.createElementNS(svgNs, 'foreignObject');
|
|
525
|
+
fo.setAttribute('width', '100%');
|
|
526
|
+
fo.setAttribute('height', '100%');
|
|
527
|
+
|
|
528
|
+
var container = document.createElement('div');
|
|
529
|
+
container.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
|
530
|
+
container.style.cssText = 'width:' + w + 'px;height:' + h + 'px;display:flex;align-items:center;justify-content:center;background:white;padding:' + pad + 'px;box-sizing:border-box;';
|
|
531
|
+
container.appendChild(clone);
|
|
532
|
+
fo.appendChild(container);
|
|
533
|
+
svg.appendChild(fo);
|
|
534
|
+
|
|
535
|
+
var svgStr = new XMLSerializer().serializeToString(svg);
|
|
536
|
+
var blob = new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' });
|
|
537
|
+
var url = URL.createObjectURL(blob);
|
|
538
|
+
|
|
539
|
+
var img = new Image();
|
|
540
|
+
img.onload = function() {
|
|
541
|
+
ctx.drawImage(img, 0, 0);
|
|
542
|
+
URL.revokeObjectURL(url);
|
|
543
|
+
try { resolve(canvas.toDataURL('image/png')); } catch(e) { resolve(undefined); }
|
|
544
|
+
};
|
|
545
|
+
img.onerror = function() {
|
|
546
|
+
URL.revokeObjectURL(url);
|
|
547
|
+
// Fallback: placeholder
|
|
548
|
+
ctx.fillStyle = '#f8fafc';
|
|
549
|
+
ctx.fillRect(0, 0, w, h);
|
|
550
|
+
ctx.strokeStyle = '#3b82f6';
|
|
551
|
+
ctx.lineWidth = 2;
|
|
552
|
+
ctx.strokeRect(pad, pad, rect.width, rect.height);
|
|
553
|
+
ctx.fillStyle = '#64748b';
|
|
554
|
+
ctx.font = '11px -apple-system, BlinkMacSystemFont, sans-serif';
|
|
555
|
+
ctx.fillText(Math.round(rect.width) + ' x ' + Math.round(rect.height) + 'px', pad + 4, pad + rect.height / 2 + 4);
|
|
556
|
+
try { resolve(canvas.toDataURL('image/png')); } catch(e) { resolve(undefined); }
|
|
557
|
+
};
|
|
558
|
+
img.src = url;
|
|
559
|
+
} catch(e) {
|
|
560
|
+
resolve(undefined);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function copyStyles(src, tgt) {
|
|
566
|
+
var cs = getComputedStyle(src);
|
|
567
|
+
var props = ['display','width','height','padding','margin','border','border-radius','background','background-color','color','font-size','font-weight','font-family','line-height','text-align','box-shadow','opacity','flex-direction','justify-content','align-items','gap','overflow'];
|
|
568
|
+
for (var i = 0; i < props.length; i++) {
|
|
569
|
+
tgt.style.setProperty(props[i], cs.getPropertyValue(props[i]));
|
|
570
|
+
}
|
|
571
|
+
var sc = src.children, tc = tgt.children;
|
|
572
|
+
for (var j = 0; j < sc.length && j < tc.length; j++) {
|
|
573
|
+
if (sc[j] instanceof HTMLElement && tc[j] instanceof HTMLElement) copyStyles(sc[j], tc[j]);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ---- Capture & Send ----
|
|
578
|
+
function captureAndSend(el) {
|
|
579
|
+
showToast('Capturing context...');
|
|
580
|
+
var ctx = extractContext(el);
|
|
581
|
+
|
|
582
|
+
captureScreenshot(el).then(function(screenshot) {
|
|
583
|
+
if (screenshot) ctx.screenshotDataUrl = screenshot;
|
|
584
|
+
|
|
585
|
+
if (import.meta.hot) {
|
|
586
|
+
import.meta.hot.send('ui-context:capture', ctx);
|
|
587
|
+
import.meta.hot.on('ui-context:capture-result', function(result) {
|
|
588
|
+
if (result.success) {
|
|
589
|
+
showToast('Context captured! Check .ui-context/latest.md');
|
|
590
|
+
} else {
|
|
591
|
+
showToast('Capture failed: ' + (result.error || 'unknown error'));
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function captureAllSelected() {
|
|
599
|
+
if (multiSelected.size === 0) {
|
|
600
|
+
showToast('No elements selected. Use Shift+Click to select multiple.');
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
showToast('Capturing ' + multiSelected.size + ' elements...');
|
|
604
|
+
|
|
605
|
+
var elements = Array.from(multiSelected);
|
|
606
|
+
var contexts = [];
|
|
607
|
+
var pending = elements.length;
|
|
608
|
+
|
|
609
|
+
elements.forEach(function(el) {
|
|
610
|
+
var ctx = extractContext(el);
|
|
611
|
+
captureScreenshot(el).then(function(screenshot) {
|
|
612
|
+
if (screenshot) ctx.screenshotDataUrl = screenshot;
|
|
613
|
+
contexts.push(ctx);
|
|
614
|
+
pending--;
|
|
615
|
+
if (pending === 0) {
|
|
616
|
+
if (import.meta.hot) {
|
|
617
|
+
import.meta.hot.send('ui-context:capture', contexts);
|
|
618
|
+
import.meta.hot.on('ui-context:capture-result', function(result) {
|
|
619
|
+
if (result.success) {
|
|
620
|
+
showToast(contexts.length + ' elements captured!');
|
|
621
|
+
clearMultiSelection();
|
|
622
|
+
} else {
|
|
623
|
+
showToast('Capture failed: ' + (result.error || 'unknown error'));
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function updateMultiBadge() {
|
|
633
|
+
if (!overlayRef) return;
|
|
634
|
+
var btn = overlayRef.shadow.getElementById('capture-all-btn');
|
|
635
|
+
var badge = overlayRef.shadow.getElementById('multi-badge');
|
|
636
|
+
if (multiSelected.size > 0) {
|
|
637
|
+
btn.style.display = '';
|
|
638
|
+
badge.style.display = '';
|
|
639
|
+
badge.textContent = String(multiSelected.size);
|
|
640
|
+
} else {
|
|
641
|
+
btn.style.display = 'none';
|
|
642
|
+
badge.style.display = 'none';
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function clearMultiSelection() {
|
|
647
|
+
multiSelected.forEach(function(el) { el.style.outline = ''; });
|
|
648
|
+
multiSelected.clear();
|
|
649
|
+
updateMultiBadge();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// ---- Freeze Mode ----
|
|
653
|
+
function toggleFreeze() {
|
|
654
|
+
if (isFrozen) {
|
|
655
|
+
unfreezeDOM();
|
|
656
|
+
showToast('DOM unfrozen');
|
|
657
|
+
} else {
|
|
658
|
+
freezeDOM();
|
|
659
|
+
showToast('DOM frozen \u2014 hover states preserved');
|
|
660
|
+
}
|
|
661
|
+
var btn = overlayRef && overlayRef.shadow.getElementById('freeze-btn');
|
|
662
|
+
if (btn) {
|
|
663
|
+
if (isFrozen) {
|
|
664
|
+
btn.classList.add('active');
|
|
665
|
+
btn.textContent = 'Frozen';
|
|
666
|
+
} else {
|
|
667
|
+
btn.classList.remove('active');
|
|
668
|
+
btn.textContent = 'Freeze';
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function freezeDOM() {
|
|
674
|
+
isFrozen = true;
|
|
675
|
+
var observer = new MutationObserver(function(mutations) {
|
|
676
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
677
|
+
var m = mutations[i];
|
|
678
|
+
for (var j = 0; j < m.addedNodes.length; j++) {
|
|
679
|
+
var node = m.addedNodes[j];
|
|
680
|
+
if (node instanceof HTMLElement && !(node.id && node.id.indexOf('ui-context') !== -1)) {
|
|
681
|
+
node.remove();
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (m.type === 'attributes' && m.target instanceof HTMLElement) {
|
|
685
|
+
if (!(m.target.id && m.target.id.indexOf('ui-context') !== -1)) {
|
|
686
|
+
var old = m.oldValue;
|
|
687
|
+
if (old !== null && m.attributeName) {
|
|
688
|
+
m.target.setAttribute(m.attributeName, old);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
observer.observe(document.body, {
|
|
695
|
+
childList: true, subtree: true, attributes: true, attributeOldValue: true
|
|
696
|
+
});
|
|
697
|
+
frozenObservers.push(observer);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function unfreezeDOM() {
|
|
701
|
+
isFrozen = false;
|
|
702
|
+
frozenObservers.forEach(function(obs) { obs.disconnect(); });
|
|
703
|
+
frozenObservers = [];
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ---- Event Handlers ----
|
|
707
|
+
function isOverlayEl(t) {
|
|
708
|
+
return t.id === 'ui-context-host' || (t.closest && t.closest('#ui-context-host'));
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function onMouseMove(e) {
|
|
712
|
+
if (!isActive) return;
|
|
713
|
+
var t = e.target;
|
|
714
|
+
if (isOverlayEl(t)) return;
|
|
715
|
+
showHighlight(t);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function onClick(e) {
|
|
719
|
+
if (!isActive) return;
|
|
720
|
+
var t = e.target;
|
|
721
|
+
if (isOverlayEl(t)) return;
|
|
722
|
+
e.preventDefault();
|
|
723
|
+
e.stopPropagation();
|
|
724
|
+
|
|
725
|
+
// Shift+Click: multi-select
|
|
726
|
+
if (e.shiftKey) {
|
|
727
|
+
if (multiSelected.has(t)) {
|
|
728
|
+
multiSelected.delete(t);
|
|
729
|
+
t.style.outline = '';
|
|
730
|
+
} else {
|
|
731
|
+
multiSelected.add(t);
|
|
732
|
+
t.style.outline = '2px dashed #f59e0b';
|
|
733
|
+
}
|
|
734
|
+
updateMultiBadge();
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
selectedEl = t;
|
|
739
|
+
captureAndSend(t);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ---- Activate / Deactivate ----
|
|
743
|
+
function activate() {
|
|
744
|
+
if (isActive) return;
|
|
745
|
+
isActive = true;
|
|
746
|
+
overlayRef = createOverlay();
|
|
747
|
+
toolbarEl.style.display = 'flex';
|
|
748
|
+
document.addEventListener('mousemove', onMouseMove, true);
|
|
749
|
+
document.addEventListener('click', onClick, true);
|
|
750
|
+
document.body.style.cursor = 'crosshair';
|
|
751
|
+
showToast('Click to capture, Shift+click for multi-select', 2000);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function deactivate() {
|
|
755
|
+
if (!isActive) return;
|
|
756
|
+
isActive = false;
|
|
757
|
+
selectedEl = null;
|
|
758
|
+
if (isFrozen) unfreezeDOM();
|
|
759
|
+
clearMultiSelection();
|
|
760
|
+
document.removeEventListener('mousemove', onMouseMove, true);
|
|
761
|
+
document.removeEventListener('click', onClick, true);
|
|
762
|
+
document.body.style.cursor = '';
|
|
763
|
+
if (overlayRef) {
|
|
764
|
+
overlayRef.host.remove();
|
|
765
|
+
overlayRef = null;
|
|
766
|
+
highlightEl = null;
|
|
767
|
+
toolbarEl = null;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// ---- Keyboard Shortcuts ----
|
|
772
|
+
document.addEventListener('keydown', function(e) {
|
|
773
|
+
var keys = SHORTCUT.split('+');
|
|
774
|
+
var needsAlt = keys.indexOf('alt') !== -1;
|
|
775
|
+
var needsCtrl = keys.indexOf('ctrl') !== -1;
|
|
776
|
+
var needsShift = keys.indexOf('shift') !== -1;
|
|
777
|
+
var mainKey = keys[keys.length - 1].toLowerCase();
|
|
778
|
+
|
|
779
|
+
if (e.altKey === needsAlt && e.ctrlKey === needsCtrl && e.shiftKey === needsShift && e.key.toLowerCase() === mainKey) {
|
|
780
|
+
e.preventDefault();
|
|
781
|
+
isActive ? deactivate() : activate();
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Freeze: Ctrl+Shift+F (only when active)
|
|
786
|
+
if (isActive && e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'f') {
|
|
787
|
+
e.preventDefault();
|
|
788
|
+
toggleFreeze();
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
// ---- Onboarding Banner ----
|
|
793
|
+
function showOnboarding() {
|
|
794
|
+
try { if (localStorage.getItem(ONBOARDING_KEY)) return; } catch(e) { return; }
|
|
795
|
+
|
|
796
|
+
// Wait for overlay ref to be set up
|
|
797
|
+
var bannerHost = document.createElement('div');
|
|
798
|
+
bannerHost.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;z-index:2147483646;pointer-events:none;';
|
|
799
|
+
document.body.appendChild(bannerHost);
|
|
800
|
+
var bannerShadow = bannerHost.attachShadow({ mode: 'open' });
|
|
801
|
+
|
|
802
|
+
var shortcutDisplay = SHORTCUT.split('+').map(function(k) { return k.charAt(0).toUpperCase() + k.slice(1); }).join(' + ');
|
|
803
|
+
|
|
804
|
+
bannerShadow.innerHTML = [
|
|
805
|
+
'<style>',
|
|
806
|
+
'.banner { position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:linear-gradient(135deg,#0f172a 0%,#1e293b 100%); border:1px solid #334155; border-radius:12px; padding:12px 20px; display:flex; gap:12px; align-items:center; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; box-shadow:0 8px 32px rgba(0,0,0,0.4); animation:bannerIn 0.4s ease; max-width:480px; pointer-events:auto; }',
|
|
807
|
+
'@keyframes bannerIn { from { opacity:0; transform:translateX(-50%) translateY(20px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }',
|
|
808
|
+
'.text { color:#e2e8f0; font-size:13px; line-height:1.4; }',
|
|
809
|
+
'.text strong { color:#60a5fa; }',
|
|
810
|
+
'.kbd { display:inline-block; background:#334155; color:#e2e8f0; padding:1px 6px; border-radius:4px; font-size:12px; font-family:monospace; border:1px solid #475569; }',
|
|
811
|
+
'.close { background:none; border:none; color:#64748b; cursor:pointer; padding:4px; font-size:16px; flex-shrink:0; }',
|
|
812
|
+
'.close:hover { color:#94a3b8; }',
|
|
813
|
+
'</style>',
|
|
814
|
+
'<div class="banner">',
|
|
815
|
+
' <span style="font-size:20px">\\u{1F3AF}</span>',
|
|
816
|
+
' <span class="text">',
|
|
817
|
+
' <strong>UI Context Kit</strong> is ready! Press <kbd class="kbd">' + shortcutDisplay + '</kbd> to capture any UI element for AI.',
|
|
818
|
+
' <strong>Shift+Click</strong> for multi-select, <strong>Ctrl+Shift+F</strong> to freeze state.',
|
|
819
|
+
' </span>',
|
|
820
|
+
' <button class="close" title="Dismiss">\\u2715</button>',
|
|
821
|
+
'</div>',
|
|
822
|
+
].join('\\n');
|
|
823
|
+
|
|
824
|
+
bannerShadow.querySelector('.close').addEventListener('click', function() {
|
|
825
|
+
bannerHost.remove();
|
|
826
|
+
try { localStorage.setItem(ONBOARDING_KEY, '1'); } catch(e) {}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
setTimeout(function() {
|
|
830
|
+
if (bannerHost.parentNode) {
|
|
831
|
+
var banner = bannerShadow.querySelector('.banner');
|
|
832
|
+
if (banner) {
|
|
833
|
+
banner.style.transition = 'opacity 0.3s ease';
|
|
834
|
+
banner.style.opacity = '0';
|
|
835
|
+
setTimeout(function() { bannerHost.remove(); }, 300);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}, 8000);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
showOnboarding();
|
|
842
|
+
console.log('[ui-context-kit] Ready. Press ' + SHORTCUT + ' to activate.');
|
|
843
|
+
})();
|
|
844
|
+
`;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/next-wrapper.ts
|
|
848
|
+
function withUIContext(nextConfig = {}, _options) {
|
|
849
|
+
if (process.env.NODE_ENV !== "development") return nextConfig;
|
|
850
|
+
return {
|
|
851
|
+
...nextConfig,
|
|
852
|
+
webpack(config, context) {
|
|
853
|
+
if (typeof nextConfig.webpack === "function") {
|
|
854
|
+
config = nextConfig.webpack(config, context);
|
|
855
|
+
}
|
|
856
|
+
return config;
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/index.ts
|
|
862
|
+
function uiContextKit(options) {
|
|
863
|
+
return createVitePlugin(options);
|
|
864
|
+
}
|
|
865
|
+
export {
|
|
866
|
+
uiContextKit,
|
|
867
|
+
withUIContext,
|
|
868
|
+
writeContext
|
|
869
|
+
};
|
|
870
|
+
//# sourceMappingURL=index.js.map
|