truecanvas 0.6.3 → 0.6.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.
|
@@ -28,6 +28,16 @@ html, body { margin: 0; height: 100%; overflow: hidden; background: var(--bg); c
|
|
|
28
28
|
#stage.panning { cursor: grabbing; }
|
|
29
29
|
#world { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }
|
|
30
30
|
.frame { position: absolute; }
|
|
31
|
+
/* real-site frames: one click to interact with them, like an embedded prototype */
|
|
32
|
+
.frame.live-frame .frame-box { cursor: pointer; }
|
|
33
|
+
.frame.live-frame:not(.interacting) .frame-box::after { content: "Click to interact"; position: absolute; left: 50%; top: 16px; transform: translateX(-50%); padding: 6px 12px; border-radius: 999px; background: rgba(28,27,25,.86); color: #fff; font-size: 12px; font-weight: 550; opacity: 0; transition: opacity .15s var(--ease-out); pointer-events: none; }
|
|
34
|
+
.frame.live-frame:not(.interacting) .frame-box:hover::after { opacity: 1; }
|
|
35
|
+
#stage.commenting .frame.live-frame .frame-box::after, #stage.panning .frame.live-frame .frame-box::after { opacity: 0 !important; }
|
|
36
|
+
.frame.interacting .frame-box { outline: 2px solid var(--accent); outline-offset: 2px; cursor: auto; }
|
|
37
|
+
.frame.interacting .frame-box iframe { pointer-events: auto; }
|
|
38
|
+
.interact-tag { display: none; margin-left: 8px; align-items: center; gap: 6px; color: var(--accent); font-weight: 600; }
|
|
39
|
+
.frame.interacting .interact-tag { display: inline-flex; }
|
|
40
|
+
.interact-tag button { height: 20px; padding: 0 8px; border-radius: 6px; border: 0; background: var(--accent); color: #fff; font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; }
|
|
31
41
|
.frame-label { position: absolute; left: 0; bottom: 100%; padding-bottom: 6px; white-space: nowrap; color: var(--muted);
|
|
32
42
|
font-size: 12px; transform-origin: 0 100%; }
|
|
33
43
|
.frame-label b { color: var(--text); font-weight: 550; margin-right: 6px; }
|
|
@@ -309,6 +319,7 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
309
319
|
* image stays underneath until the iframe has loaded.
|
|
310
320
|
*/
|
|
311
321
|
function restFrame(f, iframe, img) {
|
|
322
|
+
iframe.closest(".frame")?.classList.toggle("live-frame", showsLive(f));
|
|
312
323
|
if (showsLive(f)) {
|
|
313
324
|
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
|
|
314
325
|
iframe.setAttribute("referrerpolicy", "no-referrer");
|
|
@@ -329,11 +340,12 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
329
340
|
const el = document.createElement("div");
|
|
330
341
|
el.className = "frame";
|
|
331
342
|
el.style.cssText = "left:" + (f.x - minX) + "px;top:" + (f.y - minY) + "px;width:" + f.width + "px;height:" + f.height + "px";
|
|
332
|
-
el.innerHTML = '<div class="frame-label"><b>' + esc(f.name) + "</b>" + f.width + " \xD7 " + Math.round(f.height) + (manifest.live ? '<button class="live-btn" title="Open the live site at this width"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor"><path d="M7 4.5v15l13-7.5z"/></svg>View live</button>' : "") + "</div>" +
|
|
343
|
+
el.innerHTML = '<div class="frame-label"><b>' + esc(f.name) + "</b>" + f.width + " \xD7 " + Math.round(f.height) + (manifest.live ? '<button class="live-btn" title="Open the live site at this width"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor"><path d="M7 4.5v15l13-7.5z"/></svg>View live</button>' : "") + '<span class="interact-tag">Interacting<button type="button" class="interact-done">Done</button></span>' + "</div>" +
|
|
333
344
|
'<div class="frame-box" style="width:' + f.width + "px;height:" + f.height + 'px"><img alt="" src="' + esc(f.image) + '"><iframe title="' + esc(f.name) + '" scrolling="no" loading="lazy"></iframe></div>';
|
|
334
345
|
restFrame(f, el.querySelector("iframe"), el.querySelector("img"));
|
|
335
346
|
const liveBtn = el.querySelector(".live-btn");
|
|
336
347
|
if (liveBtn) liveBtn.onclick = (e) => { e.stopPropagation(); openLive(f); };
|
|
348
|
+
el.querySelector(".interact-done").onclick = (e) => { e.stopPropagation(); setInteracting(null); };
|
|
337
349
|
el.dataset.name = f.name;
|
|
338
350
|
world.appendChild(el);
|
|
339
351
|
labels.push(el.querySelector(".frame-label"));
|
|
@@ -392,7 +404,7 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
392
404
|
let moved = false, downAt = null;
|
|
393
405
|
stage.addEventListener("pointerdown", (e) => {
|
|
394
406
|
// pins and View live are buttons: they take their own clicks
|
|
395
|
-
if (e.target.closest && e.target.closest(".pin, .live-btn")) return;
|
|
407
|
+
if (e.target.closest && e.target.closest(".pin, .live-btn, .interact-done")) return;
|
|
396
408
|
stage.setPointerCapture(e.pointerId);
|
|
397
409
|
moved = false;
|
|
398
410
|
downAt = { x: e.clientX, y: e.clientY };
|
|
@@ -422,6 +434,16 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
422
434
|
stage.addEventListener("pointerup", end);
|
|
423
435
|
stage.addEventListener("pointercancel", end);
|
|
424
436
|
|
|
437
|
+
// the frame that has the mouse (its own scripts, clicks and forms), or null
|
|
438
|
+
let interacting = null;
|
|
439
|
+
function setInteracting(frameEl) {
|
|
440
|
+
if (interacting) interacting.classList.remove("interacting");
|
|
441
|
+
interacting = frameEl;
|
|
442
|
+
if (frameEl) frameEl.classList.add("interacting");
|
|
443
|
+
}
|
|
444
|
+
// the live session swaps frames: whatever had the mouse goes back to the canvas
|
|
445
|
+
addEventListener("tc:frames-swapped", () => setInteracting(null));
|
|
446
|
+
|
|
425
447
|
// double click (or double tap) a frame: read it at full width; elsewhere: everything in view
|
|
426
448
|
let lastTap = 0;
|
|
427
449
|
const zoomToFrameAt = (cx, cy) => {
|
|
@@ -440,6 +462,12 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
440
462
|
downAt = null;
|
|
441
463
|
// comment mode: a tap on a frame starts a comment there
|
|
442
464
|
if (commenting) return startComment(e.clientX, e.clientY);
|
|
465
|
+
// a real-site frame: one click hands it the mouse; a click elsewhere takes it back
|
|
466
|
+
// the stage captured the pointer: look under it rather than at e.target
|
|
467
|
+
const under = document.elementFromPoint(e.clientX, e.clientY);
|
|
468
|
+
const hit = under && under.closest ? under.closest(".frame.live-frame") : null;
|
|
469
|
+
if (hit && !hit.classList.contains("interacting")) { setInteracting(hit); lastTap = 0; return; }
|
|
470
|
+
if (!hit && interacting) { setInteracting(null); return; }
|
|
443
471
|
const now = Date.now();
|
|
444
472
|
if (now - lastTap < 320) { zoomToFrameAt(e.clientX, e.clientY); lastTap = 0; }
|
|
445
473
|
else lastTap = now;
|
|
@@ -452,7 +480,7 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
452
480
|
addEventListener("keydown", (e) => {
|
|
453
481
|
if (liveOpen()) { if (e.key === "Escape") closeLive(); return; }
|
|
454
482
|
if (e.target.closest && e.target.closest("input, textarea, select")) return;
|
|
455
|
-
if (e.key === "Escape") { closePop(); setCommenting(false); return; }
|
|
483
|
+
if (e.key === "Escape") { closePop(); setCommenting(false); setInteracting(null); return; }
|
|
456
484
|
if ((e.key === "c" || e.key === "C") && canComment && !e.metaKey && !e.ctrlKey) { setCommenting(!commenting); return; }
|
|
457
485
|
if (e.key === "+" || e.key === "=") zoomAt(z * 1.25, ...center());
|
|
458
486
|
else if (e.key === "-") zoomAt(z / 1.25, ...center());
|
|
@@ -541,6 +569,7 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
541
569
|
function setCommenting(on) {
|
|
542
570
|
if (!canComment) return;
|
|
543
571
|
commenting = on;
|
|
572
|
+
if (on) setInteracting(null);
|
|
544
573
|
stage.classList.toggle("commenting", on);
|
|
545
574
|
const b = document.getElementById("comment");
|
|
546
575
|
b.classList.toggle("on", on);
|
|
@@ -844,14 +873,17 @@ button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
|
844
873
|
next.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
|
|
845
874
|
next.setAttribute("referrerpolicy", "no-referrer");
|
|
846
875
|
next.src = sessionFrame(f);
|
|
876
|
+
frameEl.classList.add("live-frame");
|
|
847
877
|
next.addEventListener("load", () => { next.classList.add("ready"); if (img) img.style.visibility = "hidden"; });
|
|
848
878
|
} else {
|
|
849
879
|
next.loading = "lazy";
|
|
850
880
|
if (img) img.style.visibility = "";
|
|
851
|
-
restFrame(f, next, img);
|
|
852
881
|
}
|
|
853
882
|
old.replaceWith(next);
|
|
883
|
+
// in the page now: restFrame marks the frame as a real-site one or not
|
|
884
|
+
if (!on) restFrame(f, next, img);
|
|
854
885
|
}
|
|
886
|
+
dispatchEvent(new Event("tc:frames-swapped"));
|
|
855
887
|
renderRoom();
|
|
856
888
|
toast(on ? "Live: you're seeing the design as the studio works on it" : "The live session ended: back to the published version");
|
|
857
889
|
}
|
package/dist/cli.js
CHANGED
package/dist/share/index.js
CHANGED
package/package.json
CHANGED