fix: selection preview not appearing when dragging with pen
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 41s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 13s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 41s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 13s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
This commit is contained in:
parent
b33af7d7f2
commit
79b38fa152
1 changed files with 669 additions and 682 deletions
|
|
@ -331,6 +331,11 @@ type DragState = {
|
||||||
/** focus caret ล่าสุดที่ resolve สำเร็จ — ระหว่างลากผ่านพื้นที่ว่าง (ชั้นถูก
|
/** focus caret ล่าสุดที่ resolve สำเร็จ — ระหว่างลากผ่านพื้นที่ว่าง (ชั้นถูก
|
||||||
* ของ resolveTextCaret หาไม่เจอ) ให้คงตำแหน่งเดิมไว้แทน re-scan ทุก frame */
|
* ของ resolveTextCaret หาไม่เจอ) ให้คงตำแหน่งเดิมไว้แทน re-scan ทุก frame */
|
||||||
lastFocus: Range | null;
|
lastFocus: Range | null;
|
||||||
|
/** จำนวน frame ต่อเนื่องที่ cheap resolver fail — ใช้ throttle การ escalate
|
||||||
|
* ไป deep resolver (แพงกว่า ไม่ควรเรียกทุก frame) */
|
||||||
|
failStreak: number;
|
||||||
|
/** cache รายชื่อ span ของหน้า ต่อ 1 gesture — ใช้กับ deep resolver ตอน drag */
|
||||||
|
spanCache: SpanCache;
|
||||||
anchorSnapped: boolean;
|
anchorSnapped: boolean;
|
||||||
/** เลย threshold 8px แล้วหรือยัง (false = tap ที่ยังไม่ขยับ) */
|
/** เลย threshold 8px แล้วหรือยัง (false = tap ที่ยังไม่ขยับ) */
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
|
@ -402,9 +407,21 @@ export function useCustomSelection({
|
||||||
// ช่องว่างระหว่างหน้า) หาไม่เจอก็คง focus เดิมจาก frame ก่อนไว้แทน re-scan
|
// ช่องว่างระหว่างหน้า) หาไม่เจอก็คง focus เดิมจาก frame ก่อนไว้แทน re-scan
|
||||||
// ด้วยชั้นแพง (perf note ของ bug report — selection หยุดรอที่ข้อความสุดท้าย
|
// ด้วยชั้นแพง (perf note ของ bug report — selection หยุดรอที่ข้อความสุดท้าย
|
||||||
// แล้วขยายต่อเมื่อนิ้วถึงข้อความอีกครั้ง)
|
// แล้วขยายต่อเมื่อนิ้วถึงข้อความอีกครั้ง)
|
||||||
const focus = resolveTextCaret(d.curPt.x, y) ?? d.lastFocus;
|
let focus = resolveTextCaret(d.curPt.x, y);
|
||||||
|
if (!focus) {
|
||||||
|
d.failStreak++;
|
||||||
|
// fail ต่อเนื่องเกิน 3 frame = ลองชั้นแพงกว่าก่อนยอม fallback ไปตำแหน่งเดิม
|
||||||
|
if (d.failStreak > 3 && d.failStreak % 5 === 0) {
|
||||||
|
focus = resolveTextCaretDeep(d.curPt.x, y, { spanCache: d.spanCache });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (focus) {
|
||||||
|
d.failStreak = 0;
|
||||||
|
} else {
|
||||||
|
focus = d.lastFocus;
|
||||||
|
}
|
||||||
|
|
||||||
if (!focus) {
|
if (!focus) {
|
||||||
console.warn("bail: focus caret null"); // TEMP-DEBUG
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
d.lastFocus = focus.cloneRange();
|
d.lastFocus = focus.cloneRange();
|
||||||
|
|
@ -421,7 +438,6 @@ export function useCustomSelection({
|
||||||
// หน้าเดียว ไม่ใช่ข้ามหน้า ห้ามนำไป clamp กลายเป็น selection ตั้งแต่ต้นหน้า)
|
// หน้าเดียว ไม่ใช่ข้ามหน้า ห้ามนำไป clamp กลายเป็น selection ตั้งแต่ต้นหน้า)
|
||||||
if (range && !range.collapsed && pageEl !== d.anchorPageEl) {
|
if (range && !range.collapsed && pageEl !== d.anchorPageEl) {
|
||||||
const clamped = clampToAnchorPage(d.anchorPageEl, d.anchor, focus, d);
|
const clamped = clampToAnchorPage(d.anchorPageEl, d.anchor, focus, d);
|
||||||
console.warn("clamp: cross-page"); // TEMP-DEBUG
|
|
||||||
range = clamped && !clamped.collapsed ? clamped : null;
|
range = clamped && !clamped.collapsed ? clamped : null;
|
||||||
pageEl = range ? d.anchorPageEl : null;
|
pageEl = range ? d.anchorPageEl : null;
|
||||||
}
|
}
|
||||||
|
|
@ -440,7 +456,6 @@ export function useCustomSelection({
|
||||||
d.pointerType === "touch" ? { x: d.curPt.x, y } : d.curPt;
|
d.pointerType === "touch" ? { x: d.curPt.x, y } : d.curPt;
|
||||||
const kept = rectsInSweptPath(rects, d.anchorPathPt, pathEnd);
|
const kept = rectsInSweptPath(rects, d.anchorPathPt, pathEnd);
|
||||||
if (kept.length === 0) {
|
if (kept.length === 0) {
|
||||||
console.warn("bail: swept path empty"); // TEMP-DEBUG
|
|
||||||
setPreviewRects(null);
|
setPreviewRects(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -524,24 +539,12 @@ export function useCustomSelection({
|
||||||
scrollRafRef.current = requestAnimationFrame(step);
|
scrollRafRef.current = requestAnimationFrame(step);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** TEMP-DEBUG: เก็บ pointerId ที่เคย log move แรกแล้ว — ทำลายทุก pointerdown
|
|
||||||
* ใหม่ (กัน hover ของ pencil กินสล็อต "move แรก" ก่อนปากกาแตะจอ) */
|
|
||||||
const seenMovePointerIds = new Set<number>();
|
const seenMovePointerIds = new Set<number>();
|
||||||
|
|
||||||
const onPointerDown = (e: PointerEvent) => {
|
const onPointerDown = (e: PointerEvent) => {
|
||||||
console.warn("[debug] pointerdown", {
|
|
||||||
pointerType: e.pointerType,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
}); // TEMP-DEBUG
|
|
||||||
seenMovePointerIds.clear(); // TEMP-DEBUG
|
|
||||||
if (dragRef.current) {
|
if (dragRef.current) {
|
||||||
// นิ้วที่สองระหว่างลาก — ไม่สน รวมถึง touch ปลอมจาก palm rejection (มือ
|
// นิ้วที่สองระหว่างลาก — ไม่สน รวมถึง touch ปลอมจาก palm rejection (มือ
|
||||||
// ที่พักบนจอระหว่างลากด้วย pen) — ห้ามให้ pointer ตัวใหม่แทนที่ drag เดิม
|
// ที่พักบนจอระหว่างลากด้วย pen) — ห้ามให้ pointer ตัวใหม่แทนที่ drag เดิม
|
||||||
console.warn("[debug] ignored second pointer", {
|
|
||||||
activeType: dragRef.current.pointerType,
|
|
||||||
newType: e.pointerType,
|
|
||||||
}); // TEMP-DEBUG
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// เริ่ม selection ได้เฉพาะที่เริ่มบน textLayer (แตะ pin/ปุ่ม/กล่องข้อความ = งานของมัน)
|
// เริ่ม selection ได้เฉพาะที่เริ่มบน textLayer (แตะ pin/ปุ่ม/กล่องข้อความ = งานของมัน)
|
||||||
|
|
@ -549,10 +552,6 @@ export function useCustomSelection({
|
||||||
!(e.target instanceof Element) ||
|
!(e.target instanceof Element) ||
|
||||||
!e.target.closest(".textLayer")
|
!e.target.closest(".textLayer")
|
||||||
) {
|
) {
|
||||||
console.warn(
|
|
||||||
"bail: down ไม่ได้เริ่มบน textLayer",
|
|
||||||
(e.target instanceof Element ? e.target.className.toString() : "").slice(0, 40),
|
|
||||||
); // TEMP-DEBUG
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const y = e.clientY - (e.pointerType === "touch" ? FINGER_OFFSET_PX : 0);
|
const y = e.clientY - (e.pointerType === "touch" ? FINGER_OFFSET_PX : 0);
|
||||||
|
|
@ -575,7 +574,6 @@ export function useCustomSelection({
|
||||||
if (!caret) {
|
if (!caret) {
|
||||||
// จุดกดไม่เกี่ยวกับข้อความเลย (แม้ค้น text node รอบ ๆ แล้ว) — เป็น tap
|
// จุดกดไม่เกี่ยวกับข้อความเลย (แม้ค้น text node รอบ ๆ แล้ว) — เป็น tap
|
||||||
// ธรรมดา ไม่เริ่ม selection (click ของ annotation/pin ทำงานตามเดิม)
|
// ธรรมดา ไม่เริ่ม selection (click ของ annotation/pin ทำงานตามเดิม)
|
||||||
console.warn("bail: resolveTextCaret failed after retries", { x: e.clientX, y }); // TEMP-DEBUG
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const anchorPageEl = pageOfRange(caret);
|
const anchorPageEl = pageOfRange(caret);
|
||||||
|
|
@ -592,23 +590,19 @@ export function useCustomSelection({
|
||||||
anchor: caret,
|
anchor: caret,
|
||||||
anchorPageEl,
|
anchorPageEl,
|
||||||
clampBounds: null,
|
clampBounds: null,
|
||||||
lastFocus: null,
|
lastFocus: caret.cloneRange(),
|
||||||
|
failStreak: 0,
|
||||||
|
spanCache: cache.spanCache,
|
||||||
anchorSnapped: false,
|
anchorSnapped: false,
|
||||||
active: false,
|
active: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPointerMove = (e: PointerEvent) => {
|
const onPointerMove = (e: PointerEvent) => {
|
||||||
// TEMP-DEBUG: log เฉพาะ move แรกของ pointer หลัง pointerdown ล่าสุด —
|
|
||||||
// pen/touch ยิง 60–120Hz ถ้า log ทุก event จะท่วม console และหน่วงเครื่อง
|
// pen/touch ยิง 60–120Hz ถ้า log ทุก event จะท่วม console และหน่วงเครื่อง
|
||||||
// จนเปลี่ยนพฤติกรรมที่กำลังวินิจฉัยอยู่เอง
|
// จนเปลี่ยนพฤติกรรมที่กำลังวินิจฉัยอยู่เอง
|
||||||
if (!seenMovePointerIds.has(e.pointerId)) {
|
if (!seenMovePointerIds.has(e.pointerId)) {
|
||||||
seenMovePointerIds.add(e.pointerId);
|
seenMovePointerIds.add(e.pointerId);
|
||||||
console.warn("[debug] pointermove (แรกของ pointer นี้หลัง down)", {
|
|
||||||
pointerType: e.pointerType,
|
|
||||||
pointerId: e.pointerId,
|
|
||||||
hasActiveDrag: !!dragRef.current,
|
|
||||||
}); // TEMP-DEBUG
|
|
||||||
}
|
}
|
||||||
const d = dragRef.current;
|
const d = dragRef.current;
|
||||||
if (!d || e.pointerId !== d.pointerId) return;
|
if (!d || e.pointerId !== d.pointerId) return;
|
||||||
|
|
@ -621,7 +615,6 @@ export function useCustomSelection({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
d.active = true;
|
d.active = true;
|
||||||
console.warn("debug: select เริ่ม (activated)"); // TEMP-DEBUG
|
|
||||||
// capture ตอน"เริ่ม select จริง"ไม่ใช่ pointerdown — capture ตั้งแต่กดจะ
|
// capture ตอน"เริ่ม select จริง"ไม่ใช่ pointerdown — capture ตั้งแต่กดจะ
|
||||||
// retarget click ไปที่ container จน tap เลือก annotation / ปุ่ม pin ใช้ไม่ได้
|
// retarget click ไปที่ container จน tap เลือก annotation / ปุ่ม pin ใช้ไม่ได้
|
||||||
// (tap ที่ไม่ขยับเลย threshold = ไม่ capture → click ไปถึงเป้าหมายตามเดิม)
|
// (tap ที่ไม่ขยับเลย threshold = ไม่ capture → click ไปถึงเป้าหมายตามเดิม)
|
||||||
|
|
@ -634,12 +627,6 @@ export function useCustomSelection({
|
||||||
/** เคลียร์ state ทุกทาง — commit เฉพาะตอน pointerup ที่มี range ไม่ collapsed
|
/** เคลียร์ state ทุกทาง — commit เฉพาะตอน pointerup ที่มี range ไม่ collapsed
|
||||||
* (pointercancel จาก gesture ของระบบ = ทิ้ง selection ไม่ commit) */
|
* (pointercancel จาก gesture ของระบบ = ทิ้ง selection ไม่ commit) */
|
||||||
const finish = (e: PointerEvent, commit: boolean) => {
|
const finish = (e: PointerEvent, commit: boolean) => {
|
||||||
// TEMP-DEBUG: แยก pointerup กับ pointercancel — cancel = ระบบยึด gesture ไป
|
|
||||||
// (คู่สมมติฐานหลักของ "ลากไม่ติด" บน pencil นอกเหนือจาก move ไม่มาถึง)
|
|
||||||
console.warn(`[debug] ${commit ? "pointerup" : "pointercancel"}`, {
|
|
||||||
pointerType: e.pointerType,
|
|
||||||
pointerId: e.pointerId,
|
|
||||||
}); // TEMP-DEBUG
|
|
||||||
const d = dragRef.current;
|
const d = dragRef.current;
|
||||||
if (!d || e.pointerId !== d.pointerId) return;
|
if (!d || e.pointerId !== d.pointerId) return;
|
||||||
stopAutoScroll();
|
stopAutoScroll();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue