diff --git a/frontend/src/routes/study/write/[id]/+page.svelte b/frontend/src/routes/study/write/[id]/+page.svelte index 9159b34..f2ee1ae 100644 --- a/frontend/src/routes/study/write/[id]/+page.svelte +++ b/frontend/src/routes/study/write/[id]/+page.svelte @@ -65,12 +65,40 @@ }; } + // iPadOS Apple Pencil Scribble / Apple Intelligence 가 펜 stroke 를 텍스트 선택 + // 제스처로 해석해 callout 메뉴 ("복사하기 / 선택 영역 찾기 / 찾아보기 / 번역") 를 + // 띄우면 펜 입력이 메뉴 인터랙션으로 흡수되어 캔버스 stroke 가 막힘 (ㄱ→ㅏ hang + // 의 진짜 원인). element CSS user-select:none 만으로는 OS 레벨 동작을 못 막음. + // → document 레벨 selectstart capture 차단 + selectionchange clear 로 selection + // 이 어떤 경로로든 잡히면 즉시 해제. + function clearSelection() { + const sel = document.getSelection?.(); + if (sel && sel.rangeCount > 0) { try { sel.removeAllRanges(); } catch {} } + } + function blockSelectStart(e) { + e.preventDefault(); + clearSelection(); + } + onMount(() => { load(); document.addEventListener('gesturestart', blockGesture, { passive: false }); document.addEventListener('gesturechange', blockGesture, { passive: false }); document.addEventListener('gestureend', blockGesture, { passive: false }); document.addEventListener('wheel', blockPinchWheel, { passive: false }); + // capture phase 로 모든 자식 element 의 selectstart 차단. + document.addEventListener('selectstart', blockSelectStart, { capture: true, passive: false }); + document.addEventListener('selectionchange', clearSelection); + // html/body 까지 user-select:none 강제 — Svelte 컴포넌트 스코프가 닿지 않는 + // root element 가 selection origin 이 되는 케이스 차단. onDestroy 에서 복원. + document.documentElement.style.webkitUserSelect = 'none'; + document.documentElement.style.userSelect = 'none'; + document.body.style.webkitUserSelect = 'none'; + document.body.style.userSelect = 'none'; + // @ts-ignore - vendor prop + document.documentElement.style.webkitTouchCallout = 'none'; + // @ts-ignore + document.body.style.webkitTouchCallout = 'none'; }); onDestroy(() => { if (typeof document !== 'undefined') { @@ -78,6 +106,17 @@ document.removeEventListener('gesturechange', blockGesture); document.removeEventListener('gestureend', blockGesture); document.removeEventListener('wheel', blockPinchWheel); + document.removeEventListener('selectstart', blockSelectStart, { capture: true }); + document.removeEventListener('selectionchange', clearSelection); + // 다른 페이지에서 selection 가능하도록 원복. + document.documentElement.style.webkitUserSelect = ''; + document.documentElement.style.userSelect = ''; + document.body.style.webkitUserSelect = ''; + document.body.style.userSelect = ''; + // @ts-ignore + document.documentElement.style.webkitTouchCallout = ''; + // @ts-ignore + document.body.style.webkitTouchCallout = ''; } });