TEXTBOX กล่องกว้างคงที่สไตล์ Smallpdf: คลิกเดียวได้กล่อง 30% + ลากขอบซ้าย/ขวาปรับความกว้าง (ฟอนต์คงขนาด ข้อความ wrap ใหม่)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2026-09-11 17:41:22 +07:00
parent 9e6158ed45
commit ca74fb1843
10 changed files with 248 additions and 41 deletions

View file

@ -16,7 +16,6 @@ import {
AlignLeft,
AlignRight,
ArrowRight,
Bold,
BookmarkPlus,
Brush,
Check,
@ -106,7 +105,13 @@ import {
type Size,
} from "@/utils/coordinates";
import { getLocalUser, type LocalUser } from "@/utils/user";
import { annotationBBox, hitTestPath, hitTestShape } from "@/utils/geometry";
import {
TEXTBOX_DEFAULT_WIDTH_PCT,
annotationBBox,
hitTestPath,
hitTestShape,
textboxMaxWidthPct,
} from "@/utils/geometry";
import InkLayer, { type InkRedaction, type InkShape, type InkStroke } from "./InkLayer";
import TextboxStampLayer, { type InkStamp, type InkTextbox } from "./TextboxStampLayer";
import AnnotationSelectionLayer from "./AnnotationSelectionLayer";
@ -573,6 +578,8 @@ function PdfPage({
bold: boolean;
opacity: number;
align: "left" | "center" | "right";
/** ความกว้างกล่อง default (สไตล์ Smallpdf — คลิกเดียวได้กล่อง ลากขอบขยายทีหลัง) */
widthPct: number;
};
/** live preview มาร์กระหว่างลากคลุมด้วย tool highlight (null = ไม่มี) */
liveMark: { pageIndex: number; rects: Rect[] } | null;
@ -874,6 +881,7 @@ function PdfPage({
bold: textboxStyle.bold,
opacity: textboxStyle.opacity,
align: textboxStyle.align,
widthPct: textboxStyle.widthPct,
}
: null
}
@ -2112,6 +2120,15 @@ export default function PDFViewer({
pageEl: HTMLElement,
) {
if (bookmarkComposer) setBookmarkComposer(null); // คลิกหน้า = ปิด popup bookmark (N2)
// คลิกลงหน้า = เลิกเลือก annotation ก่อนทุก tool — แผงเครื่องมือของกรอบ selection
// ต้องปิดเมื่อคลิกไปสร้างรายการใหม่ (comment/textbox/stamp เดิม return ก่อนถึง
// logic เลิกเลือกท้ายฟังก์ชัน แผงจึงค้างเปิด) — คลิกบนตัว annotation เองถูก
// stopPropagation ที่ layer ของมันแล้ว ส่วน tool select/highlight ตั้ง selectedId
// ตาม hit-test ทีหลังจุดนี้เหมือนเดิม พฤติกรรมจึงเท่าเดิม
if (selectedId !== null) {
setSelectedId(null);
setPreviewData(null);
}
if (tool === "comment" && user) {
const position = getPointerPercent(e, pageEl);
setComposer({ pageIndex, position, x: e.clientX, y: e.clientY });
@ -2150,11 +2167,6 @@ export default function PDFViewer({
}
}
}
// คลิกพื้นที่ว่างของหน้า (tool select/draw) → เลิกเลือก annotation
if (selectedId !== null) {
setSelectedId(null);
setPreviewData(null);
}
}
async function handleComposerSave() {
@ -2253,6 +2265,9 @@ export default function PDFViewer({
...(textboxBold ? { bold: true } : {}),
opacity: textboxOpacityPct / 100,
align: textboxAlign,
// กล่องกว้างคงที่ (สไตล์ Smallpdf) — บีบด้วยพื้นที่จริงถึงขอบขวาตอนสร้าง
// ให้ตรงกับที่ editor แสดง (maxWidth ของ CSS clamp ให้อยู่แล้วระหว่างพิมพ์)
width: Math.min(TEXTBOX_DEFAULT_WIDTH_PCT, textboxMaxWidthPct(c.position.x)),
...(hasBold ? { segments } : {}),
});
}
@ -2299,6 +2314,7 @@ export default function PDFViewer({
bold: textboxBold,
opacity: textboxOpacityPct / 100,
align: textboxAlign,
widthPct: TEXTBOX_DEFAULT_WIDTH_PCT,
};
// ความกว้างหน้าแรกที่ scale 1 (≈ pt ของ PDF) — ใช้แปลงหน่วย fontSize ระหว่าง % (ที่เก็บใน DB)
// กับ px ที่แสดงใน UI (fallback 595 = ความกว้าง A4 ก่อนเอกสารโหลด)
@ -2801,7 +2817,10 @@ export default function PDFViewer({
}`}
onClick={() => setTextboxBold((o) => !o)}
>
<Bold className="h-3.5 w-3.5" />
{/* B ทึบ — แทน icon Bold แบบเส้นขอบ (lucide ไม่มีรูปทึบ) */}
<span aria-hidden className="text-[13px] font-black leading-none">
B
</span>
</button>
</PanelRow>
<PanelRow label="จัดแนว">