opencode-generative-ui 0.1.3 → 0.1.4
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.js +73 -3
- package/dist/lib/guidelines.js +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -178,7 +178,7 @@ const WIDGET_SHELL_SCRIPT = String.raw `(() => {
|
|
|
178
178
|
const zoomOutButton = document.getElementById('oc-zoom-out');
|
|
179
179
|
const fitButton = document.getElementById('oc-fit');
|
|
180
180
|
const resetButton = document.getElementById('oc-reset');
|
|
181
|
-
const
|
|
181
|
+
const declaredSVG = shell?.dataset.kind === 'svg';
|
|
182
182
|
|
|
183
183
|
if (!shell || !viewport || !stage || !content || !zoomValue) return;
|
|
184
184
|
|
|
@@ -197,6 +197,9 @@ const WIDGET_SHELL_SCRIPT = String.raw `(() => {
|
|
|
197
197
|
let panStartTy = 0;
|
|
198
198
|
let gestureScaleStart = null;
|
|
199
199
|
let gestureAnchor = null;
|
|
200
|
+
let svgEl = null;
|
|
201
|
+
let svgBaseWidth = 0;
|
|
202
|
+
let svgBaseHeight = 0;
|
|
200
203
|
|
|
201
204
|
const isEditableTarget = (target) => {
|
|
202
205
|
if (!(target instanceof Element)) return false;
|
|
@@ -205,7 +208,57 @@ const WIDGET_SHELL_SCRIPT = String.raw `(() => {
|
|
|
205
208
|
|
|
206
209
|
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
207
210
|
|
|
211
|
+
const syncSVG = () => {
|
|
212
|
+
const candidate = content.querySelector('svg');
|
|
213
|
+
if (!(candidate instanceof SVGSVGElement)) {
|
|
214
|
+
svgEl = null;
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (svgEl !== candidate) {
|
|
219
|
+
svgEl = candidate;
|
|
220
|
+
const viewBox = svgEl.viewBox?.baseVal;
|
|
221
|
+
let width = viewBox && viewBox.width > 0 ? viewBox.width : Number.parseFloat(svgEl.getAttribute('width') || '');
|
|
222
|
+
let height = viewBox && viewBox.height > 0 ? viewBox.height : Number.parseFloat(svgEl.getAttribute('height') || '');
|
|
223
|
+
|
|
224
|
+
if (!(width > 0) || !(height > 0)) {
|
|
225
|
+
const rect = svgEl.getBoundingClientRect();
|
|
226
|
+
width = rect.width;
|
|
227
|
+
height = rect.height;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!(width > 0) || !(height > 0)) {
|
|
231
|
+
width = 680;
|
|
232
|
+
height = 420;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
svgBaseWidth = width;
|
|
236
|
+
svgBaseHeight = height;
|
|
237
|
+
svgEl.style.display = 'block';
|
|
238
|
+
svgEl.style.maxWidth = 'none';
|
|
239
|
+
svgEl.style.maxHeight = 'none';
|
|
240
|
+
if (!svgEl.getAttribute('preserveAspectRatio')) {
|
|
241
|
+
svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return true;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const usesNativeSVGZoom = () => {
|
|
249
|
+
if (!syncSVG()) return false;
|
|
250
|
+
if (declaredSVG) return true;
|
|
251
|
+
return !content.querySelector('input, button, select, textarea, [contenteditable="true"], [contenteditable=""]');
|
|
252
|
+
};
|
|
253
|
+
|
|
208
254
|
const measureContent = () => {
|
|
255
|
+
if (usesNativeSVGZoom()) {
|
|
256
|
+
return {
|
|
257
|
+
width: Math.max(svgBaseWidth, 1),
|
|
258
|
+
height: Math.max(svgBaseHeight, 1),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
209
262
|
const width = Math.max(content.scrollWidth, content.offsetWidth, content.clientWidth);
|
|
210
263
|
const height = Math.max(content.scrollHeight, content.offsetHeight, content.clientHeight);
|
|
211
264
|
return {
|
|
@@ -219,7 +272,17 @@ const WIDGET_SHELL_SCRIPT = String.raw `(() => {
|
|
|
219
272
|
};
|
|
220
273
|
|
|
221
274
|
const render = () => {
|
|
222
|
-
|
|
275
|
+
if (usesNativeSVGZoom() && svgEl) {
|
|
276
|
+
stage.style.transform = 'translate(' + tx + 'px, ' + ty + 'px)';
|
|
277
|
+
svgEl.style.width = svgBaseWidth * scale + 'px';
|
|
278
|
+
svgEl.style.height = svgBaseHeight * scale + 'px';
|
|
279
|
+
} else {
|
|
280
|
+
if (svgEl) {
|
|
281
|
+
svgEl.style.width = '';
|
|
282
|
+
svgEl.style.height = '';
|
|
283
|
+
}
|
|
284
|
+
stage.style.transform = 'translate(' + tx + 'px, ' + ty + 'px) scale(' + scale + ')';
|
|
285
|
+
}
|
|
223
286
|
updateZoomLabel();
|
|
224
287
|
};
|
|
225
288
|
|
|
@@ -457,7 +520,7 @@ const WIDGET_SHELL_SCRIPT = String.raw `(() => {
|
|
|
457
520
|
const refreshLayout = () => {
|
|
458
521
|
if (!hasInitialView) {
|
|
459
522
|
hasInitialView = true;
|
|
460
|
-
if (
|
|
523
|
+
if (usesNativeSVGZoom()) {
|
|
461
524
|
fitView();
|
|
462
525
|
return;
|
|
463
526
|
}
|
|
@@ -541,6 +604,12 @@ window._runScripts = function() {
|
|
|
541
604
|
}
|
|
542
605
|
old.parentNode.replaceChild(s, old);
|
|
543
606
|
});
|
|
607
|
+
var refresh = function() {
|
|
608
|
+
if (window._ocRefresh) window._ocRefresh();
|
|
609
|
+
};
|
|
610
|
+
requestAnimationFrame(refresh);
|
|
611
|
+
setTimeout(refresh, 0);
|
|
612
|
+
setTimeout(refresh, 60);
|
|
544
613
|
};
|
|
545
614
|
window._setContent('${escapedCode}');
|
|
546
615
|
window._runScripts();
|
|
@@ -673,6 +742,7 @@ const SYSTEM_GUIDANCE = [
|
|
|
673
742
|
"Always call visualize_read_me once before the first show_widget call in a session, then set i_have_seen_read_me: true.",
|
|
674
743
|
"For HTML widgets, provide a fragment only. Do not include DOCTYPE, html, head, or body tags.",
|
|
675
744
|
"For SVG widgets, start widget_code with <svg>.",
|
|
745
|
+
"If the user may zoom in or inspect labels closely, prefer pure SVG or HTML that renders to a single inline SVG. Avoid canvas unless the interaction genuinely needs it.",
|
|
676
746
|
"Keep widgets focused and appropriately sized. Default size is 800x600 unless the content needs another size.",
|
|
677
747
|
].join("\n");
|
|
678
748
|
let registeredExitHandler = false;
|
package/dist/lib/guidelines.js
CHANGED
|
@@ -29,6 +29,7 @@ These rules apply to ALL use cases.
|
|
|
29
29
|
- **Seamless**: Users shouldn't notice where claude.ai ends and your widget begins.
|
|
30
30
|
- **Flat**: No gradients, mesh backgrounds, noise textures, or decorative effects. Clean flat surfaces.
|
|
31
31
|
- **Compact**: Show the essential inline. Explain the rest in text.
|
|
32
|
+
- **Scalable when inspected**: If the user is likely to zoom in or inspect labels closely (ERDs, architecture diagrams, charts with dense labels), prefer pure SVG or HTML that renders to a single inline SVG. Avoid canvas unless you need true pixel drawing or Chart.js-level interaction.
|
|
32
33
|
- **Text goes in your response, visuals go in the tool** — All explanatory text, descriptions, introductions, and summaries must be written as normal response text OUTSIDE the tool call. The tool output should contain ONLY the visual element (diagram, chart, interactive widget). Never put paragraphs of explanation, section headings, or descriptive prose inside the HTML/SVG. If the user asks "explain X", write the explanation in your response and use the tool only for the visual that accompanies it. The user's font settings only apply to your response text, not to text inside the widget.
|
|
33
34
|
|
|
34
35
|
### Streaming
|
|
@@ -118,6 +119,7 @@ const CHARTS_CHART_JS = `## Charts (Chart.js)
|
|
|
118
119
|
\`\`\`
|
|
119
120
|
|
|
120
121
|
**Chart.js rules**:
|
|
122
|
+
- Prefer SVG for charts the user may inspect closely or zoom heavily. Use Chart.js when live interaction, animation, or built-in chart behaviors matter more than zoom fidelity.
|
|
121
123
|
- Canvas cannot resolve CSS variables. Use hardcoded hex or Chart.js defaults.
|
|
122
124
|
- Wrap \`<canvas>\` in \`<div>\` with explicit \`height\` and \`position: relative\`.
|
|
123
125
|
- **Canvas sizing**: set height ONLY on the wrapper div, never on the canvas element itself. Use position: relative on the wrapper and responsive: true, maintainAspectRatio: false in Chart.js options. Never set CSS height directly on canvas — this causes wrong dimensions, especially for horizontal bar charts.
|
package/package.json
CHANGED