diff --git a/src/components/tools/QrCode.vue b/src/components/tools/QrCode.vue index 42e7a5b..ba5420c 100644 --- a/src/components/tools/QrCode.vue +++ b/src/components/tools/QrCode.vue @@ -113,7 +113,13 @@ const onDrag = (e) => { posRef.value[`y${point}`] = Math.round(y) } +let justDragged = false + const stopDrag = () => { + if (activeHandle.value) { + justDragged = true + setTimeout(() => { justDragged = false }, 50) + } activeHandle.value = null window.removeEventListener('mousemove', onDrag) window.removeEventListener('mouseup', stopDrag) @@ -121,6 +127,14 @@ const stopDrag = () => { window.removeEventListener('touchend', stopDrag) } +const handleFrameClick = (event) => { + if (justDragged) return + + if (!activeHandle.value) { + showHandles.value = !showHandles.value + } +} + const { height: previewHeight } = useFillHeight(previewRef, 40) // 40px extra margin let worker = null @@ -331,39 +345,36 @@ const triggerDownload = (blob, filename) => {
-
+
- -
@@ -504,26 +515,7 @@ const triggerDownload = (blob, filename) => { position: relative; } -.edit-toggle-btn { - position: absolute; - top: 0; - right: 0; - z-index: 20; - color: var(--text-secondary); - opacity: 0.6; - background: var(--panel-bg); - box-shadow: 0 2px 8px rgba(0,0,0,0.1); -} -.edit-toggle-btn:hover { - opacity: 1; - color: var(--text-strong); -} - -.edit-toggle-btn.active { - color: var(--primary-accent); - opacity: 0.9; -} :root[data-theme="light"] .preview-section { background: rgba(255, 255, 255, 0.3); @@ -619,5 +611,23 @@ const triggerDownload = (blob, filename) => { min-width: 100px !important; } +@media (max-width: 600px) { + .preview-section { + padding: 1rem 0.5rem; + gap: 1rem; + border-radius: 8px; + } + + .qr-container { + width: 100%; + aspect-ratio: 1; + min-height: unset; + } + + .qr-frame { + padding: 0.5rem; + } +} + diff --git a/src/components/tools/QrScanner.vue b/src/components/tools/QrScanner.vue index 8f171c8..19946cd 100644 --- a/src/components/tools/QrScanner.vue +++ b/src/components/tools/QrScanner.vue @@ -492,9 +492,11 @@ const isUrl = (string) => { } .switch-camera-btn { - position: absolute; - top: 1rem; - right: 1rem; + position: absolute !important; + top: auto !important; + left: auto !important; + bottom: 0.75rem !important; + right: 0.75rem !important; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.2); color: #fff; diff --git a/src/directives/tooltip.js b/src/directives/tooltip.js index 1afbc03..82dae46 100644 --- a/src/directives/tooltip.js +++ b/src/directives/tooltip.js @@ -3,11 +3,60 @@ import { showTooltip, hideTooltip, tooltipState } from '../composables/useToolti export const tooltipDirective = { mounted(el, binding) { el._tooltipText = binding.value; + let touchTimeout = null; + let isTouch = false; - el.addEventListener('mouseenter', () => showTooltip(el, el._tooltipText)); - el.addEventListener('mouseleave', hideTooltip); - el.addEventListener('focus', () => showTooltip(el, el._tooltipText)); - el.addEventListener('blur', hideTooltip); + el._handleMouseEnter = () => { + if (!isTouch) showTooltip(el, el._tooltipText); + }; + el._handleMouseLeave = () => { + if (!isTouch) hideTooltip(); + }; + el._handleFocus = () => { + if (!isTouch) showTooltip(el, el._tooltipText); + }; + el._handleBlur = () => { + if (!isTouch) hideTooltip(); + }; + + el._handleTouchStart = () => { + isTouch = true; + if (touchTimeout) clearTimeout(touchTimeout); + touchTimeout = setTimeout(() => { + showTooltip(el, el._tooltipText); + }, 400); // 400ms long press threshold + }; + + el._handleTouchEnd = () => { + if (touchTimeout) clearTimeout(touchTimeout); + hideTooltip(); + // Block ensuing simulated mouseenter events + setTimeout(() => { isTouch = false; }, 500); + }; + + el._handleTouchCancel = () => { + if (touchTimeout) clearTimeout(touchTimeout); + hideTooltip(); + setTimeout(() => { isTouch = false; }, 500); + }; + + el._handleContextMenu = (e) => { + // Prevent the OS context menu if we're showing a tooltip via long press + if (isTouch && tooltipState.isVisible && tooltipState.text === el._tooltipText) { + e.preventDefault(); + } + }; + + el.addEventListener('mouseenter', el._handleMouseEnter); + el.addEventListener('mouseleave', el._handleMouseLeave); + el.addEventListener('focus', el._handleFocus); + el.addEventListener('blur', el._handleBlur); + + el.addEventListener('touchstart', el._handleTouchStart, { passive: true }); + el.addEventListener('touchend', el._handleTouchEnd); + el.addEventListener('touchmove', el._handleTouchCancel, { passive: true }); + el.addEventListener('touchcancel', el._handleTouchCancel); + el.addEventListener('contextmenu', el._handleContextMenu); }, updated(el, binding) { el._tooltipText = binding.value; @@ -19,10 +68,17 @@ export const tooltipDirective = { } }, unmounted(el) { - el.removeEventListener('mouseenter', () => showTooltip(el, el._tooltipText)); - el.removeEventListener('mouseleave', hideTooltip); - el.removeEventListener('focus', () => showTooltip(el, el._tooltipText)); - el.removeEventListener('blur', hideTooltip); + if (el._handleMouseEnter) { + el.removeEventListener('mouseenter', el._handleMouseEnter); + el.removeEventListener('mouseleave', el._handleMouseLeave); + el.removeEventListener('focus', el._handleFocus); + el.removeEventListener('blur', el._handleBlur); + el.removeEventListener('touchstart', el._handleTouchStart); + el.removeEventListener('touchend', el._handleTouchEnd); + el.removeEventListener('touchmove', el._handleTouchCancel); + el.removeEventListener('touchcancel', el._handleTouchCancel); + el.removeEventListener('contextmenu', el._handleContextMenu); + } hideTooltip(); } };