diff --git a/lib/folk-shape.ts b/lib/folk-shape.ts index a54c3b2..f930060 100644 --- a/lib/folk-shape.ts +++ b/lib/folk-shape.ts @@ -310,9 +310,13 @@ export class FolkShape extends FolkElement { handleEvent(event: PointerEvent | KeyboardEvent | TouchEvent) { // Handle touch events for mobile drag support if (event instanceof TouchEvent) { - event.preventDefault(); - const target = event.composedPath()[0] as HTMLElement; + // Allow interactive elements to receive focus on mobile + const tag = target?.tagName?.toLowerCase(); + const isInteractive = tag === "input" || tag === "textarea" || tag === "select" || tag === "button" || target?.isContentEditable; + if (!isInteractive) { + event.preventDefault(); + } const isDragHandle = target?.closest?.(".header, [data-drag]") !== null; const isValidDragTarget = target === this || isDragHandle; diff --git a/website/canvas.html b/website/canvas.html index 5ee7af0..66cd329 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -245,7 +245,6 @@ background-size: 20px 20px; background-position: -1px -1px; position: relative; - overflow: hidden; touch-action: none; /* Prevent browser gestures, handle manually */ } @@ -253,7 +252,10 @@ position: absolute; top: 0; left: 0; + width: 100%; + height: 100%; transform-origin: 0 0; + overflow: visible; } /* Touch-friendly resize handles */ @@ -334,29 +336,101 @@ outline-offset: 4px !important; } - /* Mobile toolbar: icon-only scrollable strip */ + /* Mobile menu toggle (hidden on desktop) */ + #mobile-menu { + display: none; + } + + #mobile-zoom { + display: none; + } + @media (max-width: 768px) { - #toolbar { - max-width: calc(100vw - 32px); - overflow-x: auto; - scrollbar-width: none; - gap: 4px; - padding: 6px 8px; - touch-action: pan-x; + /* FAB toggle button */ + #mobile-menu { + display: flex; + position: fixed; + bottom: 24px; + right: 16px; + width: 56px; + height: 56px; + border: none; + border-radius: 50%; + background: #14b8a6; + color: white; + font-size: 28px; + align-items: center; + justify-content: center; + z-index: 1002; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); + cursor: pointer; + touch-action: manipulation; } - #toolbar::-webkit-scrollbar { + + /* Always-visible zoom strip */ + #mobile-zoom { + display: flex; + position: fixed; + bottom: 24px; + left: 16px; + gap: 4px; + z-index: 1002; + } + + #mobile-zoom button { + width: 40px; + height: 40px; + border: none; + border-radius: 50%; + background: white; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + font-size: 16px; + cursor: pointer; + touch-action: manipulation; + } + + /* Hide desktop toolbar, show as grid overlay when toggled */ + #toolbar { + display: none; + position: fixed; + top: 72px; + left: 8px; + right: 8px; + transform: none; + flex-wrap: wrap; + max-height: calc(100vh - 160px); + overflow-y: auto; + gap: 6px; + padding: 12px; + border-radius: 16px; + z-index: 1001; + } + + #toolbar.mobile-open { + display: flex; + } + + #toolbar button { + flex: 0 0 calc(33.33% - 4px); + padding: 10px 4px; + font-size: 12px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + /* Hide zoom/reset from toolbar grid (they're in #mobile-zoom) */ + #toolbar #zoom-in, + #toolbar #zoom-out, + #toolbar #reset-view { display: none; } - #toolbar button { - max-width: 36px; - min-width: 36px; - padding: 8px; - overflow: hidden; - white-space: nowrap; - } + #community-info { display: none; } + #memory-panel { max-width: calc(100vw - 32px); } @@ -369,6 +443,13 @@

+ +
+ + + +
+
@@ -1263,6 +1344,44 @@ updateCanvasTransform(); }); + // Mobile toolbar toggle + const mobileMenuBtn = document.getElementById("mobile-menu"); + const toolbarEl = document.getElementById("toolbar"); + + mobileMenuBtn.addEventListener("click", () => { + const isOpen = toolbarEl.classList.toggle("mobile-open"); + mobileMenuBtn.textContent = isOpen ? "✕" : "✚"; + }); + + // Auto-close toolbar after tapping a shape-creation button on mobile + toolbarEl.addEventListener("click", (e) => { + if (window.innerWidth > 768) return; + const btn = e.target.closest("button"); + if (!btn) return; + // Keep open for connect, memory, zoom controls + const keepOpen = ["new-arrow", "toggle-memory", "zoom-in", "zoom-out", "reset-view"]; + if (!keepOpen.includes(btn.id)) { + toolbarEl.classList.remove("mobile-open"); + mobileMenuBtn.textContent = "✚"; + } + }); + + // Mobile zoom controls (separate from toolbar) + document.getElementById("mz-in").addEventListener("click", () => { + scale = Math.min(scale * 1.25, maxScale); + updateCanvasTransform(); + }); + document.getElementById("mz-out").addEventListener("click", () => { + scale = Math.max(scale / 1.25, minScale); + updateCanvasTransform(); + }); + document.getElementById("mz-reset").addEventListener("click", () => { + scale = 1; + panX = 0; + panY = 0; + updateCanvasTransform(); + }); + // Touch gesture handling for pinch-to-zoom and two-finger pan let initialDistance = 0; let initialScale = 1;