From 8571ef9aaf695438821251c4dcc7489f244a891c Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 29 Sep 2026 10:51:50 +0700 Subject: [PATCH] =?UTF-8?q?toolbar:=20=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1?= =?UTF-8?q?=20tool=20dropdown=20=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87?= =?UTF-8?q?=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B9=80?= =?UTF-8?q?=E0=B8=A5=E0=B8=B7=E0=B8=AD=E0=B8=81=E0=B9=80=E0=B8=89=E0=B8=9E?= =?UTF-8?q?=E0=B8=B2=E0=B8=B0=E0=B8=95=E0=B8=AD=E0=B8=99=20active=20+=20?= =?UTF-8?q?=E0=B8=9F=E0=B8=B7=E0=B9=89=E0=B8=99=20effect=20=E0=B9=80?= =?UTF-8?q?=E0=B8=A5=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=99=E0=B8=9B=E0=B8=B8?= =?UTF-8?q?=E0=B9=88=E0=B8=A1=20active=20=E0=B8=81=E0=B8=A5=E0=B8=B1?= =?UTF-8?q?=E0=B8=9A=E0=B8=81=E0=B8=B6=E0=B9=88=E0=B8=87=E0=B8=81=E0=B8=A5?= =?UTF-8?q?=E0=B8=B2=E0=B8=87=E0=B9=81=E0=B8=96=E0=B8=9A=20(=E0=B9=80?= =?UTF-8?q?=E0=B8=AA=E0=B8=B5=E0=B8=A2=E0=B9=80=E0=B8=87=E0=B8=B5=E0=B8=A2?= =?UTF-8?q?=E0=B8=9A=E0=B8=A1=E0=B8=B2=E0=B8=95=E0=B8=B1=E0=B9=89=E0=B8=87?= =?UTF-8?q?=E0=B9=81=E0=B8=95=E0=B9=88=202026-09-23)=20=E2=80=94=20(1)=20?= =?UTF-8?q?=E0=B8=9B=E0=B8=B1=E0=B8=8D=E0=B8=AB=E0=B8=B2:=20=E0=B8=84?= =?UTF-8?q?=E0=B8=B3=E0=B8=82=E0=B8=AD=E0=B8=9C=E0=B8=B9=E0=B9=89=E0=B9=83?= =?UTF-8?q?=E0=B8=8A=E0=B9=89=202026-09-23=20=E0=B9=80=E0=B8=94=E0=B8=B4?= =?UTF-8?q?=E0=B8=A1=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B8=9B=E0=B8=B8=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=20dropdown=20=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=A1?= =?UTF-8?q?=E0=B8=B5=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B8=A2=E0=B9=88=E0=B8=AD?= =?UTF-8?q?=E0=B8=A2=20(=E0=B8=A1=E0=B8=B2=E0=B8=A3=E0=B9=8C=E0=B8=81?= =?UTF-8?q?=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=84=E0=B8=A7=E0=B8=B2=E0=B8=A1?= =?UTF-8?q?/=E0=B8=A7=E0=B8=B2=E0=B8=94=E0=B9=80=E0=B8=AA=E0=B9=89?= =?UTF-8?q?=E0=B8=99/=E0=B8=A3=E0=B8=B9=E0=B8=9B=E0=B8=97=E0=B8=A3?= =?UTF-8?q?=E0=B8=87/=E0=B8=95=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=B2?= =?UTF-8?q?=E0=B8=87)=20=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=20"=E0=B8=8A?= =?UTF-8?q?=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=95=E0=B8=B1=E0=B8=A7=E0=B9=80?= =?UTF-8?q?=E0=B8=A5=E0=B8=B7=E0=B8=AD=E0=B8=81=E0=B8=A5=E0=B9=88=E0=B8=B2?= =?UTF-8?q?=E0=B8=AA=E0=B8=B8=E0=B8=94"=20=E0=B9=81=E0=B8=97=E0=B8=99?= =?UTF-8?q?=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD=20tool=20=E0=B9=80?= =?UTF-8?q?=E0=B8=AA=E0=B8=A1=E0=B8=AD=E0=B9=80=E0=B8=9E=E0=B8=B7=E0=B9=88?= =?UTF-8?q?=E0=B8=AD=E0=B9=80=E0=B8=AB=E0=B9=87=E0=B8=99=E0=B8=84=E0=B9=88?= =?UTF-8?q?=E0=B8=B2=E0=B8=9B=E0=B8=B1=E0=B8=88=E0=B8=88=E0=B8=B8=E0=B8=9A?= =?UTF-8?q?=E0=B8=B1=E0=B8=99=E0=B9=82=E0=B8=94=E0=B8=A2=E0=B9=84=E0=B8=A1?= =?UTF-8?q?=E0=B9=88=E0=B8=95=E0=B9=89=E0=B8=AD=E0=B8=87=E0=B9=80=E0=B8=9B?= =?UTF-8?q?=E0=B8=B4=E0=B8=94=E0=B9=80=E0=B8=A1=E0=B8=99=E0=B8=B9=20?= =?UTF-8?q?=E2=80=94=20=E0=B9=81=E0=B8=95=E0=B9=88=E0=B8=97=E0=B8=B3?= =?UTF-8?q?=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD?= =?UTF-8?q?=20tool=20=E0=B8=AB=E0=B8=B2=E0=B8=A2=E0=B9=84=E0=B8=9B?= =?UTF-8?q?=E0=B8=88=E0=B8=B2=E0=B8=81=20toolbar=20=E0=B8=97=E0=B8=B1?= =?UTF-8?q?=E0=B9=89=E0=B8=87=E0=B8=AB=E0=B8=A1=E0=B8=94:=20=E0=B8=9B?= =?UTF-8?q?=E0=B8=B8=E0=B9=88=E0=B8=A1=20"=E0=B8=A7=E0=B8=B2=E0=B8=94?= =?UTF-8?q?=E0=B9=80=E0=B8=AA=E0=B9=89=E0=B8=99"=20=E0=B9=81=E0=B8=AA?= =?UTF-8?q?=E0=B8=94=E0=B8=87=20"=E0=B8=9B=E0=B8=B2=E0=B8=81=E0=B8=81?= =?UTF-8?q?=E0=B8=B2"=20=E0=B8=95=E0=B8=B1=E0=B9=89=E0=B8=87=E0=B9=81?= =?UTF-8?q?=E0=B8=95=E0=B9=88=E0=B8=A2=E0=B8=B1=E0=B8=87=E0=B9=84=E0=B8=A1?= =?UTF-8?q?=E0=B9=88=E0=B9=84=E0=B8=94=E0=B9=89=E0=B9=80=E0=B8=A5=E0=B8=B7?= =?UTF-8?q?=E0=B8=AD=E0=B8=81=E0=B8=AD=E0=B8=B0=E0=B9=84=E0=B8=A3=E0=B9=80?= =?UTF-8?q?=E0=B8=A5=E0=B8=A2=20(default=20drawPen=3Dpencil)=20=E0=B9=81?= =?UTF-8?q?=E0=B8=A5=E0=B8=B0=E0=B8=84=E0=B9=89=E0=B8=B2=E0=B8=87=E0=B8=AD?= =?UTF-8?q?=E0=B8=A2=E0=B8=B9=E0=B9=88=E0=B8=AD=E0=B8=A2=E0=B9=88=E0=B8=B2?= =?UTF-8?q?=E0=B8=87=E0=B8=99=E0=B8=B1=E0=B9=89=E0=B8=99=E0=B9=81=E0=B8=A1?= =?UTF-8?q?=E0=B9=89=E0=B8=AA=E0=B8=A5=E0=B8=B1=E0=B8=9A=E0=B9=84=E0=B8=9B?= =?UTF-8?q?=20tool=20=E0=B8=AD=E0=B8=B7=E0=B9=88=E0=B8=99=20(=E0=B8=9C?= =?UTF-8?q?=E0=B8=B9=E0=B9=89=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B8=A3=E0=B8=B2?= =?UTF-8?q?=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99=202026-09-29:=20"label=20?= =?UTF-8?q?=E0=B8=82=E0=B8=AD=E0=B8=87=20tool=20=E0=B8=97=E0=B8=B3?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B9=80=E0=B8=9B?= =?UTF-8?q?=E0=B8=A5=E0=B8=B5=E0=B9=88=E0=B8=A2=E0=B8=99=E0=B8=97=E0=B8=B1?= =?UTF-8?q?=E0=B9=89=E0=B8=87=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=A2=E0=B8=B1?= =?UTF-8?q?=E0=B8=87=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B9=84=E0=B8=94=E0=B9=89?= =?UTF-8?q?=E0=B9=80=E0=B8=A5=E0=B8=B7=E0=B8=AD=E0=B8=99=20=E0=B9=80?= =?UTF-8?q?=E0=B8=8A=E0=B9=88=E0=B8=99=20=E0=B8=A7=E0=B8=B2=E0=B8=94=20?= =?UTF-8?q?=E0=B9=81=E0=B8=95=E0=B9=88=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87?= =?UTF-8?q?=20=E0=B8=9B=E0=B8=B2=E0=B8=81=E0=B8=81=E0=B8=B2=E0=B9=80?= =?UTF-8?q?=E0=B8=A5=E0=B8=A2")=20=E2=80=94=20=E0=B8=82=E0=B8=B1=E0=B8=94?= =?UTF-8?q?=E0=B9=80=E0=B8=AB=E0=B8=95=E0=B8=B8=E0=B8=9C=E0=B8=A5=E0=B9=80?= =?UTF-8?q?=E0=B8=94=E0=B8=B4=E0=B8=A1=E0=B8=82=E0=B8=AD=E0=B8=87=E0=B8=81?= =?UTF-8?q?=E0=B8=B2=E0=B8=A3=E0=B9=83=E0=B8=AA=E0=B9=88=E0=B8=82=E0=B9=89?= =?UTF-8?q?=E0=B8=AD=E0=B8=84=E0=B8=A7=E0=B8=B2=E0=B8=A1=E0=B8=9A=E0=B8=99?= =?UTF-8?q?=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1=20(2026-09-21:=20=E0=B8=9C?= =?UTF-8?q?=E0=B8=B9=E0=B9=89=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B8=A8=E0=B8=B2?= =?UTF-8?q?=E0=B8=A5=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=84=E0=B8=B8=E0=B9=89?= =?UTF-8?q?=E0=B8=99=20icon=20=E0=B8=A5=E0=B9=89=E0=B8=A7=E0=B8=99)=20?= =?UTF-8?q?=E0=B9=80=E0=B8=9E=E0=B8=A3=E0=B8=B2=E0=B8=B0=E0=B8=A1=E0=B8=AD?= =?UTF-8?q?=E0=B8=87=E0=B8=84=E0=B8=B3=20"=E0=B8=9B=E0=B8=B2=E0=B8=81?= =?UTF-8?q?=E0=B8=81=E0=B8=B2"=20=E0=B9=81=E0=B8=A5=E0=B9=89=E0=B8=A7?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=A3=E0=B8=B9=E0=B9=89=E0=B8=A7?= =?UTF-8?q?=E0=B9=88=E0=B8=B2=E0=B8=99=E0=B8=B5=E0=B9=88=E0=B8=84=E0=B8=B7?= =?UTF-8?q?=E0=B8=AD=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1=E0=B8=A7=E0=B8=B2?= =?UTF-8?q?=E0=B8=94=E0=B9=80=E0=B8=AA=E0=B9=89=E0=B8=99=20(2)=20=E0=B9=81?= =?UTF-8?q?=E0=B8=81=E0=B9=89=E0=B8=95=E0=B8=B2=E0=B8=A1=E0=B8=84=E0=B8=B3?= =?UTF-8?q?=E0=B8=82=E0=B8=AD=E0=B8=9C=E0=B8=B9=E0=B9=89=E0=B9=83=E0=B8=8A?= =?UTF-8?q?=E0=B9=89=202026-09-29=20(=E0=B9=80=E0=B8=A5=E0=B8=B7=E0=B8=AD?= =?UTF-8?q?=E0=B8=81=E0=B8=88=E0=B8=B2=E0=B8=81=203=20=E0=B8=97=E0=B8=B2?= =?UTF-8?q?=E0=B8=87=E0=B9=80=E0=B8=A5=E0=B8=B7=E0=B8=AD=E0=B8=81=E0=B8=97?= =?UTF-8?q?=E0=B8=B5=E0=B9=88=E0=B9=80=E0=B8=AA=E0=B8=99=E0=B8=AD):=20tool?= =?UTF-8?q?=20=E0=B9=84=E0=B8=A1=E0=B9=88=20active=20=E2=86=92=20=E0=B9=81?= =?UTF-8?q?=E0=B8=AA=E0=B8=94=E0=B8=87=20text=20=E0=B8=8A=E0=B8=B7?= =?UTF-8?q?=E0=B9=88=E0=B8=AD=20tool=20+=20Icon=20=E0=B8=82=E0=B8=AD?= =?UTF-8?q?=E0=B8=87=20tool,=20title/aria-label=20=3D=20label=20=E0=B9=80?= =?UTF-8?q?=E0=B8=95=E0=B9=87=E0=B8=A1=E0=B9=80=E0=B8=AB=E0=B8=A1=E0=B8=B7?= =?UTF-8?q?=E0=B8=AD=E0=B8=99=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1=E0=B9=80?= =?UTF-8?q?=E0=B8=94=E0=B8=B5=E0=B9=88=E0=B8=A2=E0=B8=A7=E0=B8=AD=E0=B8=B7?= =?UTF-8?q?=E0=B9=88=E0=B8=99;=20tool=20active=20=E2=86=92=20=E0=B9=81?= =?UTF-8?q?=E0=B8=AA=E0=B8=94=E0=B8=87=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B8=97?= =?UTF-8?q?=E0=B8=B5=E0=B9=88=E0=B9=80=E0=B8=A5=E0=B8=B7=E0=B8=AD=E0=B8=81?= =?UTF-8?q?=E0=B9=84=E0=B8=A7=E0=B9=89=20(=E0=B8=84=E0=B9=88=E0=B8=B2?= =?UTF-8?q?=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=88=E0=B8=B0=E0=B9=83=E0=B8=8A?= =?UTF-8?q?=E0=B9=89=E0=B8=88=E0=B8=A3=E0=B8=B4=E0=B8=87=E0=B9=80=E0=B8=A1?= =?UTF-8?q?=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=A7=E0=B8=B2=E0=B8=94)=20+=20i?= =?UTF-8?q?con=20=E0=B8=82=E0=B8=AD=E0=B8=87=E0=B8=84=E0=B9=88=E0=B8=B2=20?= =?UTF-8?q?(draw/highlight=20=E0=B9=80=E0=B8=9B=E0=B8=A5=E0=B8=B5=E0=B9=88?= =?UTF-8?q?=E0=B8=A2=E0=B8=99=20icon=20=E0=B8=95=E0=B8=B2=E0=B8=A1?= =?UTF-8?q?=E0=B8=8A=E0=B8=99=E0=B8=B4=E0=B8=94=E0=B8=9B=E0=B8=B2=E0=B8=81?= =?UTF-8?q?=E0=B8=81=E0=B8=B2/=E0=B8=A3=E0=B8=B9=E0=B8=9B=E0=B9=81?= =?UTF-8?q?=E0=B8=9A=E0=B8=9A=E0=B8=A1=E0=B8=B2=E0=B8=A3=E0=B9=8C=E0=B8=81?= =?UTF-8?q?,=20shape/stamp=20=E0=B9=83=E0=B8=8A=E0=B9=89=20tool=20icon=20?= =?UTF-8?q?=E0=B9=80=E0=B8=AB=E0=B8=A1=E0=B8=B7=E0=B8=AD=E0=B8=99=E0=B9=80?= =?UTF-8?q?=E0=B8=94=E0=B8=B4=E0=B8=A1),=20title=20=3D=20"label=20?= =?UTF-8?q?=E2=80=94=20=E0=B8=84=E0=B9=88=E0=B8=B2"=20=E0=B8=95=E0=B8=B2?= =?UTF-8?q?=E0=B8=A1=20pattern=20=E0=B9=80=E0=B8=94=E0=B8=B4=E0=B8=A1=20?= =?UTF-8?q?=E2=80=94=20=E0=B8=81=E0=B8=94=E0=B8=9B=E0=B8=B8=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=20dropdown=20=E0=B9=80=E0=B8=A3=E0=B8=B5=E0=B8=A2?= =?UTF-8?q?=E0=B8=81=20switchTool=20=E0=B9=80=E0=B8=AA=E0=B8=A1=E0=B8=AD?= =?UTF-8?q?=E0=B8=AD=E0=B8=A2=E0=B8=B9=E0=B9=88=E0=B9=81=E0=B8=A5=E0=B9=89?= =?UTF-8?q?=E0=B8=A7=20(=E0=B9=80=E0=B8=84=E0=B8=A5=E0=B8=B5=E0=B8=A2?= =?UTF-8?q?=E0=B8=A3=E0=B9=8C=20popup)=20=E0=B8=9B=E0=B8=B8=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=E0=B8=88=E0=B8=B6=E0=B8=87=E0=B8=AA=E0=B8=A5=E0=B8=B1?= =?UTF-8?q?=E0=B8=9A=E0=B9=84=E0=B8=9B=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87?= =?UTF-8?q?=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B8=97=E0=B8=B1=E0=B8=99=E0=B8=97?= =?UTF-8?q?=E0=B8=B5=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B9=80=E0=B8=9B=E0=B8=B4?= =?UTF-8?q?=E0=B8=94=E0=B9=80=E0=B8=A1=E0=B8=99=E0=B8=B9=20=E0=B8=AA?= =?UTF-8?q?=E0=B8=AD=E0=B8=94=E0=B8=84=E0=B8=A5=E0=B9=89=E0=B8=AD=E0=B8=87?= =?UTF-8?q?=E0=B8=81=E0=B8=B1=E0=B8=9A=20"=E0=B8=81=E0=B8=B3=E0=B8=A5?= =?UTF-8?q?=E0=B8=B1=E0=B8=87=E0=B8=88=E0=B8=B0=E0=B9=83=E0=B8=8A=E0=B9=89?= =?UTF-8?q?=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B8=99=E0=B8=B5=E0=B9=89"=20?= =?UTF-8?q?=E2=80=94=20=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1=20active=20?= =?UTF-8?q?=E0=B8=81=E0=B8=A7=E0=B9=89=E0=B8=B2=E0=B8=87/=E0=B9=81?= =?UTF-8?q?=E0=B8=84=E0=B8=9A=E0=B8=95=E0=B8=B2=E0=B8=A1=E0=B8=84=E0=B8=A7?= =?UTF-8?q?=E0=B8=B2=E0=B8=A1=E0=B8=A2=E0=B8=B2=E0=B8=A7=E0=B8=84=E0=B9=88?= =?UTF-8?q?=E0=B8=B2=20("=E0=B8=9B=E0=B8=B2=E0=B8=81=E0=B8=81=E0=B8=B2"=20?= =?UTF-8?q?=E0=B8=AA=E0=B8=B1=E0=B9=89=E0=B8=99=E0=B8=81=E0=B8=A7=E0=B9=88?= =?UTF-8?q?=E0=B8=B2=20"=E0=B8=A7=E0=B8=B2=E0=B8=94=E0=B9=80=E0=B8=AA?= =?UTF-8?q?=E0=B9=89=E0=B8=99")=20=E0=B9=81=E0=B8=96=E0=B8=9A=E0=B8=A3?= =?UTF-8?q?=E0=B8=AD=E0=B8=87=E0=B8=A3=E0=B8=B1=E0=B8=9A=E0=B8=AD=E0=B8=A2?= =?UTF-8?q?=E0=B8=B9=E0=B9=88=E0=B9=81=E0=B8=A5=E0=B9=89=E0=B8=A7=20(white?= =?UTF-8?q?space-nowrap=20+=20max-lg:overflow-x-auto)=20(3)=20=E0=B9=81?= =?UTF-8?q?=E0=B8=81=E0=B9=89=20bug=20=E0=B9=81=E0=B8=9D=E0=B8=87=E0=B8=97?= =?UTF-8?q?=E0=B8=B5=E0=B9=88=E0=B9=80=E0=B8=88=E0=B8=AD=E0=B8=A3=E0=B8=B0?= =?UTF-8?q?=E0=B8=AB=E0=B8=A7=E0=B9=88=E0=B8=B2=E0=B8=87=E0=B9=81=E0=B8=81?= =?UTF-8?q?=E0=B9=89:=20effect=20=E0=B9=80=E0=B8=A5=E0=B8=B7=E0=B9=88?= =?UTF-8?q?=E0=B8=AD=E0=B8=99=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1=20tool?= =?UTF-8?q?=20active=20=E0=B8=81=E0=B8=A5=E0=B8=B1=E0=B8=9A=E0=B8=81?= =?UTF-8?q?=E0=B8=B6=E0=B9=88=E0=B8=87=E0=B8=81=E0=B8=A5=E0=B8=B2=E0=B8=87?= =?UTF-8?q?=E0=B9=81=E0=B8=96=E0=B8=9A=E0=B8=9A=E0=B8=99=E0=B8=88=E0=B8=AD?= =?UTF-8?q?=20<=20lg=20(UX=20review=20A2=202026-09-21)=20=E0=B9=83?= =?UTF-8?q?=E0=B8=8A=E0=B9=89=20querySelector(`button[title=3D"..."]`)=20e?= =?UTF-8?q?xact-match=20=E0=B8=8B=E0=B8=B6=E0=B9=88=E0=B8=87=E0=B9=84?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=E0=B9=80=E0=B8=88=E0=B8=AD=E0=B8=9B=E0=B8=B8?= =?UTF-8?q?=E0=B9=88=E0=B8=A1=20dropdown=20=E0=B8=97=E0=B8=B1=E0=B9=89?= =?UTF-8?q?=E0=B8=87=204=20=E0=B8=A1=E0=B8=B2=E0=B8=95=E0=B8=B1=E0=B9=89?= =?UTF-8?q?=E0=B8=87=E0=B9=81=E0=B8=95=E0=B9=88=202026-09-23=20=E0=B9=80?= =?UTF-8?q?=E0=B8=A1=E0=B8=B7=E0=B9=88=E0=B8=AD=20title=20=E0=B8=96?= =?UTF-8?q?=E0=B8=B9=E0=B8=81=E0=B9=80=E0=B8=95=E0=B8=B4=E0=B8=A1=20"=20?= =?UTF-8?q?=E2=80=94=20=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B8=A2=E0=B9=88?= =?UTF-8?q?=E0=B8=AD=E0=B8=A2"=20=E0=B8=95=E0=B9=88=E0=B8=AD=E0=B8=97?= =?UTF-8?q?=E0=B9=89=E0=B8=B2=E0=B8=A2=20=E2=80=94=20=E0=B8=9F=E0=B8=B5?= =?UTF-8?q?=E0=B9=80=E0=B8=88=E0=B8=AD=E0=B8=A3=E0=B9=8C=E0=B9=80=E0=B8=A5?= =?UTF-8?q?=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=99=E0=B8=9B=E0=B8=B8=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=E0=B8=81=E0=B8=A5=E0=B8=B1=E0=B8=9A=E0=B8=88=E0=B8=B6?= =?UTF-8?q?=E0=B8=87=E0=B9=80=E0=B8=AA=E0=B8=B5=E0=B8=A2=E0=B9=80=E0=B8=87?= =?UTF-8?q?=E0=B8=B5=E0=B8=A2=E0=B8=9A=20=E0=B9=86=20=E0=B8=81=E0=B8=B1?= =?UTF-8?q?=E0=B8=9A=204=20=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1=E0=B8=99?= =?UTF-8?q?=E0=B8=B5=E0=B9=89=E0=B8=95=E0=B8=A5=E0=B8=AD=E0=B8=94=E0=B8=A1?= =?UTF-8?q?=E0=B8=B2=20=E2=80=94=20=E0=B9=80=E0=B8=9E=E0=B8=B4=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=20data-tool=3D{key}=20=E0=B8=97=E0=B8=B5=E0=B9=88?= =?UTF-8?q?=E0=B8=9B=E0=B8=B8=E0=B9=88=E0=B8=A1=20tool=20=E0=B8=97?= =?UTF-8?q?=E0=B8=B8=E0=B8=81=E0=B8=95=E0=B8=B1=E0=B8=A7=20(=E0=B8=84?= =?UTF-8?q?=E0=B8=A3=E0=B8=9A=2010/10=20=E0=B8=84=E0=B9=88=E0=B8=B2?= =?UTF-8?q?=E0=B8=82=E0=B8=AD=E0=B8=87=20union=20Tool=20=E0=B9=84=E0=B8=A1?= =?UTF-8?q?=E0=B9=88=E0=B8=8B=E0=B9=89=E0=B8=B3)=20=E0=B9=81=E0=B8=A5?= =?UTF-8?q?=E0=B9=89=E0=B8=A7=20query=20=E0=B8=94=E0=B9=89=E0=B8=A7?= =?UTF-8?q?=E0=B8=A2=20button[data-tool=3D"..."]=20=E0=B9=81=E0=B8=97?= =?UTF-8?q?=E0=B8=99=20title=20(=E0=B8=95=E0=B8=B1=E0=B8=94=20TOOLS.find?= =?UTF-8?q?=20=E0=B8=AD=E0=B8=AD=E0=B8=81=E0=B8=88=E0=B8=B2=E0=B8=81=20eff?= =?UTF-8?q?ect=20=E2=80=94=20TOOLS=20=E0=B8=A2=E0=B8=B1=E0=B8=87=E0=B8=96?= =?UTF-8?q?=E0=B8=B9=E0=B8=81=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B8=97=E0=B8=B5?= =?UTF-8?q?=E0=B9=88=20visibleTools=20=E0=B8=88=E0=B8=B6=E0=B8=87=E0=B9=84?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=20orphan)=20(4)=20=E0=B9=81=E0=B8=81?= =?UTF-8?q?=E0=B9=89=E0=B8=95=E0=B8=B2=E0=B8=A1=20code=20review=202026-09-?= =?UTF-8?q?29:=20aria-pressed=20=E0=B8=82=E0=B8=AD=E0=B8=87=E0=B8=9B?= =?UTF-8?q?=E0=B8=B8=E0=B9=88=E0=B8=A1=20dropdown=204=20=E0=B8=95=E0=B8=B1?= =?UTF-8?q?=E0=B8=A7=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B8=95=E0=B8=B1=E0=B8=A7?= =?UTF-8?q?=E0=B9=81=E0=B8=9B=E0=B8=A3=20active=20=E0=B9=83=E0=B8=AB?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=20(const=20active=20=3D=20tool=20=3D=3D=3D?= =?UTF-8?q?=20key=20=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B2=E0=B8=A8?= =?UTF-8?q?=E0=B8=84=E0=B8=A3=E0=B8=B1=E0=B9=89=E0=B8=87=E0=B9=80=E0=B8=94?= =?UTF-8?q?=E0=B8=B5=E0=B8=A2=E0=B8=A7=E0=B9=83=E0=B8=99=20map=20=E0=B9=83?= =?UTF-8?q?=E0=B8=8A=E0=B9=89=E0=B8=A3=E0=B9=88=E0=B8=A7=E0=B8=A1=E0=B8=81?= =?UTF-8?q?=E0=B8=B1=E0=B8=9A=20activeCls)=20=E0=B9=81=E0=B8=97=E0=B8=99?= =?UTF-8?q?=20tool=20=3D=3D=3D=20"=E2=80=A6"=20=E0=B8=8B=E0=B9=89=E0=B8=B3?= =?UTF-8?q?=20(=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B9=80=E0=B8=97=E0=B9=88?= =?UTF-8?q?=E0=B8=B2=E0=B8=81=E0=B8=B1=E0=B8=99=E0=B9=80=E0=B8=9B=E0=B9=8A?= =?UTF-8?q?=E0=B8=B0=20branch=20=E0=B8=9A=E0=B8=AD=E0=B8=87=20key)=20+=20s?= =?UTF-8?q?ync=20rules/pdf-viewer.md=20=E0=B8=9B=E0=B8=A3=E0=B8=B0?= =?UTF-8?q?=E0=B9=82=E0=B8=A2=E0=B8=84=20"=E0=B8=9B=E0=B8=B8=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=20tool=20=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=20label?= =?UTF-8?q?=20=E0=B8=81=E0=B8=B3=E0=B8=AB=E0=B8=99=E0=B8=94=E0=B9=80?= =?UTF-8?q?=E0=B8=AD=E0=B8=87"=20=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B8=A3?= =?UTF-8?q?=E0=B8=B0=E0=B8=9A=E0=B8=B8=E0=B9=80=E0=B8=87=E0=B8=B7=E0=B9=88?= =?UTF-8?q?=E0=B8=AD=E0=B8=99=E0=B9=84=E0=B8=82=E0=B9=80=E0=B8=89=E0=B8=9E?= =?UTF-8?q?=E0=B8=B2=E0=B8=B0=E0=B8=95=E0=B8=AD=E0=B8=99=20tool=20active?= =?UTF-8?q?=20=E0=B8=95=E0=B8=A3=E0=B8=87=E0=B9=82=E0=B8=84=E0=B9=89?= =?UTF-8?q?=E0=B8=94=E0=B8=9B=E0=B8=B1=E0=B8=88=E0=B8=88=E0=B8=B8=E0=B8=9A?= =?UTF-8?q?=E0=B8=B1=E0=B8=99=20=E2=80=94=20=E0=B8=9C=E0=B9=88=E0=B8=B2?= =?UTF-8?q?=E0=B8=99=20code=20review=20(=E0=B9=84=E0=B8=A1=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=E0=B8=B5=20bug=20=E0=B8=A3=E0=B8=B0=E0=B8=94=E0=B8=B1?= =?UTF-8?q?=E0=B8=9A=E0=B8=95=E0=B9=89=E0=B8=AD=E0=B8=87=E0=B9=81=E0=B8=81?= =?UTF-8?q?=E0=B9=89:=20active/activeCls=20equivalent=20=E0=B8=97=E0=B8=B8?= =?UTF-8?q?=E0=B8=81=20branch,=20nested=20ternary=20stamp=20parse=20?= =?UTF-8?q?=E0=B8=96=E0=B8=B9=E0=B8=81,=20=E0=B8=95=E0=B8=B3=E0=B9=81?= =?UTF-8?q?=E0=B8=AB=E0=B8=99=E0=B9=88=E0=B8=87=E0=B9=80=E0=B8=A1=E0=B8=99?= =?UTF-8?q?=E0=B8=B9=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B9=80=E0=B8=9E=E0=B8=B5?= =?UTF-8?q?=E0=B9=89=E0=B8=A2=E0=B8=99=E0=B9=80=E0=B8=9E=E0=B8=A3=E0=B8=B2?= =?UTF-8?q?=E0=B8=B0=20openToolMenu=20=E0=B8=AD=E0=B9=88=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=20getBoundingClientRect=20=E0=B8=81=E0=B9=88=E0=B8=AD=E0=B8=99?= =?UTF-8?q?=20React=20commit=20setState=20=E0=B8=97=E0=B8=B5=E0=B9=88=20ba?= =?UTF-8?q?tch=20=E0=B9=84=E0=B8=A7=E0=B9=89=20+=20=E0=B9=80=E0=B8=A1?= =?UTF-8?q?=E0=B8=99=E0=B8=B9=E0=B8=A2=E0=B8=B6=E0=B8=94=E0=B8=82=E0=B8=AD?= =?UTF-8?q?=E0=B8=9A=E0=B8=8B=E0=B9=89=E0=B8=B2=E0=B8=A2=E0=B8=9B=E0=B8=B8?= =?UTF-8?q?=E0=B9=88=E0=B8=A1,=20effect=20A2=20=E0=B8=81=E0=B8=A5=E0=B8=B1?= =?UTF-8?q?=E0=B8=9A=E0=B8=A1=E0=B8=B2=E0=B8=97=E0=B8=B3=E0=B8=87=E0=B8=B2?= =?UTF-8?q?=E0=B8=99=E0=B8=9E=E0=B8=A3=E0=B9=89=E0=B8=AD=E0=B8=A1=20guard?= =?UTF-8?q?=20=E0=B9=80=E0=B8=94=E0=B8=B4=E0=B8=A1=E0=B8=84=E0=B8=A3?= =?UTF-8?q?=E0=B8=9A=20anyMenuOpen/selfScrollUntilRef)=20+=20tsc=20+=20vit?= =?UTF-8?q?est=20193/193=20(=E0=B8=84=E0=B8=B3=E0=B8=82=E0=B8=AD=E0=B8=9C?= =?UTF-8?q?=E0=B8=B9=E0=B9=89=E0=B9=83=E0=B8=8A=E0=B9=89=202026-09-29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- .claude/rules/pdf-viewer.md | 2 +- components/pdf/PDFViewer.tsx | 85 +++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/.claude/rules/pdf-viewer.md b/.claude/rules/pdf-viewer.md index 83e7523..af25028 100644 --- a/.claude/rules/pdf-viewer.md +++ b/.claude/rules/pdf-viewer.md @@ -21,7 +21,7 @@ paths: - Bookmark scroll: คำนวณ absolute position ครั้งเดียวแล้ว smooth-scroll ครั้งเดียว (ห้าม scroll ซ้อนสองจังหวะ) - Deep link (F12/C4): PDFViewer อ่าน `window.location.search` **ครั้งเดียวหลัง `pageSizes` พร้อม** (guard ด้วย ref — ไม่ใช้ `useSearchParams` เลี่ยงข้อจำกัด prerender ของ Next 14) — `?page=N` (1-based) scroll ไปหน้านั้น, `?ann=` scroll ไปหน้าของ annotation นั้น + `setSelectedId` — placeholder ขนาดจริงทุกหน้าทำให้คำนวณ absolute scroll ได้แม้หน้ายังไม่ render (กลไกเดียวกับ bookmark); ปุ่ม "คัดลอกลิงก์" ใน sidebar สร้าง URL รูป `/annotate/?page=&ann=` - Dialog `fixed z-50` ใน sidebar (ประวัติ) ต้อง render ผ่าน **`createPortal` ไป `document.body`** + guard `typeof document === "undefined"` ตอน SSR — `aside.sticky` สร้าง stacking context ครอบ z-index ของ dialog ไว้ ทำให้ `fixed z-50` แพ้ `textLayer` (z-[2] ระดับ root) จนคลิกปุ่มไม่ได้ (PLAN-FEAT.md §5) -- เมนู dropdown ของ tool ทั้ง 5 (highlight/draw/shape/stamp/textbox) + popup bookmark ก็ render ผ่าน **`createPortal` → `document.body` เป็น `position: fixed`** เหมือนกัน แต่ยึดพิกัดจริงของปุ่ม (`fixedMenuPos` — `getBoundingClientRect()` ตอนคลิก + clamp ให้อยู่ในจอ; popup bookmark คำนวณจาก ref ของปุ่ม 🔖 เพราะเปิดได้จากคีย์ลัด B/ปุ่มในแผง selection ด้วย) — **ห้าม**กลับไปเป็น `absolute` ใน scroll container ของแถบ tool และ**ห้ามสลับ `overflow` ของแถบตอนเปิดเมนู** (สลับแล้ว scroll position ของแถบรีเซ็ต เมนูเปิดนอกจอบนมือถือ และทั้ง app เอียงเลื่อนแนวนอนได้); เลื่อนแถบต่อหรือ **ความกว้างจอเปลี่ยน** (หมุนจอ/split view — เทียบ `menuOpenWRef` ตอนเปิด) = ปิดเมนูทั้งหมด แต่ resize ที่เปลี่ยนแค่ความสูง (URL bar ยุบ/คีย์บอร์ดของมือถือ) ปิดไม่ได้ เพราะเมนู fixed ยึด sticky header ยังอยู่กับที่ (ตำแหน่งคำนวณตอนเปิดเท่านั้น ไม่ตามปุ่มที่เลื่อนไป); outside-click ต้อง**ข้ามโหนดใน `[data-tool-menu]` ก่อน** — ref ของ wrapper มองไม่เห็นโหนดใน portal ไม่ข้ามแล้วคลิกในเมนู = ปิดเมนูเอง; **เมนูตรายางเป็น 2 หน้าใน portal เดียว** (2026-09-28 — preset + แถว "กำหนดเอง…" → หน้า popup `stampCustomMode` reset กลับหน้า preset ทุกครั้งที่เปิดเมนูใหม่): หน้ากำหนดเอง = ช่องข้อความ (พิมพ์ live ไป `stampPreset` เดียวกับ preset, maxLength `STAMP_MAX_LABEL_CHARS`) + สี + ขนาด + **preview ตรายางตามสัดส่วนจริงบนจอ** (ความกว้างหน้าแรก × scale% — ใหญ่เกินเมนูย่อทั้งชุด; กรอบ/ฟอนต์/การจัดวาง/measure ใช้ตัวเดียวกับ render จริง: `stampLayout`/`stampBoxHeight` จาก `utils/geometry.ts` + `STAMP_FONT_STACK`/`measureStampText` จาก `utils/annotation-display.ts` — **คำยาวจัดบรรทัดแบบถอยหลัง: ย่อฟอนต์ให้อยู่บรรทัดเดียวก่อนลงได้จนถึง floor 10% ของความกว้างกรอบ (คำอย่าง "ตรวจสอบแล้ว" ย่อ ~25% ก็พอ = บรรทัดเดียว ไม่ตก 2 บรรทัด — คำขอผู้ใช้ 2026-09-28 รอบสอง) คำยาวเกิน floor ค่อย wrap ≤3 บรรทัด กรอบสูงขึ้นตามจำนวนบรรทัด คำสั้น 1 บรรทัดฟอนต์เต็มสูง 0.4w เหมือนเดิมเป๊ะ — ทุกมิติเป็นสัดส่วนของความกว้างกรอบ จำนวนบรรทัดจึงไม่ขึ้นกับ scale** ปิดท้ายด้วยปุ่ม "ใช้ตรายางนี้" — ปุ่มนี้และ Enter ในช่อง = ปิดเมนูแบบเลือกค่าจบ (setState ตรง ไม่ผ่าน markMenuClosed — แตะหน้าวางได้ทันที ไม่โดน guard กลืน tap), Escape ในช่อง = ย้อนกลับหน้า preset; ปุ่ม tool แสดง label "กำหนดเอง" แทนคำเต็มเมื่อค่าปัจจุบันเป็นคำกำหนดเอง +- เมนู dropdown ของ tool ทั้ง 5 (highlight/draw/shape/stamp/textbox) + popup bookmark ก็ render ผ่าน **`createPortal` → `document.body` เป็น `position: fixed`** เหมือนกัน แต่ยึดพิกัดจริงของปุ่ม (`fixedMenuPos` — `getBoundingClientRect()` ตอนคลิก + clamp ให้อยู่ในจอ; popup bookmark คำนวณจาก ref ของปุ่ม 🔖 เพราะเปิดได้จากคีย์ลัด B/ปุ่มในแผง selection ด้วย) — **ห้าม**กลับไปเป็น `absolute` ใน scroll container ของแถบ tool และ**ห้ามสลับ `overflow` ของแถบตอนเปิดเมนู** (สลับแล้ว scroll position ของแถบรีเซ็ต เมนูเปิดนอกจอบนมือถือ และทั้ง app เอียงเลื่อนแนวนอนได้); เลื่อนแถบต่อหรือ **ความกว้างจอเปลี่ยน** (หมุนจอ/split view — เทียบ `menuOpenWRef` ตอนเปิด) = ปิดเมนูทั้งหมด แต่ resize ที่เปลี่ยนแค่ความสูง (URL bar ยุบ/คีย์บอร์ดของมือถือ) ปิดไม่ได้ เพราะเมนู fixed ยึด sticky header ยังอยู่กับที่ (ตำแหน่งคำนวณตอนเปิดเท่านั้น ไม่ตามปุ่มที่เลื่อนไป); outside-click ต้อง**ข้ามโหนดใน `[data-tool-menu]` ก่อน** — ref ของ wrapper มองไม่เห็นโหนดใน portal ไม่ข้ามแล้วคลิกในเมนู = ปิดเมนูเอง; **เมนูตรายางเป็น 2 หน้าใน portal เดียว** (2026-09-28 — preset + แถว "กำหนดเอง…" → หน้า popup `stampCustomMode` reset กลับหน้า preset ทุกครั้งที่เปิดเมนูใหม่): หน้ากำหนดเอง = ช่องข้อความ (พิมพ์ live ไป `stampPreset` เดียวกับ preset, maxLength `STAMP_MAX_LABEL_CHARS`) + สี + ขนาด + **preview ตรายางตามสัดส่วนจริงบนจอ** (ความกว้างหน้าแรก × scale% — ใหญ่เกินเมนูย่อทั้งชุด; กรอบ/ฟอนต์/การจัดวาง/measure ใช้ตัวเดียวกับ render จริง: `stampLayout`/`stampBoxHeight` จาก `utils/geometry.ts` + `STAMP_FONT_STACK`/`measureStampText` จาก `utils/annotation-display.ts` — **คำยาวจัดบรรทัดแบบถอยหลัง: ย่อฟอนต์ให้อยู่บรรทัดเดียวก่อนลงได้จนถึง floor 10% ของความกว้างกรอบ (คำอย่าง "ตรวจสอบแล้ว" ย่อ ~25% ก็พอ = บรรทัดเดียว ไม่ตก 2 บรรทัด — คำขอผู้ใช้ 2026-09-28 รอบสอง) คำยาวเกิน floor ค่อย wrap ≤3 บรรทัด กรอบสูงขึ้นตามจำนวนบรรทัด คำสั้น 1 บรรทัดฟอนต์เต็มสูง 0.4w เหมือนเดิมเป๊ะ — ทุกมิติเป็นสัดส่วนของความกว้างกรอบ จำนวนบรรทัดจึงไม่ขึ้นกับ scale** ปิดท้ายด้วยปุ่ม "ใช้ตรายางนี้" — ปุ่มนี้และ Enter ในช่อง = ปิดเมนูแบบเลือกค่าจบ (setState ตรง ไม่ผ่าน markMenuClosed — แตะหน้าวางได้ทันที ไม่โดน guard กลืน tap), Escape ในช่อง = ย้อนกลับหน้า preset; ปุ่ม tool แสดง label "กำหนดเอง" แทนคำเต็มเมื่อค่าปัจจุบันเป็นคำกำหนดเอง (ปุ่ม dropdown ทั้ง 4 — มาร์กข้อความ/วาดเส้น/รูปทรง/ตรายาง — แสดงค่าที่เลือก**เฉพาะตอน tool active** ไม่ active แสดงชื่อ tool เหมือนปุ่มอื่น เพราะแสดงค่าตลอดทำให้ชื่อ tool หายจากแถบ เช่น "วาดเส้น" เห็นเป็น "ปากกา" ตั้งแต่ยังไม่ได้เลือก, คำขอผู้ใช้ 2026-09-29) - DRAW วาดเส้นตรง: **ปล่อยเมาส์แล้ว snap อัตโนมัติ** เมื่อจุดทุกจุดห่างจากเส้นตรง จุดเริ่ม→จุดสุดท้าย ไม่เกิน max(5% ของความยาว, 3px) และเส้นยาว ≥ 24px (`isNearlyStraight`) — หรือกดค้าง **Shift** ระหว่างลาก (กด/ปล่อยกลางทางได้ — listener ระดับ window + reset ตอน blur) เพื่อบังคับตรงเสมอ → บันทึกเหลือจุดเริ่ม→จุดสุดท้าย 2 จุด (ความหนา uniform — ไม่เก็บ `widths` จาก pressure) — ใช้ได้เฉพาะโหมดวาดอิสระ ไม่ใช่ shape/eraser/redact; เส้นโค้ง/ขดจริงหลุดเกณฑ์จึงเหลือ freehand - Tool `pan` (มือเลื่อนหน้า — ปุ่มแยกถัดจาก select ในแถบ tool): ลากบน `
` = `mainRef.current.scrollBy` สวนทางการลาก (ทั้งแกน x/y) + cursor grab/grabbing; ระหว่างใช้ pan ทุก overlay ปิด pointer-events ผ่านตัวแปร `overlayFrozen` (`inkActive || pan`) **ยกเว้น InkLayer ต้องไม่ active** (ไม่งั้น pan กลายเป็นวาดเส้น) - HIGHLIGHT มี `style?` 4 แบบ: highlight ทึบเหมือนเดิม / underline = เส้นที่ขอบล่าง rect ต่อบรรทัด / strikethrough = เส้นกลางบรรทัด / squiggle = svg path ขยุกขยิก (`squigglePath`) ที่ขอบล่าง — export วาด line/sine แทน rect ทึบ; สร้างได้จาก **tool "highlight" ทางเดียว** (dropdown บนปุ่ม = รูปแบบ + สี → คลุมข้อความ = สร้างทันที พร้อม live preview ผ่าน `selectionchange` บน desktop / custom selection ของ `useCustomSelection` บนจอสัมผัส + ซ่อน native selection) — tool "เลือก" คลุมแล้วเกิด popup ไม่ได้แล้ว (ถอดออก) และคีย์ลัด `H` สลับไป tool highlight diff --git a/components/pdf/PDFViewer.tsx b/components/pdf/PDFViewer.tsx index 3f8fb77..41e0e8a 100644 --- a/components/pdf/PDFViewer.tsx +++ b/components/pdf/PDFViewer.tsx @@ -380,8 +380,10 @@ function selectionTextWithin(kept: DOMRect[], range: Range): string { * text = คำสั้น ๆ แสดงเป็นข้อความหลังไอคอนบนปุ่ม (คำขอผู้ใช้ 2026-09-21: ผู้ใช้ศาล * ไม่คุ้นไอคอนล้วน โดยเฉพาะจอสัมผัสที่ hover อ่าน title ไม่ได้ — แทน span ชื่อ * tool-active ของ H3 ที่ถอดออก) — สำหรับปุ่ม dropdown ที่มีตัวเลือกย่อย (มาร์ก - * ข้อความ/วาดเส้น/รูปทรง/ตรายาง) ข้อความบนปุ่มแสดงชื่อตัวเลือกล่าสุดแทน text เพื่อ - * เห็นค่าปัจจุบันโดยไม่ต้องเปิดเมนู (คำขอผู้ใช้ 2026-09-23) */ + * ข้อความ/วาดเส้น/รูปทรง/ตรายาง) เคยแสดงชื่อตัวเลือกล่าสุดแทน text ตลอด (คำขอผู้ใช้ + * 2026-09-23) แต่ทำให้ชื่อ tool หายไปจากแถบ ("วาดเส้น" เห็นเป็น "ปากกา" ตั้งแต่ + * ยังไม่ได้เลือกอะไร) — 2026-09-29 จึงแสดงค่าล่าสุด**เฉพาะตอน tool active** + * (ค่าที่จะใช้จริงเมื่อวาด) ไม่ active กลับมาแสดง text ชื่อ tool เหมือนปุ่มอื่น */ const TOOLS: { key: Tool; label: string; text: string; Icon: LucideIcon }[] = [ { key: "select", label: "เลือก", text: "เลือก", Icon: MousePointer2 }, { key: "pan", label: "เลื่อนหน้า (pan)", text: "เลื่อนหน้า", Icon: Hand }, @@ -1571,9 +1573,11 @@ export default function PDFViewer({ // ธรรมดาจะพลาดจอกว้างเศษส่วน เช่น 1023.5px จาก display scaling 125%/150% if (!window.matchMedia("not all and (min-width: 1024px)").matches) return; const row = toolbarRowRef.current; - const entry = TOOLS.find((t) => t.key === tool); - if (!row || !entry) return; - const btn = row.querySelector(`button[title="${entry.label}"]`); + if (!row) return; + // หาปุ่มด้วย data-tool — ห้ามยึด title ของปุ่ม (title ของปุ่ม dropdown เปลี่ยนตาม + // ค่าที่เลือก/สถานะ active ไม่ match แบบ exact — เคยเสียเงียบ ๆ ตั้งแต่ suffix + // " — ค่าย่อย" ถูกใส่ใน title 2026-09-23) + const btn = row.querySelector(`button[data-tool="${tool}"]`); if (!btn) return; const b = btn.getBoundingClientRect(); const r = row.getBoundingClientRect(); @@ -3374,23 +3378,31 @@ export default function PDFViewer({ // มุมปุ่มปลายแถวต้องน้อยกว่า/เท่ากับ container (rounded-md 6px) — ใส่ lg // 8px แล้ว active bg โป๊ะชนเส้นโค้งของ container (UX review m4 2026-09-23) 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 = `${ - 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}`; // Highlight — dropdown เลือกรูปแบบมาร์ก (4 แบบ) + สี; คลุดข้อความแล้วสร้างทันที if (key === "highlight") { - const btnText = - HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.label ?? text; - const CurrentIcon = - HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.Icon ?? Icon; + const btnText = active + ? (HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.label ?? text) + : text; + const CurrentIcon = active + ? (HIGHLIGHT_STYLE_BTNS.find((s) => s.key === highlightStyle)?.Icon ?? Icon) + : Icon; + const btnTitle = active ? `${label} — ${btnText}` : label; return (