@xom11/whiteboard 0.6.5 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +87 -1
  2. package/dist/chunk-74VEEZBV.mjs +619 -0
  3. package/dist/chunk-74VEEZBV.mjs.map +1 -0
  4. package/dist/chunk-7P7SQFOW.mjs +39 -0
  5. package/dist/chunk-7P7SQFOW.mjs.map +1 -0
  6. package/dist/chunk-C6SCVOMC.mjs +111 -0
  7. package/dist/chunk-C6SCVOMC.mjs.map +1 -0
  8. package/dist/chunk-DU2NFHRR.mjs +103 -0
  9. package/dist/chunk-DU2NFHRR.mjs.map +1 -0
  10. package/dist/chunk-DU3RHKT5.mjs +44 -0
  11. package/dist/chunk-DU3RHKT5.mjs.map +1 -0
  12. package/dist/chunk-HTBLO5JO.mjs +41 -0
  13. package/dist/chunk-HTBLO5JO.mjs.map +1 -0
  14. package/dist/chunk-IUVV52HO.mjs +144 -0
  15. package/dist/chunk-IUVV52HO.mjs.map +1 -0
  16. package/dist/chunk-KEYZ5EZT.mjs +154 -0
  17. package/dist/chunk-KEYZ5EZT.mjs.map +1 -0
  18. package/dist/chunk-P2AOIF7S.mjs +40 -0
  19. package/dist/chunk-P2AOIF7S.mjs.map +1 -0
  20. package/dist/chunk-SBDMF4NQ.mjs +212 -0
  21. package/dist/chunk-SBDMF4NQ.mjs.map +1 -0
  22. package/dist/chunk-X5R72SSJ.mjs +52 -0
  23. package/dist/chunk-X5R72SSJ.mjs.map +1 -0
  24. package/dist/chunk-ZVN356JZ.mjs +58 -0
  25. package/dist/chunk-ZVN356JZ.mjs.map +1 -0
  26. package/dist/geometry-2d.d.mts +16 -0
  27. package/dist/geometry-2d.d.ts +16 -0
  28. package/dist/geometry-2d.js +3581 -0
  29. package/dist/geometry-2d.js.map +1 -0
  30. package/dist/geometry-2d.mjs +7 -0
  31. package/dist/geometry-2d.mjs.map +1 -0
  32. package/dist/geometry-3d.d.mts +16 -0
  33. package/dist/geometry-3d.d.ts +16 -0
  34. package/dist/geometry-3d.js +4105 -0
  35. package/dist/geometry-3d.js.map +1 -0
  36. package/dist/geometry-3d.mjs +7 -0
  37. package/dist/geometry-3d.mjs.map +1 -0
  38. package/dist/graph-2d.d.mts +16 -0
  39. package/dist/graph-2d.d.ts +16 -0
  40. package/dist/graph-2d.js +2019 -0
  41. package/dist/graph-2d.js.map +1 -0
  42. package/dist/graph-2d.mjs +6 -0
  43. package/dist/graph-2d.mjs.map +1 -0
  44. package/dist/host-LZH2FZ2N.mjs +1066 -0
  45. package/dist/host-LZH2FZ2N.mjs.map +1 -0
  46. package/dist/host-PIIDSMVE.mjs +3187 -0
  47. package/dist/host-PIIDSMVE.mjs.map +1 -0
  48. package/dist/host-VDNAJMLC.mjs +2864 -0
  49. package/dist/host-VDNAJMLC.mjs.map +1 -0
  50. package/dist/host-Z3TEJKZA.mjs +466 -0
  51. package/dist/host-Z3TEJKZA.mjs.map +1 -0
  52. package/dist/index.d.mts +30 -148
  53. package/dist/index.d.ts +30 -148
  54. package/dist/index.js +8370 -5614
  55. package/dist/index.js.map +1 -1
  56. package/dist/index.mjs +395 -7294
  57. package/dist/index.mjs.map +1 -1
  58. package/dist/latex.d.mts +15 -0
  59. package/dist/latex.d.ts +15 -0
  60. package/dist/latex.js +750 -0
  61. package/dist/latex.js.map +1 -0
  62. package/dist/latex.mjs +6 -0
  63. package/dist/latex.mjs.map +1 -0
  64. package/dist/types-CinstD7T.d.mts +110 -0
  65. package/dist/types-CinstD7T.d.ts +110 -0
  66. package/package.json +26 -7
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stamps/latex/render.ts","../src/stamps/latex/types.ts","../src/stamps/latex/editor/LeftPanel.tsx","../src/stamps/latex/editor/EditorPopover.tsx","../src/stamps/shared/svgToImage.ts","../src/stamps/shared/insertImage.ts","../src/stamps/shared/useIsMobile.ts","../src/stamps/latex/host.tsx","../src/stamps/latex/index.tsx"],"names":["jsxs","Fragment","jsx","forwardRef","EditorPopover","useState","useRef","useEffect","useCallback","useImperativeHandle","LatexStampHost","useMemo","lazy"],"mappings":";;;;;;;;;;;;;;;;AAIA,SAAS,cAAA,GAAyB;AAChC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,QAAA,EAAU,OAAO,OAAO,QAAA,CAAS,MAAA;AAC7E,EAAA,OAAO,EAAA;AACT;AAEA,eAAe,YAAA,GAAgC;AAC7C,EAAA,IAAI,SAAA,KAAc,MAAM,OAAO,SAAA;AAC/B,EAAA,IAAI;AACF,IAAA,IAAI,OAAO,UAAU,UAAA,EAAY;AAC/B,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,gBAAgB,CAAA;AACxC,MAAA,IAAI,IAAI,EAAA,EAAI;AACV,QAAA,IAAI,GAAA,GAAM,MAAM,GAAA,CAAI,IAAA,EAAK;AAKzB,QAAA,MAAM,SAAS,cAAA,EAAe;AAC9B,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,wBAAA,EAA0B,CAAA,MAAA,EAAS,MAAM,CAAA,GAAA,CAAK,CAAA;AAAA,QAClE;AACA,QAAA,SAAA,GAAY,GAAA;AACZ,QAAA,OAAO,GAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,SAAA,GAAY,EAAA;AACZ,EAAA,OAAO,EAAA;AACT;AAEA,eAAsB,gBAAA,CAAiB,KAAa,WAAA,EAAuC;AACzF,EAAA,MAAM,KAAA,GAAQ,MAAM,OAAO,OAAO,CAAA;AAClC,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,OAAA,CAAQ,cAAA,CAAe,GAAA,EAAK,EAAE,WAAA,EAAa,YAAA,EAAc,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,CAAA;AAElG,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC/C,EAAA,UAAA,CAAW,MAAM,OAAA,GAAU,oFAAA;AAC3B,EAAA,UAAA,CAAW,SAAA,GAAY,IAAA;AACvB,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,UAAU,CAAA;AACpC,EAAA,MAAM,IAAA,GAAO,WAAW,qBAAA,EAAsB;AAC9C,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,IAAK,EAAA;AACvC,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,MAAM,CAAA,IAAK,EAAA;AACzC,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,UAAU,CAAA;AAEpC,EAAA,MAAM,OAAA,GAAU,MAAM,YAAA,EAAa;AAKnC,EAAA,OAAO,iDAAA,GAAoD,KAAA,GAAQ,YAAA,GAAe,MAAA,GAAS,iBAAA,GAAoB,KAAA,GAAQ,GAAA,GAAM,MAAA,GAAS,uIAAA,GAGxH,OAAA,GAAU,UAAA,GACtB,IAAA,GACA,8BAAA;AAGJ;AA7DA,IAAI,SAAA;AAAJ,IAAA,WAAA,GAAA,KAAA,CAAA;AAAA,EAAA,4BAAA,GAAA;AAAA,IAAI,SAAA,GAA2B,IAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACSxB,SAAS,kBAAkB,IAAA,EAAwC;AACxE,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,UAAU,OAAO,KAAA;AAC9C,EAAA,MAAM,CAAA,GAAI,IAAA;AACV,EAAA,OAAO,CAAA,CAAE,SAAS,OAAA,IAAW,CAAA,CAAE,YAAY,CAAA,IAAK,OAAO,EAAE,GAAA,KAAQ,QAAA;AACnE;AAbA,IAAA,UAAA,GAAA,KAAA,CAAA;AAAA,EAAA,2BAAA,GAAA;AAAA,EAAA;AAAA,CAAA,CAAA;ACgBA,SAAS,KAAA,CAAM,EAAE,KAAA,EAAO,IAAA,EAAM,SAAS,QAAA,EAAU,QAAA,EAAU,UAAA,EAAY,aAAA,EAAc,EAAe;AAClG,EAAA,MAAM,cAAc,QAAA,GAChB;AAAA,IACE,oBAAA,EAAsB,MAAA;AAAA,IACtB,mBAAA,EAAqB,aAAa,MAAA,GAAS;AAAA,MAE7C,EAAC;AACL,EAAA,MAAM,oBAAoB,MAAM;AAC9B,IAAA,IAAI,UAAU,aAAA,IAAgB;AAAA,SACzB,OAAA,EAAQ;AAAA,EACf,CAAA;AACA,EAAA,uBACEA,eAAA,CAAAC,mBAAA,EAAA,EACG,QAAA,EAAA;AAAA,IAAA,QAAA,IAAY,UAAA,oBACXC,cAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,uBAAA;AAAA,QACV,aAAA,EAAe,aAAA;AAAA,QACf,aAAA,EAAY;AAAA;AAAA,KACd;AAAA,oBAEFF,eAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,eAAA;AAAA,QACL,YAAA,EAAY,KAAA;AAAA,QACZ,aAAA,EAAa,QAAA,IAAY,CAAC,UAAA,GAAa,MAAA,GAAS,MAAA;AAAA,QAChD,aAAA,EAAY,kBAAA;AAAA,QACZ,iBAAA,EAAgB,MAAA;AAAA,QACf,GAAG,WAAA;AAAA,QACJ,SAAA,EACE,WACI,gFAAA,GACA,8IAAA;AAAA,QAGN,QAAA,EAAA;AAAA,0BAAAA,eAAA,CAAC,QAAA,EAAA,EAAO,WAAU,+GAAA,EAChB,QAAA,EAAA;AAAA,4BAAAA,eAAA,CAAC,IAAA,EAAA,EAAG,WAAU,8DAAA,EACZ,QAAA,EAAA;AAAA,8BAAAE,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wBAAA,EAA0B,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,cAC9C;AAAA,aAAA,EACH,CAAA;AAAA,4BACAA,cAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAS,iBAAA;AAAA,gBACT,YAAA,EAAY,WAAW,wCAAA,GAAsB,cAAA;AAAA,gBAC7C,SAAA,EAAU,+EAAA;AAAA,gBAEV,0CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,IAAA,EAAK,MAAA,EAAO,MAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EAAO,QAAO,cAAA,EAAe,WAAA,EAAY,KAAI,aAAA,EAAc,OAAA,EAAQ,gBAAe,OAAA,EACrI,QAAA,EAAA;AAAA,kCAAAA,cAAA,CAAC,MAAA,EAAA,EAAK,IAAG,GAAA,EAAI,EAAA,EAAG,KAAI,EAAA,EAAG,IAAA,EAAK,IAAG,IAAA,EAAK,CAAA;AAAA,kCACpCA,cAAA,CAAC,UAAK,EAAA,EAAG,IAAA,EAAK,IAAG,GAAA,EAAI,EAAA,EAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK;AAAA,iBAAA,EACtC;AAAA;AAAA;AACF,WAAA,EACF,CAAA;AAAA,0BACAA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8CAAA,EAAgD,QAAA,EAAS;AAAA;AAAA;AAAA;AAC1E,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,OAAA,CAAQ,EAAE,KAAA,EAAO,QAAA,EAAS,EAAiD;AAClF,EAAA,uCACG,SAAA,EAAA,EACC,QAAA,EAAA;AAAA,oBAAAA,cAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,0EAAA,EACX,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,IACC;AAAA,GAAA,EACH,CAAA;AAEJ;AAwDO,SAAS,SAAA,CAAU;AAAA,EACxB,WAAA;AAAA,EACA,mBAAA;AAAA,EACA,eAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA,EAAwB;AACtB,EAAA,uBACEF,eAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,yBAAA;AAAA,MACN,IAAA,EAAK,QAAA;AAAA,MACL,OAAA;AAAA,MACA,QAAA;AAAA,MACA,UAAA;AAAA,MACA,aAAA;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAAE,cAAA,CAAC,WAAQ,KAAA,EAAM,0CAAA,EACb,QAAA,kBAAAF,eAAA,CAAC,KAAA,EAAA,EAAI,WAAU,0BAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,eAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,MAAM,mBAAA,CAAoB,KAAK,CAAA;AAAA,cACxC,gBAAc,CAAC,WAAA;AAAA,cACf,SAAA,EAAW;AAAA,gBACT,kDAAA;AAAA,gBACA,CAAC,cACG,2EAAA,GACA;AAAA,eACN,CAAE,KAAK,GAAG,CAAA;AAAA,cAEV,QAAA,EAAA;AAAA,gCAAAE,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mBAAA,EAAoB,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,gCAC1CA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,kCAAA,EAAmC,QAAA,EAAA,SAAA,EAAO;AAAA;AAAA;AAAA,WAC5D;AAAA,0BACAF,eAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,MAAM,mBAAA,CAAoB,IAAI,CAAA;AAAA,cACvC,cAAA,EAAc,WAAA;AAAA,cACd,SAAA,EAAW;AAAA,gBACT,kDAAA;AAAA,gBACA,cACI,2EAAA,GACA;AAAA,eACN,CAAE,KAAK,GAAG,CAAA;AAAA,cAEV,QAAA,EAAA;AAAA,gCAAAE,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mBAAA,EAAoB,QAAA,EAAA,OAAA,EAAK,CAAA;AAAA,gCACzCA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,kCAAA,EAAmC,QAAA,EAAA,WAAA,EAAS;AAAA;AAAA;AAAA;AAC9D,SAAA,EACF,CAAA,EACF,CAAA;AAAA,QAEC,SAAS,GAAA,CAAI,CAAC,KAAA,qBACbA,cAAA,CAAC,WAA0B,KAAA,EAAO,KAAA,CAAM,KAAA,EACtC,QAAA,kBAAAA,cAAA,CAAC,SAAI,SAAA,EAAU,sBAAA,EACZ,gBAAM,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,qBAChBA,cAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAEC,IAAA,EAAK,QAAA;AAAA,YACL,gBAAc,CAAA,CAAE,OAAA;AAAA,YAChB,OAAA,EAAS,MAAM,eAAA,CAAgB,CAAA,CAAE,OAAO,CAAA;AAAA,YACxC,OAAO,CAAA,CAAE,OAAA;AAAA,YACT,SAAA,EAAU,0JAAA;AAAA,YAET,QAAA,EAAA,CAAA,CAAE;AAAA,WAAA;AAAA,UAPE,CAAA,CAAE;AAAA,SASV,CAAA,EACH,CAAA,EAAA,EAdY,KAAA,CAAM,KAepB,CACD,CAAA;AAAA,uCAEA,OAAA,EAAA,EAAQ,KAAA,EAAM,oBACb,QAAA,kBAAAF,eAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iDAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,eAAA,CAAC,MAAA,EAAA,EAAK,WAAU,gCAAA,EACd,QAAA,EAAA;AAAA,4BAAAE,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qEAAA,EAAsE,QAAA,EAAA,OAAA,EAAK,CAAA;AAAA,YAAM;AAAA,WAAA,EAElG,CAAA;AAAA,0BACAF,eAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gCAAA,EACd,QAAA,EAAA;AAAA,4BAAAE,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qEAAA,EAAsE,QAAA,EAAA,KAAA,EAAG,CAAA;AAAA,YAAM;AAAA,WAAA,EAEhG;AAAA,SAAA,EACF,CAAA,EACF;AAAA;AAAA;AAAA,GACF;AAEJ;AA5NA,IAoGM,QAAA;AApGN,IAAA,cAAA,GAAA,KAAA,CAAA;AAAA,EAAA,uCAAA,GAAA;AAAA,IAAA,YAAA;AAoGA,IAAM,QAAA,GAAqD;AAAA,MACzD;AAAA,QACE,KAAA,EAAO,sCAAA;AAAA,QACP,KAAA,EAAO;AAAA,UACL,EAAE,KAAA,EAAO,iBAAA,EAAW,OAAA,EAAS,UAAA,EAAO,SAAS,cAAA,EAAe;AAAA,UAC5D,EAAE,KAAA,EAAO,oBAAA,EAAY,OAAA,EAAS,OAAA,EAAM,SAAS,MAAA,EAAO;AAAA,UACpD,EAAE,KAAA,EAAO,kBAAA,EAAU,OAAA,EAAS,SAAA,EAAM,SAAS,MAAA,EAAO;AAAA,UAClD,EAAE,KAAA,EAAO,UAAA,EAAO,OAAA,EAAS,SAAA,EAAM,SAAS,WAAA,EAAY;AAAA,UACpD,EAAE,KAAA,EAAO,YAAA,EAAS,OAAA,EAAS,eAAA,EAAO,SAAS,cAAA;AAAe;AAC5D,OACF;AAAA,MACA;AAAA,QACE,KAAA,EAAO,6BAAA;AAAA,QACP,KAAA,EAAO;AAAA,UACL,EAAE,KAAA,EAAO,WAAA,EAAQ,OAAA,EAAS,QAAA,EAAK,SAAS,iBAAA,EAAkB;AAAA,UAC1D,EAAE,KAAA,EAAO,SAAA,EAAQ,OAAA,EAAS,QAAA,EAAK,SAAS,kBAAA,EAAmB;AAAA,UAC3D,EAAE,KAAA,EAAO,iBAAA,EAAa,OAAA,EAAS,QAAA,EAAK,SAAS,eAAA,EAAgB;AAAA,UAC7D,EAAE,KAAA,EAAO,oBAAA,EAAY,OAAA,EAAS,KAAA,EAAO,SAAS,kBAAA;AAAmB;AACnE,OACF;AAAA,MACA;AAAA,QACE,KAAA,EAAO,iBAAA;AAAA,QACP,KAAA,EAAO;AAAA,UACL,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,SAAA,EAAU;AAAA,UAC/C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,QAAA,EAAS;AAAA,UAC9C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,MAAA,EAAO;AAAA,UAC5C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,SAAA,EAAU;AAAA,UAC/C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,OAAA,EAAQ;AAAA,UAC7C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,OAAA,EAAQ;AAAA,UAC7C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,OAAA,EAAQ;AAAA,UAC7C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,SAAA,EAAU;AAAA,UAC/C,EAAE,KAAA,EAAO,QAAA,EAAK,OAAA,EAAS,QAAA,EAAK,SAAS,MAAA;AAAO;AAC9C;AACF,KACF;AAAA,EAAA;AAAA,CAAA,CAAA;ACtIA,IA0CM,WAAA,EAEO,aAAA;AA5Cb,IAAA,kBAAA,GAAA,KAAA,CAAA;AAAA,EAAA,2CAAA,GAAA;AAAA,IAAA,YAAA;AASA,IAAA,WAAA,EAAA;AAiCA,IAAM,WAAA,GAAc,GAAA;AAEb,IAAM,aAAA,GAAgBC,gBAAA,CAAuC,SAASC,cAAAA,CAC3E;AAAA,MACE,CAAA;AAAA,MACA,CAAA;AAAA,MACA,YAAA;AAAA,MACA,QAAA;AAAA,MACA,OAAA;AAAA,MACA,WAAA,EAAa,qBAAA;AAAA,MACb,mBAAA;AAAA,MACA,aAAA,GAAgB,KAAA;AAAA,MAChB,QAAA,GAAW,KAAA;AAAA,MACX;AAAA,OAEF,GAAA,EACA;AACA,MAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,eAAS,YAAY,CAAA;AAC/C,MAAA,MAAM,CAAC,mBAAmB,CAAA,GAAIA,cAAA,CAAS,KAAK,CAAA;AAC5C,MAAA,MAAM,cAAc,qBAAA,IAAyB,mBAAA;AAI7C,MAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIA,eAAwB,IAAI,CAAA;AAChE,MAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAwB,IAAI,CAAA;AACtD,MAAA,MAAM,WAAA,GAAcC,aAA6C,IAAI,CAAA;AACrE,MAAA,MAAM,QAAA,GAAWA,aAAyB,IAAI,CAAA;AAE9C,MAAAC,eAAA,CAAU,MAAM;AACd,QAAA,IAAI,WAAA,CAAY,OAAA,EAAS,YAAA,CAAa,WAAA,CAAY,OAAO,CAAA;AACzD,QAAA,WAAA,CAAY,OAAA,GAAU,WAAW,YAAY;AAC3C,UAAA,IAAI;AACF,YAAA,MAAM,GAAA,GAAM,MAAM,gBAAA,CAAiB,KAAA,EAAO,WAAW,CAAA;AACrD,YAAA,aAAA,CAAc,GAAG,CAAA;AACjB,YAAA,QAAA,CAAS,IAAI,CAAA;AAAA,UACf,SAAS,GAAA,EAAK;AACZ,YAAA,aAAA,CAAc,IAAI,CAAA;AAClB,YAAA,QAAA,CAAU,IAAc,OAAO,CAAA;AAAA,UACjC;AAAA,QACF,GAAG,WAAW,CAAA;AACd,QAAA,OAAO,MAAM;AACX,UAAA,IAAI,WAAA,CAAY,OAAA,EAAS,YAAA,CAAa,WAAA,CAAY,OAAO,CAAA;AAAA,QAC3D,CAAA;AAAA,MACF,CAAA,EAAG,CAAC,KAAA,EAAO,WAAW,CAAC,CAAA;AAEvB,MAAA,MAAM,YAAA,GAAeC,kBAAY,MAAM;AACrC,QAAA,IAAI,CAAC,UAAA,EAAY;AACjB,QAAA,QAAA,CAAS,UAAA,EAAY,OAAO,WAAW,CAAA;AAAA,MACzC,GAAG,CAAC,UAAA,EAAY,KAAA,EAAO,WAAA,EAAa,QAAQ,CAAC,CAAA;AAE7C,MAAA,MAAM,aAAA,GAAgBA,iBAAA;AAAA,QACpB,CAAC,CAAA,KAA2B;AAC1B,UAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,OAAA,EAAQ;AAChC,UAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,OAAA,IAAW,CAAC,EAAE,QAAA,EAAU;AACpC,YAAA,CAAA,CAAE,cAAA,EAAe;AACjB,YAAA,YAAA,EAAa;AAAA,UACf;AAAA,QACF,CAAA;AAAA,QACA,CAAC,SAAS,YAAY;AAAA,OACxB;AAGA,MAAAC,yBAAA;AAAA,QACE,GAAA;AAAA,QACA,OAAO;AAAA,UACL,cAAA,EAAgB,CAAC,OAAA,KAAoB;AACnC,YAAA,MAAM,KAAK,QAAA,CAAS,OAAA;AACpB,YAAA,IAAI,CAAC,EAAA,EAAI;AACP,cAAA,QAAA,CAAS,CAAC,CAAA,KAAM,CAAA,GAAI,OAAO,CAAA;AAC3B,cAAA;AAAA,YACF;AACA,YAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,cAAA,IAAkB,KAAA,CAAM,MAAA;AACzC,YAAA,MAAM,GAAA,GAAM,EAAA,CAAG,YAAA,IAAgB,KAAA,CAAM,MAAA;AACrC,YAAA,MAAM,IAAA,GAAO,MAAM,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,GAAI,OAAA,GAAU,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAC9D,YAAA,QAAA,CAAS,IAAI,CAAA;AACb,YAAA,qBAAA,CAAsB,MAAM;AAC1B,cAAA,EAAA,CAAG,KAAA,EAAM;AACT,cAAA,MAAM,GAAA,GAAM,QAAQ,OAAA,CAAQ,MAAA;AAC5B,cAAA,IAAI;AACF,gBAAA,EAAA,CAAG,iBAAA,CAAkB,KAAK,GAAG,CAAA;AAAA,cAC/B,CAAA,CAAA,MAAQ;AAAA,cAER;AAAA,YACF,CAAC,CAAA;AAAA,UACH,CAAA;AAAA,UACA,UAAA,EAAY,MAAM,KAAA,CAAM,IAAA,EAAK,CAAE,SAAS,CAAA,IAAK,CAAC,CAAC,UAAA,IAAc,CAAC,KAAA;AAAA,UAC9D,WAAW,MAAM;AACf,YAAA,IAAI,CAAC,UAAA,IAAc,KAAA,IAAS,CAAC,KAAA,CAAM,IAAA,IAAQ,OAAO,KAAA;AAClD,YAAA,QAAA,CAAS,UAAA,EAAY,OAAO,WAAW,CAAA;AACvC,YAAA,OAAO,IAAA;AAAA,UACT;AAAA,SACF,CAAA;AAAA,QACA,CAAC,KAAA,EAAO,UAAA,EAAY,KAAA,EAAO,aAAa,QAAQ;AAAA,OAClD;AAGA,MAAA,MAAM,gBAAA,GAAmB,CAAA,GAAI,CAAA,IAAK,CAAA,GAAI,CAAA;AACtC,MAAA,MAAM,YAAA,GAAoC,WACtC,EAAE,QAAA,EAAU,SAAS,KAAA,EAAO,CAAA,EAAG,QAAQ,EAAA,EAAG,GAC1C,mBACE,EAAE,QAAA,EAAU,YAAY,GAAA,EAAK,CAAA,EAAG,MAAM,CAAA,EAAG,MAAA,EAAQ,IAAG,GACpD;AAAA,QACE,QAAA,EAAU,UAAA;AAAA,QACV,GAAA,EAAK,KAAA;AAAA,QACL,IAAA,EAAM,gBAAgB,mBAAA,GAAsB,KAAA;AAAA,QAC5C,SAAA,EAAW,uBAAA;AAAA,QACX,MAAA,EAAQ;AAAA,OACV;AAEN,MAAA,uBACET,eAAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,KAAA,EAAO,YAAA;AAAA,UACP,iBAAA,EAAgB,MAAA;AAAA,UAChB,oBAAA,EAAoB,WAAW,MAAA,GAAS,MAAA;AAAA,UACxC,SAAA,EACE,WACI,sCAAA,GACA,gHAAA;AAAA,UAEN,IAAA,EAAK,QAAA;AAAA,UACL,YAAA,EAAW,mCAAA;AAAA,UAEX,QAAA,EAAA;AAAA,4BAAAA,gBAAC,QAAA,EAAA,EAAO,SAAA,EAAW,wHAAwH,QAAA,GAAW,EAAA,GAAK,eAAe,CAAA,CAAA,EACvK,QAAA,EAAA;AAAA,cAAA,QAAA,oBACCE,cAAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,QAAA;AAAA,kBACL,OAAA,EAAS,YAAA;AAAA,kBACT,YAAA,EAAW,2BAAA;AAAA,kBACX,SAAA,EAAU,8FAAA;AAAA,kBAEV,0BAAAF,eAAAA,CAAC,KAAA,EAAA,EAAI,OAAM,IAAA,EAAK,MAAA,EAAO,MAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EAAO,QAAO,cAAA,EAAe,WAAA,EAAY,KAAI,aAAA,EAAc,OAAA,EAAQ,gBAAe,OAAA,EACrI,QAAA,EAAA;AAAA,oCAAAE,cAAAA,CAAC,UAAK,EAAA,EAAG,GAAA,EAAI,IAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,GAAA,EAAI,CAAA;AAAA,oCACnCA,cAAAA,CAAC,MAAA,EAAA,EAAK,EAAA,EAAG,GAAA,EAAI,IAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA;AAAA,oCACrCA,cAAAA,CAAC,MAAA,EAAA,EAAK,EAAA,EAAG,GAAA,EAAI,IAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK;AAAA,mBAAA,EACvC;AAAA;AAAA,eACF;AAAA,8BAEFF,eAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,sDAAA,EACZ,QAAA,EAAA;AAAA,gCAAAE,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wBAAA,EAAyB,QAAA,EAAA,QAAA,EAAC,CAAA;AAAA,gBAAO;AAAA,eAAA,EAEnD,CAAA;AAAA,cACC,4BACCA,cAAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,QAAA;AAAA,kBACL,OAAA,EAAS,YAAA;AAAA,kBACT,QAAA,EAAU,CAAC,UAAA,IAAc,CAAC,CAAC,KAAA;AAAA,kBAC3B,aAAA,EAAY,yBAAA;AAAA,kBACZ,SAAA,EAAU,wGAAA;AAAA,kBACX,QAAA,EAAA;AAAA;AAAA,eAED;AAAA,8BAEFA,cAAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBACC,OAAA,EAAS,OAAA;AAAA,kBACT,YAAA,EAAW,cAAA;AAAA,kBACX,SAAA,EAAU,sFAAA;AAAA,kBAEV,0BAAAF,eAAAA,CAAC,KAAA,EAAA,EAAI,OAAM,IAAA,EAAK,MAAA,EAAO,MAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EAAO,QAAO,cAAA,EAAe,WAAA,EAAY,KAAI,aAAA,EAAc,OAAA,EAAQ,gBAAe,OAAA,EACrI,QAAA,EAAA;AAAA,oCAAAE,cAAAA,CAAC,UAAK,EAAA,EAAG,GAAA,EAAI,IAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA;AAAA,oCACpCA,cAAAA,CAAC,MAAA,EAAA,EAAK,EAAA,EAAG,IAAA,EAAK,IAAG,GAAA,EAAI,EAAA,EAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK;AAAA,mBAAA,EACtC;AAAA;AAAA;AACF,aAAA,EACF,CAAA;AAAA,4BACAF,gBAAC,KAAA,EAAA,EAAI,SAAA,EAAW,gBAAgB,QAAA,GAAW,+BAAA,GAAkC,EAAE,CAAA,CAAA,EAC7E,QAAA,EAAA;AAAA,8BAAAE,cAAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBACC,GAAA,EAAK,QAAA;AAAA,kBACL,IAAA,EAAK,MAAA;AAAA,kBACL,IAAA,EAAK,SAAA;AAAA,kBACL,KAAA;AAAA,kBACA,UAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,kBACxC,SAAA,EAAW,aAAA;AAAA,kBACX,WAAA,EAAY,wBAAA;AAAA,kBACZ,SAAA,EAAW,CAAA,oIAAA,EACT,QAAA,GAAW,yBAAA,GAA4B,UACzC,CAAA,CAAA;AAAA,kBACA,SAAA,EAAS;AAAA;AAAA,eACX;AAAA,8BACAA,cAAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAW;AAAA,oBACT,iEAAA;AAAA,oBACA,WAAW,8BAAA,GAAiC,cAAA;AAAA,oBAC5C,QAAQ,0CAAA,GAA6C;AAAA,mBACvD,CAAE,KAAK,GAAG,CAAA;AAAA,kBAET,QAAA,EAAA,KAAA,mBACCF,eAAAA,CAAC,MAAA,EAAA,EAAK,WAAU,SAAA,EAAU,QAAA,EAAA;AAAA,oBAAA,YAAA;AAAA,oBAAM,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,EAAE;AAAA,mBAAA,EAAE,IACjD,UAAA,mBACFE,cAAAA,CAAC,MAAA,EAAA,EAAK,yBAAyB,EAAE,MAAA,EAAQ,UAAA,EAAW,EAAG,oBAEvDA,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0BAAyB,QAAA,EAAA,uBAAA,EAAW;AAAA;AAAA,eAExD;AAAA,cACC,CAAC,QAAA,oBACAF,eAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,mCAAA,EACb,QAAA,EAAA;AAAA,gCAAAA,eAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,4BAAA,EACb,QAAA,EAAA;AAAA,kBAAA,WAAA,GAAc,OAAA,GAAU,QAAA;AAAA,kBAAS;AAAA,iBAAA,EACpC,CAAA;AAAA,gCACAA,eAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,kCAAAE,cAAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBACC,OAAA,EAAS,OAAA;AAAA,sBACT,SAAA,EAAU,qHAAA;AAAA,sBACX,QAAA,EAAA;AAAA;AAAA,mBAED;AAAA,kCACAA,cAAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBACC,OAAA,EAAS,YAAA;AAAA,sBACT,QAAA,EAAU,CAAC,UAAA,IAAc,CAAC,CAAC,KAAA;AAAA,sBAC3B,SAAA,EAAU,mHAAA;AAAA,sBACX,QAAA,EAAA;AAAA;AAAA;AAED,iBAAA,EACF;AAAA,eAAA,EACF,CAAA;AAAA,cAED,QAAA,oBACCF,eAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACZ,QAAA,EAAA;AAAA,gBAAA,WAAA,GAAc,OAAA,GAAU,QAAA;AAAA,gBAAS;AAAA,eAAA,EACpC;AAAA,aAAA,EAEJ;AAAA;AAAA;AAAA,OACF;AAAA,IAEJ,CAAC,CAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;AChQD,eAAe,WAAW,KAAA,EAAgC;AACxD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,MAAA,EAAQ;AAClD,IAAA,MAAM,GAAA,GAAM,IAAI,WAAA,EAAY,CAAE,OAAO,KAAK,CAAA;AAC1C,IAAA,MAAM,SAAS,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,WAAW,GAAG,CAAA;AACxD,IAAA,OAAO,KAAA,CAAM,KAAK,IAAI,UAAA,CAAW,MAAM,CAAC,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,CAAE,IAAI,CAAA,CAAA,KAAK,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA;AAAA,EAC1G;AAEA,EAAA,IAAI,EAAA,GAAK,UAAA;AACT,EAAA,IAAI,EAAA,GAAK,UAAA;AACT,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,CAAA,GAAI,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAC5B,IAAA,EAAA,IAAM,CAAA;AACN,IAAA,EAAA,GAAK,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,QAAU,CAAA;AAC7B,IAAA,EAAA,IAAM,CAAA,GAAI,CAAA;AACV,IAAA,EAAA,GAAK,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,aAAA,GAAgB,UAAU,CAAA;AAAA,EAC/C;AACA,EAAA,OAAA,CAAQ,OAAO,CAAA,EAAG,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAA,GAAA,CAAK,EAAA,KAAO,GAAG,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAC3F;AAEA,SAAS,SAAA,CAAU,KAAa,IAAA,EAAkC;AAChE,EAAA,MAAM,KAAK,IAAI,MAAA,CAAO,CAAA,YAAA,EAAe,IAAI,wBAAwB,GAAG,CAAA;AACpE,EAAA,MAAM,CAAA,GAAI,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AACtB,EAAA,IAAI,CAAA,EAAG,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,UAAA,CAAW,CAAA,CAAE,CAAC,CAAC,CAAC,CAAC,CAAA;AAEtD,EAAA,MAAM,EAAA,GAAK,GAAA,CAAI,KAAA,CAAM,wBAAwB,CAAA;AAC7C,EAAA,IAAI,EAAA,EAAI;AACN,IAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,CAAC,CAAA,CAAE,IAAA,GAAO,KAAA,CAAM,KAAK,CAAA,CAAE,GAAA,CAAI,UAAU,CAAA;AACtD,IAAA,IAAI,MAAM,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,IAAA,KAAS,UAAU,KAAA,CAAM,CAAC,IAAI,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AAAA,EAC/F;AACA,EAAA,OAAO,IAAA,KAAS,UAAU,GAAA,GAAM,GAAA;AAClC;AAKA,eAAsB,kBAAkB,GAAA,EAAsC;AAC5E,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,GAAA,EAAK,OAAO,CAAA;AACpC,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,GAAA,EAAK,QAAQ,CAAA;AAEtC,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,GAAG,CAAC,CAAA;AAC7C,EAAA,MAAM,OAAA,GAAU,4BAAA,GAA+B,IAAA,CAAK,IAAI,CAAA;AACxD,EAAA,MAAM,MAAA,GAAS,MAAM,UAAA,CAAW,OAAO,CAAA;AACvC,EAAA,OAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,UAAU,eAAA,EAAgB;AACrE;AAnDA,IAAA,eAAA,GAAA,KAAA,CAAA;AAAA,EAAA,iCAAA,GAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACwCA,SAAS,uBACP,GAAA,EACA,MAAA,EACA,OACA,MAAA,EACA,UAAA,EACA,GACA,CAAA,EACA;AACA,EAAA,MAAM,WACJ,GAAA,EAAK,WAAA,EAAY,IAAK,EAAE,SAAS,CAAA,EAAG,OAAA,EAAS,CAAA,EAAG,KAAA,EAAO,KAAK,MAAA,EAAQ,GAAA,EAAK,MAAM,EAAE,KAAA,EAAO,GAAE,EAAE;AAC9F,EAAA,MAAM,EAAA,GACJ,CAAA,IAAK,QAAA,CAAS,OAAA,GAAA,CAAW,QAAA,CAAS,KAAA,IAAS,GAAA,IAAO,CAAA,IAAK,QAAA,CAAS,IAAA,EAAM,KAAA,IAAS,CAAA,CAAA,GAAK,KAAA,GAAQ,CAAA;AAC9F,EAAA,MAAM,EAAA,GACJ,CAAA,IAAK,QAAA,CAAS,OAAA,GAAA,CAAW,QAAA,CAAS,MAAA,IAAU,GAAA,IAAO,CAAA,IAAK,QAAA,CAAS,IAAA,EAAM,KAAA,IAAS,CAAA,CAAA,GAAK,MAAA,GAAS,CAAA;AAChG,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,EAAA,EAAI,QAAA,GAAW,IAAA,CAAK,GAAA,KAAQ,GAAA,GAAM,IAAA,CAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAC,CAAA;AAAA,IACvE,CAAA,EAAG,EAAA;AAAA,IACH,CAAA,EAAG,EAAA;AAAA,IACH,KAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA,EAAO,CAAA;AAAA,IACP,WAAA,EAAa,aAAA;AAAA,IACb,eAAA,EAAiB,aAAA;AAAA,IACjB,SAAA,EAAW,OAAA;AAAA,IACX,WAAA,EAAa,CAAA;AAAA,IACb,WAAA,EAAa,OAAA;AAAA,IACb,SAAA,EAAW,CAAA;AAAA,IACX,OAAA,EAAS,GAAA;AAAA,IACT,UAAU,EAAC;AAAA,IACX,SAAA,EAAW,IAAA;AAAA,IACX,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,KAAW,GAAG,CAAA;AAAA,IACpC,YAAA,EAAc,CAAA;AAAA,IACd,OAAA,EAAS,CAAA;AAAA,IACT,SAAA,EAAW,KAAA;AAAA,IACX,aAAA,EAAe,IAAA;AAAA,IACf,OAAA,EAAS,KAAK,GAAA,EAAI;AAAA,IAClB,IAAA,EAAM,IAAA;AAAA,IACN,MAAA,EAAQ,KAAA;AAAA,IACR,MAAA,EAAQ,OAAA;AAAA,IACR,KAAA,EAAO,CAAC,CAAA,EAAG,CAAC;AAAA,GACd;AACF;AAeA,eAAsB,gBAAA,CACpB,KACA,IAAA,EACiC;AACjC,EAAA,MAAM,EAAE,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,UAAS,GAAI,MAAM,iBAAA,CAAkB,IAAA,CAAK,SAAS,CAAA;AAC3F,EAAA,GAAA,CAAI,QAAA,CAAS,CAAC,EAAE,EAAA,EAAI,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,IAAA,CAAK,GAAA,EAAI,EAAG,CAAC,CAAA;AACrE,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,cAAA,CAAe,KAAA,EAAO,MAAM,CAAA;AAEpD,EAAA,MAAM,QAAA,GAAW,IAAI,gBAAA,EAAiB;AACtC,EAAA,MAAM,SAAA,GAAY,KAAK,gBAAA,IAAoB,IAAA;AAE3C,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,MAAM,UAAU,QAAA,CAAS,GAAA;AAAA,MAAI,CAAC,CAAA,KAC5B,CAAA,CAAE,EAAA,KAAO,SAAA,GAAY,EAAE,GAAG,CAAA,EAAG,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,MAAA,EAAO,GAAI;AAAA,KACrE;AACA,IAAA,GAAA,CAAI,YAAY,EAAE,QAAA,EAAU,SAAS,QAAA,EAAU,wBAAA,IAA4B,CAAA;AAC3E,IAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,WAAW,SAAA,EAAU;AAAA,EACvD;AAEA,EAAA,MAAM,UAAA,GAAa,sBAAA;AAAA,IACjB,GAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAK,QAAA,EAAU,CAAA;AAAA,IACf,KAAK,QAAA,EAAU;AAAA,GACjB;AACA,EAAA,GAAA,CAAI,WAAA,CAAY;AAAA,IACd,QAAA,EAAU,CAAC,GAAG,QAAA,EAAU,UAAU,CAAA;AAAA,IAClC,UAAU,wBAAA;AAAyB,GACpC,CAAA;AACD,EAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,SAAA,EAAW,WAAW,EAAA,EAAG;AAC3D;AArIA,IAmCM,wBAAA;AAnCN,IAAA,gBAAA,GAAA,KAAA,CAAA;AAAA,EAAA,kCAAA,GAAA;AAAA,IAAA,eAAA,EAAA;AAmCA,IAAM,2BAA2B,OAAY;AAAA,MAC3C,oBAAoB,EAAC;AAAA,MACrB,iBAAA,EAAmB;AAAA,KACrB,CAAA;AAAA,EAAA;AAAA,CAAA,CAAA;ACxBA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,MAAA,CAAO,YAAY,OAAO,KAAA;AAChE,EAAA,IAAI;AACF,IAAA,OAAO,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA,CAAE,OAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAMO,SAAS,WAAA,GAA2B;AACzC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIK,eAAsB,OAAO;AAAA,IACrD,QAAA,EAAU,UAAU,YAAY,CAAA;AAAA,IAChC,WAAA,EAAa,UAAU,cAAc;AAAA,GACvC,CAAE,CAAA;AAEF,EAAAE,gBAAU,MAAM;AACd,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,OAAO,UAAA,EAAY;AACzD,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,YAAY,CAAA;AAC1C,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,cAAc,CAAA;AAC5C,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,QAAA,CAAS,EAAE,QAAA,EAAU,GAAA,CAAI,SAAS,WAAA,EAAa,GAAA,CAAI,SAAS,CAAA;AAAA,IAC9D,CAAA;AACA,IAAA,MAAA,EAAO;AACP,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,MAAM,CAAA;AACrC,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,MAAM,CAAA;AACrC,IAAA,OAAO,MAAM;AACX,MAAA,GAAA,CAAI,mBAAA,CAAoB,UAAU,MAAM,CAAA;AACxC,MAAA,GAAA,CAAI,mBAAA,CAAoB,UAAU,MAAM,CAAA;AAAA,IAC1C,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,KAAA;AACT;AAlDA,IAIM,YAAA,EACA,cAAA;AALN,IAAA,gBAAA,GAAA,KAAA,CAAA;AAAA,EAAA,kCAAA,GAAA;AAAA,IAAA,YAAA;AAIA,IAAM,YAAA,GAAe,oBAAA;AACrB,IAAM,cAAA,GAAiB,eAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACLvB,IAAA,YAAA,GAAA,EAAA;AAAA,QAAA,CAAA,YAAA,EAAA;AAAA,EAAA,cAAA,EAAA,MAAA;AAAA,CAAA,CAAA;AAAA,IAoBa,cAAA;AApBb,IAAA,SAAA,GAAA,KAAA,CAAA;AAAA,EAAA,2BAAA,GAAA;AAAA,IAAA,YAAA;AAUA,IAAA,cAAA,EAAA;AACA,IAAA,kBAAA,EAAA;AAIA,IAAA,gBAAA,EAAA;AACA,IAAA,gBAAA,EAAA;AACA,IAAA,UAAA,EAAA;AAGO,IAAM,cAAA,GAAiBJ,gBAAAA;AAAA,MAC5B,SAASO,eAAAA,CAAe,EAAE,KAAK,cAAA,EAAgB,OAAA,IAAW,GAAA,EAAK;AAC7D,QAAA,MAAM,SAAA,GAAYJ,aAAiC,IAAI,CAAA;AACvD,QAAA,MAAM,EAAE,QAAA,EAAS,GAAI,WAAA,EAAY;AACjC,QAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAID,eAAS,KAAK,CAAA;AAElD,QAAA,MAAM,OAAA,GAAUM,cAAQ,MAAM;AAC5B,UAAA,IAAI,cAAA,IAAkB,iBAAA,CAAkB,cAAA,CAAe,UAAU,CAAA,EAAG;AAClE,YAAA,OAAO;AAAA,cACL,YAAA,EAAc,eAAe,UAAA,CAAW,GAAA;AAAA,cACxC,WAAA,EAAa,CAAC,CAAC,cAAA,CAAe,UAAA,CAAW;AAAA,aAC3C;AAAA,UACF;AACA,UAAA,OAAO,EAAE,YAAA,EAAc,EAAA,EAAI,WAAA,EAAa,KAAA,EAAM;AAAA,QAChD,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAEnB,QAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIN,cAAAA,CAAS,QAAQ,WAAW,CAAA;AAElE,QAAA,MAAM,YAAA,GAAeG,iBAAAA;AAAA,UACnB,OAAO,SAAA,EAAmB,GAAA,EAAa,EAAA,KAAgB;AACrD,YAAA,IAAI,CAAC,GAAA,EAAK;AACV,YAAA,IAAI;AACF,cAAA,MAAM,iBAAiB,GAAA,EAAK;AAAA,gBAC1B,SAAA;AAAA,gBACA,gBAAgB,OAAwB;AAAA,kBACtC,IAAA,EAAM,OAAA;AAAA,kBACN,OAAA,EAAS,CAAA;AAAA,kBACT,GAAA;AAAA,kBACA,WAAA,EAAa;AAAA,iBACf,CAAA;AAAA,gBACA,gBAAA,EAAkB,gBAAgB,EAAA,IAAM;AAAA,eACzC,CAAA;AAAA,YACH,SAAS,GAAA,EAAK;AACZ,cAAA,OAAA,CAAQ,KAAA,CAAM,wBAAwB,GAAG,CAAA;AAAA,YAC3C;AACA,YAAA,OAAA,EAAQ;AAAA,UACV,CAAA;AAAA,UACA,CAAC,GAAA,EAAK,cAAA,EAAgB,EAAA,EAAI,OAAO;AAAA,SACnC;AAEA,QAAAC,yBAAAA;AAAA,UACE,GAAA;AAAA,UACA,OAAO;AAAA,YACL,SAAA,EAAW,MAAM,SAAA,CAAU,OAAA,EAAS,WAAU,IAAK,KAAA;AAAA,YACnD,UAAA,EAAY,MAAM,SAAA,CAAU,OAAA,EAAS,YAAW,IAAK;AAAA,WACvD,CAAA;AAAA,UACA;AAAC,SACH;AAEA,QAAA,uBACET,eAAAA,CAAAC,mBAAAA,EAAA,EACE,QAAA,EAAA;AAAA,0BAAAC,cAAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cACC,WAAA;AAAA,cACA,mBAAA,EAAqB,cAAA;AAAA,cACrB,iBAAiB,CAAC,CAAA,KAAM,SAAA,CAAU,OAAA,EAAS,eAAe,CAAC,CAAA;AAAA,cAC3D,OAAA;AAAA,cACA,QAAA;AAAA,cACA,UAAA;AAAA,cACA,aAAA,EAAe,MAAM,aAAA,CAAc,KAAK;AAAA;AAAA,WAC1C;AAAA,0BACAA,cAAAA;AAAA,YAAC,aAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,SAAA;AAAA,cACL,CAAA,EAAG,CAAA;AAAA,cACH,CAAA,EAAG,CAAA;AAAA,cACH,cAAc,OAAA,CAAQ,YAAA;AAAA,cACtB,WAAA;AAAA,cACA,mBAAA,EAAqB,cAAA;AAAA,cACrB,QAAA,EAAU,YAAA;AAAA,cACV,OAAA;AAAA,cACA,eAAe,CAAC,QAAA;AAAA,cAChB,QAAA;AAAA,cACA,YAAA,EAAc,MAAM,aAAA,CAAc,IAAI;AAAA;AAAA;AACxC,SAAA,EACF,CAAA;AAAA,MAEJ;AAAA,KACF;AAAA,EAAA;AAAA,CAAA,CAAA;;;AC7FA,WAAA,EAAA;AAKA,UAAA,EAAA;AAKA,IAAMQ,eAAAA,GAAiBE,UAAA;AAAA,EAAK,MAC1B,0DAAiB,IAAA,CAAK,CAAC,OAAO,EAAE,OAAA,EAAS,CAAA,CAAE,cAAA,EAAe,CAAE;AAC9D,CAAA;AAEA,IAAM,SAAA,mBACJV,cAAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,IAAA,EAAK,MAAA,EAAO,IAAA,EAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,WAAA,EAAY,KAAA,EAAM,aAAA,EAAc,OAAA,EAAQ,cAAA,EAAe,OAAA,EAAQ,aAAA,EAAY,MAAA,EAC3J,QAAA,kBAAAA,cAAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,2BAAA,EAA4B,CAAA,EACtC,CAAA;AAGK,IAAM,UAAA,GAAwB;AAAA,EACnC,IAAA,EAAM,OAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,qCAAA;AAAA,EACd,WAAA,EAAa,SAAA;AAAA,EACb,aAAA,EAAe,qBAAA;AAAA,EACf,iBAAA,EAAmB,iBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,iBAAA,CAAkB,IAAI,CAAA,EAAG;AAC5B,MAAA,MAAM,IAAI,MAAM,yEAAiE,CAAA;AAAA,IACnF;AACA,IAAA,OAAO,gBAAA,CAAiB,IAAA,CAAK,GAAA,EAAK,IAAA,CAAK,WAAW,CAAA;AAAA,EACpD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,iBAAA,CAAkB,IAAI,CAAA,EAAG,OAAO,IAAA;AACrC,IAAA,MAAM,YAAY,MAAM,gBAAA,CAAiB,IAAA,CAAK,GAAA,EAAK,KAAK,WAAW,CAAA;AACnE,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,OAAA,GAAU,4BAAA,IACd,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AAEhF,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,EACtD,CAAA;AAAA,EACA,IAAA,EAAMQ;AACR","file":"latex.js","sourcesContent":["let cachedCss: string | null = null;\n\n// Absolute origin để inlined CSS có thể load fonts khi SVG render trong\n// Excalidraw / image context (relative paths fail trong nested URL contexts).\nfunction absoluteOrigin(): string {\n if (typeof window !== 'undefined' && window.location) return window.location.origin;\n return '';\n}\n\nasync function loadKatexCss(): Promise<string> {\n if (cachedCss !== null) return cachedCss;\n try {\n if (typeof fetch === 'function') {\n const res = await fetch('/katex.min.css');\n if (res.ok) {\n let css = await res.text();\n // Rewrite relative font URLs → absolute origin URLs.\n // KaTeX CSS uses url(fonts/...) — relative to /katex.min.css → /fonts/...\n // Trong SVG <foreignObject> được render thành image, relative resolves\n // tới page URL (/room/...) thay vì root, gây 404.\n const origin = absoluteOrigin();\n if (origin) {\n css = css.replace(/url\\((['\"]?)(fonts\\/)/g, `url($1${origin}/$2`);\n }\n cachedCss = css;\n return css;\n }\n }\n } catch {\n /* ignore */\n }\n cachedCss = '';\n return '';\n}\n\nexport async function renderLatexToSvg(src: string, displayMode: boolean): Promise<string> {\n const katex = await import('katex');\n const html = katex.default.renderToString(src, { displayMode, throwOnError: true, output: 'html' });\n\n const measureDiv = document.createElement('div');\n measureDiv.style.cssText = 'position:absolute;top:-9999px;left:-9999px;visibility:hidden;display:inline-block;';\n measureDiv.innerHTML = html;\n document.body.appendChild(measureDiv);\n const rect = measureDiv.getBoundingClientRect();\n const width = Math.ceil(rect.width) || 50;\n const height = Math.ceil(rect.height) || 20;\n document.body.removeChild(measureDiv);\n\n const cssText = await loadKatexCss();\n\n // KaTeX render với text màu đen mặc định. Excalidraw apply CSS filter\n // invert+hue-rotate trên canvas khi dark mode → text tự thành sáng. Đừng\n // override color ở đây vì sẽ đánh nhau với filter.\n return '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"' + width + '\" height=\"' + height + '\" viewBox=\"0 0 ' + width + ' ' + height + '\">' +\n '<foreignObject width=\"100%\" height=\"100%\">' +\n '<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"font-size:16px;line-height:1.2;\">' +\n '<style>' + cssText + '</style>' +\n html +\n '</div>' +\n '</foreignObject>' +\n '</svg>';\n}\n","import type { BaseStampCustomData } from '../shared/types';\n\nexport interface LatexCustomData extends BaseStampCustomData {\n kind: 'latex';\n version: 1;\n src: string;\n displayMode: boolean;\n}\n\nexport function isLatexCustomData(data: unknown): data is LatexCustomData {\n if (!data || typeof data !== 'object') return false;\n const d = data as Partial<LatexCustomData>;\n return d.kind === 'latex' && d.version === 1 && typeof d.src === 'string';\n}\n","'use client';\n\nimport React from 'react';\n\n// ---------- Shared shell (latex copy — geometry copy stays in src/stamp/StampLeftPanel.tsx) ----------\n\ninterface ShellProps {\n title: string;\n icon: React.ReactNode;\n onClose: () => void;\n children: React.ReactNode;\n isMobile?: boolean;\n drawerOpen?: boolean;\n onDrawerClose?: () => void;\n}\n\nfunction Shell({ title, icon, onClose, children, isMobile, drawerOpen, onDrawerClose }: ShellProps) {\n const mobileAttrs = isMobile\n ? {\n 'data-mobile-drawer': 'true',\n 'data-drawer-state': drawerOpen ? 'open' : 'closed',\n }\n : {};\n const handleHeaderClose = () => {\n if (isMobile) onDrawerClose?.();\n else onClose();\n };\n return (\n <>\n {isMobile && drawerOpen && (\n <div\n className=\"stamp-drawer-backdrop\"\n onPointerDown={onDrawerClose}\n aria-hidden=\"true\"\n />\n )}\n <aside\n role=\"complementary\"\n aria-label={title}\n aria-hidden={isMobile && !drawerOpen ? 'true' : undefined}\n data-testid=\"stamp-left-panel\"\n data-stamp-area=\"true\"\n {...mobileAttrs}\n className={\n isMobile\n ? 'stamp-drawer-mobile flex flex-col border-r border-slate-200 bg-white shadow-md'\n : 'absolute left-0 top-0 z-30 flex h-full w-60 flex-col border-r border-slate-200 bg-white shadow-md animate-in slide-in-from-left duration-200'\n }\n >\n <header className=\"flex items-center justify-between border-b border-slate-200 bg-gradient-to-r from-slate-50 to-white px-3 py-2\">\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-slate-800\">\n <span className=\"text-base leading-none\">{icon}</span>\n {title}\n </h3>\n <button\n onClick={handleHeaderClose}\n aria-label={isMobile ? 'Đóng ngăn công cụ' : 'Đóng'}\n className=\"rounded p-1 text-slate-500 transition hover:bg-slate-100 hover:text-slate-800\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n </svg>\n </button>\n </header>\n <div className=\"min-h-0 flex-1 overflow-y-auto p-3 space-y-4\">{children}</div>\n </aside>\n </>\n );\n}\n\nfunction Section({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <section>\n <h4 className=\"mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500\">\n {label}\n </h4>\n {children}\n </section>\n );\n}\n\n// ---------- LaTeX left panel ----------\n\ninterface LatexLeftPanelProps {\n displayMode: boolean;\n onDisplayModeChange: (b: boolean) => void;\n onInsertSnippet: (snippet: string) => void;\n onClose: () => void;\n isMobile?: boolean;\n drawerOpen?: boolean;\n onDrawerClose?: () => void;\n}\n\ninterface SnippetDef {\n label: string;\n preview: string;\n snippet: string;\n}\n\nconst SNIPPETS: { group: string; items: SnippetDef[] }[] = [\n {\n group: 'Phân số & luỹ thừa',\n items: [\n { label: 'Phân số', preview: 'a⁄b', snippet: '\\\\frac{a}{b}' },\n { label: 'Luỹ thừa', preview: 'x²', snippet: '^{2}' },\n { label: 'Chỉ số', preview: 'x₁', snippet: '_{1}' },\n { label: 'Căn', preview: '√x', snippet: '\\\\sqrt{x}' },\n { label: 'Căn n', preview: 'ⁿ√x', snippet: '\\\\sqrt[n]{x}' },\n ],\n },\n {\n group: 'Tổng & tích phân',\n items: [\n { label: 'Tổng', preview: 'Σ', snippet: '\\\\sum_{i=1}^{n}' },\n { label: 'Tích', preview: 'Π', snippet: '\\\\prod_{i=1}^{n}' },\n { label: 'Tích phân', preview: '∫', snippet: '\\\\int_{a}^{b}' },\n { label: 'Giới hạn', preview: 'lim', snippet: '\\\\lim_{x \\\\to 0}' },\n ],\n },\n {\n group: 'Ký hiệu',\n items: [\n { label: 'α', preview: 'α', snippet: '\\\\alpha' },\n { label: 'β', preview: 'β', snippet: '\\\\beta' },\n { label: 'π', preview: 'π', snippet: '\\\\pi' },\n { label: 'θ', preview: 'θ', snippet: '\\\\theta' },\n { label: '≠', preview: '≠', snippet: '\\\\neq' },\n { label: '≤', preview: '≤', snippet: '\\\\leq' },\n { label: '≥', preview: '≥', snippet: '\\\\geq' },\n { label: '∞', preview: '∞', snippet: '\\\\infty' },\n { label: '→', preview: '→', snippet: '\\\\to' },\n ],\n },\n];\n\nexport function LeftPanel({\n displayMode,\n onDisplayModeChange,\n onInsertSnippet,\n onClose,\n isMobile,\n drawerOpen,\n onDrawerClose,\n}: LatexLeftPanelProps) {\n return (\n <Shell\n title=\"Công thức LaTeX\"\n icon=\"∑\"\n onClose={onClose}\n isMobile={isMobile}\n drawerOpen={drawerOpen}\n onDrawerClose={onDrawerClose}\n >\n <Section label=\"Chế độ hiển thị\">\n <div className=\"grid grid-cols-2 gap-1.5\">\n <button\n type=\"button\"\n onClick={() => onDisplayModeChange(false)}\n aria-pressed={!displayMode}\n className={[\n 'rounded-md border px-2 py-1.5 text-xs transition',\n !displayMode\n ? 'border-emerald-500 bg-emerald-50 text-emerald-700 ring-1 ring-emerald-300'\n : 'border-slate-200 bg-white text-slate-700 hover:border-slate-300 hover:bg-slate-50',\n ].join(' ')}\n >\n <span className=\"block font-medium\">Inline</span>\n <span className=\"block text-[10px] text-slate-500\">$ ... $</span>\n </button>\n <button\n type=\"button\"\n onClick={() => onDisplayModeChange(true)}\n aria-pressed={displayMode}\n className={[\n 'rounded-md border px-2 py-1.5 text-xs transition',\n displayMode\n ? 'border-emerald-500 bg-emerald-50 text-emerald-700 ring-1 ring-emerald-300'\n : 'border-slate-200 bg-white text-slate-700 hover:border-slate-300 hover:bg-slate-50',\n ].join(' ')}\n >\n <span className=\"block font-medium\">Block</span>\n <span className=\"block text-[10px] text-slate-500\">$$ ... $$</span>\n </button>\n </div>\n </Section>\n\n {SNIPPETS.map((group) => (\n <Section key={group.group} label={group.group}>\n <div className=\"flex flex-wrap gap-1\">\n {group.items.map((s) => (\n <button\n key={s.snippet}\n type=\"button\"\n data-snippet={s.snippet}\n onClick={() => onInsertSnippet(s.snippet)}\n title={s.snippet}\n className=\"rounded border border-slate-200 bg-white px-2 py-1 text-xs text-slate-700 transition hover:border-emerald-300 hover:bg-emerald-50 hover:text-emerald-700\"\n >\n {s.preview}\n </button>\n ))}\n </div>\n </Section>\n ))}\n\n <Section label=\"Phím tắt\">\n <div className=\"flex flex-wrap gap-2 text-[11px] text-slate-600\">\n <span className=\"inline-flex items-center gap-1\">\n <kbd className=\"rounded border border-slate-300 bg-slate-50 px-1.5 py-0.5 font-mono\">Enter</kbd>\n chèn\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <kbd className=\"rounded border border-slate-300 bg-slate-50 px-1.5 py-0.5 font-mono\">Esc</kbd>\n đóng\n </span>\n </div>\n </Section>\n </Shell>\n );\n}\n\n// Back-compat alias\nexport { LeftPanel as LatexLeftPanel };\n","'use client';\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { renderLatexToSvg } from '../render';\n\ninterface Props {\n /**\n * Legacy: vị trí absolute x/y nếu cần (test). Khi cả 2 = 0 và `centered` !== false,\n * popover sẽ tự center floating ở giữa khu vực bảng. Khi `withLeftPanel` = true,\n * vị trí center được offset 120px để chừa chỗ panel trái.\n */\n x: number;\n y: number;\n initialValue: string;\n onInsert: (svgString: string, src: string, displayMode: boolean) => void;\n onClose: () => void;\n /** Khi controlled từ parent (StampLeftPanel), parent set giá trị này. */\n displayMode?: boolean;\n onDisplayModeChange?: (b: boolean) => void;\n /** Khi true, position center offset cho panel trái. */\n withLeftPanel?: boolean;\n /** Mobile mode: full-screen + hamburger header. */\n isMobile?: boolean;\n /** Trigger mở snippet drawer trên mobile. */\n onOpenDrawer?: () => void;\n}\n\nexport interface EditorPopoverHandle {\n /** Chèn snippet vào vị trí con trỏ trong textbox. */\n insertAtCursor: (snippet: string) => void;\n /** Có content hợp lệ để chèn không (input không rỗng + preview ok). */\n hasContent: () => boolean;\n /** Trigger insert programmatically — return true nếu chèn thành công. */\n tryInsert: () => boolean;\n}\n\nconst DEBOUNCE_MS = 100;\n\nexport const EditorPopover = forwardRef<EditorPopoverHandle, Props>(function EditorPopover(\n {\n x,\n y,\n initialValue,\n onInsert,\n onClose,\n displayMode: controlledDisplayMode,\n onDisplayModeChange,\n withLeftPanel = false,\n isMobile = false,\n onOpenDrawer,\n },\n ref,\n) {\n const [value, setValue] = useState(initialValue);\n const [internalDisplayMode] = useState(false);\n const displayMode = controlledDisplayMode ?? internalDisplayMode;\n // onDisplayModeChange chỉ dùng khi controlled từ parent — không cần local setter\n void onDisplayModeChange;\n\n const [previewSvg, setPreviewSvg] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(async () => {\n try {\n const svg = await renderLatexToSvg(value, displayMode);\n setPreviewSvg(svg);\n setError(null);\n } catch (err) {\n setPreviewSvg(null);\n setError((err as Error).message);\n }\n }, DEBOUNCE_MS);\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n };\n }, [value, displayMode]);\n\n const handleInsert = useCallback(() => {\n if (!previewSvg) return;\n onInsert(previewSvg, value, displayMode);\n }, [previewSvg, value, displayMode, onInsert]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleInsert();\n }\n },\n [onClose, handleInsert],\n );\n\n // Imperative API: snippet button trong panel trái gọi vào, click-outside auto-insert.\n useImperativeHandle(\n ref,\n () => ({\n insertAtCursor: (snippet: string) => {\n const el = inputRef.current;\n if (!el) {\n setValue((v) => v + snippet);\n return;\n }\n const start = el.selectionStart ?? value.length;\n const end = el.selectionEnd ?? value.length;\n const next = value.slice(0, start) + snippet + value.slice(end);\n setValue(next);\n requestAnimationFrame(() => {\n el.focus();\n const pos = start + snippet.length;\n try {\n el.setSelectionRange(pos, pos);\n } catch {\n /* ignore */\n }\n });\n },\n hasContent: () => value.trim().length > 0 && !!previewSvg && !error,\n tryInsert: () => {\n if (!previewSvg || error || !value.trim()) return false;\n onInsert(previewSvg, value, displayMode);\n return true;\n },\n }),\n [value, previewSvg, error, displayMode, onInsert],\n );\n\n // Position: nếu x/y > 0 → dùng legacy absolute (cho tests cũ). Còn không thì center floating.\n const isLegacyPosition = x > 0 || y > 0;\n const wrapperStyle: React.CSSProperties = isMobile\n ? { position: 'fixed', inset: 0, zIndex: 50 }\n : isLegacyPosition\n ? { position: 'absolute', top: y, left: x, zIndex: 50 }\n : {\n position: 'absolute',\n top: '50%',\n left: withLeftPanel ? 'calc(50% + 120px)' : '50%',\n transform: 'translate(-50%, -50%)',\n zIndex: 50,\n };\n\n return (\n <div\n style={wrapperStyle}\n data-stamp-area=\"true\"\n data-mobile-editor={isMobile ? 'true' : undefined}\n className={\n isMobile\n ? 'flex h-full w-full flex-col bg-white'\n : 'w-[420px] max-w-[calc(100vw-280px)] rounded-lg border border-slate-300 bg-white shadow-2xl ring-1 ring-black/5'\n }\n role=\"dialog\"\n aria-label=\"Nhập công thức LaTeX\"\n >\n <header className={`flex items-center gap-2 border-b border-slate-200 bg-gradient-to-r from-indigo-600 to-purple-600 px-3 py-2 text-white${isMobile ? '' : ' rounded-t-lg'}`}>\n {isMobile && (\n <button\n type=\"button\"\n onClick={onOpenDrawer}\n aria-label=\"Mở ngăn snippet\"\n className=\"-ml-1 inline-flex h-10 w-10 items-center justify-center rounded transition hover:bg-white/15\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" />\n <line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\" />\n <line x1=\"4\" y1=\"18\" x2=\"20\" y2=\"18\" />\n </svg>\n </button>\n )}\n <h3 className=\"flex flex-1 items-center gap-2 text-sm font-semibold\">\n <span className=\"text-base leading-none\">∑</span>\n Công thức LaTeX\n </h3>\n {isMobile && (\n <button\n type=\"button\"\n onClick={handleInsert}\n disabled={!previewSvg || !!error}\n data-testid=\"latex-insert-btn-mobile\"\n className=\"rounded bg-white/15 px-3 py-1.5 text-xs font-semibold transition hover:bg-white/25 disabled:opacity-50\"\n >\n Chèn\n </button>\n )}\n <button\n onClick={onClose}\n aria-label=\"Đóng\"\n className=\"inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n </svg>\n </button>\n </header>\n <div className={`space-y-2 p-3${isMobile ? ' flex min-h-0 flex-1 flex-col' : ''}`}>\n <input\n ref={inputRef}\n type=\"text\"\n role=\"textbox\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Vd: \\frac{a^2+b^2}{c}\"\n className={`w-full rounded border border-slate-300 px-2 py-1.5 font-mono outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-200${\n isMobile ? ' min-h-[44px] text-base' : ' text-sm'\n }`}\n autoFocus\n />\n <div\n className={[\n 'flex items-center justify-center rounded border p-3 text-center',\n isMobile ? 'min-h-0 flex-1 overflow-auto' : 'min-h-[64px]',\n error ? 'border-rose-300 bg-rose-50 text-rose-700' : 'border-slate-200 bg-slate-50',\n ].join(' ')}\n >\n {error ? (\n <span className=\"text-xs\">Lỗi: {error.slice(0, 80)}</span>\n ) : previewSvg ? (\n <span dangerouslySetInnerHTML={{ __html: previewSvg }} />\n ) : (\n <span className=\"text-xs text-slate-400\">(xem trước)</span>\n )}\n </div>\n {!isMobile && (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-[11px] text-slate-500\">\n {displayMode ? 'Block' : 'Inline'} · Enter để chèn\n </span>\n <div className=\"flex gap-2\">\n <button\n onClick={onClose}\n className=\"rounded border border-slate-300 bg-white px-3 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-100\"\n >\n Huỷ\n </button>\n <button\n onClick={handleInsert}\n disabled={!previewSvg || !!error}\n className=\"rounded bg-indigo-600 px-3 py-1 text-xs font-medium text-white transition hover:bg-indigo-700 disabled:opacity-50\"\n >\n Chèn\n </button>\n </div>\n </div>\n )}\n {isMobile && (\n <div className=\"text-center text-[11px] text-slate-500\">\n {displayMode ? 'Block' : 'Inline'} · Bấm Chèn ở thanh trên\n </div>\n )}\n </div>\n </div>\n );\n});\n\n// Back-compat aliases\nexport { EditorPopover as LatexEditorPopover };\nexport type { EditorPopoverHandle as LatexEditorHandle };\n","export interface SvgImageResult {\n dataURL: string;\n fileId: string;\n width: number;\n height: number;\n mimeType: 'image/svg+xml';\n}\n\nasync function hashString(input: string): Promise<string> {\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const buf = new TextEncoder().encode(input);\n const digest = await crypto.subtle.digest('SHA-256', buf);\n return Array.from(new Uint8Array(digest)).slice(0, 16).map(b => b.toString(16).padStart(2, '0')).join('');\n }\n // Double-hash FNV-1a (32-bit chained) → 16 hex chars\n let h1 = 0x811c9dc5;\n let h2 = 0xcbf29ce4;\n for (let i = 0; i < input.length; i++) {\n const c = input.charCodeAt(i);\n h1 ^= c;\n h1 = Math.imul(h1, 0x01000193);\n h2 ^= c + i;\n h2 = Math.imul(h2, 0x100000001b3 & 0xffffffff);\n }\n return (h1 >>> 0).toString(16).padStart(8, '0') + (h2 >>> 0).toString(16).padStart(8, '0');\n}\n\nfunction parseSize(svg: string, attr: 'width' | 'height'): number {\n const re = new RegExp(`<svg[^>]*\\\\s${attr}=\"(\\\\d+(?:\\\\.\\\\d+)?)`, 'i');\n const m = svg.match(re);\n if (m) return Math.max(1, Math.round(parseFloat(m[1])));\n // Fallback: try viewBox\n const vb = svg.match(/viewBox=\"([\\d.\\s-]+)\"/i);\n if (vb) {\n const parts = vb[1].trim().split(/\\s+/).map(parseFloat);\n if (parts.length === 4) return Math.max(1, Math.round(attr === 'width' ? parts[2] : parts[3]));\n }\n return attr === 'width' ? 200 : 100;\n}\n\n// SVG → image element data. Skips canvas rasterization entirely (canvas with\n// foreignObject + external resources gets tainted by browser security model,\n// blocking toDataURL). Excalidraw renders SVG natively via mimeType 'image/svg+xml'.\nexport async function svgToImageElement(svg: string): Promise<SvgImageResult> {\n const width = parseSize(svg, 'width');\n const height = parseSize(svg, 'height');\n // Use UTF-8 safe base64 encoding (btoa fails on non-Latin1 chars)\n const utf8 = unescape(encodeURIComponent(svg));\n const dataURL = 'data:image/svg+xml;base64,' + btoa(utf8);\n const fileId = await hashString(dataURL);\n return { dataURL, fileId, width, height, mimeType: 'image/svg+xml' };\n}\n","import { svgToImageElement } from './svgToImage';\nimport type { ExcalidrawElement } from '../../types';\n\n// Excalidraw imperative API — không có public type chính xác. Giữ untyped ở\n// boundary và yêu cầu caller pass đúng instance.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ExApi = any;\n\nexport interface InsertStampImageOptions {\n /** SVG string sẵn sàng render (geometry export hoặc katex render). */\n svgString: string;\n /**\n * Factory tạo customData từ kích thước SVG vừa đo được. Đặt làm factory để\n * các stamp loại khác nhau (geometry cần svgWidth/svgHeight, latex không cần)\n * đều có thể chèn data tuỳ ý.\n */\n makeCustomData: (width: number, height: number) => unknown;\n /** Nếu đang re-edit, id của element cũ — sẽ update thay vì tạo mới. */\n editingElementId?: string | null;\n /** Vị trí gốc (lúc tạo mới). Bỏ qua khi đang re-edit. */\n position?: { x?: number; y?: number };\n}\n\nexport interface InsertStampImageResult {\n fileId: string;\n width: number;\n height: number;\n /** Element id (mới hoặc cũ tuỳ flow). */\n elementId: string;\n}\n\n// Bỏ qua appState (selectedElementIds + croppingElementId) sau khi insert để\n// Excalidraw không tự động bật crop mode cho element vừa thêm → tránh trigger\n// crop intercept handler vô tận.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst clearAppStateAfterInsert = (): any => ({\n selectedElementIds: {},\n croppingElementId: null,\n});\n\nfunction buildStampImageElement(\n api: ExApi,\n fileId: string,\n width: number,\n height: number,\n customData: unknown,\n x?: number,\n y?: number,\n) {\n const appState =\n api?.getAppState() ?? { scrollX: 0, scrollY: 0, width: 800, height: 600, zoom: { value: 1 } };\n const cx =\n x ?? appState.scrollX + (appState.width ?? 800) / 2 / (appState.zoom?.value ?? 1) - width / 2;\n const cy =\n y ?? appState.scrollY + (appState.height ?? 600) / 2 / (appState.zoom?.value ?? 1) - height / 2;\n return {\n type: 'image' as const,\n id: 'stamp_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8),\n x: cx,\n y: cy,\n width,\n height,\n fileId,\n customData,\n angle: 0,\n strokeColor: 'transparent',\n backgroundColor: 'transparent',\n fillStyle: 'solid',\n strokeWidth: 1,\n strokeStyle: 'solid',\n roughness: 0,\n opacity: 100,\n groupIds: [],\n roundness: null,\n seed: Math.floor(Math.random() * 1e9),\n versionNonce: 0,\n version: 1,\n isDeleted: false,\n boundElements: null,\n updated: Date.now(),\n link: null,\n locked: false,\n status: 'saved',\n scale: [1, 1],\n };\n}\n\n/**\n * Chèn (hoặc thay thế) một stamp image vào Excalidraw scene.\n *\n * Flow:\n * 1. svgToImageElement(svg) → fileId + dataURL + kích thước\n * 2. api.addFiles([...]) — đăng ký SVG dưới fileId\n * 3. Nếu editingElementId → update element cũ (giữ position, đổi fileId+customData+size)\n * Còn lại → tạo image element mới ở giữa viewport (hoặc position truyền vào)\n *\n * Đoạn này trước đây nằm 2 chỗ (handleGeometryInsert + handleLatexInsert),\n * chỉ khác customData. Gộp lại để: thêm stamp type mới chỉ cần truyền\n * `makeCustomData`.\n */\nexport async function insertStampImage(\n api: ExApi,\n opts: InsertStampImageOptions,\n): Promise<InsertStampImageResult> {\n const { dataURL, fileId, width, height, mimeType } = await svgToImageElement(opts.svgString);\n api.addFiles([{ id: fileId, dataURL, mimeType, created: Date.now() }]);\n const customData = opts.makeCustomData(width, height);\n\n const elements = api.getSceneElements() as readonly ExcalidrawElement[];\n const editingId = opts.editingElementId ?? null;\n\n if (editingId) {\n const updated = elements.map((e) =>\n e.id === editingId ? { ...e, fileId, customData, width, height } : e,\n );\n api.updateScene({ elements: updated, appState: clearAppStateAfterInsert() });\n return { fileId, width, height, elementId: editingId };\n }\n\n const newElement = buildStampImageElement(\n api,\n fileId,\n width,\n height,\n customData,\n opts.position?.x,\n opts.position?.y,\n );\n api.updateScene({\n elements: [...elements, newElement],\n appState: clearAppStateAfterInsert(),\n });\n return { fileId, width, height, elementId: newElement.id };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nconst MOBILE_QUERY = '(max-width: 768px)';\nconst NO_HOVER_QUERY = '(hover: none)';\n\nexport interface MobileState {\n /** Viewport ≤ 768px — dùng để chuyển full-screen modal + drawer. */\n isMobile: boolean;\n /** Device không có hover (touch-only) — dùng để ẩn hover tooltips. */\n isTouchOnly: boolean;\n}\n\nfunction readMatch(query: string): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n try {\n return window.matchMedia(query).matches;\n } catch {\n return false;\n }\n}\n\n/**\n * SSR-safe mobile detection qua matchMedia. Trả về `{ isMobile, isTouchOnly }`.\n * Re-render khi viewport resize hoặc input modality đổi (e.g., gắn Bluetooth mouse).\n */\nexport function useIsMobile(): MobileState {\n const [state, setState] = useState<MobileState>(() => ({\n isMobile: readMatch(MOBILE_QUERY),\n isTouchOnly: readMatch(NO_HOVER_QUERY),\n }));\n\n useEffect(() => {\n if (typeof window === 'undefined' || !window.matchMedia) return;\n const mql = window.matchMedia(MOBILE_QUERY);\n const tql = window.matchMedia(NO_HOVER_QUERY);\n const update = () => {\n setState({ isMobile: mql.matches, isTouchOnly: tql.matches });\n };\n update();\n mql.addEventListener('change', update);\n tql.addEventListener('change', update);\n return () => {\n mql.removeEventListener('change', update);\n tql.removeEventListener('change', update);\n };\n }, []);\n\n return state;\n}\n","'use client';\n\nimport {\n forwardRef,\n useCallback,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { LeftPanel as LatexLeftPanel } from './editor/LeftPanel';\nimport {\n EditorPopover as LatexEditorPopover,\n type EditorPopoverHandle as LatexEditorHandle,\n} from './editor/EditorPopover';\nimport { insertStampImage } from '../shared/insertImage';\nimport { useIsMobile } from '../shared/useIsMobile';\nimport { isLatexCustomData, type LatexCustomData } from './types';\nimport type { StampHostProps, StampHostHandle } from '../shared/types';\n\nexport const LatexStampHost = forwardRef<StampHostHandle, StampHostProps>(\n function LatexStampHost({ api, editingElement, onClose }, ref) {\n const editorRef = useRef<LatexEditorHandle | null>(null);\n const { isMobile } = useIsMobile();\n const [drawerOpen, setDrawerOpen] = useState(false);\n\n const initial = useMemo(() => {\n if (editingElement && isLatexCustomData(editingElement.customData)) {\n return {\n initialValue: editingElement.customData.src,\n displayMode: !!editingElement.customData.displayMode,\n };\n }\n return { initialValue: '', displayMode: false };\n }, [editingElement]);\n\n const [displayMode, setDisplayMode] = useState(initial.displayMode);\n\n const handleInsert = useCallback(\n async (svgString: string, src: string, dm: boolean) => {\n if (!api) return;\n try {\n await insertStampImage(api, {\n svgString,\n makeCustomData: (): LatexCustomData => ({\n kind: 'latex',\n version: 1,\n src,\n displayMode: dm,\n }),\n editingElementId: editingElement?.id ?? null,\n });\n } catch (err) {\n console.error('Latex insert failed:', err);\n }\n onClose();\n },\n [api, editingElement?.id, onClose],\n );\n\n useImperativeHandle(\n ref,\n () => ({\n tryInsert: () => editorRef.current?.tryInsert() ?? false,\n hasContent: () => editorRef.current?.hasContent() ?? false,\n }),\n [],\n );\n\n return (\n <>\n <LatexLeftPanel\n displayMode={displayMode}\n onDisplayModeChange={setDisplayMode}\n onInsertSnippet={(s) => editorRef.current?.insertAtCursor(s)}\n onClose={onClose}\n isMobile={isMobile}\n drawerOpen={drawerOpen}\n onDrawerClose={() => setDrawerOpen(false)}\n />\n <LatexEditorPopover\n ref={editorRef}\n x={0}\n y={0}\n initialValue={initial.initialValue}\n displayMode={displayMode}\n onDisplayModeChange={setDisplayMode}\n onInsert={handleInsert}\n onClose={onClose}\n withLeftPanel={!isMobile}\n isMobile={isMobile}\n onOpenDrawer={() => setDrawerOpen(true)}\n />\n </>\n );\n },\n);\n","'use client';\n\nimport { lazy } from 'react';\nimport { renderLatexToSvg } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport { isLatexCustomData, type LatexCustomData } from './types';\n\nexport { isLatexCustomData };\nexport type { LatexCustomData };\n\nconst LatexStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.LatexStampHost })),\n);\n\nconst LatexIcon = (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M17 5 H7 L13 12 L7 19 H17\" />\n </svg>\n);\n\nexport const latexStamp: StampType = {\n kind: 'latex',\n shortcutKey: 'l',\n toolbarLabel: 'L',\n toolbarTitle: 'Chèn công thức LaTeX (L)',\n toolbarIcon: LatexIcon,\n toolbarTestId: 'stamp-toolbar-latex',\n matchesCustomData: isLatexCustomData,\n async renderSvgFromCustomData(data) {\n if (!isLatexCustomData(data)) {\n throw new Error('latexStamp.renderSvgFromCustomData: customData không phải latex');\n }\n return renderLatexToSvg(data.src, data.displayMode);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as LatexCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isLatexCustomData(data)) return null;\n const svgString = await renderLatexToSvg(data.src, data.displayMode);\n const utf8 = unescape(encodeURIComponent(svgString));\n const dataURL = 'data:image/svg+xml;base64,' + (\n typeof btoa !== 'undefined' ? btoa(utf8) : Buffer.from(utf8).toString('base64')\n );\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n },\n Host: LatexStampHost,\n};\n"]}
package/dist/latex.mjs ADDED
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ export { latexStamp } from './chunk-7P7SQFOW.mjs';
3
+ export { isLatexCustomData } from './chunk-X5R72SSJ.mjs';
4
+ import './chunk-BJTO5JO5.mjs';
5
+ //# sourceMappingURL=latex.mjs.map
6
+ //# sourceMappingURL=latex.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"latex.mjs"}
@@ -0,0 +1,110 @@
1
+ import { ReactNode, ComponentType, RefAttributes } from 'react';
2
+ import { ExcalidrawElement } from '@excalidraw/excalidraw/element/types';
3
+
4
+ /**
5
+ * Kết quả trả về từ `restoreFileFromCustomData`. Chứa đủ thông tin để
6
+ * consumer gọi `api.addFiles(...)`.
7
+ */
8
+ interface RestoredStampFile {
9
+ fileId: string;
10
+ dataURL: string;
11
+ mimeType: 'image/svg+xml' | 'image/png';
12
+ }
13
+ /**
14
+ * Tối thiểu mọi custom data của stamp cần có. Các stamp cụ thể (geometry,
15
+ * latex, ...) extend interface này với fields riêng.
16
+ */
17
+ interface BaseStampCustomData {
18
+ kind: string;
19
+ version: number;
20
+ }
21
+ /**
22
+ * Props mà mỗi StampHost nhận từ Whiteboard. Host component tự
23
+ * quản lý state nội bộ (panel ref, undo stack, displayMode...) — main view
24
+ * chỉ điều phối show/hide.
25
+ */
26
+ interface StampHostProps {
27
+ api: any;
28
+ /**
29
+ * Element đang re-edit (double-click) hoặc null nếu đang tạo mới.
30
+ * Host tự parse customData để load state ban đầu.
31
+ */
32
+ editingElement: {
33
+ id: string;
34
+ customData: unknown;
35
+ } | null;
36
+ /** Đóng stamp panel (gọi sau khi insert hoặc khi user huỷ). */
37
+ onClose: () => void;
38
+ /** Dark theme flag. */
39
+ isDark: boolean;
40
+ }
41
+ /**
42
+ * Imperative API mà main view truy cập qua ref:
43
+ * - tryInsert(): khi user click ra ngoài → auto-commit nếu valid.
44
+ * Trả về true nếu chèn thành công, false nếu chưa có nội dung.
45
+ * - hasContent(): có nội dung để chèn không.
46
+ */
47
+ interface StampHostHandle {
48
+ tryInsert(): boolean;
49
+ hasContent(): boolean;
50
+ }
51
+ /**
52
+ * Component contract của Host. Chấp nhận cả `forwardRef` thông thường lẫn
53
+ * `React.lazy(() => import('./host'))` (LazyExoticComponent forwards ref).
54
+ */
55
+ type StampHostComponent = ComponentType<StampHostProps & RefAttributes<StampHostHandle>>;
56
+ /**
57
+ * Định nghĩa 1 loại stamp. Mỗi stamp khai báo:
58
+ * - kind: unique string (khớp với customData.kind)
59
+ * - phím tắt + UI toolbar
60
+ * - cách nhận biết customData thuộc về stamp này (matchesCustomData)
61
+ * - cách re-render SVG từ customData (cho restore sau reload)
62
+ * - Host component: bọc trọn editor + left panel + insert logic
63
+ *
64
+ * Main view dispatch generic: `<stamp.Host ... />` — không cần biết kind.
65
+ */
66
+ interface StampType {
67
+ /** Unique kind. VD: 'geometry', 'latex'. Phải khớp với customData.kind. */
68
+ kind: string;
69
+ /** Phím tắt mở/đóng stamp (lowercase, 1 ký tự). VD: 'g', 'l'. */
70
+ shortcutKey: string;
71
+ /** Chữ hiển thị overlay góc dưới nút toolbar (e.g. "G"). */
72
+ toolbarLabel: string;
73
+ /** Tooltip + aria-label của nút toolbar. */
74
+ toolbarTitle: string;
75
+ /** Icon SVG (ReactNode) trong nút toolbar. */
76
+ toolbarIcon: ReactNode;
77
+ /** Test data-testid cho nút toolbar (optional). */
78
+ toolbarTestId?: string;
79
+ /** Type guard: customData có thuộc về stamp này không. */
80
+ matchesCustomData(data: unknown): boolean;
81
+ /**
82
+ * Re-render SVG từ customData. Dùng khi restore math-stamp file sau reload
83
+ * page (Excalidraw không persist binary file payload, chỉ giữ fileId trong
84
+ * element). SVG render với light palette (nét đậm) — Excalidraw tự đảo
85
+ * màu trong dark mode qua CSS filter.
86
+ */
87
+ renderSvgFromCustomData(data: unknown): Promise<string>;
88
+ /**
89
+ * Regenerate file SVG/PNG cho element thuộc stamp này khi reload từ persisted
90
+ * snapshot. Trả về `RestoredStampFile` để consumer gọi `api.addFiles`, hoặc
91
+ * `null` nếu element không cần file (vd stamp chỉ là text overlay).
92
+ *
93
+ * Khi method này có mặt, `restoreMissingStampFiles` sẽ ưu tiên gọi method
94
+ * này thay vì dùng `renderSvgFromCustomData`. Stamp tự chịu trách nhiệm lấy
95
+ * `fileId` từ element và render file.
96
+ */
97
+ restoreFileFromCustomData?: (element: ExcalidrawElement) => Promise<RestoredStampFile | null>;
98
+ /**
99
+ * Host component bọc toàn bộ UI editing (panel + left panel + insert
100
+ * handler). Whiteboard mount Host khi activeStamp khớp kind.
101
+ */
102
+ Host: StampHostComponent;
103
+ /**
104
+ * Đánh dấu stamp chưa production-ready. Consumer mặc định bỏ qua
105
+ * (xem `DEFAULT_STAMPS` chỉ gồm stamp không `experimental`).
106
+ */
107
+ experimental?: boolean;
108
+ }
109
+
110
+ export type { BaseStampCustomData as B, StampType as S };
@@ -0,0 +1,110 @@
1
+ import { ReactNode, ComponentType, RefAttributes } from 'react';
2
+ import { ExcalidrawElement } from '@excalidraw/excalidraw/element/types';
3
+
4
+ /**
5
+ * Kết quả trả về từ `restoreFileFromCustomData`. Chứa đủ thông tin để
6
+ * consumer gọi `api.addFiles(...)`.
7
+ */
8
+ interface RestoredStampFile {
9
+ fileId: string;
10
+ dataURL: string;
11
+ mimeType: 'image/svg+xml' | 'image/png';
12
+ }
13
+ /**
14
+ * Tối thiểu mọi custom data của stamp cần có. Các stamp cụ thể (geometry,
15
+ * latex, ...) extend interface này với fields riêng.
16
+ */
17
+ interface BaseStampCustomData {
18
+ kind: string;
19
+ version: number;
20
+ }
21
+ /**
22
+ * Props mà mỗi StampHost nhận từ Whiteboard. Host component tự
23
+ * quản lý state nội bộ (panel ref, undo stack, displayMode...) — main view
24
+ * chỉ điều phối show/hide.
25
+ */
26
+ interface StampHostProps {
27
+ api: any;
28
+ /**
29
+ * Element đang re-edit (double-click) hoặc null nếu đang tạo mới.
30
+ * Host tự parse customData để load state ban đầu.
31
+ */
32
+ editingElement: {
33
+ id: string;
34
+ customData: unknown;
35
+ } | null;
36
+ /** Đóng stamp panel (gọi sau khi insert hoặc khi user huỷ). */
37
+ onClose: () => void;
38
+ /** Dark theme flag. */
39
+ isDark: boolean;
40
+ }
41
+ /**
42
+ * Imperative API mà main view truy cập qua ref:
43
+ * - tryInsert(): khi user click ra ngoài → auto-commit nếu valid.
44
+ * Trả về true nếu chèn thành công, false nếu chưa có nội dung.
45
+ * - hasContent(): có nội dung để chèn không.
46
+ */
47
+ interface StampHostHandle {
48
+ tryInsert(): boolean;
49
+ hasContent(): boolean;
50
+ }
51
+ /**
52
+ * Component contract của Host. Chấp nhận cả `forwardRef` thông thường lẫn
53
+ * `React.lazy(() => import('./host'))` (LazyExoticComponent forwards ref).
54
+ */
55
+ type StampHostComponent = ComponentType<StampHostProps & RefAttributes<StampHostHandle>>;
56
+ /**
57
+ * Định nghĩa 1 loại stamp. Mỗi stamp khai báo:
58
+ * - kind: unique string (khớp với customData.kind)
59
+ * - phím tắt + UI toolbar
60
+ * - cách nhận biết customData thuộc về stamp này (matchesCustomData)
61
+ * - cách re-render SVG từ customData (cho restore sau reload)
62
+ * - Host component: bọc trọn editor + left panel + insert logic
63
+ *
64
+ * Main view dispatch generic: `<stamp.Host ... />` — không cần biết kind.
65
+ */
66
+ interface StampType {
67
+ /** Unique kind. VD: 'geometry', 'latex'. Phải khớp với customData.kind. */
68
+ kind: string;
69
+ /** Phím tắt mở/đóng stamp (lowercase, 1 ký tự). VD: 'g', 'l'. */
70
+ shortcutKey: string;
71
+ /** Chữ hiển thị overlay góc dưới nút toolbar (e.g. "G"). */
72
+ toolbarLabel: string;
73
+ /** Tooltip + aria-label của nút toolbar. */
74
+ toolbarTitle: string;
75
+ /** Icon SVG (ReactNode) trong nút toolbar. */
76
+ toolbarIcon: ReactNode;
77
+ /** Test data-testid cho nút toolbar (optional). */
78
+ toolbarTestId?: string;
79
+ /** Type guard: customData có thuộc về stamp này không. */
80
+ matchesCustomData(data: unknown): boolean;
81
+ /**
82
+ * Re-render SVG từ customData. Dùng khi restore math-stamp file sau reload
83
+ * page (Excalidraw không persist binary file payload, chỉ giữ fileId trong
84
+ * element). SVG render với light palette (nét đậm) — Excalidraw tự đảo
85
+ * màu trong dark mode qua CSS filter.
86
+ */
87
+ renderSvgFromCustomData(data: unknown): Promise<string>;
88
+ /**
89
+ * Regenerate file SVG/PNG cho element thuộc stamp này khi reload từ persisted
90
+ * snapshot. Trả về `RestoredStampFile` để consumer gọi `api.addFiles`, hoặc
91
+ * `null` nếu element không cần file (vd stamp chỉ là text overlay).
92
+ *
93
+ * Khi method này có mặt, `restoreMissingStampFiles` sẽ ưu tiên gọi method
94
+ * này thay vì dùng `renderSvgFromCustomData`. Stamp tự chịu trách nhiệm lấy
95
+ * `fileId` từ element và render file.
96
+ */
97
+ restoreFileFromCustomData?: (element: ExcalidrawElement) => Promise<RestoredStampFile | null>;
98
+ /**
99
+ * Host component bọc toàn bộ UI editing (panel + left panel + insert
100
+ * handler). Whiteboard mount Host khi activeStamp khớp kind.
101
+ */
102
+ Host: StampHostComponent;
103
+ /**
104
+ * Đánh dấu stamp chưa production-ready. Consumer mặc định bỏ qua
105
+ * (xem `DEFAULT_STAMPS` chỉ gồm stamp không `experimental`).
106
+ */
107
+ experimental?: boolean;
108
+ }
109
+
110
+ export type { BaseStampCustomData as B, StampType as S };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xom11/whiteboard",
3
- "version": "0.6.5",
3
+ "version": "0.9.1",
4
4
  "private": false,
5
5
  "description": "Excalidraw + JSXGraph + KaTeX whiteboard component (drawing, geometry stamps, LaTeX stamps).",
6
6
  "license": "MIT",
@@ -13,6 +13,26 @@
13
13
  "import": "./dist/index.mjs",
14
14
  "require": "./dist/index.js"
15
15
  },
16
+ "./geometry-2d": {
17
+ "types": "./dist/geometry-2d.d.ts",
18
+ "import": "./dist/geometry-2d.mjs",
19
+ "require": "./dist/geometry-2d.js"
20
+ },
21
+ "./geometry-3d": {
22
+ "types": "./dist/geometry-3d.d.ts",
23
+ "import": "./dist/geometry-3d.mjs",
24
+ "require": "./dist/geometry-3d.js"
25
+ },
26
+ "./latex": {
27
+ "types": "./dist/latex.d.ts",
28
+ "import": "./dist/latex.mjs",
29
+ "require": "./dist/latex.js"
30
+ },
31
+ "./graph-2d": {
32
+ "types": "./dist/graph-2d.d.ts",
33
+ "import": "./dist/graph-2d.mjs",
34
+ "require": "./dist/graph-2d.js"
35
+ },
16
36
  "./styles.css": "./dist/index.css"
17
37
  },
18
38
  "files": [
@@ -30,21 +50,20 @@
30
50
  "build": "tsup && node scripts/inject-use-client.mjs",
31
51
  "dev": "tsup --watch --onSuccess \"node scripts/inject-use-client.mjs\"",
32
52
  "test": "jest",
53
+ "test:e2e": "playwright test",
33
54
  "typecheck": "tsc --noEmit",
34
55
  "clean": "rm -rf dist .yalc yalc.lock",
35
56
  "demo": "vite --config scripts/demo/vite.config.ts"
36
57
  },
37
58
  "peerDependencies": {
38
- "next": ">=14.0.0",
39
- "react": ">=18.0.0",
40
- "react-dom": ">=18.0.0"
41
- },
42
- "dependencies": {
43
59
  "@excalidraw/excalidraw": "^0.18.1",
44
60
  "jsxgraph": "^1.12.2",
45
- "katex": "^0.16.45"
61
+ "katex": "^0.16.45",
62
+ "react": ">=18.0.0",
63
+ "react-dom": ">=18.0.0"
46
64
  },
47
65
  "devDependencies": {
66
+ "@playwright/test": "^1.60.0",
48
67
  "@tailwindcss/vite": "^4.3.0",
49
68
  "@testing-library/jest-dom": "^6.9.1",
50
69
  "@testing-library/react": "^16.3.2",