toolbar: ปุ่ม tool dropdown แสดงค่าที่เลือกเฉพาะตอน active + ฟื้น effect เลื่อนปุ่ม active กลับกึ่งกลางแถบ (เสียเงียบมาตั้งแต่ 2026-09-23) — (1) ปัญหา: คำขอผู้ใช้ 2026-09-23 เดิมให้ปุ่ม dropdown ที่มีค่าย่อย (มาร์กข้อความ/วาดเส้น/รูปทรง/ตรายาง) แสดง "ชื่อตัวเลือกล่าสุด" แทนชื่อ tool เสมอเพื่อเห็นค่าปัจจุบันโดยไม่ต้องเปิดเมนู — แต่ทำให้ชื่อ tool หายไปจาก toolbar ทั้งหมด: ปุ่ม "วาดเส้น" แสดง "ปากกา" ตั้งแต่ยังไม่ได้เลือกอะไรเลย (default drawPen=pencil) และค้างอยู่อย่างนั้นแม้สลับไป tool อื่น (ผู้ใช้รายงาน 2026-09-29: "label ของ tool ทำไมไม่เปลี่ยนทั้งที่ยังไม่ได้เลือน เช่น วาด แต่แสดง ปากกาเลย") — ขัดเหตุผลเดิมของการใส่ข้อความบนปุ่ม (2026-09-21: ผู้ใช้ศาลไม่คุ้น icon ล้วน) เพราะมองคำ "ปากกา" แล้วไม่รู้ว่านี่คือปุ่มวาดเส้น (2) แก้ตามคำขอผู้ใช้ 2026-09-29 (เลือกจาก 3 ทางเลือกที่เสนอ): tool ไม่ active → แสดง text ชื่อ tool + Icon ของ tool, title/aria-label = label เต็มเหมือนปุ่มเดี่ยวอื่น; tool active → แสดงค่าที่เลือกไว้ (ค่าที่จะใช้จริงเมื่อวาด) + icon ของค่า (draw/highlight เปลี่ยน icon ตามชนิดปากกา/รูปแบบมาร์ก, shape/stamp ใช้ tool icon เหมือนเดิม), title = "label — ค่า" ตาม pattern เดิม — กดปุ่ม dropdown เรียก switchTool เสมออยู่แล้ว (เคลียร์ popup) ปุ่มจึงสลับไปแสดงค่าทันทีที่เปิดเมนู สอดคล้องกับ "กำลังจะใช้ค่านี้" — ปุ่ม active กว้าง/แคบตามความยาวค่า ("ปากกา" สั้นกว่า "วาดเส้น") แถบรองรับอยู่แล้ว (whitespace-nowrap + max-lg:overflow-x-auto) (3) แก้ bug แฝงที่เจอระหว่างแก้: effect เลื่อนปุ่ม tool active กลับกึ่งกลางแถบบนจอ < lg (UX review A2 2026-09-21) ใช้ querySelector(button[title="..."]) exact-match ซึ่งไม่เจอปุ่ม dropdown ทั้ง 4 มาตั้งแต่ 2026-09-23 เมื่อ title ถูกเติม " — ค่าย่อย" ต่อท้าย — ฟีเจอร์เลื่อนปุ่มกลับจึงเสียเงียบ ๆ กับ 4 ปุ่มนี้ตลอดมา — เพิ่ม data-tool={key} ที่ปุ่ม tool ทุกตัว (ครบ 10/10 ค่าของ union Tool ไม่ซ้ำ) แล้ว query ด้วย button[data-tool="..."] แทน title (ตัด TOOLS.find ออกจาก effect — TOOLS ยังถูกใช้ที่ visibleTools จึงไม่ orphan) (4) แก้ตาม code review 2026-09-29: aria-pressed ของปุ่ม dropdown 4 ตัวใช้ตัวแปร active ใหม่ (const active = tool === key ประกาศครั้งเดียวใน map ใช้ร่วมกับ activeCls) แทน tool === "…" ซ้ำ (ค่าเท่ากันเป๊ะ branch บอง key) + sync rules/pdf-viewer.md ประโยค "ปุ่ม tool แสดง label กำหนดเอง" ให้ระบุเงื่อนไขเฉพาะตอน tool active ตรงโค้ดปัจจุบัน — ผ่าน code review (ไม่มี bug ระดับต้องแก้: active/activeCls equivalent ทุก branch, nested ternary stamp parse ถูก, ตำแหน่งเมนูไม่เพี้ยนเพราะ openToolMenu อ่าน getBoundingClientRect ก่อน React commit setState ที่ batch ไว้ + เมนูยึดขอบซ้ายปุ่ม, effect A2 กลับมาทำงานพร้อม guard เดิมครบ anyMenuOpen/selfScrollUntilRef) + tsc + vitest 193/193 (คำขอผู้ใช้ 2026-09-29)
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 45s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
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 45s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
651e354cad
commit
8571ef9aaf
2 changed files with 55 additions and 32 deletions
File diff suppressed because one or more lines are too long
|
|
@ -380,8 +380,10 @@ function selectionTextWithin(kept: DOMRect[], range: Range): string {
|
||||||
* text = คำสั้น ๆ แสดงเป็นข้อความหลังไอคอนบนปุ่ม (คำขอผู้ใช้ 2026-09-21: ผู้ใช้ศาล
|
* text = คำสั้น ๆ แสดงเป็นข้อความหลังไอคอนบนปุ่ม (คำขอผู้ใช้ 2026-09-21: ผู้ใช้ศาล
|
||||||
* ไม่คุ้นไอคอนล้วน โดยเฉพาะจอสัมผัสที่ hover อ่าน title ไม่ได้ — แทน span ชื่อ
|
* ไม่คุ้นไอคอนล้วน โดยเฉพาะจอสัมผัสที่ hover อ่าน title ไม่ได้ — แทน span ชื่อ
|
||||||
* tool-active ของ H3 ที่ถอดออก) — สำหรับปุ่ม dropdown ที่มีตัวเลือกย่อย (มาร์ก
|
* tool-active ของ H3 ที่ถอดออก) — สำหรับปุ่ม dropdown ที่มีตัวเลือกย่อย (มาร์ก
|
||||||
* ข้อความ/วาดเส้น/รูปทรง/ตรายาง) ข้อความบนปุ่มแสดงชื่อตัวเลือกล่าสุดแทน text เพื่อ
|
* ข้อความ/วาดเส้น/รูปทรง/ตรายาง) เคยแสดงชื่อตัวเลือกล่าสุดแทน text ตลอด (คำขอผู้ใช้
|
||||||
* เห็นค่าปัจจุบันโดยไม่ต้องเปิดเมนู (คำขอผู้ใช้ 2026-09-23) */
|
* 2026-09-23) แต่ทำให้ชื่อ tool หายไปจากแถบ ("วาดเส้น" เห็นเป็น "ปากกา" ตั้งแต่
|
||||||
|
* ยังไม่ได้เลือกอะไร) — 2026-09-29 จึงแสดงค่าล่าสุด**เฉพาะตอน tool active**
|
||||||
|
* (ค่าที่จะใช้จริงเมื่อวาด) ไม่ active กลับมาแสดง text ชื่อ tool เหมือนปุ่มอื่น */
|
||||||
const TOOLS: { key: Tool; label: string; text: string; Icon: LucideIcon }[] = [
|
const TOOLS: { key: Tool; label: string; text: string; Icon: LucideIcon }[] = [
|
||||||
{ key: "select", label: "เลือก", text: "เลือก", Icon: MousePointer2 },
|
{ key: "select", label: "เลือก", text: "เลือก", Icon: MousePointer2 },
|
||||||
{ key: "pan", label: "เลื่อนหน้า (pan)", text: "เลื่อนหน้า", Icon: Hand },
|
{ key: "pan", label: "เลื่อนหน้า (pan)", text: "เลื่อนหน้า", Icon: Hand },
|
||||||
|
|
@ -1571,9 +1573,11 @@ export default function PDFViewer({
|
||||||
// ธรรมดาจะพลาดจอกว้างเศษส่วน เช่น 1023.5px จาก display scaling 125%/150%
|
// ธรรมดาจะพลาดจอกว้างเศษส่วน เช่น 1023.5px จาก display scaling 125%/150%
|
||||||
if (!window.matchMedia("not all and (min-width: 1024px)").matches) return;
|
if (!window.matchMedia("not all and (min-width: 1024px)").matches) return;
|
||||||
const row = toolbarRowRef.current;
|
const row = toolbarRowRef.current;
|
||||||
const entry = TOOLS.find((t) => t.key === tool);
|
if (!row) return;
|
||||||
if (!row || !entry) return;
|
// หาปุ่มด้วย data-tool — ห้ามยึด title ของปุ่ม (title ของปุ่ม dropdown เปลี่ยนตาม
|
||||||
const btn = row.querySelector<HTMLButtonElement>(`button[title="${entry.label}"]`);
|
// ค่าที่เลือก/สถานะ active ไม่ match แบบ exact — เคยเสียเงียบ ๆ ตั้งแต่ suffix
|
||||||
|
// " — ค่าย่อย" ถูกใส่ใน title 2026-09-23)
|
||||||
|
const btn = row.querySelector<HTMLButtonElement>(`button[data-tool="${tool}"]`);
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
const b = btn.getBoundingClientRect();
|
const b = btn.getBoundingClientRect();
|
||||||
const r = row.getBoundingClientRect();
|
const r = row.getBoundingClientRect();
|
||||||
|
|
@ -3374,23 +3378,31 @@ export default function PDFViewer({
|
||||||
// มุมปุ่มปลายแถวต้องน้อยกว่า/เท่ากับ container (rounded-md 6px) — ใส่ lg
|
// มุมปุ่มปลายแถวต้องน้อยกว่า/เท่ากับ container (rounded-md 6px) — ใส่ lg
|
||||||
// 8px แล้ว active bg โป๊ะชนเส้นโค้งของ container (UX review m4 2026-09-23)
|
// 8px แล้ว active bg โป๊ะชนเส้นโค้งของ container (UX review m4 2026-09-23)
|
||||||
const corner = i === 0 ? "rounded-l-md" : i === visibleTools.length - 1 ? "rounded-r-md" : "";
|
const corner = i === 0 ? "rounded-l-md" : i === visibleTools.length - 1 ? "rounded-r-md" : "";
|
||||||
|
// tool นี้กำลัง active ไหม — ปุ่ม dropdown ที่มีค่าย่อย (มาร์กข้อความ/
|
||||||
|
// วาดเส้น/รูปทรง/ตรายาง) ใช้ตัดสินว่าจะแสดงชื่อ tool (ไม่ active) หรือค่า
|
||||||
|
// ที่เลือกไว้ (active = ค่าที่จะใช้จริงเมื่อวาด) — คำขอผู้ใช้ 2026-09-29
|
||||||
|
const active = tool === key;
|
||||||
const activeCls = `${
|
const activeCls = `${
|
||||||
tool === key ? "bg-primary text-white" : "text-ink-soft hover:bg-ink/[0.05]"
|
active ? "bg-primary text-white" : "text-ink-soft hover:bg-ink/[0.05]"
|
||||||
} ${FOCUS_RING_CLS}`;
|
} ${FOCUS_RING_CLS}`;
|
||||||
|
|
||||||
// Highlight — dropdown เลือกรูปแบบมาร์ก (4 แบบ) + สี; คลุดข้อความแล้วสร้างทันที
|
// Highlight — dropdown เลือกรูปแบบมาร์ก (4 แบบ) + สี; คลุดข้อความแล้วสร้างทันที
|
||||||
if (key === "highlight") {
|
if (key === "highlight") {
|
||||||
const btnText =
|
const btnText = active
|
||||||
HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.label ?? text;
|
? (HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.label ?? text)
|
||||||
const CurrentIcon =
|
: text;
|
||||||
HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.Icon ?? Icon;
|
const CurrentIcon = active
|
||||||
|
? (HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.Icon ?? Icon)
|
||||||
|
: Icon;
|
||||||
|
const btnTitle = active ? `${label} — ${btnText}` : label;
|
||||||
return (
|
return (
|
||||||
<div key={key} ref={highlightMenuRef} className="relative">
|
<div key={key} ref={highlightMenuRef} className="relative">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title={`${label} — ${btnText}`}
|
data-tool={key}
|
||||||
aria-label={`${label} — ${btnText}`}
|
title={btnTitle}
|
||||||
aria-pressed={tool === "highlight"}
|
aria-label={btnTitle}
|
||||||
|
aria-pressed={active}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded={highlightMenuOpen}
|
aria-expanded={highlightMenuOpen}
|
||||||
className={`${corner} flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${activeCls}`}
|
className={`${corner} flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${activeCls}`}
|
||||||
|
|
@ -3472,15 +3484,17 @@ export default function PDFViewer({
|
||||||
|
|
||||||
// DRAW: ปุ่มวาดเส้น = dropdown เลือกชนิดปากกา (ปากกา/ไฮไลต์)
|
// DRAW: ปุ่มวาดเส้น = dropdown เลือกชนิดปากกา (ปากกา/ไฮไลต์)
|
||||||
if (key === "draw") {
|
if (key === "draw") {
|
||||||
const CurrentPenIcon = DRAW_PEN_ICONS[drawPen];
|
const CurrentPenIcon = active ? DRAW_PEN_ICONS[drawPen] : Icon;
|
||||||
const btnText = DRAW_PENS[drawPen].label;
|
const btnText = active ? DRAW_PENS[drawPen].label : text;
|
||||||
|
const btnTitle = active ? `${label} — ${btnText}` : label;
|
||||||
return (
|
return (
|
||||||
<div key={key} ref={drawMenuRef} className="relative">
|
<div key={key} ref={drawMenuRef} className="relative">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title={`${label} — ${btnText}`}
|
data-tool={key}
|
||||||
aria-label={`${label} — ${btnText}`}
|
title={btnTitle}
|
||||||
aria-pressed={tool === "draw"}
|
aria-label={btnTitle}
|
||||||
|
aria-pressed={active}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded={drawMenuOpen}
|
aria-expanded={drawMenuOpen}
|
||||||
className={`${corner} flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${activeCls}`}
|
className={`${corner} flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${activeCls}`}
|
||||||
|
|
@ -3580,15 +3594,18 @@ export default function PDFViewer({
|
||||||
|
|
||||||
// F5/B1: ปุ่มรูปทรง = dropdown เลือกชนิดรูปทรง
|
// F5/B1: ปุ่มรูปทรง = dropdown เลือกชนิดรูปทรง
|
||||||
if (key === "shape") {
|
if (key === "shape") {
|
||||||
const btnText =
|
const btnText = active
|
||||||
SHAPE_KIND_BTNS.find((k) => k.key === shapeKind)?.label ?? text;
|
? (SHAPE_KIND_BTNS.find((k) => k.key === shapeKind)?.label ?? text)
|
||||||
|
: text;
|
||||||
|
const btnTitle = active ? `${label} — ${btnText}` : label;
|
||||||
return (
|
return (
|
||||||
<div key={key} ref={shapeMenuRef} className="relative">
|
<div key={key} ref={shapeMenuRef} className="relative">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title={`${label} — ${btnText}`}
|
data-tool={key}
|
||||||
aria-label={`${label} — ${btnText}`}
|
title={btnTitle}
|
||||||
aria-pressed={tool === "shape"}
|
aria-label={btnTitle}
|
||||||
|
aria-pressed={active}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded={shapeMenuOpen}
|
aria-expanded={shapeMenuOpen}
|
||||||
className={`${corner} flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${activeCls}`}
|
className={`${corner} flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${activeCls}`}
|
||||||
|
|
@ -3663,12 +3680,15 @@ export default function PDFViewer({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// F18/B5: ปุ่มตรายาง = dropdown เลือก preset — เลือกคำกำหนดเองแล้วปุ่มแสดง
|
// F18/B5: ปุ่มตรายาง = dropdown เลือก preset — ตอน active เลือกคำกำหนด
|
||||||
// "กำหนดเอง" แทนคำเต็ม (คำจริงเห็นในเมนู/preview ตอนเลือกอยู่แล้ว)
|
// เองแล้วปุ่มแสดง "กำหนดเอง" แทนคำเต็ม (คำจริงเห็นในเมนู/preview ตอนเลือก)
|
||||||
if (key === "stamp") {
|
if (key === "stamp") {
|
||||||
const btnText = STAMP_PRESETS.includes(stampPreset)
|
const btnText = active
|
||||||
? stampPreset
|
? STAMP_PRESETS.includes(stampPreset)
|
||||||
: "กำหนดเอง";
|
? stampPreset
|
||||||
|
: "กำหนดเอง"
|
||||||
|
: text;
|
||||||
|
const btnTitle = active ? `${label} — ${btnText}` : label;
|
||||||
/** จบหน้ากำหนดเองแบบเลือกค่าจบ (ปุ่ม "ใช้ตรายางนี้"/Enter): ช่องว่างล้วน =
|
/** จบหน้ากำหนดเองแบบเลือกค่าจบ (ปุ่ม "ใช้ตรายางนี้"/Enter): ช่องว่างล้วน =
|
||||||
* กลับ preset แรก ให้สถานะทุกจุด (ปุ่ม tool/แถวเมนู/ตอนวาง) สอดคล้องกัน —
|
* กลับ preset แรก ให้สถานะทุกจุด (ปุ่ม tool/แถวเมนู/ตอนวาง) สอดคล้องกัน —
|
||||||
* ปิดเมนูด้วย setState ตรง ไม่ผ่าน markMenuClosed ให้แตะหน้าวางได้ทันที */
|
* ปิดเมนูด้วย setState ตรง ไม่ผ่าน markMenuClosed ให้แตะหน้าวางได้ทันที */
|
||||||
|
|
@ -3680,9 +3700,10 @@ export default function PDFViewer({
|
||||||
<div key={key} ref={stampMenuRef} className="relative">
|
<div key={key} ref={stampMenuRef} className="relative">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title={`${label} — ${btnText}`}
|
data-tool={key}
|
||||||
aria-label={`${label} — ${btnText}`}
|
title={btnTitle}
|
||||||
aria-pressed={tool === "stamp"}
|
aria-label={btnTitle}
|
||||||
|
aria-pressed={active}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded={stampMenuOpen}
|
aria-expanded={stampMenuOpen}
|
||||||
className={`flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${corner} ${activeCls}`}
|
className={`flex items-center gap-1 whitespace-nowrap px-2 py-2 transition ${corner} ${activeCls}`}
|
||||||
|
|
@ -3907,6 +3928,7 @@ export default function PDFViewer({
|
||||||
<div key={key} ref={textboxMenuRef} className="relative">
|
<div key={key} ref={textboxMenuRef} className="relative">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-tool={key}
|
||||||
title={label}
|
title={label}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
aria-pressed={tool === "textbox"}
|
aria-pressed={tool === "textbox"}
|
||||||
|
|
@ -4024,6 +4046,7 @@ export default function PDFViewer({
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
type="button"
|
type="button"
|
||||||
|
data-tool={key}
|
||||||
title={label}
|
title={label}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
aria-pressed={tool === key}
|
aria-pressed={tool === key}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue