@templatical/editor 0.43.1 → 0.43.2

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 (195) hide show
  1. package/dist/{AiChatSidebar-DOCvzGBe.js → AiChatSidebar-DTtMEXNR.js} +7 -7
  2. package/dist/{AiFeatureMenu-xCJeD07B.js → AiFeatureMenu-DFj-_S9b.js} +4 -4
  3. package/dist/{BlockIssueBadge-BJ3A6mqh.js → BlockIssueBadge-CyLmqjLl.js} +2 -2
  4. package/dist/{BlockPreviewCanvas-BQXfqhzu.js → BlockPreviewCanvas-C6F66f3k.js} +2 -2
  5. package/dist/{CloudHeaderExtras-TFmBJ0Ry.js → CloudHeaderExtras-Z4K6OMn0.js} +2 -2
  6. package/dist/{CloudPanels-BnSBbYnw.js → CloudPanels-DOJ2Vske.js} +4 -4
  7. package/dist/{CollaboratorBar-7ry8EdUh.js → CollaboratorBar-BCkl3dKY.js} +2 -2
  8. package/dist/{CommentsPanel-CGEhbjx0.js → CommentsPanel-DPMi6-rb.js} +1 -1
  9. package/dist/{CommentsSidebar-CUpst9ou.js → CommentsSidebar-C1mQcyh3.js} +10 -10
  10. package/dist/{CountdownToolbar-B1a_UIuB.js → CountdownToolbar-DKmzpk__.js} +3 -3
  11. package/dist/{DesignReferenceSidebar-DkAmsBgM.js → DesignReferenceSidebar-CGRzOOb8.js} +5 -5
  12. package/dist/{IssuesPanel-lTpsCV4a.js → IssuesPanel-CsdxjjRm.js} +5 -5
  13. package/dist/{LogicTagInsertButton-B5YymR4U.js → LogicTagInsertButton-BX4jOUQy.js} +3 -3
  14. package/dist/{MediaEditModal-B1X8qtJb.js → MediaEditModal-Bm8uOKBx.js} +270 -230
  15. package/dist/{MediaPanels-DO30L1KS.js → MediaPanels-BNWtEntu.js} +1 -1
  16. package/dist/{MergeTagInput-B0M_-X0K.js → MergeTagInput-Mj8CFfX1.js} +1 -1
  17. package/dist/{MergeTagModeToggle-B-8W1tK7.js → MergeTagModeToggle-CinojvpV.js} +3 -3
  18. package/dist/{ParagraphEditor-DVGeU0Am.js → ParagraphEditor-DdOteRjD.js} +9 -9
  19. package/dist/{RestoreVersionDialog-DOhgYkiA.js → RestoreVersionDialog-B-i0wPjh.js} +3 -3
  20. package/dist/{RichTextEditorContent-AaLAArA0.js → RichTextEditorContent-CDtKOWsh.js} +4 -4
  21. package/dist/{SaveBlockDialog-36MzPD2S.js → SaveBlockDialog-DTcGPDMT.js} +8 -8
  22. package/dist/{SavedBlocksBrowserModal-B5nFKszf.js → SavedBlocksBrowserModal-BA5yXQi0.js} +9 -9
  23. package/dist/{SavedBlocksPanels-BZkbP7eB.js → SavedBlocksPanels-mfSALJqB.js} +1 -1
  24. package/dist/{SavedBlocksPickBar-ChoCMdu7.js → SavedBlocksPickBar-BOjduGI4.js} +2 -2
  25. package/dist/{TemplateScoringPanel-C6ellAcs.js → TemplateScoringPanel-Byz-ajjf.js} +11 -11
  26. package/dist/{TemplateSettings-C_0qK-zs.js → TemplateSettings-Bf5rfsU8.js} +5 -5
  27. package/dist/{TestEmailModal-CKW-VArm.js → TestEmailModal-DJo2yFLh.js} +6 -6
  28. package/dist/{TestEmailPanel-BylYzjyM.js → TestEmailPanel-CXIFWfDm.js} +1 -1
  29. package/dist/{TitleEditor-DoQ286_n.js → TitleEditor-BDtokkQF.js} +6 -6
  30. package/dist/{ToggleSwitch-CtZ6adlX.js → ToggleSwitch-BCYZvP1e.js} +1 -1
  31. package/dist/{Toolbar-B6dG5_IP.js → Toolbar-XOtJ_N58.js} +20 -20
  32. package/dist/{TplModal-DHwQXySQ.js → TplModal-C3mzMXof.js} +1 -1
  33. package/dist/{VersionHistoryMenu-DdspCRSA.js → VersionHistoryMenu-DRoFz15y.js} +5 -5
  34. package/dist/{VersionHistoryPanels-C6QxmRUH.js → VersionHistoryPanels-CeF0JnXI.js} +1 -1
  35. package/dist/{VersionPreviewBanner-XUuLe36h.js → VersionPreviewBanner-DQvIEKUU.js} +1 -1
  36. package/dist/{WrapperBlock-BumDudbj.js → WrapperBlock-DZwp4G7P.js} +1 -1
  37. package/dist/{blockTypeIcons-Sg8SY1NQ.js → blockTypeIcons-CAPoy6X7.js} +2 -2
  38. package/dist/bundle-stats.json +6 -6
  39. package/dist/{ca-BTf1Zbqb.js → ca-CYQH3J0b.js} +6 -2
  40. package/dist/cdn/chunks/{AiChatSidebar-DiG-EQ-E.js → AiChatSidebar-DW1uRkKE.js} +2 -2
  41. package/dist/cdn/chunks/{AiChatSidebar-DiG-EQ-E.js.map → AiChatSidebar-DW1uRkKE.js.map} +1 -1
  42. package/dist/cdn/chunks/{AiFeatureMenu-4TgzmLb3.js → AiFeatureMenu-CHsgD78N.js} +3 -3
  43. package/dist/cdn/chunks/{AiFeatureMenu-4TgzmLb3.js.map → AiFeatureMenu-CHsgD78N.js.map} +1 -1
  44. package/dist/cdn/chunks/{BlockIssueBadge-ChQ0mPf5.js → BlockIssueBadge-CIqu8JnG.js} +2 -2
  45. package/dist/cdn/chunks/{BlockIssueBadge-ChQ0mPf5.js.map → BlockIssueBadge-CIqu8JnG.js.map} +1 -1
  46. package/dist/cdn/chunks/{BlockPreviewCanvas-BpvK3Sh9.js → BlockPreviewCanvas-Dtea66SI.js} +3 -3
  47. package/dist/cdn/chunks/{BlockPreviewCanvas-BpvK3Sh9.js.map → BlockPreviewCanvas-Dtea66SI.js.map} +1 -1
  48. package/dist/cdn/chunks/{CloudHeaderExtras-DqgOaWym.js → CloudHeaderExtras-Kjj78__m.js} +3 -3
  49. package/dist/cdn/chunks/{CloudHeaderExtras-DqgOaWym.js.map → CloudHeaderExtras-Kjj78__m.js.map} +1 -1
  50. package/dist/cdn/chunks/{CloudPanels-INOiwkET.js → CloudPanels-DARXXD0q.js} +4 -4
  51. package/dist/cdn/chunks/{CloudPanels-INOiwkET.js.map → CloudPanels-DARXXD0q.js.map} +1 -1
  52. package/dist/cdn/chunks/{CollaboratorBar-CQ9EpYKQ.js → CollaboratorBar-C7O9mQJ_.js} +3 -3
  53. package/dist/cdn/chunks/{CollaboratorBar-CQ9EpYKQ.js.map → CollaboratorBar-C7O9mQJ_.js.map} +1 -1
  54. package/dist/cdn/chunks/{CommentsPanel-CjhxGMjL.js → CommentsPanel-KH5_JS7_.js} +2 -2
  55. package/dist/cdn/chunks/{CommentsPanel-CjhxGMjL.js.map → CommentsPanel-KH5_JS7_.js.map} +1 -1
  56. package/dist/cdn/chunks/{CommentsSidebar-D9bqMnlt.js → CommentsSidebar-ucrNvgAQ.js} +2 -2
  57. package/dist/cdn/chunks/{CommentsSidebar-D9bqMnlt.js.map → CommentsSidebar-ucrNvgAQ.js.map} +1 -1
  58. package/dist/cdn/chunks/{CountdownToolbar-CIRx5w1U.js → CountdownToolbar-DVInzkj9.js} +4 -4
  59. package/dist/cdn/chunks/{CountdownToolbar-CIRx5w1U.js.map → CountdownToolbar-DVInzkj9.js.map} +1 -1
  60. package/dist/cdn/chunks/{DesignReferenceSidebar-C1-SbE_R.js → DesignReferenceSidebar-pleljkEJ.js} +2 -2
  61. package/dist/cdn/chunks/{DesignReferenceSidebar-C1-SbE_R.js.map → DesignReferenceSidebar-pleljkEJ.js.map} +1 -1
  62. package/dist/cdn/chunks/{IssuesPanel-qKY6UDd-.js → IssuesPanel-CPcQIUni.js} +2 -2
  63. package/dist/cdn/chunks/{IssuesPanel-qKY6UDd-.js.map → IssuesPanel-CPcQIUni.js.map} +1 -1
  64. package/dist/cdn/chunks/{LogicTagInsertButton-CItDS_U1.js → LogicTagInsertButton-DSrW9v0x.js} +2 -2
  65. package/dist/cdn/chunks/{LogicTagInsertButton-CItDS_U1.js.map → LogicTagInsertButton-DSrW9v0x.js.map} +1 -1
  66. package/dist/cdn/chunks/{MediaEditModal-CzUtXkpk.js → MediaEditModal-BHp7euCZ.js} +272 -232
  67. package/dist/cdn/chunks/MediaEditModal-BHp7euCZ.js.map +1 -0
  68. package/dist/cdn/chunks/{MediaPanels-DOLsh7WC.js → MediaPanels-CGIRlO48.js} +2 -2
  69. package/dist/cdn/chunks/{MediaPanels-DOLsh7WC.js.map → MediaPanels-CGIRlO48.js.map} +1 -1
  70. package/dist/cdn/chunks/{MergeTagInput-B33u3nxj.js → MergeTagInput-BSFofWuc.js} +2 -2
  71. package/dist/cdn/chunks/{MergeTagInput-B33u3nxj.js.map → MergeTagInput-BSFofWuc.js.map} +1 -1
  72. package/dist/cdn/chunks/{MergeTagModeToggle-Bz1CH3NT.js → MergeTagModeToggle-D7KlTFZV.js} +2 -2
  73. package/dist/cdn/chunks/{MergeTagModeToggle-Bz1CH3NT.js.map → MergeTagModeToggle-D7KlTFZV.js.map} +1 -1
  74. package/dist/cdn/chunks/{ParagraphEditor-B6A4yQ0A.js → ParagraphEditor-CokXagL1.js} +6 -6
  75. package/dist/cdn/chunks/{ParagraphEditor-B6A4yQ0A.js.map → ParagraphEditor-CokXagL1.js.map} +1 -1
  76. package/dist/cdn/chunks/{RestoreVersionDialog-B9rujl9w.js → RestoreVersionDialog-C-XEncAM.js} +3 -3
  77. package/dist/cdn/chunks/{RestoreVersionDialog-B9rujl9w.js.map → RestoreVersionDialog-C-XEncAM.js.map} +1 -1
  78. package/dist/cdn/chunks/{RichTextEditorContent-dOg1xFqv.js → RichTextEditorContent-DS_2UsJX.js} +4 -4
  79. package/dist/cdn/chunks/{RichTextEditorContent-dOg1xFqv.js.map → RichTextEditorContent-DS_2UsJX.js.map} +1 -1
  80. package/dist/cdn/chunks/{SaveBlockDialog-BNAeb6--.js → SaveBlockDialog-D8OAKF_k.js} +6 -6
  81. package/dist/cdn/chunks/{SaveBlockDialog-BNAeb6--.js.map → SaveBlockDialog-D8OAKF_k.js.map} +1 -1
  82. package/dist/cdn/chunks/{SavedBlocksBrowserModal--FoVqEnT.js → SavedBlocksBrowserModal-B6GN_llD.js} +6 -6
  83. package/dist/cdn/chunks/{SavedBlocksBrowserModal--FoVqEnT.js.map → SavedBlocksBrowserModal-B6GN_llD.js.map} +1 -1
  84. package/dist/cdn/chunks/{SavedBlocksPanels-qKyPnVgk.js → SavedBlocksPanels-hRCREVxS.js} +2 -2
  85. package/dist/cdn/chunks/{SavedBlocksPanels-qKyPnVgk.js.map → SavedBlocksPanels-hRCREVxS.js.map} +1 -1
  86. package/dist/cdn/chunks/{SavedBlocksPickBar-BvLwca4B.js → SavedBlocksPickBar-C5YrK0iB.js} +3 -3
  87. package/dist/cdn/chunks/{SavedBlocksPickBar-BvLwca4B.js.map → SavedBlocksPickBar-C5YrK0iB.js.map} +1 -1
  88. package/dist/cdn/chunks/{TemplateScoringPanel-ebsrVge3.js → TemplateScoringPanel-DXyQjcdU.js} +2 -2
  89. package/dist/cdn/chunks/{TemplateScoringPanel-ebsrVge3.js.map → TemplateScoringPanel-DXyQjcdU.js.map} +1 -1
  90. package/dist/cdn/chunks/{TemplateSettings-BaEVdUTM.js → TemplateSettings-da14bXlk.js} +4 -4
  91. package/dist/cdn/chunks/{TemplateSettings-BaEVdUTM.js.map → TemplateSettings-da14bXlk.js.map} +1 -1
  92. package/dist/cdn/chunks/{TestEmailModal-DpYirvEL.js → TestEmailModal-Dl_cAyj-.js} +6 -6
  93. package/dist/cdn/chunks/{TestEmailModal-DpYirvEL.js.map → TestEmailModal-Dl_cAyj-.js.map} +1 -1
  94. package/dist/cdn/chunks/{TestEmailPanel-PAT70a2g.js → TestEmailPanel-CVZGOKaQ.js} +2 -2
  95. package/dist/cdn/chunks/{TestEmailPanel-PAT70a2g.js.map → TestEmailPanel-CVZGOKaQ.js.map} +1 -1
  96. package/dist/cdn/chunks/{TitleEditor-Dc7YB6Uy.js → TitleEditor-DO8OdG_w.js} +5 -5
  97. package/dist/cdn/chunks/{TitleEditor-Dc7YB6Uy.js.map → TitleEditor-DO8OdG_w.js.map} +1 -1
  98. package/dist/cdn/chunks/{ToggleSwitch-D-8dCEbI.js → ToggleSwitch-D5Wi6Env.js} +2 -2
  99. package/dist/cdn/chunks/{ToggleSwitch-D-8dCEbI.js.map → ToggleSwitch-D5Wi6Env.js.map} +1 -1
  100. package/dist/cdn/chunks/{Toolbar-BDzhrmGV.js → Toolbar-CXDE8xM-.js} +7 -7
  101. package/dist/cdn/chunks/{Toolbar-BDzhrmGV.js.map → Toolbar-CXDE8xM-.js.map} +1 -1
  102. package/dist/cdn/chunks/{TplModal-BbHRUc1Y.js → TplModal-CFi-Psf7.js} +2 -2
  103. package/dist/cdn/chunks/{TplModal-BbHRUc1Y.js.map → TplModal-CFi-Psf7.js.map} +1 -1
  104. package/dist/cdn/chunks/{VersionHistoryMenu-C_bBKOYU.js → VersionHistoryMenu-1bfCr14E.js} +2 -2
  105. package/dist/cdn/chunks/{VersionHistoryMenu-C_bBKOYU.js.map → VersionHistoryMenu-1bfCr14E.js.map} +1 -1
  106. package/dist/cdn/chunks/{VersionHistoryPanels-BoJsPIKa.js → VersionHistoryPanels-DbgbyAdW.js} +2 -2
  107. package/dist/cdn/chunks/{VersionHistoryPanels-BoJsPIKa.js.map → VersionHistoryPanels-DbgbyAdW.js.map} +1 -1
  108. package/dist/cdn/chunks/{VersionPreviewBanner-CKyrcgxJ.js → VersionPreviewBanner-BaOTRIrm.js} +2 -2
  109. package/dist/cdn/chunks/{VersionPreviewBanner-CKyrcgxJ.js.map → VersionPreviewBanner-BaOTRIrm.js.map} +1 -1
  110. package/dist/cdn/chunks/{WrapperBlock-CGg6rBnx.js → WrapperBlock-BYNfYvl3.js} +2 -2
  111. package/dist/cdn/chunks/{WrapperBlock-CGg6rBnx.js.map → WrapperBlock-BYNfYvl3.js.map} +1 -1
  112. package/dist/cdn/chunks/{blockTypeIcons-CdfCfqHE.js → blockTypeIcons-B_IAjnTD.js} +2 -2
  113. package/dist/cdn/chunks/{blockTypeIcons-CdfCfqHE.js.map → blockTypeIcons-B_IAjnTD.js.map} +1 -1
  114. package/dist/cdn/chunks/{ca-BTf1Zbqb.js → ca-CYQH3J0b.js} +7 -3
  115. package/dist/cdn/chunks/ca-CYQH3J0b.js.map +1 -0
  116. package/dist/cdn/chunks/{de-xzrh9PA3.js → de-JvCTMJPy.js} +7 -3
  117. package/dist/cdn/chunks/de-JvCTMJPy.js.map +1 -0
  118. package/dist/cdn/chunks/{editor-modal-CZQArGNj.js → editor-modal-BDOfw3l0.js} +472 -428
  119. package/dist/cdn/chunks/editor-modal-BDOfw3l0.js.map +1 -0
  120. package/dist/cdn/chunks/en-DxpXHoxJ.js +2 -0
  121. package/dist/cdn/chunks/{es-BT4LSpE2.js → es-CAv-Vldw.js} +7 -3
  122. package/dist/cdn/chunks/es-CAv-Vldw.js.map +1 -0
  123. package/dist/cdn/chunks/{extensions-Dy251lPd.js → extensions-1hJBHnGv.js} +2 -2
  124. package/dist/cdn/chunks/{extensions-Dy251lPd.js.map → extensions-1hJBHnGv.js.map} +1 -1
  125. package/dist/{fr-CI7OJoql.js → cdn/chunks/fr-DVPtxpR6.js} +8 -2
  126. package/dist/cdn/chunks/fr-DVPtxpR6.js.map +1 -0
  127. package/dist/cdn/chunks/{icons-CQnjugjy.js → icons-BrAyaJ0T.js} +6 -6
  128. package/dist/cdn/chunks/{icons-CQnjugjy.js.map → icons-BrAyaJ0T.js.map} +1 -1
  129. package/dist/{ja-DhlWUfke.js → cdn/chunks/ja-dcWzjStz.js} +8 -2
  130. package/dist/cdn/chunks/ja-dcWzjStz.js.map +1 -0
  131. package/dist/{nl-D8y5OWGB.js → cdn/chunks/nl-LZNqQgE4.js} +8 -2
  132. package/dist/cdn/chunks/nl-LZNqQgE4.js.map +1 -0
  133. package/dist/cdn/chunks/{pt-BR-DMCbUBvJ.js → pt-BR-D6P_mNwA.js} +7 -3
  134. package/dist/cdn/chunks/pt-BR-D6P_mNwA.js.map +1 -0
  135. package/dist/cdn/chunks/{src-BwsQ27UT.js → src-D5ceT_Y4.js} +2 -2
  136. package/dist/cdn/chunks/{src-BwsQ27UT.js.map → src-D5ceT_Y4.js.map} +1 -1
  137. package/dist/cdn/chunks/{useEditorCore-DoX2RgTl.js → useEditorCore-BTKqlRIg.js} +5 -5
  138. package/dist/cdn/chunks/{useEditorCore-DoX2RgTl.js.map → useEditorCore-BTKqlRIg.js.map} +1 -1
  139. package/dist/cdn/editor.css +1 -1
  140. package/dist/cdn/editor.js +14 -14
  141. package/dist/{check-BMx_TLAS.js → check-CA6fvSIp.js} +1 -1
  142. package/dist/{chevron-down-KZf5mh9_.js → chevron-down-DYGJgpqJ.js} +1 -1
  143. package/dist/{chevron-right-BLZ60Vxw.js → chevron-right-Dm5_X4j6.js} +1 -1
  144. package/dist/{chevron-up-B9ynB8eU.js → chevron-up-Ds9ormyq.js} +1 -1
  145. package/dist/{circle-alert-pjvFFDrF.js → circle-alert-BYXE0olE.js} +1 -1
  146. package/dist/{clock-CiiTBfF3.js → clock-Ch1-h0sb.js} +1 -1
  147. package/dist/{copy-BF0_pQjH.js → copy-DLNyaPza.js} +1 -1
  148. package/dist/{createLucideIcon-C2YOVVWx.js → createLucideIcon-BrPnbRQT.js} +5 -5
  149. package/dist/{de-xzrh9PA3.js → de-JvCTMJPy.js} +6 -2
  150. package/dist/{editor-modal-99FB40dl.js → editor-modal-Cl5GFWTQ.js} +484 -440
  151. package/dist/en-CdCFaV2V.js +2 -0
  152. package/dist/{es-BT4LSpE2.js → es-CAv-Vldw.js} +6 -2
  153. package/dist/{extensions-DqQXxrvP.js → extensions-AKHJ0lax.js} +1 -1
  154. package/dist/{eye-RdXYgrQZ.js → eye-_7xKFxQh.js} +1 -1
  155. package/dist/{cdn/chunks/fr-CI7OJoql.js → fr-DVPtxpR6.js} +6 -4
  156. package/dist/{image-up-BwdoReQt.js → image-up-Ck-xPOGS.js} +1 -1
  157. package/dist/{info-zFMSGrL1.js → info-CBBNLK_i.js} +1 -1
  158. package/dist/{cdn/chunks/ja-DhlWUfke.js → ja-dcWzjStz.js} +6 -4
  159. package/dist/{link-D7orqFBT.js → link-BA_V9Nns.js} +1 -1
  160. package/dist/{list-BGt4Pgxc.js → list-BTujzSSo.js} +1 -1
  161. package/dist/{loader-circle-BVBgkRGz.js → loader-circle-B5OQFRUF.js} +1 -1
  162. package/dist/{message-circle-D8YLsarv.js → message-circle-DWXr27lZ.js} +1 -1
  163. package/dist/{cdn/chunks/nl-D8y5OWGB.js → nl-LZNqQgE4.js} +6 -4
  164. package/dist/{package-CuFvk0L5.js → package-DXW9QTU3.js} +1 -1
  165. package/dist/{pencil-DRYbmZW7.js → pencil-QmONZtjF.js} +1 -1
  166. package/dist/{plus-Dv0b3kh0.js → plus-XxHKE_GT.js} +1 -1
  167. package/dist/{pt-BR-DMCbUBvJ.js → pt-BR-D6P_mNwA.js} +6 -2
  168. package/dist/{refresh-cw-gYTIS5bJ.js → refresh-cw-DhXU8dCo.js} +1 -1
  169. package/dist/{search-D84_Ki_a.js → search-CpFaMrnL.js} +1 -1
  170. package/dist/{send-Cn-8_H_U.js → send-CFK3e6tQ.js} +1 -1
  171. package/dist/{shield-check-BOoVWJtD.js → shield-check-ndwpOTik.js} +1 -1
  172. package/dist/{smartphone-4Ej3PBw5.js → smartphone-DndMh9W6.js} +1 -1
  173. package/dist/{socialIcons-CzRJskEk.js → socialIcons-D43BLoTu.js} +1 -1
  174. package/dist/{sparkles-D3vnNe7O.js → sparkles-c1M5lwAQ.js} +1 -1
  175. package/dist/style.css +1 -1
  176. package/dist/templatical-editor.js +28 -28
  177. package/dist/{text-align-start-Ci2h_Xly.js → text-align-start-DOtBufPq.js} +1 -1
  178. package/dist/{trash-wmi4UKvv.js → trash-DmaPdiUk.js} +1 -1
  179. package/dist/{triangle-alert-DJ0r2wmQ.js → triangle-alert-DqvxsSNL.js} +1 -1
  180. package/dist/{upload-XMde-TXf.js → upload-CO_hogFv.js} +1 -1
  181. package/dist/{useEditorCore-CAhRAya8.js → useEditorCore-0K8joh4X.js} +11 -11
  182. package/dist/{useScrollToBlock-B4F7KCrf.js → useScrollToBlock-BU5A9eS9.js} +1 -1
  183. package/dist/{x-BWrtpVl-.js → x-BCop_VsL.js} +1 -1
  184. package/package.json +8 -8
  185. package/dist/cdn/chunks/MediaEditModal-CzUtXkpk.js.map +0 -1
  186. package/dist/cdn/chunks/ca-BTf1Zbqb.js.map +0 -1
  187. package/dist/cdn/chunks/de-xzrh9PA3.js.map +0 -1
  188. package/dist/cdn/chunks/editor-modal-CZQArGNj.js.map +0 -1
  189. package/dist/cdn/chunks/en-9unxBjV8.js +0 -2
  190. package/dist/cdn/chunks/es-BT4LSpE2.js.map +0 -1
  191. package/dist/cdn/chunks/fr-CI7OJoql.js.map +0 -1
  192. package/dist/cdn/chunks/ja-DhlWUfke.js.map +0 -1
  193. package/dist/cdn/chunks/nl-D8y5OWGB.js.map +0 -1
  194. package/dist/cdn/chunks/pt-BR-DMCbUBvJ.js.map +0 -1
  195. package/dist/en-BvOjRLiw.js +0 -2
@@ -1 +0,0 @@
1
- {"version":3,"file":"editor-modal-CZQArGNj.js","names":["_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_unref","_Fragment","_renderList","_createVNode","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_resolveComponent","_openBlock","_createElementBlock","_createElementVNode","_normalizeStyle","_unref","_withModifiers","_createVNode","_normalizeClass","_toDisplayString","_createCommentVNode","_withDirectives","_withKeys","_vModelText","Trash2","_Fragment","_renderList","_createBlock","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_createElementVNode","_normalizeStyle","_createVNode","_unref","_createTextVNode","_toDisplayString","_Fragment","_renderList","_createBlock","MediaFolderTreeNode","_withDirectives","_withKeys","_withModifiers","_vModelText","_createCommentVNode","_sfc_main","_openBlock","_createElementBlock","_normalizeStyle","_createElementVNode","_toDisplayString","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_openBlock","_createElementBlock","_unref","_normalizeClass","_Fragment","_renderList","_createElementVNode","_createVNode","_toDisplayString","_normalizeStyle","_createBlock","MediaFileIcon","_createCommentVNode","_withModifiers","_createTextVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_openBlock","_createBlock","_Teleport","_unref","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_withModifiers","_createElementVNode","_toDisplayString","_withDirectives","_vModelText","_createCommentVNode","_createTextVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_createTextVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_normalizeStyle","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_createVNode","MediaFileIcon","_createElementVNode","_toDisplayString","_Fragment","_createTextVNode","_createCommentVNode","_unref","_normalizeClass","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_openBlock","_createBlock","_Teleport","_unref","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_withModifiers","_createElementVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_normalizeClass","_unref","_createVNode","_createElementVNode","_toDisplayString","_Fragment","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_Transition","_withCtx","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_unref","_createBlock","StorageProgressRing","_createCommentVNode","_createVNode","_Transition","_withCtx","MediaFolderTree","_normalizeStyle","MediaBreadcrumb","_Fragment","_renderList","MediaUploadZone","_createTextVNode","MediaGrid","MediaImportUrlModal","MediaReplaceModal","_withModifiers","_normalizeClass","MediaPreviewPanel","MediaMovePicker","_sfc_main","_openBlock","_createBlock","_Teleport","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_withModifiers","_createElementVNode","MediaLibraryChrome","_createCommentVNode","_sfc_main","onBeforeUnmount","watch","inject","renderList","withCtx","resolveComponent","openBlock","provide","ref","defineComponent","withDirectives","onScopeDispose","unref","createVNode","withKeys","normalizeClass","Teleport","computed","createElementBlock","createCommentVNode","withModifiers","normalizeStyle","createBlock","Transition","toRef","vModelText","createBaseVNode","isRef","toValue","Fragment","toDisplayString","defineAsyncComponent","createTextVNode","useDebounceFn","useEventListener","useClipboard","useFileDialog","useDropZone","useIntersectionObserver","onClickOutside","Copy","ChevronRight","Search","Check","Plus","PanelLeft","Grid2x2","LoaderCircle","Folder","Trash","Link","X","List","Pencil","Upload","Star","File","_plugin_vue_export_helper_default","MEDIA_LIMITS_KEY","POPOVER_TARGET_KEY","TRANSLATIONS_KEY","UI_THEME_KEY","UI_LOCALE_KEY","en","useI18n","injected","translationsOverride","t","format","template","key","values","ALL_CATEGORIES","useMediaCategories","limitsOverride","limits","isMediaLibraryEnabled","maxFileSize","availableCategories","accept","mimeTypes","allAcceptedMimeTypes","category","allAcceptedInputString","isAcceptedMimeType","mimeType","isAcceptedFile","file","isImageMimeType","getCategoryForMimeType","types","FOCUSABLE_SELECTOR","useFocusTrap","previouslyFocused","cleanupListener","pendingRaf","rootOf","el","root","getFocusableElements","containerRef","handleKeydown","event","focusable","first","last","current","activate","wasActive","deactivate","restoreFocus","opts","stopWatch","active","isActive","container","_hoisted_1$11","\"color\"","_hoisted_2$10","_hoisted_3$10","MediaBreadcrumb_default","__emit","props","__props","emit","breadcrumbPath","path","buildPath","folder","folderList","targetId","_cache","index","_hoisted_1$10","_hoisted_2$9","_hoisted_3$9","_hoisted_4$8","_hoisted_5$7","_hoisted_6$5","_hoisted_7$4","_hoisted_8$4","_hoisted_9$4","MediaFolderTreeNode_default","isExpanded","isRenaming","renameValue","isCreatingSubfolder","subfolderName","confirmDelete","hasChildren","canCreateSubfolder","isDescendantActive","containsFolder","shouldExpand","folders","id","toggleExpand","startRename","confirmRename","trimmed","cancelRename","startCreateSubfolder","confirmCreateSubfolder","cancelCreateSubfolder","handleDeleteClick","onRowFocusOut","next","_component_MediaFolderTreeNode","$event","child","name","parentId","_hoisted_1$9","_hoisted_2$8","_hoisted_3$8","MediaFolderTree_default","isCreating","newFolderName","startCreateFolder","confirmCreateFolder","cancelCreate","MediaFileIcon_default","FILE_TYPE_MAP","DEFAULT_CONFIG","config","formatAbsoluteDate","parsed","iso","tag","locale","options","_hoisted_1$8","_hoisted_2$7","_hoisted_3$7","_hoisted_4$7","_hoisted_5$6","_hoisted_6$4","_hoisted_7$3","_hoisted_8$3","_hoisted_9$3","_hoisted_10$3","_hoisted_11$2","_hoisted_12$2","_hoisted_13$2","_hoisted_14$1","_hoisted_15$1","_hoisted_16$1","_hoisted_17$1","_hoisted_18$1","_hoisted_19$1","_hoisted_20$1","_hoisted_21$1","_hoisted_22$1","_hoisted_23$1","_hoisted_24$1","_hoisted_25$1","_hoisted_26$1","_hoisted_27$1","_hoisted_28$1","MediaGrid_default","mimeOf","item","thumbOf","isSelectable","showEdit","showReplace","handleItemClick","handleItemDblClick","handleItemKeydown","isSelected","actionChromeClass","uiLocale","sentinelRef","isIntersecting","formatSize","bytes","formatDate","dateStr","i","RefreshCw","_hoisted_1$7","_hoisted_2$6","_hoisted_3$6","_hoisted_4$6","_hoisted_5$5","_hoisted_6$3","_hoisted_7$2","_hoisted_8$2","_hoisted_9$2","_hoisted_10$2","MediaImportUrlModal_default","tplUiTheme","popoverTarget","urlValue","visible","handleImport","trimmedUrl","handleClose","dialogRef","_hoisted_1$6","_hoisted_2$5","_hoisted_3$5","_hoisted_4$5","MediaMovePicker_default","pickerRef","flattenFolders","result","depth","_hoisted_1$5","_hoisted_2$4","_hoisted_3$4","\"border\"","_hoisted_4$4","_hoisted_5$4","_hoisted_6$2","MediaPreviewPanel_default","buildFolderPath","newPath","currentPath","found","folderPath","isImage","filename","_hoisted_1$4","_hoisted_2$3","_hoisted_3$3","_hoisted_4$3","_hoisted_5$3","_hoisted_6$1","_hoisted_7$1","_hoisted_8$1","_hoisted_9$1","_hoisted_10$1","_hoisted_11$1","_hoisted_12$1","_hoisted_13$1","MediaReplaceModal_default","fileInputRef","selectedFile","extension","parts","acceptPattern","hasUsage","handleFileChange","input","handleReplace","_hoisted_1$3","_hoisted_2$2","_hoisted_3$2","_hoisted_4$2","_hoisted_5$2","MediaUploadZone_default","dropZoneRef","validateFiles","valid","fileList","emitValidFiles","files","openFilePicker","onChange","isOverDropZone","_hoisted_1$2","_hoisted_2$1","_hoisted_3$1","_hoisted_4$1","_hoisted_5$1","StorageProgressRing_default","showTooltip","size","strokeWidth","radius","circumference","percentage","strokeDashoffset","progressColor","formatBytes","k","sizes","usedFormatted","limitFormatted","remainingBytes","remainingFormatted","tooltipText","\"stroke\"","treeFolders","nodes","roots","node","parent","useMediaLibraryUI","getTranslations","translations","layoutMode","showSidebar","searchInput","editingItem","showImportUrlModal","showMovePicker","selectedUrl","library","hasFrequentlyUsed","displayItems","hasUsedFiles","info","folderTree","categoryLabels","getCategoryLabel","show","debouncedSearch","value","handleSearchInput","handleUpload","handleSelect","handleCreateFolder","handleRenameFolder","folderId","handleDeleteFolder","handleEditItem","handleEditSave","cropData","mediaId","altText","handleImportFromUrl","url","handleMoveToFolder","handleReplaceItem","handleReplaceFile","resetUI","useMediaLibrary","items","currentFolderId","viewMode","searchQuery","categoryFilter","sortOption","isLoading","isUploading","hasMore","nextCursor","uploadProgress","selectedItems","previewItem","frequentlyUsedItems","deleteUsageInfo","showDeleteWarning","pendingDeleteIds","isImportingFromUrl","importFromUrlError","isReplacing","replaceError","showReplaceWarning","pendingReplaceItem","replaceUsageInfo","storageInfo","browseRequestId","notify","error","foldersProvider","provider","resolvedTemplateId","currentListParams","params","cursor","templateId","loadItems","requestId","page","loadMore","search","query","filterByCategory","sortBy","option","navigateToFolder","showFrequentlyUsed","loadFrequentlyUsed","uploadFile","create","media","uploadFiles","moveSelected","move","movedItems","targetFolderId","movedMap","updateFile","update","patch","updated","captureLocalAssets","captured","ids","dropLocalAssets","deleteSelected","providerDelete","asset","frequentlyUsed","checkUsageBeforeDelete","checkUsage","usage","cancelDelete","importFromUrl","importUrl","toggleSelection","clearSelection","selectItem","loadStorage","storage","loadFolders","foldersApi","createFolder","findFolderInTree","renameFolder","deleteFolder","deleteFn","checkUsageBeforeReplace","cancelReplace","applyReplaced","replaceFile","replace","replaceMediaDirectly","_hoisted_1$1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_hoisted_33","_hoisted_34","_hoisted_35","_hoisted_36","_hoisted_37","MediaLibraryChrome_default","__expose","MediaEditModal","resolvedConfirmTestId","mediaLimits","ui","canCreate","canFolders","canImport","canDelete","canUpdate","canReplace","canFrequentlyUsed","canMove","canCreateFolder","canRenameFolder","canDeleteFolder","isInitialLoad","isConfirmable","selectedDeletableCount","count","entry","nestedDialogOpen","handleEscape","confirmSelection","handleCategoryChange","args","localeModules","localesFromGlob","modules","supportedLocales","canonicalize","findSupportedLocale","canonical","s","getBaseLocale","resolveLocale","loadMediaTranslations","loader","_hoisted_1","MediaLibraryModal_default","popoverTargetRef","chromeRef","handleConfirm","\"width\"","\"height\""],"sources":["../../../../media-library/src/keys.ts","../../../../media-library/src/i18n/locales/en.ts","../../../../media-library/src/composables/useI18n.ts","../../../../media-library/src/composables/useMediaCategories.ts","../../../../media-library/src/composables/useFocusTrap.ts","../../../../media-library/src/components/media/MediaBreadcrumb.vue","../../../../media-library/src/components/media/MediaBreadcrumb.vue","../../../../media-library/src/components/media/MediaFolderTreeNode.vue","../../../../media-library/src/components/media/MediaFolderTreeNode.vue","../../../../media-library/src/components/media/MediaFolderTree.vue","../../../../media-library/src/components/media/MediaFolderTree.vue","../../../../media-library/src/components/media/MediaFileIcon.vue","../../../../media-library/src/components/media/MediaFileIcon.vue","../../../../media-library/src/utils/formatAbsoluteDate.ts","../../../../media-library/src/components/media/MediaGrid.vue","../../../../media-library/src/components/media/MediaGrid.vue","../../../../media-library/src/components/media/MediaImportUrlModal.vue","../../../../media-library/src/components/media/MediaImportUrlModal.vue","../../../../media-library/src/components/media/MediaMovePicker.vue","../../../../media-library/src/components/media/MediaMovePicker.vue","../../../../media-library/src/components/media/MediaPreviewPanel.vue","../../../../media-library/src/components/media/MediaPreviewPanel.vue","../../../../media-library/src/components/media/MediaReplaceModal.vue","../../../../media-library/src/components/media/MediaReplaceModal.vue","../../../../media-library/src/components/media/MediaUploadZone.vue","../../../../media-library/src/components/media/MediaUploadZone.vue","../../../../media-library/src/components/media/StorageProgressRing.vue","../../../../media-library/src/components/media/StorageProgressRing.vue","../../../../media-library/src/utils/treeFolders.ts","../../../../media-library/src/composables/useMediaLibraryUI.ts","../../../../media-library/src/composable.ts","../../../../media-library/src/components/MediaLibraryChrome.vue","../../../../media-library/src/components/MediaLibraryChrome.vue","../../../../media-library/src/i18n/index.ts","../../../../media-library/src/components/MediaLibraryModal.vue","../../../../media-library/src/components/MediaLibraryModal.vue"],"sourcesContent":["import type { MediaCategory, MediaOptions } from \"@templatical/types\";\nimport type { InjectionKey, Ref } from \"vue\";\nimport type { MediaTranslations } from \"./i18n\";\n\n/**\n * Client pre-check limits plus the host's `accept` filter.\n *\n * `maxFileSize` / `mimeTypes` are {@link MediaOptions} fields and may be\n * getters — Cloud fills them after construction — so every reader must\n * access them inside a computed, never by destructuring at setup.\n *\n * **Provided by whichever component roots the tree** — `MediaLibraryModal`\n * and `standalone/MediaLibrary.vue` — and consumed by `useMediaCategories`,\n * which five descendants call. It is a provide rather than prop-drilling\n * *only* because of that depth; the values themselves cross the package\n * boundary as props.\n *\n * Vue matches injection keys by identity, so a bare-string\n * `inject(\"mediaLimits\")` never resolves the `Symbol` a host provided — it\n * yields `undefined` silently, and the library opens with no limits and\n * nothing to debug. Props make the cross-package boundary typed; a single\n * exported key makes the intra-package hop impossible to spell two\n * different ways.\n */\nexport type MediaLimits = Pick<MediaOptions, \"maxFileSize\" | \"mimeTypes\"> & {\n accept?: MediaCategory[];\n};\n\nexport const MEDIA_LIMITS_KEY: InjectionKey<MediaLimits> = Symbol(\n \"templaticalMediaLimits\",\n);\n\n/**\n * Mount target for modal/overlay teleports inside `MediaLibraryModal` and\n * its nested sub-modals (replace, edit, import URL). Set via the\n * `popoverTarget` prop on `MediaLibraryModal` and provided here so the\n * sub-modals can teleport to the same element.\n *\n * When the ref resolves to `null` (or no provider is in scope, e.g. the\n * standalone visual SDK from `./standalone/visual.ts`), modals fall back\n * to `document.body` — preserving the original teleport behavior.\n *\n * Host integration: editors that mount `MediaLibraryModal` inside a\n * shadow-aware tree should pass their popover root element (e.g.\n * `core.popoverRoot.value`) through the prop so media-library modals land\n * inside the editor's shadow boundary rather than escaping to body.\n */\nexport const POPOVER_TARGET_KEY: InjectionKey<Ref<HTMLElement | null>> = Symbol(\n \"templaticalMediaPopoverTarget\",\n);\n\n/**\n * The active locale's strings, for the twelve components in this package that\n * call `useI18n()`.\n *\n * **Provided by whichever component roots the tree** — `MediaLibraryModal` from\n * the translations it loads for its `locale` prop, `standalone/MediaLibrary.vue`\n * from its own `translations` prop.\n *\n * A `Symbol` for the same reason {@link MEDIA_LIMITS_KEY} is one, and this key\n * exists because that reason was learned the hard way: translations used to be\n * injected under the bare string `\"translations\"`, which never resolves the\n * `Symbol` `@templatical/editor` provides under the same name. A modal mounted\n * inside the editor got `undefined` and every `t.mediaLibrary.*` read threw.\n * Strings cross the package boundary as a `locale` prop; this key carries them\n * the rest of the way.\n */\nexport const TRANSLATIONS_KEY: InjectionKey<Ref<MediaTranslations | null>> =\n Symbol(\"templaticalMediaTranslations\");\n\n/**\n * Resolved UI theme (`\"light\"` / `\"dark\"`) for the overlay chrome, stamped onto\n * `data-tpl-theme` by this package's modals.\n *\n * Set from `MediaLibraryModal`'s `uiTheme` prop. The three sub-modals need it\n * too and teleport away from the modal's own DOM, so it travels by provide\n * rather than by prop-drilling through the markup.\n *\n * A `Symbol` for the reason `MEDIA_LIMITS_KEY` is one, and the third key added\n * after learning it: these modals injected the bare string `\"tplUiTheme\"`, which\n * never resolves the identically-named `Symbol` `@templatical/editor` provides.\n * `data-tpl-theme` was therefore always `undefined` and the media library stayed\n * light inside a dark editor.\n *\n * Absent in the standalone SDK, which has no light/dark concept — the modals\n * inject with a `null` default and simply stamp nothing.\n */\nexport const UI_THEME_KEY: InjectionKey<Readonly<Ref<string | undefined>>> =\n Symbol(\"templaticalMediaUiTheme\");\n\n/**\n * The host's BCP-47 locale, exactly as it arrived on `MediaLibraryModal`'s\n * `locale` prop — unresolved, so it may be unsupported or malformed.\n *\n * For `Intl` formatting only. Translated *strings* come from\n * `TRANSLATIONS_KEY`, already resolved to a bundle this package ships; this is\n * for the labels `Intl` builds itself (dates), where the browser's own locale\n * would otherwise win and disagree with every string beside them.\n *\n * Travels by provide for the reason `UI_THEME_KEY` does: the grid and the\n * preview panel are deep descendants, and the sub-modals teleport away from the\n * modal's own DOM. Always format through `formatAbsoluteDate`, which guards the\n * malformed case.\n */\nexport const UI_LOCALE_KEY: InjectionKey<Readonly<Ref<string | undefined>>> =\n Symbol(\"templaticalMediaUiLocale\");\n","const en = {\n mediaLibrary: {\n title: \"Media Library\",\n searchPlaceholder: \"Search files...\",\n allFiles: \"All Files\",\n filterAll: \"All Types\",\n filterImages: \"Images\",\n filterDocuments: \"Documents\",\n filterVideos: \"Videos\",\n filterAudio: \"Audio\",\n newFolder: \"New Folder\",\n folderName: \"Folder name\",\n noFiles: \"No files found\",\n noSearchResults: \"No files match your search\",\n dropOrClick: \"Drop files here or click to upload\",\n acceptedFormats: \"Images, PDF, Video, Audio, Documents (max 10MB)\",\n uploading: \"Uploading...\",\n uploadingProgress: \"Uploading {current} of {total}...\",\n selectImage: \"Select Image\",\n selectFile: \"Select File\",\n deleteSelected: \"Delete\",\n copyUrl: \"Copy URL\",\n copied: \"Copied!\",\n browseMedia: \"Browse Media Library\",\n renameFolder: \"Rename folder\",\n addSubfolder: \"Add subfolder\",\n subfolderName: \"Subfolder name\",\n sortNewest: \"Newest First\",\n sortOldest: \"Oldest First\",\n sortNameAsc: \"Name A-Z\",\n sortNameDesc: \"Name Z-A\",\n sortSizeAsc: \"Smallest First\",\n sortSizeDesc: \"Largest First\",\n moveSelected: \"Move\",\n moveToRoot: \"All Files\",\n currentFolder: \"(current)\",\n confirmDelete: \"Confirm?\",\n renameFile: \"Rename\",\n editFile: \"Edit File\",\n fileName: \"Filename\",\n altText: \"Alt Text\",\n altTextPlaceholder: \"Describe this image for accessibility\",\n saveChanges: \"Save\",\n cancel: \"Cancel\",\n frequentlyUsed: \"Frequently Used\",\n deleteWarningTitle: \"Delete File\",\n deleteWarningMessage: \"Are you sure? This action is irreversible.\",\n deleteWarningUsageNote:\n \"The following files are used in templates. Deleting them may break those templates.\",\n deleteAnyway: \"Delete\",\n usedInTemplates: \"Used in {count} template(s)\",\n viewGrid: \"Grid view\",\n viewList: \"List view\",\n showFolders: \"Show folders\",\n hideFolders: \"Hide folders\",\n importFromUrl: \"Import from URL\",\n importUrlPlaceholder: \"https://example.com/image.jpg\",\n import: \"Import\",\n importing: \"Importing...\",\n importError: \"Failed to import from URL\",\n replaceFile: \"Replace File\",\n replaceWarningTitle: \"Replace File\",\n replaceWarningMessage:\n \"You are about to replace this file. The replacement must have the same file extension ({extension}).\",\n replaceWarningUsageNote:\n \"This file is used in {count} template(s). Replacing it will update all references.\",\n replaceSelectFile: \"Select replacement file\",\n replace: \"Replace\",\n replacing: \"Replacing...\",\n replaceError: \"Failed to replace file\",\n saving: \"Saving...\",\n cropAspectRatio: \"Aspect Ratio\",\n cropFree: \"Free\",\n cropSquare: \"1:1\",\n cropLandscape43: \"4:3\",\n cropLandscape169: \"16:9\",\n cropOriginal: \"Original\",\n cropMaxWidth: \"Max Width\",\n cropMaxHeight: \"Max Height\",\n cropOutputSize: \"Output Size\",\n cropPixels: \"px\",\n cropOptional: \"(optional)\",\n close: \"Close\",\n searchAriaLabel: \"Search files\",\n filterAriaLabel: \"Filter by type\",\n filesListAriaLabel: \"Files\",\n loading: \"Loading\",\n expandFolder: \"Expand folder\",\n collapseFolder: \"Collapse folder\",\n deleteFolder: \"Delete folder\",\n deleteFolderConfirm: \"Confirm delete\",\n storageTooltip: \"{used} of {total} used ({remaining} remaining)\",\n },\n};\n\nexport default en;\n","import type { MediaTranslations } from \"../i18n\";\nimport { type Ref, inject, isRef } from \"vue\";\nimport { TRANSLATIONS_KEY } from \"../keys\";\nimport en from \"../i18n/locales/en\";\n\nexport interface UseI18nReturn {\n /** Current translations object */\n t: MediaTranslations;\n /** Format a string with placeholders */\n format: (template: string, values: Record<string, string | number>) => string;\n}\n\n/**\n * Composable for internationalization.\n * Provides access to the current locale's translations.\n *\n * Resolution order: an explicit override, then `TRANSLATIONS_KEY` from the host\n * component, then bundled English.\n *\n * **English is a floor, not a feature.** `t` is read as `t.mediaLibrary.x` in 28\n * template positions in `MediaLibraryModal` alone and across eleven sibling\n * components, so an unresolved injection is not a missing label — it is a\n * `TypeError` on first render. The fallback keeps a wiring mistake to the wrong\n * *language* rather than a blank library, which is why the previous non-null\n * assertion is gone: it claimed something the type system could not back.\n *\n * The value is unwrapped **once, at setup** — deliberately, since every consumer\n * reads `t.mediaLibrary.x` directly rather than through a ref. A host whose\n * translations arrive asynchronously must therefore withhold its subtree until\n * they land (`MediaLibraryModal` gates on exactly that) rather than expecting a\n * later value to propagate.\n *\n * @param translationsOverride - Optional translations to use instead of the injected value\n */\nexport function useI18n(\n translationsOverride?: MediaTranslations,\n): UseI18nReturn {\n const injected =\n translationsOverride ??\n inject<Ref<MediaTranslations | null> | null>(TRANSLATIONS_KEY, null);\n const resolved = isRef(injected) ? injected.value : injected;\n const t = resolved ?? en;\n\n /**\n * Format a string with placeholders.\n * e.g., format('{minutes}m ago', { minutes: 5 }) -> '5m ago'\n */\n function format(\n template: string,\n values: Record<string, string | number>,\n ): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key) => {\n return key in values ? String(values[key]) : `{${key}}`;\n });\n }\n\n return {\n t,\n format,\n };\n}\n","import type { MediaCategory } from \"../types\";\nimport { computed, inject, type ComputedRef } from \"vue\";\nimport { MEDIA_LIMITS_KEY, type MediaLimits } from \"../keys\";\n\nexport type { MediaCategory };\n\n/** Category tabs when the host set no `accept` and the provider set no `mimeTypes`. */\nconst ALL_CATEGORIES: MediaCategory[] = [\n \"images\",\n \"documents\",\n \"videos\",\n \"audio\",\n];\n\nexport interface UseMediaCategoriesReturn {\n isMediaLibraryEnabled: ComputedRef<boolean>;\n allAcceptedMimeTypes: ComputedRef<string[]>;\n allAcceptedInputString: ComputedRef<string>;\n maxFileSize: ComputedRef<number>;\n availableCategories: ComputedRef<MediaCategory[]>;\n isAcceptedMimeType: (mimeType: string, accept?: MediaCategory[]) => boolean;\n isAcceptedFile: (file: { type: string; size: number }) => boolean;\n isImageMimeType: (mimeType: string) => boolean;\n getCategoryForMimeType: (mimeType: string) => MediaCategory | null;\n}\n\nexport function useMediaCategories(\n /**\n * The limits to read, for callers that cannot inject them.\n *\n * A component never sees its own `provide` — Vue resolves `inject` against\n * the *parent* chain — so the two hosts that provide `MEDIA_LIMITS_KEY`\n * cannot also inject it, and must pass their value here instead. Descendants\n * (`MediaGrid`, `MediaEditModal`, `MediaUploadZone`, `MediaPreviewPanel`)\n * omit the argument and inject. Mirrors `useI18n(override?)`.\n */\n limitsOverride?: MediaLimits,\n): UseMediaCategoriesReturn {\n // Explicit null default + throw rather than a `!` assertion: without a\n // provider every read below became `undefined.maxFileSize`. Reaching this\n // message means a descendant was rendered outside both hosts, or a host\n // forgot the argument above.\n const injected = limitsOverride ?? inject(MEDIA_LIMITS_KEY, null);\n if (!injected) {\n throw new Error(\n \"[Templatical] useMediaCategories() needs media limits in scope. Render it under <MediaLibraryModal> or provide MEDIA_LIMITS_KEY yourself.\",\n );\n }\n // Nested functions below close over this; a narrowed `const` from the\n // throw is not carried into them (TS18047).\n const limits: MediaLimits = injected;\n\n // A provider in scope *is* the feature being on — there is no separate\n // `useMediaLibrary` flag. The composable cannot be constructed without\n // limits, so this is constantly true for every successful caller.\n const isMediaLibraryEnabled = computed(() => true);\n\n // Read `maxFileSize` / `mimeTypes` / `accept` *inside* the computed. Cloud\n // implements the first two as getters over plan config that arrives after\n // construction; capturing them at setup pins the client pre-check to\n // `undefined` for the whole session.\n // Omit is no cap (`??`, so an explicit `0` stays a cap of 0). Infinity\n // keeps `ComputedRef<number>` and `file.size <= n` without a skip branch.\n const maxFileSize = computed(\n () => limits.maxFileSize ?? Number.POSITIVE_INFINITY,\n );\n\n const availableCategories = computed((): MediaCategory[] => {\n const accept = limits.accept;\n if (accept && accept.length > 0) {\n return accept;\n }\n const mimeTypes = limits.mimeTypes;\n if (mimeTypes) {\n return Object.keys(mimeTypes) as MediaCategory[];\n }\n return ALL_CATEGORIES;\n });\n\n const allAcceptedMimeTypes = computed(() => {\n const mimeTypes = limits.mimeTypes;\n if (!mimeTypes) {\n return [];\n }\n return availableCategories.value.flatMap(\n (category) => mimeTypes[category] ?? [],\n );\n });\n\n const allAcceptedInputString = computed(() =>\n allAcceptedMimeTypes.value.join(\",\"),\n );\n\n function isAcceptedMimeType(\n mimeType: string,\n accept?: MediaCategory[],\n ): boolean {\n const mimeTypes = limits.mimeTypes;\n if (!mimeTypes) {\n return true;\n }\n\n const categories =\n accept && accept.length > 0\n ? accept\n : (Object.keys(mimeTypes) as MediaCategory[]);\n\n return categories.some(\n (category) => mimeTypes[category]?.includes(mimeType) ?? false,\n );\n }\n\n function isAcceptedFile(file: { type: string; size: number }): boolean {\n return isAcceptedMimeType(file.type) && file.size <= maxFileSize.value;\n }\n\n function isImageMimeType(mimeType: string): boolean {\n const mimeTypes = limits.mimeTypes;\n if (!mimeTypes) {\n return mimeType.startsWith(\"image/\");\n }\n return mimeTypes.images?.includes(mimeType) ?? false;\n }\n\n function getCategoryForMimeType(mimeType: string): MediaCategory | null {\n const mimeTypes = limits.mimeTypes;\n if (!mimeTypes) {\n return null;\n }\n\n for (const [category, types] of Object.entries(mimeTypes)) {\n if (types?.includes(mimeType)) {\n return category as MediaCategory;\n }\n }\n\n return null;\n }\n\n return {\n isMediaLibraryEnabled,\n allAcceptedMimeTypes,\n allAcceptedInputString,\n maxFileSize,\n availableCategories,\n isAcceptedMimeType,\n isAcceptedFile,\n isImageMimeType,\n getCategoryForMimeType,\n };\n}\n","import { useEventListener } from \"@vueuse/core\";\nimport { onScopeDispose, watch, type Ref } from \"vue\";\n\nconst FOCUSABLE_SELECTOR =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Lightweight focus trap for dialogs.\n *\n * Uses the container's `getRootNode()` so Tab wrapping works both in\n * standalone `document` and when the library is teleported into the\n * editor's shadow root — `document.activeElement` would be the host.\n */\nexport function useFocusTrap(\n containerRef: Ref<HTMLElement | null>,\n active: Ref<boolean>,\n): void {\n let previouslyFocused: HTMLElement | null = null;\n let cleanupListener: (() => void) | null = null;\n let pendingRaf: number | null = null;\n\n function rootOf(el: HTMLElement | null): Pick<Document, \"activeElement\"> {\n if (!el) return document;\n const root = el.getRootNode();\n if (root && \"activeElement\" in root) {\n return root as Document | ShadowRoot;\n }\n return document;\n }\n\n function getFocusableElements(): HTMLElement[] {\n if (!containerRef.value) return [];\n return Array.from(\n containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).filter((el) => el.offsetParent !== null);\n }\n\n function handleKeydown(event: KeyboardEvent): void {\n if (event.key !== \"Tab\") return;\n\n const focusable = getFocusableElements();\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const current = rootOf(containerRef.value).activeElement;\n\n if (event.shiftKey) {\n if (current === first) {\n event.preventDefault();\n last.focus();\n }\n } else if (current === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n function activate(): void {\n const wasActive = cleanupListener !== null || pendingRaf !== null;\n if (wasActive) {\n deactivate({ restoreFocus: false });\n }\n\n if (!wasActive) {\n previouslyFocused = rootOf(containerRef.value)\n .activeElement as HTMLElement | null;\n }\n\n pendingRaf = requestAnimationFrame(() => {\n pendingRaf = null;\n const focusable = getFocusableElements();\n if (focusable.length > 0) {\n const autofocus = containerRef.value?.querySelector<HTMLElement>(\n \"[autofocus], input:not([disabled])\",\n );\n (autofocus ?? focusable[0]).focus();\n }\n });\n\n cleanupListener = useEventListener(containerRef, \"keydown\", handleKeydown);\n }\n\n function deactivate(opts: { restoreFocus?: boolean } = {}): void {\n const restoreFocus = opts.restoreFocus !== false;\n\n if (pendingRaf !== null) {\n if (typeof cancelAnimationFrame !== \"undefined\") {\n cancelAnimationFrame(pendingRaf);\n }\n pendingRaf = null;\n }\n\n cleanupListener?.();\n cleanupListener = null;\n\n if (restoreFocus && previouslyFocused && previouslyFocused.focus) {\n previouslyFocused.focus();\n previouslyFocused = null;\n }\n }\n\n const stopWatch = watch(\n [active, containerRef],\n ([isActive, container]) => {\n if (isActive && container) {\n activate();\n } else {\n deactivate();\n }\n },\n { flush: \"post\" },\n );\n\n onScopeDispose(() => {\n stopWatch();\n deactivate();\n });\n}\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n}>();\n\nconst { t } = useI18n();\n\nconst breadcrumbPath = computed(() => {\n if (!props.currentFolderId) return [];\n const path: MediaFolderNode[] = [];\n buildPath(props.folders, props.currentFolderId, path);\n return path;\n});\n\nfunction buildPath(\n folderList: MediaFolderNode[],\n targetId: string,\n path: MediaFolderNode[],\n): boolean {\n for (const folder of folderList) {\n if (folder.id === targetId) {\n path.push(folder);\n return true;\n }\n if (buildPath(folder.children, targetId, path)) {\n path.unshift(folder);\n return true;\n }\n }\n return false;\n}\n</script>\n\n<template>\n <div\n v-if=\"breadcrumbPath.length > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-1 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <button\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', null)\"\n >\n {{ t.mediaLibrary.allFiles }}\n </button>\n <template v-for=\"(folder, index) in breadcrumbPath\" :key=\"folder.id\">\n <ChevronRight :size=\"12\" :stroke-width=\"2\" />\n <button\n v-if=\"index < breadcrumbPath.length - 1\"\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', folder.id)\"\n >\n {{ folder.name }}\n </button>\n <span v-else style=\"color: var(--tpl-text)\">{{ folder.name }}</span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n}>();\n\nconst { t } = useI18n();\n\nconst breadcrumbPath = computed(() => {\n if (!props.currentFolderId) return [];\n const path: MediaFolderNode[] = [];\n buildPath(props.folders, props.currentFolderId, path);\n return path;\n});\n\nfunction buildPath(\n folderList: MediaFolderNode[],\n targetId: string,\n path: MediaFolderNode[],\n): boolean {\n for (const folder of folderList) {\n if (folder.id === targetId) {\n path.push(folder);\n return true;\n }\n if (buildPath(folder.children, targetId, path)) {\n path.unshift(folder);\n return true;\n }\n }\n return false;\n}\n</script>\n\n<template>\n <div\n v-if=\"breadcrumbPath.length > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-1 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <button\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', null)\"\n >\n {{ t.mediaLibrary.allFiles }}\n </button>\n <template v-for=\"(folder, index) in breadcrumbPath\" :key=\"folder.id\">\n <ChevronRight :size=\"12\" :stroke-width=\"2\" />\n <button\n v-if=\"index < breadcrumbPath.length - 1\"\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', folder.id)\"\n >\n {{ folder.name }}\n </button>\n <span v-else style=\"color: var(--tpl-text)\">{{ folder.name }}</span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight, Folder, Pencil, Plus, Trash2 } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n folder: MediaFolderNode;\n currentFolderId: string | null;\n depth: number;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isExpanded = ref(false);\nconst isRenaming = ref(false);\nconst renameValue = ref(\"\");\nconst isCreatingSubfolder = ref(false);\nconst subfolderName = ref(\"\");\nconst confirmDelete = ref(false);\n\nconst MAX_DEPTH = 5;\n\nconst hasChildren = computed(() => props.folder.children.length > 0);\n\nconst canCreateSubfolder = computed(() => props.depth < MAX_DEPTH - 1);\n\nconst isActive = computed(() => props.currentFolderId === props.folder.id);\n\nconst isDescendantActive = computed(() => {\n if (!props.currentFolderId) return false;\n return containsFolder(props.folder.children, props.currentFolderId);\n});\n\nconst shouldExpand = computed(\n () => isExpanded.value || isDescendantActive.value,\n);\n\nfunction containsFolder(folders: MediaFolderNode[], id: string): boolean {\n for (const folder of folders) {\n if (folder.id === id) return true;\n if (containsFolder(folder.children, id)) return true;\n }\n return false;\n}\n\nfunction toggleExpand(): void {\n isExpanded.value = !isExpanded.value;\n}\n\nfunction startRename(): void {\n isRenaming.value = true;\n renameValue.value = props.folder.name;\n}\n\nfunction confirmRename(): void {\n const trimmed = renameValue.value.trim();\n if (trimmed && trimmed !== props.folder.name) {\n emit(\"renameFolder\", props.folder.id, trimmed);\n }\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction cancelRename(): void {\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction startCreateSubfolder(): void {\n isCreatingSubfolder.value = true;\n subfolderName.value = \"\";\n isExpanded.value = true;\n}\n\nfunction confirmCreateSubfolder(): void {\n if (subfolderName.value.trim()) {\n emit(\"createFolder\", subfolderName.value.trim(), props.folder.id);\n }\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction cancelCreateSubfolder(): void {\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction handleDeleteClick(): void {\n if (confirmDelete.value) {\n emit(\"deleteFolder\", props.folder.id);\n confirmDelete.value = false;\n return;\n }\n confirmDelete.value = true;\n}\n\nfunction onRowFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && (event.currentTarget as HTMLElement).contains(next)) {\n return;\n }\n confirmDelete.value = false;\n}\n</script>\n\n<template>\n <div>\n <!-- Folder row -->\n <div\n class=\"tpl:group tpl:flex tpl:w-full tpl:items-center tpl:gap-1 tpl:py-1.5 tpl:pr-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n :style=\"{\n paddingLeft: `${depth * 16 + 8}px`,\n backgroundColor: isActive ? 'var(--tpl-bg-active)' : 'transparent',\n color: isActive ? 'var(--tpl-primary)' : 'var(--tpl-text)',\n }\"\n @focusout=\"onRowFocusOut\"\n >\n <!-- Expand/collapse chevron -->\n <button\n v-if=\"hasChildren || isCreatingSubfolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-4 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :aria-expanded=\"shouldExpand\"\n :aria-label=\"\n shouldExpand\n ? t.mediaLibrary.collapseFolder\n : t.mediaLibrary.expandFolder\n \"\n @click.stop=\"toggleExpand\"\n >\n <ChevronRight\n class=\"tpl:transition-transform tpl:duration-150\"\n :class=\"{ 'tpl:rotate-90': shouldExpand }\"\n :size=\"10\"\n :stroke-width=\"2\"\n />\n </button>\n <span v-else class=\"tpl:size-4 tpl:shrink-0\" />\n\n <!-- Folder icon + name (clickable) -->\n <button\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-1.5\"\n @click=\"emit('navigate', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <template v-if=\"!isRenaming\">\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n </template>\n </button>\n\n <!-- Inline rename input -->\n <input\n v-if=\"isRenaming\"\n v-model=\"renameValue\"\n type=\"text\"\n class=\"tpl:min-w-0 tpl:flex-1 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n autofocus\n @keydown.enter=\"confirmRename\"\n @keydown.escape.stop=\"cancelRename\"\n @blur=\"confirmRename\"\n @click.stop\n />\n\n <span\n v-if=\"!isRenaming\"\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-0.5 tpl:transition-opacity\"\n :class=\"\n isActive || confirmDelete\n ? 'tpl:opacity-100'\n : 'tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100'\n \"\n >\n <button\n v-if=\"canCreateSubfolder && canCreateFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.addSubfolder\"\n :aria-label=\"t.mediaLibrary.addSubfolder\"\n @click.stop=\"startCreateSubfolder\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canRenameFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.renameFolder\"\n :aria-label=\"t.mediaLibrary.renameFolder\"\n @click.stop=\"startRename\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canDeleteFolder\"\n type=\"button\"\n data-testid=\"media-folder-delete\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n :aria-label=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n @click.stop=\"handleDeleteClick\"\n >\n <Trash2\n :size=\"12\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-danger)\"\n />\n </button>\n </span>\n </div>\n\n <!-- Children (expanded) -->\n <div v-if=\"shouldExpand\">\n <MediaFolderTreeNode\n v-for=\"child in folder.children\"\n :key=\"child.id\"\n :folder=\"child\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"depth + 1\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"\n (name, parentId) => emit('createFolder', name, parentId)\n \"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- Subfolder creation input -->\n <div\n v-if=\"isCreatingSubfolder\"\n :style=\"{ paddingLeft: `${(depth + 1) * 16 + 8}px` }\"\n class=\"tpl:py-1.5 tpl:pr-2\"\n >\n <input\n v-model=\"subfolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.subfolderName\"\n autofocus\n @keydown.enter=\"confirmCreateSubfolder\"\n @keydown.escape.stop=\"cancelCreateSubfolder\"\n @blur=\"confirmCreateSubfolder\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight, Folder, Pencil, Plus, Trash2 } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n folder: MediaFolderNode;\n currentFolderId: string | null;\n depth: number;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isExpanded = ref(false);\nconst isRenaming = ref(false);\nconst renameValue = ref(\"\");\nconst isCreatingSubfolder = ref(false);\nconst subfolderName = ref(\"\");\nconst confirmDelete = ref(false);\n\nconst MAX_DEPTH = 5;\n\nconst hasChildren = computed(() => props.folder.children.length > 0);\n\nconst canCreateSubfolder = computed(() => props.depth < MAX_DEPTH - 1);\n\nconst isActive = computed(() => props.currentFolderId === props.folder.id);\n\nconst isDescendantActive = computed(() => {\n if (!props.currentFolderId) return false;\n return containsFolder(props.folder.children, props.currentFolderId);\n});\n\nconst shouldExpand = computed(\n () => isExpanded.value || isDescendantActive.value,\n);\n\nfunction containsFolder(folders: MediaFolderNode[], id: string): boolean {\n for (const folder of folders) {\n if (folder.id === id) return true;\n if (containsFolder(folder.children, id)) return true;\n }\n return false;\n}\n\nfunction toggleExpand(): void {\n isExpanded.value = !isExpanded.value;\n}\n\nfunction startRename(): void {\n isRenaming.value = true;\n renameValue.value = props.folder.name;\n}\n\nfunction confirmRename(): void {\n const trimmed = renameValue.value.trim();\n if (trimmed && trimmed !== props.folder.name) {\n emit(\"renameFolder\", props.folder.id, trimmed);\n }\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction cancelRename(): void {\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction startCreateSubfolder(): void {\n isCreatingSubfolder.value = true;\n subfolderName.value = \"\";\n isExpanded.value = true;\n}\n\nfunction confirmCreateSubfolder(): void {\n if (subfolderName.value.trim()) {\n emit(\"createFolder\", subfolderName.value.trim(), props.folder.id);\n }\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction cancelCreateSubfolder(): void {\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction handleDeleteClick(): void {\n if (confirmDelete.value) {\n emit(\"deleteFolder\", props.folder.id);\n confirmDelete.value = false;\n return;\n }\n confirmDelete.value = true;\n}\n\nfunction onRowFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && (event.currentTarget as HTMLElement).contains(next)) {\n return;\n }\n confirmDelete.value = false;\n}\n</script>\n\n<template>\n <div>\n <!-- Folder row -->\n <div\n class=\"tpl:group tpl:flex tpl:w-full tpl:items-center tpl:gap-1 tpl:py-1.5 tpl:pr-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n :style=\"{\n paddingLeft: `${depth * 16 + 8}px`,\n backgroundColor: isActive ? 'var(--tpl-bg-active)' : 'transparent',\n color: isActive ? 'var(--tpl-primary)' : 'var(--tpl-text)',\n }\"\n @focusout=\"onRowFocusOut\"\n >\n <!-- Expand/collapse chevron -->\n <button\n v-if=\"hasChildren || isCreatingSubfolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-4 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :aria-expanded=\"shouldExpand\"\n :aria-label=\"\n shouldExpand\n ? t.mediaLibrary.collapseFolder\n : t.mediaLibrary.expandFolder\n \"\n @click.stop=\"toggleExpand\"\n >\n <ChevronRight\n class=\"tpl:transition-transform tpl:duration-150\"\n :class=\"{ 'tpl:rotate-90': shouldExpand }\"\n :size=\"10\"\n :stroke-width=\"2\"\n />\n </button>\n <span v-else class=\"tpl:size-4 tpl:shrink-0\" />\n\n <!-- Folder icon + name (clickable) -->\n <button\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-1.5\"\n @click=\"emit('navigate', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <template v-if=\"!isRenaming\">\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n </template>\n </button>\n\n <!-- Inline rename input -->\n <input\n v-if=\"isRenaming\"\n v-model=\"renameValue\"\n type=\"text\"\n class=\"tpl:min-w-0 tpl:flex-1 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n autofocus\n @keydown.enter=\"confirmRename\"\n @keydown.escape.stop=\"cancelRename\"\n @blur=\"confirmRename\"\n @click.stop\n />\n\n <span\n v-if=\"!isRenaming\"\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-0.5 tpl:transition-opacity\"\n :class=\"\n isActive || confirmDelete\n ? 'tpl:opacity-100'\n : 'tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100'\n \"\n >\n <button\n v-if=\"canCreateSubfolder && canCreateFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.addSubfolder\"\n :aria-label=\"t.mediaLibrary.addSubfolder\"\n @click.stop=\"startCreateSubfolder\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canRenameFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.renameFolder\"\n :aria-label=\"t.mediaLibrary.renameFolder\"\n @click.stop=\"startRename\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canDeleteFolder\"\n type=\"button\"\n data-testid=\"media-folder-delete\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n :aria-label=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n @click.stop=\"handleDeleteClick\"\n >\n <Trash2\n :size=\"12\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-danger)\"\n />\n </button>\n </span>\n </div>\n\n <!-- Children (expanded) -->\n <div v-if=\"shouldExpand\">\n <MediaFolderTreeNode\n v-for=\"child in folder.children\"\n :key=\"child.id\"\n :folder=\"child\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"depth + 1\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"\n (name, parentId) => emit('createFolder', name, parentId)\n \"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- Subfolder creation input -->\n <div\n v-if=\"isCreatingSubfolder\"\n :style=\"{ paddingLeft: `${(depth + 1) * 16 + 8}px` }\"\n class=\"tpl:py-1.5 tpl:pr-2\"\n >\n <input\n v-model=\"subfolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.subfolderName\"\n autofocus\n @keydown.enter=\"confirmCreateSubfolder\"\n @keydown.escape.stop=\"cancelCreateSubfolder\"\n @blur=\"confirmCreateSubfolder\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFolderTreeNode from \"./MediaFolderTreeNode.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaViewMode } from \"../../composable\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { File, Plus, Star } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n viewMode: MediaViewMode;\n hasFrequentlyUsed: boolean;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n (e: \"showFrequentlyUsed\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isCreating = ref(false);\nconst newFolderName = ref(\"\");\n\nfunction startCreateFolder(): void {\n isCreating.value = true;\n newFolderName.value = \"\";\n}\n\nfunction confirmCreateFolder(): void {\n if (newFolderName.value.trim()) {\n emit(\"createFolder\", newFolderName.value.trim());\n }\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n\nfunction cancelCreate(): void {\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n</script>\n\n<template>\n <div\n data-testid=\"media-folder-tree\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:overflow-y-auto\"\n >\n <!-- All Images root -->\n <button\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n backgroundColor:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('navigate', null)\"\n >\n <File :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.allFiles }}\n </button>\n\n <!-- Recursive folder tree -->\n <MediaFolderTreeNode\n v-for=\"folder in folders\"\n :key=\"folder.id\"\n :folder=\"folder\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"0\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"(name, parentId) => emit('createFolder', name, parentId)\"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- New folder input -->\n <div v-if=\"isCreating\" class=\"tpl:px-3 tpl:py-2\">\n <input\n v-model=\"newFolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.folderName\"\n autofocus\n @keydown.enter=\"confirmCreateFolder\"\n @keydown.escape.stop=\"cancelCreate\"\n @blur=\"confirmCreateFolder\"\n />\n </div>\n\n <!-- New folder button -->\n <button\n v-if=\"!isCreating && canCreateFolder\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"startCreateFolder\"\n >\n <Plus :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.newFolder }}\n </button>\n\n <!-- Spacer to push frequently used to bottom -->\n <div class=\"tpl:flex-1\" />\n\n <!-- Frequently Used -->\n <button\n v-if=\"hasFrequentlyUsed\"\n data-testid=\"media-frequently-used\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:border-t tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: 'var(--tpl-border)',\n backgroundColor:\n viewMode === 'frequently-used'\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'frequently-used'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('showFrequentlyUsed')\"\n >\n <Star :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.frequentlyUsed }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFolderTreeNode from \"./MediaFolderTreeNode.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaViewMode } from \"../../composable\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { File, Plus, Star } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n viewMode: MediaViewMode;\n hasFrequentlyUsed: boolean;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n (e: \"showFrequentlyUsed\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isCreating = ref(false);\nconst newFolderName = ref(\"\");\n\nfunction startCreateFolder(): void {\n isCreating.value = true;\n newFolderName.value = \"\";\n}\n\nfunction confirmCreateFolder(): void {\n if (newFolderName.value.trim()) {\n emit(\"createFolder\", newFolderName.value.trim());\n }\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n\nfunction cancelCreate(): void {\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n</script>\n\n<template>\n <div\n data-testid=\"media-folder-tree\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:overflow-y-auto\"\n >\n <!-- All Images root -->\n <button\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n backgroundColor:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('navigate', null)\"\n >\n <File :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.allFiles }}\n </button>\n\n <!-- Recursive folder tree -->\n <MediaFolderTreeNode\n v-for=\"folder in folders\"\n :key=\"folder.id\"\n :folder=\"folder\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"0\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"(name, parentId) => emit('createFolder', name, parentId)\"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- New folder input -->\n <div v-if=\"isCreating\" class=\"tpl:px-3 tpl:py-2\">\n <input\n v-model=\"newFolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.folderName\"\n autofocus\n @keydown.enter=\"confirmCreateFolder\"\n @keydown.escape.stop=\"cancelCreate\"\n @blur=\"confirmCreateFolder\"\n />\n </div>\n\n <!-- New folder button -->\n <button\n v-if=\"!isCreating && canCreateFolder\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"startCreateFolder\"\n >\n <Plus :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.newFolder }}\n </button>\n\n <!-- Spacer to push frequently used to bottom -->\n <div class=\"tpl:flex-1\" />\n\n <!-- Frequently Used -->\n <button\n v-if=\"hasFrequentlyUsed\"\n data-testid=\"media-frequently-used\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:border-t tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: 'var(--tpl-border)',\n backgroundColor:\n viewMode === 'frequently-used'\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'frequently-used'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('showFrequentlyUsed')\"\n >\n <Star :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.frequentlyUsed }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n mimeType: string;\n}>();\n\ninterface FileTypeConfig {\n label: string;\n color: string;\n bgColor: string;\n icon: \"document\" | \"video\" | \"audio\";\n}\n\nconst FILE_TYPE_MAP: Record<string, FileTypeConfig> = {\n \"application/pdf\": {\n label: \"PDF\",\n color: \"var(--tpl-danger)\",\n bgColor: \"var(--tpl-danger-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/msword\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-excel\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-powerpoint\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"text/csv\": {\n label: \"CSV\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"text/plain\": {\n label: \"TXT\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n },\n \"video/mp4\": {\n label: \"MP4\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/quicktime\": {\n label: \"MOV\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/webm\": {\n label: \"WEBM\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"audio/mpeg\": {\n label: \"MP3\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/wav\": {\n label: \"WAV\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/ogg\": {\n label: \"OGG\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n};\n\nconst DEFAULT_CONFIG: FileTypeConfig = {\n label: \"FILE\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n};\n\nconst config = computed(\n (): FileTypeConfig => FILE_TYPE_MAP[props.mimeType] ?? DEFAULT_CONFIG,\n);\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:aspect-square tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2\"\n :style=\"{ backgroundColor: config.bgColor }\"\n >\n <svg\n v-if=\"config.icon === 'document'\"\n width=\"60\"\n height=\"60\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14 2 14 8 20 8\" />\n <line x1=\"8\" y1=\"13\" x2=\"16\" y2=\"13\" />\n <line x1=\"8\" y1=\"17\" x2=\"12\" y2=\"17\" />\n </svg>\n\n <svg\n v-else-if=\"config.icon === 'video'\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" />\n <polygon points=\"10,8 16,12 10,16\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n\n <svg\n v-else\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M9 18V5l12-2v13\" />\n <circle cx=\"6\" cy=\"18\" r=\"3\" />\n <circle cx=\"18\" cy=\"16\" r=\"3\" />\n </svg>\n\n <span\n class=\"tpl:text-xs tpl:font-bold tpl:tracking-wider\"\n :style=\"{ color: config.color }\"\n >\n {{ config.label }}\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n mimeType: string;\n}>();\n\ninterface FileTypeConfig {\n label: string;\n color: string;\n bgColor: string;\n icon: \"document\" | \"video\" | \"audio\";\n}\n\nconst FILE_TYPE_MAP: Record<string, FileTypeConfig> = {\n \"application/pdf\": {\n label: \"PDF\",\n color: \"var(--tpl-danger)\",\n bgColor: \"var(--tpl-danger-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/msword\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-excel\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-powerpoint\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"text/csv\": {\n label: \"CSV\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"text/plain\": {\n label: \"TXT\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n },\n \"video/mp4\": {\n label: \"MP4\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/quicktime\": {\n label: \"MOV\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/webm\": {\n label: \"WEBM\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"audio/mpeg\": {\n label: \"MP3\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/wav\": {\n label: \"WAV\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/ogg\": {\n label: \"OGG\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n};\n\nconst DEFAULT_CONFIG: FileTypeConfig = {\n label: \"FILE\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n};\n\nconst config = computed(\n (): FileTypeConfig => FILE_TYPE_MAP[props.mimeType] ?? DEFAULT_CONFIG,\n);\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:aspect-square tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2\"\n :style=\"{ backgroundColor: config.bgColor }\"\n >\n <svg\n v-if=\"config.icon === 'document'\"\n width=\"60\"\n height=\"60\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14 2 14 8 20 8\" />\n <line x1=\"8\" y1=\"13\" x2=\"16\" y2=\"13\" />\n <line x1=\"8\" y1=\"17\" x2=\"12\" y2=\"17\" />\n </svg>\n\n <svg\n v-else-if=\"config.icon === 'video'\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" />\n <polygon points=\"10,8 16,12 10,16\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n\n <svg\n v-else\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M9 18V5l12-2v13\" />\n <circle cx=\"6\" cy=\"18\" r=\"3\" />\n <circle cx=\"18\" cy=\"16\" r=\"3\" />\n </svg>\n\n <span\n class=\"tpl:text-xs tpl:font-bold tpl:tracking-wider\"\n :style=\"{ color: config.color }\"\n >\n {{ config.label }}\n </span>\n </div>\n</template>\n","/**\n * An absolute date label in the **host's** locale.\n *\n * `toLocaleDateString(undefined, …)` formats in the *runtime's* locale — the\n * browser's or the OS's — not the one that reached this package as\n * `MediaLibraryModal`'s `locale` prop. So the library's captions read in the\n * browser's language while every string around them was translated from the\n * editor's `init({ locale })`.\n *\n * Two guards, both reachable from consumer input:\n *\n * - **A malformed tag makes `Intl` throw `RangeError`.** The locale originates\n * in the host's free-text config, so a bad value would take down the grid\n * for a caption. Falls back to the runtime locale — the behaviour before a\n * locale was threaded through at all — rather than to a hardcoded language.\n * A well-formed but *unregistered* tag (`\"zz\"`) is not caught: `Intl`\n * resolves its own fallback.\n * - **An unparseable date yields `\"\"`**, never a literal \"Invalid Date\".\n *\n * A deliberate twin of `formatAbsoluteDateTime` in `@templatical/editor`. They\n * are copies, not a shared module: this package cannot import from the editor\n * (the dependency runs the other way) and `@templatical/types` is the\n * dependency-free leaf rather than a home for display helpers. Behavioural\n * drift between them fails `tests/formatAbsoluteDate.test.ts`, which carries\n * the editor suite's own cases.\n */\nexport function formatAbsoluteDate(\n iso: string,\n locale: string | undefined,\n options?: Intl.DateTimeFormatOptions,\n): string {\n const parsed = new Date(iso);\n if (Number.isNaN(parsed.getTime())) return \"\";\n\n const tag = locale?.trim().replace(/_/g, \"-\") || undefined;\n\n try {\n return parsed.toLocaleDateString(tag, options);\n } catch {\n return parsed.toLocaleDateString(undefined, options);\n }\n}\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset, MediaCategory } from \"@templatical/types\";\nimport { useIntersectionObserver } from \"@vueuse/core\";\nimport { Check, File, LoaderCircle, Pencil, RefreshCw } from \"@lucide/vue\";\nimport { inject, ref } from \"vue\";\n\nconst props = defineProps<{\n items: MediaAsset[];\n selectedIds: Set<string>;\n isLoading: boolean;\n hasMore: boolean;\n accept?: MediaCategory[];\n layout?: \"grid\" | \"list\";\n searchQuery?: string;\n canUpdate?: boolean;\n canReplace?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", item: MediaAsset): void;\n (e: \"toggle\", id: string): void;\n (e: \"loadMore\"): void;\n (e: \"edit\", item: MediaAsset): void;\n (e: \"replace\", item: MediaAsset): void;\n (e: \"confirm\", item: MediaAsset): void;\n}>();\n\nconst { isAcceptedMimeType, isImageMimeType } = useMediaCategories();\n\nfunction mimeOf(item: MediaAsset): string {\n return item.mimeType ?? \"\";\n}\n\nfunction thumbOf(item: MediaAsset): string {\n return item.thumbnailUrl ?? item.url;\n}\n\nfunction isSelectable(item: MediaAsset): boolean {\n if (!props.accept || props.accept.length === 0) {\n return true;\n }\n\n return isAcceptedMimeType(mimeOf(item), props.accept);\n}\n\nfunction showEdit(item: MediaAsset): boolean {\n return props.canUpdate === true && item.canUpdate !== false;\n}\n\nfunction showReplace(item: MediaAsset): boolean {\n return props.canReplace === true && item.canUpdate !== false;\n}\n\nfunction handleItemClick(item: MediaAsset, event: MouseEvent): void {\n if (event.metaKey || event.ctrlKey) {\n emit(\"toggle\", item.id);\n return;\n }\n emit(\"select\", item);\n}\n\nfunction handleItemDblClick(item: MediaAsset): void {\n if (isSelectable(item)) {\n emit(\"confirm\", item);\n }\n}\n\nfunction handleItemKeydown(item: MediaAsset, event: KeyboardEvent): void {\n if (event.key !== \"Enter\" && event.key !== \" \") {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n emit(\"select\", item);\n}\n\nfunction isSelected(item: MediaAsset): boolean {\n return props.selectedIds.has(item.id);\n}\n\nfunction actionChromeClass(item: MediaAsset): string {\n return isSelected(item)\n ? \"tpl:opacity-100\"\n : \"tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100\";\n}\n\nconst { t } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst sentinelRef = ref<HTMLElement | null>(null);\n\nuseIntersectionObserver(\n sentinelRef,\n ([{ isIntersecting }]) => {\n if (isIntersecting && props.hasMore && !props.isLoading) {\n emit(\"loadMore\");\n }\n },\n { threshold: 0.1 },\n);\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:p-4\">\n <div\n v-if=\"isLoading && items.length === 0\"\n role=\"status\"\n aria-busy=\"true\"\n :aria-label=\"t.mediaLibrary.loading\"\n :class=\"\n layout === 'list'\n ? 'tpl:flex tpl:flex-col tpl:gap-1'\n : 'tpl:grid tpl:grid-cols-4 tpl:gap-3'\n \"\n >\n <div\n v-for=\"i in 8\"\n :key=\"i\"\n class=\"tpl-pulse tpl:rounded-lg\"\n :class=\"layout === 'list' ? 'tpl:h-12' : 'tpl:aspect-square'\"\n style=\"background-color: var(--tpl-bg-hover)\"\n aria-hidden=\"true\"\n />\n </div>\n\n <div\n v-else-if=\"items.length === 0\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:py-16\"\n >\n <File\n class=\"tpl:mb-3\"\n :size=\"40\"\n :stroke-width=\"1\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n searchQuery ? t.mediaLibrary.noSearchResults : t.mediaLibrary.noFiles\n }}\n </p>\n </div>\n\n <div\n v-else-if=\"layout !== 'list'\"\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-3\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-item tpl:group tpl:relative tpl:overflow-hidden tpl:rounded-lg tpl:border-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n 'tpl:cursor-pointer',\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n isSelected(item) ? 'tpl-media-item--selected' : '',\n ]\"\n :style=\"{\n borderColor: isSelected(item) ? 'var(--tpl-primary)' : 'transparent',\n backgroundColor:\n !isSelectable(item) && !isSelected(item)\n ? 'var(--tpl-bg)'\n : 'var(--tpl-bg-hover)',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div class=\"tpl:aspect-square\">\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <MediaFileIcon v-else :mime-type=\"mimeOf(item)\" />\n </div>\n <div class=\"tpl:px-2 tpl:py-1.5\">\n <p\n class=\"tpl:truncate tpl:text-[10px] tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p\n class=\"tpl:flex tpl:justify-between tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <span v-if=\"item.size != null\">{{ formatSize(item.size) }}</span>\n <span\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n {{ item.width }}&times;{{ item.height }}\n </span>\n </p>\n </div>\n <div\n class=\"tpl:absolute tpl:top-1.5 tpl:left-1.5 tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:absolute tpl:top-1.5 tpl:right-1.5 tpl:flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div\n v-else\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-list-item tpl:group tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-lg tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n ]\"\n :style=\"{\n backgroundColor: isSelected(item)\n ? 'var(--tpl-bg-hover)'\n : 'transparent',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div\n class=\"tpl:size-10 tpl:shrink-0 tpl:overflow-hidden tpl:rounded\"\n style=\"background-color: var(--tpl-bg-hover)\"\n >\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <div v-else class=\"tpl-list-icon tpl:size-full\">\n <MediaFileIcon :mime-type=\"mimeOf(item)\" />\n </div>\n </div>\n\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p class=\"tpl:text-[10px]\" style=\"color: var(--tpl-text-muted)\">\n <template v-if=\"item.size != null\">{{\n formatSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> &middot; </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n &middot;\n {{ item.width }}&times;{{ item.height }}\n </template>\n </p>\n </div>\n\n <div\n class=\"tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:flex tpl:size-5 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div ref=\"sentinelRef\" class=\"tpl:h-4\" />\n\n <div\n v-if=\"isLoading && items.length > 0\"\n class=\"tpl:flex tpl:justify-center tpl:py-4\"\n >\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-media-item:not(.tpl-media-item--selected):hover {\n border-color: var(--tpl-border) !important;\n}\n\n.tpl-media-list-item:hover {\n background-color: var(--tpl-bg-hover) !important;\n}\n\n.tpl-list-icon {\n transform: scale(0.35);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset, MediaCategory } from \"@templatical/types\";\nimport { useIntersectionObserver } from \"@vueuse/core\";\nimport { Check, File, LoaderCircle, Pencil, RefreshCw } from \"@lucide/vue\";\nimport { inject, ref } from \"vue\";\n\nconst props = defineProps<{\n items: MediaAsset[];\n selectedIds: Set<string>;\n isLoading: boolean;\n hasMore: boolean;\n accept?: MediaCategory[];\n layout?: \"grid\" | \"list\";\n searchQuery?: string;\n canUpdate?: boolean;\n canReplace?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", item: MediaAsset): void;\n (e: \"toggle\", id: string): void;\n (e: \"loadMore\"): void;\n (e: \"edit\", item: MediaAsset): void;\n (e: \"replace\", item: MediaAsset): void;\n (e: \"confirm\", item: MediaAsset): void;\n}>();\n\nconst { isAcceptedMimeType, isImageMimeType } = useMediaCategories();\n\nfunction mimeOf(item: MediaAsset): string {\n return item.mimeType ?? \"\";\n}\n\nfunction thumbOf(item: MediaAsset): string {\n return item.thumbnailUrl ?? item.url;\n}\n\nfunction isSelectable(item: MediaAsset): boolean {\n if (!props.accept || props.accept.length === 0) {\n return true;\n }\n\n return isAcceptedMimeType(mimeOf(item), props.accept);\n}\n\nfunction showEdit(item: MediaAsset): boolean {\n return props.canUpdate === true && item.canUpdate !== false;\n}\n\nfunction showReplace(item: MediaAsset): boolean {\n return props.canReplace === true && item.canUpdate !== false;\n}\n\nfunction handleItemClick(item: MediaAsset, event: MouseEvent): void {\n if (event.metaKey || event.ctrlKey) {\n emit(\"toggle\", item.id);\n return;\n }\n emit(\"select\", item);\n}\n\nfunction handleItemDblClick(item: MediaAsset): void {\n if (isSelectable(item)) {\n emit(\"confirm\", item);\n }\n}\n\nfunction handleItemKeydown(item: MediaAsset, event: KeyboardEvent): void {\n if (event.key !== \"Enter\" && event.key !== \" \") {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n emit(\"select\", item);\n}\n\nfunction isSelected(item: MediaAsset): boolean {\n return props.selectedIds.has(item.id);\n}\n\nfunction actionChromeClass(item: MediaAsset): string {\n return isSelected(item)\n ? \"tpl:opacity-100\"\n : \"tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100\";\n}\n\nconst { t } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst sentinelRef = ref<HTMLElement | null>(null);\n\nuseIntersectionObserver(\n sentinelRef,\n ([{ isIntersecting }]) => {\n if (isIntersecting && props.hasMore && !props.isLoading) {\n emit(\"loadMore\");\n }\n },\n { threshold: 0.1 },\n);\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:p-4\">\n <div\n v-if=\"isLoading && items.length === 0\"\n role=\"status\"\n aria-busy=\"true\"\n :aria-label=\"t.mediaLibrary.loading\"\n :class=\"\n layout === 'list'\n ? 'tpl:flex tpl:flex-col tpl:gap-1'\n : 'tpl:grid tpl:grid-cols-4 tpl:gap-3'\n \"\n >\n <div\n v-for=\"i in 8\"\n :key=\"i\"\n class=\"tpl-pulse tpl:rounded-lg\"\n :class=\"layout === 'list' ? 'tpl:h-12' : 'tpl:aspect-square'\"\n style=\"background-color: var(--tpl-bg-hover)\"\n aria-hidden=\"true\"\n />\n </div>\n\n <div\n v-else-if=\"items.length === 0\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:py-16\"\n >\n <File\n class=\"tpl:mb-3\"\n :size=\"40\"\n :stroke-width=\"1\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n searchQuery ? t.mediaLibrary.noSearchResults : t.mediaLibrary.noFiles\n }}\n </p>\n </div>\n\n <div\n v-else-if=\"layout !== 'list'\"\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-3\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-item tpl:group tpl:relative tpl:overflow-hidden tpl:rounded-lg tpl:border-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n 'tpl:cursor-pointer',\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n isSelected(item) ? 'tpl-media-item--selected' : '',\n ]\"\n :style=\"{\n borderColor: isSelected(item) ? 'var(--tpl-primary)' : 'transparent',\n backgroundColor:\n !isSelectable(item) && !isSelected(item)\n ? 'var(--tpl-bg)'\n : 'var(--tpl-bg-hover)',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div class=\"tpl:aspect-square\">\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <MediaFileIcon v-else :mime-type=\"mimeOf(item)\" />\n </div>\n <div class=\"tpl:px-2 tpl:py-1.5\">\n <p\n class=\"tpl:truncate tpl:text-[10px] tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p\n class=\"tpl:flex tpl:justify-between tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <span v-if=\"item.size != null\">{{ formatSize(item.size) }}</span>\n <span\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n {{ item.width }}&times;{{ item.height }}\n </span>\n </p>\n </div>\n <div\n class=\"tpl:absolute tpl:top-1.5 tpl:left-1.5 tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:absolute tpl:top-1.5 tpl:right-1.5 tpl:flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div\n v-else\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-list-item tpl:group tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-lg tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n ]\"\n :style=\"{\n backgroundColor: isSelected(item)\n ? 'var(--tpl-bg-hover)'\n : 'transparent',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div\n class=\"tpl:size-10 tpl:shrink-0 tpl:overflow-hidden tpl:rounded\"\n style=\"background-color: var(--tpl-bg-hover)\"\n >\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <div v-else class=\"tpl-list-icon tpl:size-full\">\n <MediaFileIcon :mime-type=\"mimeOf(item)\" />\n </div>\n </div>\n\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p class=\"tpl:text-[10px]\" style=\"color: var(--tpl-text-muted)\">\n <template v-if=\"item.size != null\">{{\n formatSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> &middot; </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n &middot;\n {{ item.width }}&times;{{ item.height }}\n </template>\n </p>\n </div>\n\n <div\n class=\"tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:flex tpl:size-5 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div ref=\"sentinelRef\" class=\"tpl:h-4\" />\n\n <div\n v-if=\"isLoading && items.length > 0\"\n class=\"tpl:flex tpl:justify-center tpl:py-4\"\n >\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-media-item:not(.tpl-media-item--selected):hover {\n border-color: var(--tpl-border) !important;\n}\n\n.tpl-media-list-item:hover {\n background-color: var(--tpl-bg-hover) !important;\n}\n\n.tpl-list-icon {\n transform: scale(0.35);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n isImporting: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"import\", url: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst urlValue = ref(\"\");\n\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n urlValue.value = \"\";\n }\n },\n);\n\nfunction handleImport(): void {\n const trimmedUrl = urlValue.value.trim();\n if (!trimmedUrl || props.isImporting) {\n return;\n }\n\n emit(\"import\", trimmedUrl);\n}\n\nfunction handleClose(): void {\n if (!props.isImporting) {\n emit(\"close\");\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleImport();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"handleClose\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-import-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-import-title\"\n class=\"tpl:mb-4 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.importFromUrl }}\n </h3>\n\n <!-- URL Input -->\n <div class=\"tpl:mb-3\">\n <input\n v-model=\"urlValue\"\n type=\"url\"\n :aria-label=\"t.mediaLibrary.importFromUrl\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-import-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.importUrlPlaceholder\"\n :disabled=\"isImporting\"\n autofocus\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-import-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isImporting\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isImporting,\n }\"\n @click=\"handleClose\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!urlValue.trim() || isImporting\"\n @click=\"handleImport\"\n >\n <span\n v-if=\"isImporting\"\n class=\"tpl:flex tpl:items-center tpl:gap-1.5\"\n >\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.mediaLibrary.importing }}\n </span>\n <span v-else>\n {{ t.mediaLibrary.import }}\n </span>\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n isImporting: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"import\", url: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst urlValue = ref(\"\");\n\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n urlValue.value = \"\";\n }\n },\n);\n\nfunction handleImport(): void {\n const trimmedUrl = urlValue.value.trim();\n if (!trimmedUrl || props.isImporting) {\n return;\n }\n\n emit(\"import\", trimmedUrl);\n}\n\nfunction handleClose(): void {\n if (!props.isImporting) {\n emit(\"close\");\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleImport();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"handleClose\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-import-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-import-title\"\n class=\"tpl:mb-4 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.importFromUrl }}\n </h3>\n\n <!-- URL Input -->\n <div class=\"tpl:mb-3\">\n <input\n v-model=\"urlValue\"\n type=\"url\"\n :aria-label=\"t.mediaLibrary.importFromUrl\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-import-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.importUrlPlaceholder\"\n :disabled=\"isImporting\"\n autofocus\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-import-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isImporting\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isImporting,\n }\"\n @click=\"handleClose\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!urlValue.trim() || isImporting\"\n @click=\"handleImport\"\n >\n <span\n v-if=\"isImporting\"\n class=\"tpl:flex tpl:items-center tpl:gap-1.5\"\n >\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.mediaLibrary.importing }}\n </span>\n <span v-else>\n {{ t.mediaLibrary.import }}\n </span>\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { File, Folder } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", folderId: string | null): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst pickerRef = ref<HTMLDivElement | null>(null);\n\ninterface FlatFolder {\n id: string;\n name: string;\n depth: number;\n}\n\nfunction flattenFolders(\n folders: MediaFolderNode[],\n depth: number = 0,\n): FlatFolder[] {\n const result: FlatFolder[] = [];\n for (const folder of folders) {\n result.push({ id: folder.id, name: folder.name, depth });\n if (folder.children.length) {\n result.push(...flattenFolders(folder.children, depth + 1));\n }\n }\n return result;\n}\n\nonClickOutside(pickerRef, () => {\n emit(\"close\");\n});\n</script>\n\n<template>\n <div\n ref=\"pickerRef\"\n data-testid=\"media-move-picker\"\n class=\"tpl:absolute tpl:bottom-full tpl:left-0 tpl:z-10 tpl:mb-2 tpl:w-56 tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:shadow-lg\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg-elevated);\n \"\n >\n <div class=\"tpl:max-h-56 tpl:overflow-y-auto tpl:py-1\">\n <button\n v-if=\"currentFolderId !== null\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n style=\"color: var(--tpl-text)\"\n @click=\"emit('select', null)\"\n >\n <File class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.moveToRoot }}\n </button>\n <button\n v-for=\"folder in flattenFolders(folders)\"\n :key=\"folder.id\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:py-1.5 tpl:pr-3 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n :style=\"{\n paddingLeft: `${folder.depth * 16 + 12}px`,\n color:\n folder.id === currentFolderId\n ? 'var(--tpl-text-dim)'\n : 'var(--tpl-text)',\n opacity: folder.id === currentFolderId ? 0.5 : 1,\n }\"\n :disabled=\"folder.id === currentFolderId\"\n @click=\"emit('select', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n <span\n v-if=\"folder.id === currentFolderId\"\n class=\"tpl:shrink-0\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ t.mediaLibrary.currentFolder }}\n </span>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { File, Folder } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", folderId: string | null): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst pickerRef = ref<HTMLDivElement | null>(null);\n\ninterface FlatFolder {\n id: string;\n name: string;\n depth: number;\n}\n\nfunction flattenFolders(\n folders: MediaFolderNode[],\n depth: number = 0,\n): FlatFolder[] {\n const result: FlatFolder[] = [];\n for (const folder of folders) {\n result.push({ id: folder.id, name: folder.name, depth });\n if (folder.children.length) {\n result.push(...flattenFolders(folder.children, depth + 1));\n }\n }\n return result;\n}\n\nonClickOutside(pickerRef, () => {\n emit(\"close\");\n});\n</script>\n\n<template>\n <div\n ref=\"pickerRef\"\n data-testid=\"media-move-picker\"\n class=\"tpl:absolute tpl:bottom-full tpl:left-0 tpl:z-10 tpl:mb-2 tpl:w-56 tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:shadow-lg\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg-elevated);\n \"\n >\n <div class=\"tpl:max-h-56 tpl:overflow-y-auto tpl:py-1\">\n <button\n v-if=\"currentFolderId !== null\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n style=\"color: var(--tpl-text)\"\n @click=\"emit('select', null)\"\n >\n <File class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.moveToRoot }}\n </button>\n <button\n v-for=\"folder in flattenFolders(folders)\"\n :key=\"folder.id\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:py-1.5 tpl:pr-3 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n :style=\"{\n paddingLeft: `${folder.depth * 16 + 12}px`,\n color:\n folder.id === currentFolderId\n ? 'var(--tpl-text-dim)'\n : 'var(--tpl-text)',\n opacity: folder.id === currentFolderId ? 0.5 : 1,\n }\"\n :disabled=\"folder.id === currentFolderId\"\n @click=\"emit('select', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n <span\n v-if=\"folder.id === currentFolderId\"\n class=\"tpl:shrink-0\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ t.mediaLibrary.currentFolder }}\n </span>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { Folder } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\n\nconst props = defineProps<{\n item: MediaAsset;\n folders?: MediaFolderNode[];\n}>();\n\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst { isImageMimeType } = useMediaCategories();\n\nfunction buildFolderPath(\n folderList: MediaFolderNode[],\n targetId: string,\n currentPath: string[] = [],\n): string[] | null {\n for (const folder of folderList) {\n const newPath = [...currentPath, folder.name];\n if (folder.id === targetId) {\n return newPath;\n }\n if (folder.children.length) {\n const found = buildFolderPath(folder.children, targetId, newPath);\n if (found) return found;\n }\n }\n return null;\n}\n\nconst folderPath = computed(() => {\n if (!props.item.folderId || !props.folders) {\n return null;\n }\n const path = buildFolderPath(props.folders, props.item.folderId);\n return path ? path.join(\"/\") : null;\n});\n\nconst mimeType = computed(() => props.item.mimeType ?? \"\");\nconst isImage = computed(() => isImageMimeType(mimeType.value));\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nconst filename = computed(() => props.item.filename || props.item.url);\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <img\n v-if=\"isImage\"\n :src=\"item.url\"\n :alt=\"item.alt || filename\"\n class=\"tpl:size-10 tpl:shrink-0 tpl:rounded tpl:object-cover\"\n style=\"border: 1px solid var(--tpl-border)\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:size-10 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:overflow-hidden tpl:rounded\"\n style=\"border: 1px solid var(--tpl-border)\"\n >\n <MediaFileIcon :mime-type=\"mimeType\" class=\"tpl-preview-icon\" />\n </div>\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ filename }}\n </p>\n <p\n class=\"tpl:mt-0.5 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <template v-if=\"item.size != null\">{{\n formatSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> &middot; </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template v-if=\"isImage && item.width && item.height\">\n &middot; {{ item.width }}&times;{{ item.height }}px\n </template>\n <template v-if=\"folderPath\">\n &middot;\n <Folder class=\"tpl:mb-px tpl:inline\" :size=\"9\" :stroke-width=\"2\" />\n {{ folderPath }}\n </template>\n </p>\n <p\n v-if=\"isImage\"\n class=\"tpl:mt-0.5 tpl:truncate tpl:text-[10px] tpl:italic\"\n :class=\"{ 'tpl:invisible': !item.alt }\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ item.alt || \"&nbsp;\" }}\n </p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-preview-icon {\n transform: scale(0.45);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { Folder } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\n\nconst props = defineProps<{\n item: MediaAsset;\n folders?: MediaFolderNode[];\n}>();\n\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst { isImageMimeType } = useMediaCategories();\n\nfunction buildFolderPath(\n folderList: MediaFolderNode[],\n targetId: string,\n currentPath: string[] = [],\n): string[] | null {\n for (const folder of folderList) {\n const newPath = [...currentPath, folder.name];\n if (folder.id === targetId) {\n return newPath;\n }\n if (folder.children.length) {\n const found = buildFolderPath(folder.children, targetId, newPath);\n if (found) return found;\n }\n }\n return null;\n}\n\nconst folderPath = computed(() => {\n if (!props.item.folderId || !props.folders) {\n return null;\n }\n const path = buildFolderPath(props.folders, props.item.folderId);\n return path ? path.join(\"/\") : null;\n});\n\nconst mimeType = computed(() => props.item.mimeType ?? \"\");\nconst isImage = computed(() => isImageMimeType(mimeType.value));\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nconst filename = computed(() => props.item.filename || props.item.url);\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <img\n v-if=\"isImage\"\n :src=\"item.url\"\n :alt=\"item.alt || filename\"\n class=\"tpl:size-10 tpl:shrink-0 tpl:rounded tpl:object-cover\"\n style=\"border: 1px solid var(--tpl-border)\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:size-10 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:overflow-hidden tpl:rounded\"\n style=\"border: 1px solid var(--tpl-border)\"\n >\n <MediaFileIcon :mime-type=\"mimeType\" class=\"tpl-preview-icon\" />\n </div>\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ filename }}\n </p>\n <p\n class=\"tpl:mt-0.5 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <template v-if=\"item.size != null\">{{\n formatSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> &middot; </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template v-if=\"isImage && item.width && item.height\">\n &middot; {{ item.width }}&times;{{ item.height }}px\n </template>\n <template v-if=\"folderPath\">\n &middot;\n <Folder class=\"tpl:mb-px tpl:inline\" :size=\"9\" :stroke-width=\"2\" />\n {{ folderPath }}\n </template>\n </p>\n <p\n v-if=\"isImage\"\n class=\"tpl:mt-0.5 tpl:truncate tpl:text-[10px] tpl:italic\"\n :class=\"{ 'tpl:invisible': !item.alt }\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ item.alt || \"&nbsp;\" }}\n </p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-preview-icon {\n transform: scale(0.45);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport type { MediaAsset, MediaUsageInfo } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n item: MediaAsset | null;\n usageInfo: MediaUsageInfo | null;\n isReplacing: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"replace\", file: File): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst fileInputRef = ref<HTMLInputElement | null>(null);\nconst selectedFile = ref<File | null>(null);\n\nconst extension = computed(() => {\n if (!props.item) {\n return \"\";\n }\n\n const parts = (props.item.filename ?? \"\").split(\".\");\n return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : \"\";\n});\n\nconst acceptPattern = computed(() => {\n return extension.value ? `.${extension.value}` : \"*\";\n});\n\nconst hasUsage = computed(() => {\n return (props.usageInfo?.templateCount ?? 0) > 0;\n});\n\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) {\n selectedFile.value = null;\n if (fileInputRef.value) {\n fileInputRef.value.value = \"\";\n }\n }\n },\n);\n\nfunction handleFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n selectedFile.value = input.files[0];\n }\n}\n\nfunction handleReplace(): void {\n if (selectedFile.value) {\n emit(\"replace\", selectedFile.value);\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible && props.item !== null);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Escape\") {\n emit(\"close\");\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible && item\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"emit('close')\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-replace-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-replace-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.replaceWarningTitle }}\n </h3>\n\n <!-- Warning message -->\n <p class=\"tpl:mb-2 tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n t.mediaLibrary.replaceWarningMessage.replace(\n \"{extension}\",\n `.${extension}`,\n )\n }}\n </p>\n\n <!-- Usage warning -->\n <p\n v-if=\"hasUsage\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{\n t.mediaLibrary.replaceWarningUsageNote.replace(\n \"{count}\",\n usageInfo!.templateCount.toString(),\n )\n }}\n </p>\n\n <!-- Current file info -->\n <div\n class=\"tpl:mb-3 tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename }}\n </p>\n </div>\n\n <!-- File input -->\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.replaceSelectFile }}\n </label>\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :accept=\"acceptPattern\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-replace-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n @change=\"handleFileChange\"\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-replace-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isReplacing\"\n @click=\"emit('close')\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!selectedFile || isReplacing\"\n @click=\"handleReplace\"\n >\n {{\n isReplacing ? t.mediaLibrary.replacing : t.mediaLibrary.replace\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport type { MediaAsset, MediaUsageInfo } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n item: MediaAsset | null;\n usageInfo: MediaUsageInfo | null;\n isReplacing: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"replace\", file: File): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst fileInputRef = ref<HTMLInputElement | null>(null);\nconst selectedFile = ref<File | null>(null);\n\nconst extension = computed(() => {\n if (!props.item) {\n return \"\";\n }\n\n const parts = (props.item.filename ?? \"\").split(\".\");\n return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : \"\";\n});\n\nconst acceptPattern = computed(() => {\n return extension.value ? `.${extension.value}` : \"*\";\n});\n\nconst hasUsage = computed(() => {\n return (props.usageInfo?.templateCount ?? 0) > 0;\n});\n\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) {\n selectedFile.value = null;\n if (fileInputRef.value) {\n fileInputRef.value.value = \"\";\n }\n }\n },\n);\n\nfunction handleFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n selectedFile.value = input.files[0];\n }\n}\n\nfunction handleReplace(): void {\n if (selectedFile.value) {\n emit(\"replace\", selectedFile.value);\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible && props.item !== null);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Escape\") {\n emit(\"close\");\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible && item\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"emit('close')\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-replace-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-replace-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.replaceWarningTitle }}\n </h3>\n\n <!-- Warning message -->\n <p class=\"tpl:mb-2 tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n t.mediaLibrary.replaceWarningMessage.replace(\n \"{extension}\",\n `.${extension}`,\n )\n }}\n </p>\n\n <!-- Usage warning -->\n <p\n v-if=\"hasUsage\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{\n t.mediaLibrary.replaceWarningUsageNote.replace(\n \"{count}\",\n usageInfo!.templateCount.toString(),\n )\n }}\n </p>\n\n <!-- Current file info -->\n <div\n class=\"tpl:mb-3 tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename }}\n </p>\n </div>\n\n <!-- File input -->\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.replaceSelectFile }}\n </label>\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :accept=\"acceptPattern\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-replace-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n @change=\"handleFileChange\"\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-replace-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isReplacing\"\n @click=\"emit('close')\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!selectedFile || isReplacing\"\n @click=\"handleReplace\"\n >\n {{\n isReplacing ? t.mediaLibrary.replacing : t.mediaLibrary.replace\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport { useDropZone, useFileDialog } from \"@vueuse/core\";\nimport { LoaderCircle, Upload } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n isUploading: boolean;\n uploadProgress: { current: number; total: number } | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"upload\", files: File[]): void;\n}>();\n\nconst { t, format } = useI18n();\n\nconst { allAcceptedInputString, isAcceptedFile } = useMediaCategories();\n\nconst dropZoneRef = ref<HTMLElement>();\n\nfunction validateFiles(fileList: File[] | FileList): File[] {\n const valid: File[] = [];\n for (const file of Array.from(fileList)) {\n if (isAcceptedFile(file)) {\n valid.push(file);\n }\n }\n return valid;\n}\n\nfunction emitValidFiles(files: File[] | FileList): void {\n const valid = validateFiles(files);\n if (valid.length) {\n emit(\"upload\", valid);\n }\n}\n\nconst { isOverDropZone } = useDropZone(dropZoneRef, {\n onDrop: (files) => {\n if (files?.length) {\n emitValidFiles(files);\n }\n },\n});\n\nconst { open: openFilePicker, onChange } = useFileDialog({\n accept: allAcceptedInputString.value,\n multiple: true,\n});\n\nonChange((fileList) => {\n if (fileList?.length) {\n emitValidFiles(fileList);\n }\n});\n</script>\n\n<template>\n <button\n ref=\"dropZoneRef\"\n type=\"button\"\n data-testid=\"media-upload-zone\"\n class=\"tpl-upload-zone tpl:flex tpl:w-full tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border-2 tpl:border-dashed tpl:p-5 tpl:text-center tpl:transition-all tpl:duration-150\"\n :class=\"isOverDropZone ? 'tpl-upload-zone-active' : ''\"\n :aria-label=\"t.mediaLibrary.dropOrClick\"\n :aria-busy=\"isUploading\"\n style=\"\n border-color: var(--tpl-border-light);\n background-color: var(--tpl-bg);\n \"\n @click=\"openFilePicker()\"\n >\n <div v-if=\"isUploading\" class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n <span class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">{{\n uploadProgress && uploadProgress.total > 1\n ? format(t.mediaLibrary.uploadingProgress, {\n current: uploadProgress.current,\n total: uploadProgress.total,\n })\n : t.mediaLibrary.uploading\n }}</span>\n </div>\n <template v-else>\n <Upload\n class=\"tpl:mb-2\"\n :size=\"24\"\n :stroke-width=\"1.5\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{ t.mediaLibrary.dropOrClick }}\n </p>\n <p class=\"tpl:mt-1 tpl:text-[10px]\" style=\"color: var(--tpl-text-dim)\">\n {{ t.mediaLibrary.acceptedFormats }}\n </p>\n </template>\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport { useDropZone, useFileDialog } from \"@vueuse/core\";\nimport { LoaderCircle, Upload } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n isUploading: boolean;\n uploadProgress: { current: number; total: number } | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"upload\", files: File[]): void;\n}>();\n\nconst { t, format } = useI18n();\n\nconst { allAcceptedInputString, isAcceptedFile } = useMediaCategories();\n\nconst dropZoneRef = ref<HTMLElement>();\n\nfunction validateFiles(fileList: File[] | FileList): File[] {\n const valid: File[] = [];\n for (const file of Array.from(fileList)) {\n if (isAcceptedFile(file)) {\n valid.push(file);\n }\n }\n return valid;\n}\n\nfunction emitValidFiles(files: File[] | FileList): void {\n const valid = validateFiles(files);\n if (valid.length) {\n emit(\"upload\", valid);\n }\n}\n\nconst { isOverDropZone } = useDropZone(dropZoneRef, {\n onDrop: (files) => {\n if (files?.length) {\n emitValidFiles(files);\n }\n },\n});\n\nconst { open: openFilePicker, onChange } = useFileDialog({\n accept: allAcceptedInputString.value,\n multiple: true,\n});\n\nonChange((fileList) => {\n if (fileList?.length) {\n emitValidFiles(fileList);\n }\n});\n</script>\n\n<template>\n <button\n ref=\"dropZoneRef\"\n type=\"button\"\n data-testid=\"media-upload-zone\"\n class=\"tpl-upload-zone tpl:flex tpl:w-full tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border-2 tpl:border-dashed tpl:p-5 tpl:text-center tpl:transition-all tpl:duration-150\"\n :class=\"isOverDropZone ? 'tpl-upload-zone-active' : ''\"\n :aria-label=\"t.mediaLibrary.dropOrClick\"\n :aria-busy=\"isUploading\"\n style=\"\n border-color: var(--tpl-border-light);\n background-color: var(--tpl-bg);\n \"\n @click=\"openFilePicker()\"\n >\n <div v-if=\"isUploading\" class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n <span class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">{{\n uploadProgress && uploadProgress.total > 1\n ? format(t.mediaLibrary.uploadingProgress, {\n current: uploadProgress.current,\n total: uploadProgress.total,\n })\n : t.mediaLibrary.uploading\n }}</span>\n </div>\n <template v-else>\n <Upload\n class=\"tpl:mb-2\"\n :size=\"24\"\n :stroke-width=\"1.5\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{ t.mediaLibrary.dropOrClick }}\n </p>\n <p class=\"tpl:mt-1 tpl:text-[10px]\" style=\"color: var(--tpl-text-dim)\">\n {{ t.mediaLibrary.acceptedFormats }}\n </p>\n </template>\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n usedBytes: number;\n limitBytes: number;\n size?: number;\n}>();\n\nconst { t, format } = useI18n();\nconst showTooltip = ref(false);\n\nconst size = computed(() => props.size ?? 24);\nconst strokeWidth = computed(() => Math.max(2, size.value / 8));\nconst radius = computed(() => (size.value - strokeWidth.value) / 2);\nconst circumference = computed(() => 2 * Math.PI * radius.value);\n\nconst percentage = computed(() => {\n if (props.limitBytes <= 0) return 0;\n return Math.min(100, (props.usedBytes / props.limitBytes) * 100);\n});\n\nconst strokeDashoffset = computed(() => {\n return circumference.value - (percentage.value / 100) * circumference.value;\n});\n\nconst progressColor = computed(() => {\n if (percentage.value >= 95) {\n return \"var(--tpl-danger)\";\n }\n if (percentage.value >= 75) {\n return \"var(--tpl-warning)\";\n }\n return \"var(--tpl-primary)\";\n});\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n const value = bytes / Math.pow(k, i);\n // Show 1 decimal for MB/GB, no decimals for B/KB\n const decimals = i >= 2 ? 1 : 0;\n return `${value.toFixed(decimals)} ${sizes[i]}`;\n}\n\nconst usedFormatted = computed(() => formatBytes(props.usedBytes));\nconst limitFormatted = computed(() => formatBytes(props.limitBytes));\nconst remainingBytes = computed(() =>\n Math.max(0, props.limitBytes - props.usedBytes),\n);\nconst remainingFormatted = computed(() => formatBytes(remainingBytes.value));\n\nconst tooltipText = computed(() =>\n format(t.mediaLibrary.storageTooltip, {\n used: usedFormatted.value,\n total: limitFormatted.value,\n remaining: remainingFormatted.value,\n }),\n);\n</script>\n\n<template>\n <div\n data-testid=\"media-storage-ring\"\n class=\"tpl:relative tpl:inline-flex tpl:cursor-help tpl:items-center tpl:justify-center\"\n tabindex=\"0\"\n :aria-label=\"tooltipText\"\n @mouseenter=\"showTooltip = true\"\n @mouseleave=\"showTooltip = false\"\n @focus=\"showTooltip = true\"\n @blur=\"showTooltip = false\"\n >\n <svg\n :width=\"size\"\n :height=\"size\"\n class=\"tpl:-rotate-90\"\n :viewBox=\"`0 0 ${size} ${size}`\"\n >\n <!-- Background circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n style=\"stroke: var(--tpl-border)\"\n />\n <!-- Progress circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n :stroke=\"progressColor\"\n stroke-linecap=\"round\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"strokeDashoffset\"\n class=\"tpl:transition-all tpl:duration-300 tpl:ease-out\"\n />\n </svg>\n\n <!-- Tooltip -->\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0 tpl:translate-y-1\"\n enter-to-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-to-class=\"tpl:opacity-0 tpl:translate-y-1\"\n >\n <div\n v-if=\"showTooltip\"\n class=\"tpl:absolute tpl:top-full tpl:left-1/2 tpl:z-50 tpl:mt-2 tpl:-translate-x-1/2 tpl:rounded-md tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:whitespace-nowrap tpl:shadow-lg\"\n style=\"background-color: var(--tpl-text); color: var(--tpl-bg-elevated)\"\n >\n {{ tooltipText }}\n <div\n class=\"tpl:absolute tpl:-top-1 tpl:left-1/2 tpl:size-2 tpl:-translate-x-1/2 tpl:rotate-45\"\n style=\"background-color: var(--tpl-text)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n usedBytes: number;\n limitBytes: number;\n size?: number;\n}>();\n\nconst { t, format } = useI18n();\nconst showTooltip = ref(false);\n\nconst size = computed(() => props.size ?? 24);\nconst strokeWidth = computed(() => Math.max(2, size.value / 8));\nconst radius = computed(() => (size.value - strokeWidth.value) / 2);\nconst circumference = computed(() => 2 * Math.PI * radius.value);\n\nconst percentage = computed(() => {\n if (props.limitBytes <= 0) return 0;\n return Math.min(100, (props.usedBytes / props.limitBytes) * 100);\n});\n\nconst strokeDashoffset = computed(() => {\n return circumference.value - (percentage.value / 100) * circumference.value;\n});\n\nconst progressColor = computed(() => {\n if (percentage.value >= 95) {\n return \"var(--tpl-danger)\";\n }\n if (percentage.value >= 75) {\n return \"var(--tpl-warning)\";\n }\n return \"var(--tpl-primary)\";\n});\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n const value = bytes / Math.pow(k, i);\n // Show 1 decimal for MB/GB, no decimals for B/KB\n const decimals = i >= 2 ? 1 : 0;\n return `${value.toFixed(decimals)} ${sizes[i]}`;\n}\n\nconst usedFormatted = computed(() => formatBytes(props.usedBytes));\nconst limitFormatted = computed(() => formatBytes(props.limitBytes));\nconst remainingBytes = computed(() =>\n Math.max(0, props.limitBytes - props.usedBytes),\n);\nconst remainingFormatted = computed(() => formatBytes(remainingBytes.value));\n\nconst tooltipText = computed(() =>\n format(t.mediaLibrary.storageTooltip, {\n used: usedFormatted.value,\n total: limitFormatted.value,\n remaining: remainingFormatted.value,\n }),\n);\n</script>\n\n<template>\n <div\n data-testid=\"media-storage-ring\"\n class=\"tpl:relative tpl:inline-flex tpl:cursor-help tpl:items-center tpl:justify-center\"\n tabindex=\"0\"\n :aria-label=\"tooltipText\"\n @mouseenter=\"showTooltip = true\"\n @mouseleave=\"showTooltip = false\"\n @focus=\"showTooltip = true\"\n @blur=\"showTooltip = false\"\n >\n <svg\n :width=\"size\"\n :height=\"size\"\n class=\"tpl:-rotate-90\"\n :viewBox=\"`0 0 ${size} ${size}`\"\n >\n <!-- Background circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n style=\"stroke: var(--tpl-border)\"\n />\n <!-- Progress circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n :stroke=\"progressColor\"\n stroke-linecap=\"round\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"strokeDashoffset\"\n class=\"tpl:transition-all tpl:duration-300 tpl:ease-out\"\n />\n </svg>\n\n <!-- Tooltip -->\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0 tpl:translate-y-1\"\n enter-to-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-to-class=\"tpl:opacity-0 tpl:translate-y-1\"\n >\n <div\n v-if=\"showTooltip\"\n class=\"tpl:absolute tpl:top-full tpl:left-1/2 tpl:z-50 tpl:mt-2 tpl:-translate-x-1/2 tpl:rounded-md tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:whitespace-nowrap tpl:shadow-lg\"\n style=\"background-color: var(--tpl-text); color: var(--tpl-bg-elevated)\"\n >\n {{ tooltipText }}\n <div\n class=\"tpl:absolute tpl:-top-1 tpl:left-1/2 tpl:size-2 tpl:-translate-x-1/2 tpl:rotate-45\"\n style=\"background-color: var(--tpl-text)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","import type { MediaFolder } from \"@templatical/types\";\n\n/**\n * A folder plus the children derived from {@link MediaFolder.parentId}.\n *\n * {@link MediaFoldersProvider.list} is flat; the tree is a UI concern so a\n * store never has to return nested nodes.\n */\nexport interface MediaFolderNode extends MediaFolder {\n children: MediaFolderNode[];\n}\n\n/**\n * Group a flat folder list into a tree via `parentId`.\n *\n * A `parentId` that is null, omitted, or not in the list is a root — a\n * dangling reference must still render rather than vanish.\n */\nexport function treeFolders(folders: MediaFolder[]): MediaFolderNode[] {\n const nodes = new Map<string, MediaFolderNode>();\n for (const folder of folders) {\n nodes.set(folder.id, { ...folder, children: [] });\n }\n\n const roots: MediaFolderNode[] = [];\n for (const node of nodes.values()) {\n const parentId = node.parentId ?? null;\n const parent = parentId ? nodes.get(parentId) : undefined;\n if (parent) {\n parent.children.push(node);\n } else {\n roots.push(node);\n }\n }\n return roots;\n}\n","import type { CropData } from \"../components/media/MediaEditModal.vue\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { useMediaLibrary } from \"../composable\";\nimport { treeFolders, type MediaFolderNode } from \"../utils/treeFolders\";\n\ntype UseMediaLibraryReturn = ReturnType<typeof useMediaLibrary>;\nimport { useClipboard, useDebounceFn } from \"@vueuse/core\";\nimport { computed, ref, watch, type ComputedRef, type Ref } from \"vue\";\n\nexport interface UseMediaLibraryUIOptions {\n library: UseMediaLibraryReturn;\n translations:\n | { mediaLibrary: Record<string, string> }\n | Ref<{ mediaLibrary: Record<string, string> }>;\n}\n\nexport interface UseMediaLibraryUIReturn {\n layoutMode: Ref<\"grid\" | \"list\">;\n showSidebar: Ref<boolean>;\n searchInput: Ref<string>;\n editingItem: Ref<MediaAsset | null>;\n showImportUrlModal: Ref<boolean>;\n showMovePicker: Ref<boolean>;\n\n selectedUrl: ComputedRef<string | null>;\n hasFrequentlyUsed: ComputedRef<boolean>;\n displayItems: ComputedRef<MediaAsset[]>;\n hasUsedFiles: ComputedRef<boolean>;\n folderTree: ComputedRef<MediaFolderNode[]>;\n\n copy: (text: string) => Promise<void>;\n copied: Ref<boolean>;\n\n getCategoryLabel: (category: string) => string;\n\n handleSearchInput: (value: string) => void;\n handleUpload: (files: File[]) => Promise<void>;\n handleSelect: (item: MediaAsset) => void;\n handleCreateFolder: (name: string, parentId?: string | null) => Promise<void>;\n handleRenameFolder: (folderId: string, name: string) => Promise<void>;\n handleDeleteFolder: (folderId: string) => Promise<void>;\n handleEditItem: (item: MediaAsset) => void;\n handleEditSave: (\n mediaId: string,\n filename: string,\n altText?: string,\n cropData?: CropData,\n ) => Promise<void>;\n handleImportFromUrl: (url: string) => Promise<void>;\n handleMoveToFolder: (folderId: string | null) => Promise<void>;\n handleDeleteClick: () => Promise<void>;\n handleReplaceItem: (item: MediaAsset) => void;\n handleReplaceFile: (file: File) => Promise<void>;\n\n resetUI: () => void;\n}\n\nexport function useMediaLibraryUI(\n options: UseMediaLibraryUIOptions,\n): UseMediaLibraryUIReturn {\n const { library, translations } = options;\n\n function getTranslations() {\n if (\"value\" in translations && typeof translations.value === \"object\") {\n return (translations as Ref<{ mediaLibrary: Record<string, string> }>)\n .value;\n }\n return translations as { mediaLibrary: Record<string, string> };\n }\n\n const layoutMode = ref<\"grid\" | \"list\">(\"grid\");\n const showSidebar = ref(false);\n const searchInput = ref(\"\");\n const editingItem = ref<MediaAsset | null>(null);\n const showImportUrlModal = ref(false);\n const showMovePicker = ref(false);\n\n const selectedUrl = computed(() => library.previewItem.value?.url ?? null);\n\n const hasFrequentlyUsed = computed(() => {\n return library.frequentlyUsedItems.value.length > 0;\n });\n\n const displayItems = computed(() => {\n if (library.viewMode.value === \"frequently-used\") {\n return library.frequentlyUsedItems.value;\n }\n return library.items.value;\n });\n\n const hasUsedFiles = computed(() => {\n return Object.values(library.deleteUsageInfo.value).some(\n (info) => info.templateCount > 0,\n );\n });\n\n const folderTree = computed(() => treeFolders(library.folders.value));\n\n const categoryLabels: Record<string, () => string> = {\n images: () => getTranslations().mediaLibrary.filterImages,\n documents: () => getTranslations().mediaLibrary.filterDocuments,\n videos: () => getTranslations().mediaLibrary.filterVideos,\n audio: () => getTranslations().mediaLibrary.filterAudio,\n };\n\n function getCategoryLabel(category: string): string {\n return categoryLabels[category]?.() ?? category;\n }\n\n watch(showSidebar, (show) => {\n if (show) {\n library.loadFolders();\n }\n });\n\n const debouncedSearch = useDebounceFn((value: string) => {\n library.search(value);\n }, 300);\n\n function handleSearchInput(value: string): void {\n searchInput.value = value;\n debouncedSearch(value);\n }\n\n const { copy, copied } = useClipboard({ copiedDuring: 2000, legacy: true });\n\n async function handleUpload(files: File[]): Promise<void> {\n await library.uploadFiles(files);\n }\n\n function handleSelect(item: MediaAsset): void {\n library.selectItem(item);\n }\n\n async function handleCreateFolder(\n name: string,\n parentId?: string | null,\n ): Promise<void> {\n await library.createFolder(name, parentId);\n }\n\n async function handleRenameFolder(\n folderId: string,\n name: string,\n ): Promise<void> {\n await library.renameFolder(folderId, name);\n }\n\n async function handleDeleteFolder(folderId: string): Promise<void> {\n await library.deleteFolder(folderId);\n }\n\n function handleEditItem(item: MediaAsset): void {\n editingItem.value = item;\n }\n\n async function handleEditSave(\n mediaId: string,\n filename: string,\n altText?: string,\n cropData?: CropData,\n ): Promise<void> {\n if (cropData) {\n await library.replaceMediaDirectly(mediaId, cropData.file);\n }\n await library.updateFile(mediaId, filename, altText);\n editingItem.value = null;\n }\n\n async function handleImportFromUrl(url: string): Promise<void> {\n const result = await library.importFromUrl(url);\n if (result) {\n showImportUrlModal.value = false;\n }\n }\n\n async function handleMoveToFolder(folderId: string | null): Promise<void> {\n showMovePicker.value = false;\n await library.moveSelected(folderId);\n }\n\n async function handleDeleteClick(): Promise<void> {\n await library.checkUsageBeforeDelete();\n }\n\n function handleReplaceItem(item: MediaAsset): void {\n library.checkUsageBeforeReplace(item);\n }\n\n async function handleReplaceFile(file: File): Promise<void> {\n await library.replaceFile(file);\n }\n\n function resetUI(): void {\n library.clearSelection();\n library.cancelDelete();\n library.cancelReplace();\n searchInput.value = \"\";\n // Assign, don't `search(\"\")`: that would list on close. The next open\n // already calls `loadItems()` with whatever `searchQuery` holds.\n library.searchQuery.value = \"\";\n library.categoryFilter.value = null;\n library.sortOption.value = \"newest\";\n library.viewMode.value = \"files\";\n editingItem.value = null;\n showImportUrlModal.value = false;\n }\n\n return {\n layoutMode,\n showSidebar,\n searchInput,\n editingItem,\n showImportUrlModal,\n showMovePicker,\n selectedUrl,\n hasFrequentlyUsed,\n displayItems,\n hasUsedFiles,\n folderTree,\n copy,\n copied,\n getCategoryLabel,\n handleSearchInput,\n handleUpload,\n handleSelect,\n handleCreateFolder,\n handleRenameFolder,\n handleDeleteFolder,\n handleEditItem,\n handleEditSave,\n handleImportFromUrl,\n handleMoveToFolder,\n handleDeleteClick,\n handleReplaceItem,\n handleReplaceFile,\n resetUI,\n };\n}\n","import type {\n MediaAsset,\n MediaAssetPatch,\n MediaCategory,\n MediaCreateInput,\n MediaFolder,\n MediaFoldersProvider,\n MediaFolderInput,\n MediaListParams,\n MediaProvider,\n MediaStorageInfo,\n MediaUsageInfo,\n} from \"@templatical/types\";\nimport { ref, toValue, type MaybeRefOrGetter, type Ref } from \"vue\";\n\nexport type MediaViewMode = \"files\" | \"frequently-used\";\n\nexport interface UseMediaLibraryOptions {\n /**\n * Storage backend. The composable is transport-agnostic and never talks\n * to a network itself.\n */\n provider: MediaProvider;\n onError?: (error: Error) => void;\n /**\n * Forwarded on `list` / `create` / `importFromUrl` when a template is\n * loaded. Read at call time — a ref that fills after setup must still\n * reach the store.\n */\n templateId?: MaybeRefOrGetter<string | undefined>;\n}\n\n/**\n * Reactive state over a {@link MediaProvider}.\n *\n * Owns the listing, selection, preview, sequential upload loop and the\n * delete-usage flow. Chrome that should hide for a `false` method is the\n * modal's job; this composable simply does not call a disabled method.\n */\nexport function useMediaLibrary(options: UseMediaLibraryOptions) {\n const { provider } = options;\n\n const items: Ref<MediaAsset[]> = ref([]);\n const folders: Ref<MediaFolder[]> = ref([]);\n const currentFolderId: Ref<string | null> = ref(null);\n const viewMode: Ref<MediaViewMode> = ref(\"files\");\n const searchQuery: Ref<string> = ref(\"\");\n const categoryFilter: Ref<MediaCategory | null> = ref(null);\n const sortOption: Ref<string> = ref(\"newest\");\n const isLoading = ref(false);\n const isUploading = ref(false);\n const hasMore = ref(false);\n const nextCursor: Ref<string | null> = ref(null);\n const uploadProgress: Ref<{ current: number; total: number } | null> =\n ref(null);\n const selectedItems: Ref<Set<string>> = ref(new Set());\n const previewItem: Ref<MediaAsset | null> = ref(null);\n const frequentlyUsedItems: Ref<MediaAsset[]> = ref([]);\n const deleteUsageInfo: Ref<Record<string, MediaUsageInfo>> = ref({});\n const showDeleteWarning = ref(false);\n const pendingDeleteIds: Ref<string[]> = ref([]);\n const isImportingFromUrl = ref(false);\n const importFromUrlError: Ref<string | null> = ref(null);\n const isReplacing = ref(false);\n const replaceError: Ref<string | null> = ref(null);\n const showReplaceWarning = ref(false);\n const pendingReplaceItem: Ref<MediaAsset | null> = ref(null);\n const replaceUsageInfo: Ref<MediaUsageInfo | null> = ref(null);\n const storageInfo: Ref<MediaStorageInfo | null> = ref(null);\n\n // Monotonic token so an out-of-order list/loadMore response (folder\n // switch / search change before the previous request settled) cannot\n // overwrite the current view with stale data.\n let browseRequestId = 0;\n\n /**\n * A handler that throws must not turn a completed write into a rejected\n * one — the UI would report a failure for an asset that was stored.\n */\n function notify(run: () => void): void {\n try {\n run();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function foldersProvider(): MediaFoldersProvider | null {\n return provider.folders === false ? null : provider.folders;\n }\n\n /**\n * `sort` is absent: {@link MediaListParams} has no sort field.\n * `sortOption` stays as local UI state; changing it reloads the current\n * filters without inventing an order the store did not advertise.\n */\n function resolvedTemplateId(): string | undefined {\n return toValue(options.templateId);\n }\n\n function currentListParams(cursor?: string): MediaListParams {\n const params: MediaListParams = {};\n if (searchQuery.value) {\n params.search = searchQuery.value;\n } else if (currentFolderId.value != null) {\n params.folderId = currentFolderId.value;\n }\n if (categoryFilter.value) {\n params.category = categoryFilter.value;\n }\n if (cursor) {\n params.cursor = cursor;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n params.templateId = templateId;\n }\n return params;\n }\n\n async function loadItems(): Promise<void> {\n const requestId = ++browseRequestId;\n isLoading.value = true;\n const params = currentListParams();\n try {\n const page = await provider.list(params);\n if (requestId !== browseRequestId) return;\n items.value = page.items;\n nextCursor.value = page.nextCursor ?? null;\n hasMore.value = !!page.nextCursor;\n } catch (error) {\n if (requestId !== browseRequestId) return;\n options.onError?.(error as Error);\n } finally {\n if (requestId === browseRequestId) {\n isLoading.value = false;\n }\n }\n }\n\n async function loadMore(): Promise<void> {\n if (!hasMore.value || !nextCursor.value || isLoading.value) return;\n\n const requestId = ++browseRequestId;\n isLoading.value = true;\n const params = currentListParams(nextCursor.value);\n try {\n const page = await provider.list(params);\n if (requestId !== browseRequestId) return;\n items.value = [...items.value, ...page.items];\n nextCursor.value = page.nextCursor ?? null;\n hasMore.value = !!page.nextCursor;\n } catch (error) {\n if (requestId !== browseRequestId) return;\n options.onError?.(error as Error);\n } finally {\n if (requestId === browseRequestId) {\n isLoading.value = false;\n }\n }\n }\n\n async function search(query: string): Promise<void> {\n searchQuery.value = query;\n await loadItems();\n }\n\n async function filterByCategory(\n category: MediaCategory | null,\n ): Promise<void> {\n categoryFilter.value = category;\n await loadItems();\n }\n\n async function sortBy(option: string): Promise<void> {\n sortOption.value = option;\n await loadItems();\n }\n\n async function navigateToFolder(folderId: string | null): Promise<void> {\n viewMode.value = \"files\";\n currentFolderId.value = folderId;\n searchQuery.value = \"\";\n selectedItems.value = new Set();\n previewItem.value = null;\n await loadItems();\n }\n\n async function showFrequentlyUsed(): Promise<void> {\n viewMode.value = \"frequently-used\";\n currentFolderId.value = null;\n searchQuery.value = \"\";\n selectedItems.value = new Set();\n previewItem.value = null;\n await loadFrequentlyUsed();\n }\n\n async function uploadFile(file: File): Promise<MediaAsset | null> {\n const { create } = provider;\n if (typeof create !== \"function\") {\n return null;\n }\n\n isUploading.value = true;\n try {\n const input: MediaCreateInput = { file };\n if (currentFolderId.value != null) {\n input.folderId = currentFolderId.value;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n input.templateId = templateId;\n }\n const media = await create(input);\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n return media;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n } finally {\n isUploading.value = false;\n }\n }\n\n async function uploadFiles(files: File[]): Promise<void> {\n const { create } = provider;\n if (typeof create !== \"function\") {\n return;\n }\n\n isUploading.value = true;\n uploadProgress.value = { current: 0, total: files.length };\n try {\n for (let i = 0; i < files.length; i++) {\n uploadProgress.value = { current: i + 1, total: files.length };\n try {\n const input: MediaCreateInput = { file: files[i] };\n if (currentFolderId.value != null) {\n input.folderId = currentFolderId.value;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n input.templateId = templateId;\n }\n const media = await create(input);\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n } finally {\n isUploading.value = false;\n uploadProgress.value = null;\n }\n }\n\n async function moveSelected(targetFolderId: string | null): Promise<void> {\n if (selectedItems.value.size === 0) {\n return;\n }\n\n const foldersApi = foldersProvider();\n const { move } = foldersApi ?? { move: false };\n if (typeof move !== \"function\") {\n return;\n }\n\n try {\n const movedItems = await move([...selectedItems.value], targetFolderId);\n if (currentFolderId.value === null) {\n const movedMap = new Map(movedItems.map((item) => [item.id, item]));\n items.value = items.value.map((item) => movedMap.get(item.id) ?? item);\n } else {\n items.value = items.value.filter(\n (item) => !selectedItems.value.has(item.id),\n );\n }\n selectedItems.value = new Set();\n previewItem.value = null;\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function updateFile(\n mediaId: string,\n filename: string,\n altText?: string,\n ): Promise<void> {\n const { update } = provider;\n if (typeof update !== \"function\") {\n return;\n }\n\n try {\n const patch: MediaAssetPatch = { filename };\n if (altText !== undefined) {\n patch.alt = altText;\n }\n const updated = await update(mediaId, patch);\n items.value = items.value.map((item) =>\n item.id === mediaId ? updated : item,\n );\n if (previewItem.value?.id === mediaId) {\n previewItem.value = updated;\n }\n notify(() => provider.onUpdated?.(updated));\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function captureLocalAssets(ids: string[]): MediaAsset[] {\n const captured = new Map<string, MediaAsset>();\n for (const item of items.value) {\n if (ids.includes(item.id)) {\n captured.set(item.id, item);\n }\n }\n for (const item of frequentlyUsedItems.value) {\n if (ids.includes(item.id) && !captured.has(item.id)) {\n captured.set(item.id, item);\n }\n }\n return [...captured.values()];\n }\n\n function dropLocalAssets(ids: string[]): void {\n items.value = items.value.filter((item) => !ids.includes(item.id));\n frequentlyUsedItems.value = frequentlyUsedItems.value.filter(\n (item) => !ids.includes(item.id),\n );\n selectedItems.value = new Set();\n previewItem.value = null;\n }\n\n async function deleteSelected(): Promise<void> {\n if (selectedItems.value.size === 0) return;\n\n const { delete: providerDelete } = provider;\n if (typeof providerDelete !== \"function\") {\n return;\n }\n\n const ids = [...selectedItems.value];\n const captured = captureLocalAssets(ids);\n\n try {\n await providerDelete(ids);\n dropLocalAssets(ids);\n for (const asset of captured) {\n notify(() => provider.onDeleted?.(asset));\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function loadFrequentlyUsed(): Promise<void> {\n const { frequentlyUsed } = provider;\n if (typeof frequentlyUsed !== \"function\") {\n return;\n }\n\n try {\n frequentlyUsedItems.value = await frequentlyUsed();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function checkUsageBeforeDelete(): Promise<boolean> {\n if (selectedItems.value.size === 0) {\n return false;\n }\n\n pendingDeleteIds.value = [...selectedItems.value];\n\n const { checkUsage } = provider;\n if (typeof checkUsage !== \"function\") {\n deleteUsageInfo.value = {};\n showDeleteWarning.value = true;\n return false;\n }\n\n try {\n const usage = await checkUsage(pendingDeleteIds.value);\n deleteUsageInfo.value = usage;\n\n const hasUsage = Object.values(usage).some(\n (info) => info.templateCount > 0,\n );\n\n showDeleteWarning.value = true;\n return hasUsage;\n } catch (error) {\n options.onError?.(error as Error);\n return false;\n }\n }\n\n async function confirmDelete(): Promise<void> {\n const { delete: providerDelete } = provider;\n if (typeof providerDelete !== \"function\") {\n return;\n }\n\n showDeleteWarning.value = false;\n\n if (pendingDeleteIds.value.length === 0) {\n return;\n }\n\n const ids = pendingDeleteIds.value;\n const captured = captureLocalAssets(ids);\n\n try {\n await providerDelete(ids);\n dropLocalAssets(ids);\n pendingDeleteIds.value = [];\n deleteUsageInfo.value = {};\n for (const asset of captured) {\n notify(() => provider.onDeleted?.(asset));\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function cancelDelete(): void {\n showDeleteWarning.value = false;\n pendingDeleteIds.value = [];\n deleteUsageInfo.value = {};\n }\n\n async function importFromUrl(url: string): Promise<MediaAsset | null> {\n const importUrl = provider.importFromUrl;\n if (typeof importUrl !== \"function\") {\n return null;\n }\n\n isImportingFromUrl.value = true;\n importFromUrlError.value = null;\n try {\n const media = await importUrl(\n url,\n currentFolderId.value,\n resolvedTemplateId(),\n );\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n return media;\n } catch (error) {\n importFromUrlError.value =\n error instanceof Error ? error.message : \"Import failed\";\n options.onError?.(error as Error);\n return null;\n } finally {\n isImportingFromUrl.value = false;\n }\n }\n\n function toggleSelection(id: string): void {\n const next = new Set(selectedItems.value);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n selectedItems.value = next;\n }\n\n function clearSelection(): void {\n selectedItems.value = new Set();\n previewItem.value = null;\n }\n\n function selectItem(item: MediaAsset): void {\n previewItem.value = item;\n selectedItems.value = new Set([item.id]);\n }\n\n async function loadStorage(): Promise<void> {\n const { storage } = provider;\n if (typeof storage !== \"function\") {\n storageInfo.value = null;\n return;\n }\n\n try {\n storageInfo.value = await storage();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function loadFolders(): Promise<void> {\n const foldersApi = foldersProvider();\n if (!foldersApi) {\n return;\n }\n\n try {\n folders.value = await foldersApi.list();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function createFolder(\n name: string,\n parentId?: string | null,\n ): Promise<MediaFolder | null> {\n const foldersApi = foldersProvider();\n const { create } = foldersApi ?? { create: false };\n if (typeof create !== \"function\") {\n return null;\n }\n\n try {\n const input: MediaFolderInput = { name };\n if (parentId !== undefined) {\n input.parentId = parentId;\n }\n const folder = await create(input);\n await loadFolders();\n return folder;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n }\n }\n\n function findFolderInTree(\n folderList: MediaFolder[],\n id: string,\n ): MediaFolder | null {\n for (const folder of folderList) {\n if (folder.id === id) return folder;\n }\n return null;\n }\n\n async function renameFolder(folderId: string, name: string): Promise<void> {\n const foldersApi = foldersProvider();\n const { update } = foldersApi ?? { update: false };\n if (typeof update !== \"function\") {\n return;\n }\n\n try {\n await update(folderId, { name });\n await loadFolders();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function deleteFolder(folderId: string): Promise<void> {\n const foldersApi = foldersProvider();\n const { delete: deleteFn } = foldersApi ?? { delete: false };\n if (typeof deleteFn !== \"function\") {\n return;\n }\n\n try {\n const folder = findFolderInTree(folders.value, folderId);\n const parentId = folder?.parentId ?? null;\n\n await deleteFn(folderId);\n\n if (currentFolderId.value === folderId) {\n currentFolderId.value = parentId;\n }\n\n await loadFolders();\n await loadItems();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function checkUsageBeforeReplace(item: MediaAsset): Promise<void> {\n pendingReplaceItem.value = item;\n replaceError.value = null;\n\n const { checkUsage } = provider;\n if (typeof checkUsage !== \"function\") {\n replaceUsageInfo.value = null;\n showReplaceWarning.value = true;\n return;\n }\n\n try {\n const usage = await checkUsage([item.id]);\n replaceUsageInfo.value = usage[item.id] ?? null;\n showReplaceWarning.value = true;\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function cancelReplace(): void {\n showReplaceWarning.value = false;\n pendingReplaceItem.value = null;\n replaceUsageInfo.value = null;\n replaceError.value = null;\n }\n\n function applyReplaced(updated: MediaAsset): void {\n items.value = items.value.map((item) =>\n item.id === updated.id ? updated : item,\n );\n\n frequentlyUsedItems.value = frequentlyUsedItems.value.map((item) =>\n item.id === updated.id ? updated : item,\n );\n\n if (previewItem.value?.id === updated.id) {\n previewItem.value = updated;\n }\n\n notify(() => provider.onUpdated?.(updated));\n }\n\n async function replaceFile(file: File): Promise<MediaAsset | null> {\n const { replace } = provider;\n if (typeof replace !== \"function\" || !pendingReplaceItem.value) {\n return null;\n }\n\n isReplacing.value = true;\n replaceError.value = null;\n\n try {\n const updated = await replace(pendingReplaceItem.value.id, file);\n applyReplaced(updated);\n\n showReplaceWarning.value = false;\n pendingReplaceItem.value = null;\n replaceUsageInfo.value = null;\n\n return updated;\n } catch (error) {\n replaceError.value =\n error instanceof Error ? error.message : \"Replace failed\";\n options.onError?.(error as Error);\n return null;\n } finally {\n isReplacing.value = false;\n }\n }\n\n async function replaceMediaDirectly(\n mediaId: string,\n file: File,\n ): Promise<MediaAsset | null> {\n const { replace } = provider;\n if (typeof replace !== \"function\") {\n return null;\n }\n\n try {\n const updated = await replace(mediaId, file);\n applyReplaced(updated);\n return updated;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n }\n }\n\n return {\n items,\n folders,\n currentFolderId,\n viewMode,\n searchQuery,\n categoryFilter,\n sortOption,\n isLoading,\n isUploading,\n uploadProgress,\n hasMore,\n selectedItems,\n previewItem,\n frequentlyUsedItems,\n deleteUsageInfo,\n showDeleteWarning,\n loadItems,\n loadMore,\n search,\n filterByCategory,\n sortBy,\n navigateToFolder,\n showFrequentlyUsed,\n uploadFile,\n uploadFiles,\n moveSelected,\n updateFile,\n deleteSelected,\n isImportingFromUrl,\n importFromUrlError,\n importFromUrl,\n toggleSelection,\n clearSelection,\n selectItem,\n loadFolders,\n createFolder,\n renameFolder,\n deleteFolder,\n findFolderInTree,\n loadFrequentlyUsed,\n checkUsageBeforeDelete,\n confirmDelete,\n cancelDelete,\n isReplacing,\n replaceError,\n showReplaceWarning,\n pendingReplaceItem,\n replaceUsageInfo,\n checkUsageBeforeReplace,\n cancelReplace,\n replaceFile,\n replaceMediaDirectly,\n storageInfo,\n loadStorage,\n };\n}\n","<script setup lang=\"ts\">\nimport MediaBreadcrumb from \"./media/MediaBreadcrumb.vue\";\nimport MediaFolderTree from \"./media/MediaFolderTree.vue\";\nimport MediaGrid from \"./media/MediaGrid.vue\";\nimport MediaImportUrlModal from \"./media/MediaImportUrlModal.vue\";\nimport MediaMovePicker from \"./media/MediaMovePicker.vue\";\nimport MediaPreviewPanel from \"./media/MediaPreviewPanel.vue\";\nimport MediaReplaceModal from \"./media/MediaReplaceModal.vue\";\nimport MediaUploadZone from \"./media/MediaUploadZone.vue\";\nimport StorageProgressRing from \"./media/StorageProgressRing.vue\";\nimport { useMediaCategories } from \"../composables/useMediaCategories\";\nimport { useMediaLibraryUI } from \"../composables/useMediaLibraryUI\";\nimport { useMediaLibrary } from \"../composable\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport {\n Check,\n Copy,\n Grid2x2,\n Link,\n List,\n PanelLeft,\n Search,\n X,\n} from \"@lucide/vue\";\nimport {\n computed,\n defineAsyncComponent,\n onBeforeUnmount,\n provide,\n watch,\n} from \"vue\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { MEDIA_LIMITS_KEY } from \"../keys\";\n\nconst props = defineProps<{\n provider: MediaProvider;\n accept?: MediaCategory[];\n templateId?: string;\n onError?: (error: Error) => void;\n translations: MediaTranslations;\n showClose?: boolean;\n showConfirm?: boolean;\n confirmTestId?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"confirm\", item: MediaAsset): void;\n (e: \"ready\"): void;\n}>();\n\nconst MediaEditModal = defineAsyncComponent(\n () => import(\"./media/MediaEditModal.vue\"),\n);\n\nconst t = computed(() => props.translations);\nconst resolvedConfirmTestId = computed(\n () => props.confirmTestId ?? \"media-confirm\",\n);\n\nconst mediaLimits = {\n get maxFileSize() {\n return props.provider.maxFileSize;\n },\n get mimeTypes() {\n return props.provider.mimeTypes;\n },\n get accept() {\n return props.accept;\n },\n};\nprovide(MEDIA_LIMITS_KEY, mediaLimits);\n\nconst { isAcceptedMimeType, availableCategories } =\n useMediaCategories(mediaLimits);\n\nconst library = useMediaLibrary({\n provider: props.provider,\n templateId: () => props.templateId,\n onError: (error) => props.onError?.(error),\n});\n\nconst ui = useMediaLibraryUI({\n library,\n translations: t,\n});\n\nconst canCreate = computed(() => typeof props.provider.create === \"function\");\nconst canFolders = computed(() => props.provider.folders !== false);\nconst canImport = computed(\n () => typeof props.provider.importFromUrl === \"function\",\n);\nconst canDelete = computed(() => typeof props.provider.delete === \"function\");\nconst canUpdate = computed(() => typeof props.provider.update === \"function\");\nconst canReplace = computed(() => typeof props.provider.replace === \"function\");\nconst canFrequentlyUsed = computed(\n () => typeof props.provider.frequentlyUsed === \"function\",\n);\nconst canMove = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.move === \"function\";\n});\nconst canCreateFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.create === \"function\";\n});\nconst canRenameFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.update === \"function\";\n});\nconst canDeleteFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.delete === \"function\";\n});\n\nconst isInitialLoad = computed(\n () => library.isLoading.value && ui.displayItems.value.length === 0,\n);\n\nconst isConfirmable = computed(() => {\n const item = library.previewItem.value;\n if (!item) {\n return false;\n }\n if (!props.accept?.length) {\n return true;\n }\n return isAcceptedMimeType(item.mimeType ?? \"\", props.accept);\n});\n\nconst selectedDeletableCount = computed(() => {\n if (!canDelete.value) {\n return 0;\n }\n let count = 0;\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n count += 1;\n }\n }\n return count;\n});\n\nconst nestedDialogOpen = computed(\n () =>\n ui.showImportUrlModal.value ||\n ui.editingItem.value !== null ||\n library.showReplaceWarning.value,\n);\n\nfunction handleEscape(): boolean {\n if (nestedDialogOpen.value) {\n return true;\n }\n if (ui.showMovePicker.value) {\n ui.showMovePicker.value = false;\n return true;\n }\n if (library.showDeleteWarning.value) {\n library.cancelDelete();\n return true;\n }\n return false;\n}\n\nfunction confirmSelection(): void {\n if (!isConfirmable.value || !library.previewItem.value) {\n return;\n }\n emit(\"confirm\", library.previewItem.value);\n}\n\nfunction handleCategoryChange(event: Event): void {\n const value = (event.target as HTMLSelectElement).value;\n library.filterByCategory(value ? (value as MediaCategory) : null);\n}\n\nasync function handleDeleteClick(): Promise<void> {\n const next = new Set<string>();\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n next.add(id);\n }\n }\n library.selectedItems.value = next;\n await ui.handleDeleteClick();\n}\n\nwatch(\n () => props.accept,\n (accept) => {\n if (accept?.length === 1) {\n library.categoryFilter.value = accept[0];\n }\n },\n { immediate: true },\n);\n\nlibrary.loadItems();\nlibrary.loadFrequentlyUsed();\nlibrary.loadStorage();\nemit(\"ready\");\n\nonBeforeUnmount(() => {\n ui.resetUI();\n});\n\ndefineExpose({ nestedDialogOpen, handleEscape, library, ui });\n</script>\n\n<template>\n <div\n class=\"tpl:relative tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-5 tpl:py-3.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <h2\n id=\"tpl-media-library-title\"\n class=\"tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.title }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <StorageProgressRing\n v-if=\"library.storageInfo.value\"\n :used-bytes=\"library.storageInfo.value.usedBytes\"\n :limit-bytes=\"library.storageInfo.value.limitBytes\"\n :size=\"22\"\n />\n <div class=\"tpl:relative\">\n <input\n :value=\"ui.searchInput.value\"\n type=\"search\"\n :disabled=\"isInitialLoad\"\n :aria-label=\"t.mediaLibrary.searchAriaLabel\"\n class=\"tpl:w-52 tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-3 tpl:pl-8 tpl:text-xs tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:outline-none tpl:focus:shadow-[var(--tpl-ring)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.searchPlaceholder\"\n @input=\"\n ui.handleSearchInput(($event.target as HTMLInputElement).value)\n \"\n />\n <Search\n class=\"tpl:absolute tpl:top-1/2 tpl:left-2.5 tpl:-translate-y-1/2\"\n :size=\"13\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-text-dim)\"\n />\n </div>\n <button\n v-if=\"showClose\"\n type=\"button\"\n :aria-label=\"t.mediaLibrary.close\"\n class=\"tpl:flex tpl:size-7 tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"emit('close')\"\n >\n <X :size=\"18\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Body -->\n <div class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:overflow-hidden\">\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:-ml-48 tpl:opacity-0\"\n enter-to-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-150 tpl:ease-in\"\n leave-from-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-to-class=\"tpl:-ml-48 tpl:opacity-0\"\n >\n <div\n v-if=\"canFolders && ui.showSidebar.value\"\n class=\"tpl:flex tpl:w-48 tpl:shrink-0 tpl:flex-col tpl:border-r\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <MediaFolderTree\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n :view-mode=\"library.viewMode.value\"\n :has-frequently-used=\"\n canFrequentlyUsed && ui.hasFrequentlyUsed.value\n \"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"library.navigateToFolder\"\n @create-folder=\"ui.handleCreateFolder\"\n @rename-folder=\"ui.handleRenameFolder\"\n @delete-folder=\"ui.handleDeleteFolder\"\n @show-frequently-used=\"library.showFrequentlyUsed\"\n />\n </div>\n </Transition>\n\n <!-- Content area -->\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:flex-col\">\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-4 tpl:py-2.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"canFolders\"\n type=\"button\"\n data-testid=\"media-folder-toggle\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: ui.showSidebar.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor: ui.showSidebar.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n border: ui.showSidebar.value\n ? '1px solid var(--tpl-border)'\n : '1px solid transparent',\n }\"\n :title=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-label=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-pressed=\"ui.showSidebar.value\"\n @click=\"ui.showSidebar.value = !ui.showSidebar.value\"\n >\n <PanelLeft :size=\"16\" :stroke-width=\"2\" />\n </button>\n\n <template v-if=\"library.viewMode.value === 'frequently-used'\">\n <span\n class=\"tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.frequentlyUsed }}\n </span>\n </template>\n <template v-else>\n <MediaBreadcrumb\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @navigate=\"library.navigateToFolder\"\n />\n </template>\n\n <div\n class=\"tpl:flex tpl:rounded-md tpl:p-0.5\"\n style=\"\n border: 1px solid var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewGrid\"\n :aria-label=\"t.mediaLibrary.viewGrid\"\n :aria-pressed=\"ui.layoutMode.value === 'grid'\"\n @click=\"ui.layoutMode.value = 'grid'\"\n >\n <Grid2x2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewList\"\n :aria-label=\"t.mediaLibrary.viewList\"\n :aria-pressed=\"ui.layoutMode.value === 'list'\"\n @click=\"ui.layoutMode.value = 'list'\"\n >\n <List :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n v-if=\"availableCategories.length > 1\"\n :aria-label=\"t.mediaLibrary.filterAriaLabel\"\n class=\"tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-7 tpl:pl-2.5 tpl:text-xs tpl:transition-all tpl:duration-150 tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :value=\"library.categoryFilter.value ?? ''\"\n @change=\"handleCategoryChange\"\n >\n <option value=\"\">\n {{ t.mediaLibrary.filterAll }}\n </option>\n <option\n v-for=\"category in availableCategories\"\n :key=\"category\"\n :value=\"category\"\n >\n {{ ui.getCategoryLabel(category) }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto\">\n <div\n v-if=\"\n library.viewMode.value === 'files' && (canCreate || canImport)\n \"\n class=\"tpl:px-4 tpl:pt-3\"\n >\n <MediaUploadZone\n v-if=\"canCreate\"\n :is-uploading=\"library.isUploading.value\"\n :upload-progress=\"library.uploadProgress.value\"\n @upload=\"ui.handleUpload\"\n />\n <button\n v-if=\"canImport\"\n data-testid=\"media-import-url\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-dashed tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text-muted);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showImportUrlModal.value = true\"\n >\n <Link :size=\"14\" :stroke-width=\"2\" />\n {{ t.mediaLibrary.importFromUrl }}\n </button>\n </div>\n\n <MediaGrid\n :items=\"ui.displayItems.value\"\n :selected-ids=\"library.selectedItems.value\"\n :is-loading=\"library.isLoading.value\"\n :has-more=\"\n library.viewMode.value === 'files' && library.hasMore.value\n \"\n :accept=\"accept\"\n :layout=\"ui.layoutMode.value\"\n :search-query=\"library.searchQuery.value\"\n :can-update=\"canUpdate\"\n :can-replace=\"canReplace\"\n @select=\"ui.handleSelect\"\n @toggle=\"library.toggleSelection\"\n @load-more=\"library.loadMore\"\n @edit=\"ui.handleEditItem\"\n @replace=\"ui.handleReplaceItem\"\n @confirm=\"confirmSelection\"\n />\n </div>\n </div>\n </div>\n\n <MediaImportUrlModal\n :visible=\"ui.showImportUrlModal.value\"\n :is-importing=\"library.isImportingFromUrl.value\"\n :error=\"library.importFromUrlError.value\"\n @import=\"ui.handleImportFromUrl\"\n @close=\"ui.showImportUrlModal.value = false\"\n />\n\n <MediaEditModal\n v-if=\"ui.editingItem.value\"\n :visible=\"true\"\n :item=\"ui.editingItem.value\"\n @save=\"ui.handleEditSave\"\n @close=\"ui.editingItem.value = null\"\n />\n\n <MediaReplaceModal\n :visible=\"library.showReplaceWarning.value\"\n :item=\"library.pendingReplaceItem.value\"\n :usage-info=\"library.replaceUsageInfo.value\"\n :is-replacing=\"library.isReplacing.value\"\n :error=\"library.replaceError.value\"\n @replace=\"ui.handleReplaceFile\"\n @close=\"library.cancelReplace\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"library.showDeleteWarning.value\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-delete-title\"\n class=\"tpl:absolute tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"library.cancelDelete\"\n >\n <div\n class=\"tpl-scale-in tpl:mx-4 tpl:w-full tpl:max-w-sm tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-media-delete-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.deleteWarningTitle }}\n </h3>\n <p\n class=\"tpl:text-xs\"\n :class=\"ui.hasUsedFiles.value ? 'tpl:mb-2' : 'tpl:mb-4'\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningMessage }}\n </p>\n <p\n v-if=\"ui.hasUsedFiles.value\"\n data-testid=\"media-delete-usage\"\n class=\"tpl:mb-4 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningUsageNote }}\n </p>\n\n <div\n v-if=\"ui.hasUsedFiles.value\"\n class=\"tpl:mb-4 tpl:max-h-32 tpl:overflow-y-auto tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div\n v-for=\"(info, mediaId) in library.deleteUsageInfo.value\"\n :key=\"mediaId\"\n class=\"tpl:text-xs\"\n style=\"color: var(--tpl-text)\"\n >\n <template v-if=\"info.templateCount > 0\">\n <span class=\"tpl:font-medium\">\n {{\n ui.displayItems.value.find((i) => i.id === mediaId)\n ?.filename || mediaId\n }}\n </span>\n <span style=\"color: var(--tpl-text-muted)\">\n -\n {{\n t.mediaLibrary.usedInTemplates.replace(\n \"{count}\",\n info.templateCount.toString(),\n )\n }}\n </span>\n </template>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.cancelDelete\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.confirmDelete\"\n >\n {{\n ui.hasUsedFiles.value\n ? t.mediaLibrary.deleteAnyway\n : t.mediaLibrary.confirmDelete\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Footer -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-t tpl:px-5 tpl:py-3\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-3\">\n <MediaPreviewPanel\n v-if=\"library.previewItem.value\"\n :item=\"library.previewItem.value\"\n :folders=\"ui.folderTree.value\"\n />\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-5\">\n <div\n v-if=\"library.selectedItems.value.size > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n v-if=\"library.previewItem.value\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: ui.copied.value\n ? 'var(--tpl-success)'\n : 'var(--tpl-border)',\n color: ui.copied.value ? 'var(--tpl-success)' : 'var(--tpl-text)',\n backgroundColor: 'var(--tpl-bg)',\n }\"\n @click=\"ui.copy(ui.selectedUrl.value!)\"\n >\n <Copy v-if=\"!ui.copied.value\" :size=\"12\" :stroke-width=\"2\" />\n <Check v-else :size=\"12\" :stroke-width=\"2\" />\n {{\n ui.copied.value ? t.mediaLibrary.copied : t.mediaLibrary.copyUrl\n }}\n </button>\n <div v-if=\"canMove\" class=\"tpl:relative\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showMovePicker.value = !ui.showMovePicker.value\"\n >\n {{ t.mediaLibrary.moveSelected }}\n </button>\n <MediaMovePicker\n v-if=\"ui.showMovePicker.value\"\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @select=\"ui.handleMoveToFolder\"\n @close=\"ui.showMovePicker.value = false\"\n />\n </div>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"selectedDeletableCount > 0\"\n data-testid=\"media-delete\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"handleDeleteClick\"\n >\n {{ t.mediaLibrary.deleteSelected }}\n </button>\n <button\n v-if=\"showConfirm\"\n :data-testid=\"resolvedConfirmTestId\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-4 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!isConfirmable\"\n @click=\"confirmSelection\"\n >\n {{\n accept?.length\n ? t.mediaLibrary.selectImage\n : t.mediaLibrary.selectFile\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaBreadcrumb from \"./media/MediaBreadcrumb.vue\";\nimport MediaFolderTree from \"./media/MediaFolderTree.vue\";\nimport MediaGrid from \"./media/MediaGrid.vue\";\nimport MediaImportUrlModal from \"./media/MediaImportUrlModal.vue\";\nimport MediaMovePicker from \"./media/MediaMovePicker.vue\";\nimport MediaPreviewPanel from \"./media/MediaPreviewPanel.vue\";\nimport MediaReplaceModal from \"./media/MediaReplaceModal.vue\";\nimport MediaUploadZone from \"./media/MediaUploadZone.vue\";\nimport StorageProgressRing from \"./media/StorageProgressRing.vue\";\nimport { useMediaCategories } from \"../composables/useMediaCategories\";\nimport { useMediaLibraryUI } from \"../composables/useMediaLibraryUI\";\nimport { useMediaLibrary } from \"../composable\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport {\n Check,\n Copy,\n Grid2x2,\n Link,\n List,\n PanelLeft,\n Search,\n X,\n} from \"@lucide/vue\";\nimport {\n computed,\n defineAsyncComponent,\n onBeforeUnmount,\n provide,\n watch,\n} from \"vue\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { MEDIA_LIMITS_KEY } from \"../keys\";\n\nconst props = defineProps<{\n provider: MediaProvider;\n accept?: MediaCategory[];\n templateId?: string;\n onError?: (error: Error) => void;\n translations: MediaTranslations;\n showClose?: boolean;\n showConfirm?: boolean;\n confirmTestId?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"confirm\", item: MediaAsset): void;\n (e: \"ready\"): void;\n}>();\n\nconst MediaEditModal = defineAsyncComponent(\n () => import(\"./media/MediaEditModal.vue\"),\n);\n\nconst t = computed(() => props.translations);\nconst resolvedConfirmTestId = computed(\n () => props.confirmTestId ?? \"media-confirm\",\n);\n\nconst mediaLimits = {\n get maxFileSize() {\n return props.provider.maxFileSize;\n },\n get mimeTypes() {\n return props.provider.mimeTypes;\n },\n get accept() {\n return props.accept;\n },\n};\nprovide(MEDIA_LIMITS_KEY, mediaLimits);\n\nconst { isAcceptedMimeType, availableCategories } =\n useMediaCategories(mediaLimits);\n\nconst library = useMediaLibrary({\n provider: props.provider,\n templateId: () => props.templateId,\n onError: (error) => props.onError?.(error),\n});\n\nconst ui = useMediaLibraryUI({\n library,\n translations: t,\n});\n\nconst canCreate = computed(() => typeof props.provider.create === \"function\");\nconst canFolders = computed(() => props.provider.folders !== false);\nconst canImport = computed(\n () => typeof props.provider.importFromUrl === \"function\",\n);\nconst canDelete = computed(() => typeof props.provider.delete === \"function\");\nconst canUpdate = computed(() => typeof props.provider.update === \"function\");\nconst canReplace = computed(() => typeof props.provider.replace === \"function\");\nconst canFrequentlyUsed = computed(\n () => typeof props.provider.frequentlyUsed === \"function\",\n);\nconst canMove = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.move === \"function\";\n});\nconst canCreateFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.create === \"function\";\n});\nconst canRenameFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.update === \"function\";\n});\nconst canDeleteFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.delete === \"function\";\n});\n\nconst isInitialLoad = computed(\n () => library.isLoading.value && ui.displayItems.value.length === 0,\n);\n\nconst isConfirmable = computed(() => {\n const item = library.previewItem.value;\n if (!item) {\n return false;\n }\n if (!props.accept?.length) {\n return true;\n }\n return isAcceptedMimeType(item.mimeType ?? \"\", props.accept);\n});\n\nconst selectedDeletableCount = computed(() => {\n if (!canDelete.value) {\n return 0;\n }\n let count = 0;\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n count += 1;\n }\n }\n return count;\n});\n\nconst nestedDialogOpen = computed(\n () =>\n ui.showImportUrlModal.value ||\n ui.editingItem.value !== null ||\n library.showReplaceWarning.value,\n);\n\nfunction handleEscape(): boolean {\n if (nestedDialogOpen.value) {\n return true;\n }\n if (ui.showMovePicker.value) {\n ui.showMovePicker.value = false;\n return true;\n }\n if (library.showDeleteWarning.value) {\n library.cancelDelete();\n return true;\n }\n return false;\n}\n\nfunction confirmSelection(): void {\n if (!isConfirmable.value || !library.previewItem.value) {\n return;\n }\n emit(\"confirm\", library.previewItem.value);\n}\n\nfunction handleCategoryChange(event: Event): void {\n const value = (event.target as HTMLSelectElement).value;\n library.filterByCategory(value ? (value as MediaCategory) : null);\n}\n\nasync function handleDeleteClick(): Promise<void> {\n const next = new Set<string>();\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n next.add(id);\n }\n }\n library.selectedItems.value = next;\n await ui.handleDeleteClick();\n}\n\nwatch(\n () => props.accept,\n (accept) => {\n if (accept?.length === 1) {\n library.categoryFilter.value = accept[0];\n }\n },\n { immediate: true },\n);\n\nlibrary.loadItems();\nlibrary.loadFrequentlyUsed();\nlibrary.loadStorage();\nemit(\"ready\");\n\nonBeforeUnmount(() => {\n ui.resetUI();\n});\n\ndefineExpose({ nestedDialogOpen, handleEscape, library, ui });\n</script>\n\n<template>\n <div\n class=\"tpl:relative tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-5 tpl:py-3.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <h2\n id=\"tpl-media-library-title\"\n class=\"tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.title }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <StorageProgressRing\n v-if=\"library.storageInfo.value\"\n :used-bytes=\"library.storageInfo.value.usedBytes\"\n :limit-bytes=\"library.storageInfo.value.limitBytes\"\n :size=\"22\"\n />\n <div class=\"tpl:relative\">\n <input\n :value=\"ui.searchInput.value\"\n type=\"search\"\n :disabled=\"isInitialLoad\"\n :aria-label=\"t.mediaLibrary.searchAriaLabel\"\n class=\"tpl:w-52 tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-3 tpl:pl-8 tpl:text-xs tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:outline-none tpl:focus:shadow-[var(--tpl-ring)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.searchPlaceholder\"\n @input=\"\n ui.handleSearchInput(($event.target as HTMLInputElement).value)\n \"\n />\n <Search\n class=\"tpl:absolute tpl:top-1/2 tpl:left-2.5 tpl:-translate-y-1/2\"\n :size=\"13\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-text-dim)\"\n />\n </div>\n <button\n v-if=\"showClose\"\n type=\"button\"\n :aria-label=\"t.mediaLibrary.close\"\n class=\"tpl:flex tpl:size-7 tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"emit('close')\"\n >\n <X :size=\"18\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Body -->\n <div class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:overflow-hidden\">\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:-ml-48 tpl:opacity-0\"\n enter-to-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-150 tpl:ease-in\"\n leave-from-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-to-class=\"tpl:-ml-48 tpl:opacity-0\"\n >\n <div\n v-if=\"canFolders && ui.showSidebar.value\"\n class=\"tpl:flex tpl:w-48 tpl:shrink-0 tpl:flex-col tpl:border-r\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <MediaFolderTree\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n :view-mode=\"library.viewMode.value\"\n :has-frequently-used=\"\n canFrequentlyUsed && ui.hasFrequentlyUsed.value\n \"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"library.navigateToFolder\"\n @create-folder=\"ui.handleCreateFolder\"\n @rename-folder=\"ui.handleRenameFolder\"\n @delete-folder=\"ui.handleDeleteFolder\"\n @show-frequently-used=\"library.showFrequentlyUsed\"\n />\n </div>\n </Transition>\n\n <!-- Content area -->\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:flex-col\">\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-4 tpl:py-2.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"canFolders\"\n type=\"button\"\n data-testid=\"media-folder-toggle\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: ui.showSidebar.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor: ui.showSidebar.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n border: ui.showSidebar.value\n ? '1px solid var(--tpl-border)'\n : '1px solid transparent',\n }\"\n :title=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-label=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-pressed=\"ui.showSidebar.value\"\n @click=\"ui.showSidebar.value = !ui.showSidebar.value\"\n >\n <PanelLeft :size=\"16\" :stroke-width=\"2\" />\n </button>\n\n <template v-if=\"library.viewMode.value === 'frequently-used'\">\n <span\n class=\"tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.frequentlyUsed }}\n </span>\n </template>\n <template v-else>\n <MediaBreadcrumb\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @navigate=\"library.navigateToFolder\"\n />\n </template>\n\n <div\n class=\"tpl:flex tpl:rounded-md tpl:p-0.5\"\n style=\"\n border: 1px solid var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewGrid\"\n :aria-label=\"t.mediaLibrary.viewGrid\"\n :aria-pressed=\"ui.layoutMode.value === 'grid'\"\n @click=\"ui.layoutMode.value = 'grid'\"\n >\n <Grid2x2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewList\"\n :aria-label=\"t.mediaLibrary.viewList\"\n :aria-pressed=\"ui.layoutMode.value === 'list'\"\n @click=\"ui.layoutMode.value = 'list'\"\n >\n <List :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n v-if=\"availableCategories.length > 1\"\n :aria-label=\"t.mediaLibrary.filterAriaLabel\"\n class=\"tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-7 tpl:pl-2.5 tpl:text-xs tpl:transition-all tpl:duration-150 tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :value=\"library.categoryFilter.value ?? ''\"\n @change=\"handleCategoryChange\"\n >\n <option value=\"\">\n {{ t.mediaLibrary.filterAll }}\n </option>\n <option\n v-for=\"category in availableCategories\"\n :key=\"category\"\n :value=\"category\"\n >\n {{ ui.getCategoryLabel(category) }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto\">\n <div\n v-if=\"\n library.viewMode.value === 'files' && (canCreate || canImport)\n \"\n class=\"tpl:px-4 tpl:pt-3\"\n >\n <MediaUploadZone\n v-if=\"canCreate\"\n :is-uploading=\"library.isUploading.value\"\n :upload-progress=\"library.uploadProgress.value\"\n @upload=\"ui.handleUpload\"\n />\n <button\n v-if=\"canImport\"\n data-testid=\"media-import-url\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-dashed tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text-muted);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showImportUrlModal.value = true\"\n >\n <Link :size=\"14\" :stroke-width=\"2\" />\n {{ t.mediaLibrary.importFromUrl }}\n </button>\n </div>\n\n <MediaGrid\n :items=\"ui.displayItems.value\"\n :selected-ids=\"library.selectedItems.value\"\n :is-loading=\"library.isLoading.value\"\n :has-more=\"\n library.viewMode.value === 'files' && library.hasMore.value\n \"\n :accept=\"accept\"\n :layout=\"ui.layoutMode.value\"\n :search-query=\"library.searchQuery.value\"\n :can-update=\"canUpdate\"\n :can-replace=\"canReplace\"\n @select=\"ui.handleSelect\"\n @toggle=\"library.toggleSelection\"\n @load-more=\"library.loadMore\"\n @edit=\"ui.handleEditItem\"\n @replace=\"ui.handleReplaceItem\"\n @confirm=\"confirmSelection\"\n />\n </div>\n </div>\n </div>\n\n <MediaImportUrlModal\n :visible=\"ui.showImportUrlModal.value\"\n :is-importing=\"library.isImportingFromUrl.value\"\n :error=\"library.importFromUrlError.value\"\n @import=\"ui.handleImportFromUrl\"\n @close=\"ui.showImportUrlModal.value = false\"\n />\n\n <MediaEditModal\n v-if=\"ui.editingItem.value\"\n :visible=\"true\"\n :item=\"ui.editingItem.value\"\n @save=\"ui.handleEditSave\"\n @close=\"ui.editingItem.value = null\"\n />\n\n <MediaReplaceModal\n :visible=\"library.showReplaceWarning.value\"\n :item=\"library.pendingReplaceItem.value\"\n :usage-info=\"library.replaceUsageInfo.value\"\n :is-replacing=\"library.isReplacing.value\"\n :error=\"library.replaceError.value\"\n @replace=\"ui.handleReplaceFile\"\n @close=\"library.cancelReplace\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"library.showDeleteWarning.value\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-delete-title\"\n class=\"tpl:absolute tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"library.cancelDelete\"\n >\n <div\n class=\"tpl-scale-in tpl:mx-4 tpl:w-full tpl:max-w-sm tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-media-delete-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.deleteWarningTitle }}\n </h3>\n <p\n class=\"tpl:text-xs\"\n :class=\"ui.hasUsedFiles.value ? 'tpl:mb-2' : 'tpl:mb-4'\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningMessage }}\n </p>\n <p\n v-if=\"ui.hasUsedFiles.value\"\n data-testid=\"media-delete-usage\"\n class=\"tpl:mb-4 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningUsageNote }}\n </p>\n\n <div\n v-if=\"ui.hasUsedFiles.value\"\n class=\"tpl:mb-4 tpl:max-h-32 tpl:overflow-y-auto tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div\n v-for=\"(info, mediaId) in library.deleteUsageInfo.value\"\n :key=\"mediaId\"\n class=\"tpl:text-xs\"\n style=\"color: var(--tpl-text)\"\n >\n <template v-if=\"info.templateCount > 0\">\n <span class=\"tpl:font-medium\">\n {{\n ui.displayItems.value.find((i) => i.id === mediaId)\n ?.filename || mediaId\n }}\n </span>\n <span style=\"color: var(--tpl-text-muted)\">\n -\n {{\n t.mediaLibrary.usedInTemplates.replace(\n \"{count}\",\n info.templateCount.toString(),\n )\n }}\n </span>\n </template>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.cancelDelete\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.confirmDelete\"\n >\n {{\n ui.hasUsedFiles.value\n ? t.mediaLibrary.deleteAnyway\n : t.mediaLibrary.confirmDelete\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Footer -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-t tpl:px-5 tpl:py-3\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-3\">\n <MediaPreviewPanel\n v-if=\"library.previewItem.value\"\n :item=\"library.previewItem.value\"\n :folders=\"ui.folderTree.value\"\n />\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-5\">\n <div\n v-if=\"library.selectedItems.value.size > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n v-if=\"library.previewItem.value\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: ui.copied.value\n ? 'var(--tpl-success)'\n : 'var(--tpl-border)',\n color: ui.copied.value ? 'var(--tpl-success)' : 'var(--tpl-text)',\n backgroundColor: 'var(--tpl-bg)',\n }\"\n @click=\"ui.copy(ui.selectedUrl.value!)\"\n >\n <Copy v-if=\"!ui.copied.value\" :size=\"12\" :stroke-width=\"2\" />\n <Check v-else :size=\"12\" :stroke-width=\"2\" />\n {{\n ui.copied.value ? t.mediaLibrary.copied : t.mediaLibrary.copyUrl\n }}\n </button>\n <div v-if=\"canMove\" class=\"tpl:relative\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showMovePicker.value = !ui.showMovePicker.value\"\n >\n {{ t.mediaLibrary.moveSelected }}\n </button>\n <MediaMovePicker\n v-if=\"ui.showMovePicker.value\"\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @select=\"ui.handleMoveToFolder\"\n @close=\"ui.showMovePicker.value = false\"\n />\n </div>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"selectedDeletableCount > 0\"\n data-testid=\"media-delete\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"handleDeleteClick\"\n >\n {{ t.mediaLibrary.deleteSelected }}\n </button>\n <button\n v-if=\"showConfirm\"\n :data-testid=\"resolvedConfirmTestId\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-4 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!isConfirmable\"\n @click=\"confirmSelection\"\n >\n {{\n accept?.length\n ? t.mediaLibrary.selectImage\n : t.mediaLibrary.selectFile\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","/// <reference types=\"vite/client\" />\n\nimport type en from \"./locales/en\";\n\nexport type MediaTranslations = typeof en;\n\n// Vite resolves this glob at build time. Adding a new locale file\n// automatically registers it — no array to keep in sync.\nconst localeModules = import.meta.glob<{ default: MediaTranslations }>(\n \"./locales/*.ts\",\n);\n\nfunction localesFromGlob(modules: Record<string, unknown>): string[] {\n return Object.keys(modules)\n .map((path) => path.match(/\\/([^/]+)\\.ts$/)?.[1])\n .filter((locale): locale is string => Boolean(locale));\n}\n\nconst supportedLocales = localesFromGlob(localeModules);\n\nfunction canonicalize(locale: string): string {\n return locale.trim().replace(/_/g, \"-\").toLowerCase();\n}\n\nfunction findSupportedLocale(locale: string): string | undefined {\n const canonical = canonicalize(locale);\n return supportedLocales.find((s) => canonicalize(s) === canonical);\n}\n\nfunction getBaseLocale(locale: string): string {\n return canonicalize(locale).split(\"-\")[0];\n}\n\nfunction resolveLocale(locale: string): string {\n return (\n findSupportedLocale(locale) ??\n findSupportedLocale(getBaseLocale(locale)) ??\n \"en\"\n );\n}\n\nexport async function loadMediaTranslations(\n locale: string,\n): Promise<MediaTranslations> {\n const target = resolveLocale(locale);\n const loader = localeModules[`./locales/${target}.ts`];\n const module = await loader();\n return module.default;\n}\n","<script setup lang=\"ts\">\nimport MediaLibraryChrome from \"./MediaLibraryChrome.vue\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport { useEventListener } from \"@vueuse/core\";\nimport { useFocusTrap } from \"../composables/useFocusTrap\";\nimport { computed, provide, ref, toRef, watch } from \"vue\";\nimport {\n POPOVER_TARGET_KEY,\n TRANSLATIONS_KEY,\n UI_LOCALE_KEY,\n UI_THEME_KEY,\n} from \"../keys\";\nimport { loadMediaTranslations, type MediaTranslations } from \"../i18n\";\n\nconst props = defineProps<{\n visible: boolean;\n /**\n * Storage backend. **A prop, not an injection.** An injection would have\n * to agree with the host on key identity: a bare string never resolves\n * the `Symbol` a host provides, and the miss is silent. A prop is checked\n * at compile time.\n */\n provider: MediaProvider;\n accept?: MediaCategory[];\n popoverTarget?: HTMLElement | null;\n /**\n * BCP-47 locale for this package's own strings, defaulting to English.\n *\n * A locale rather than the strings themselves: media-library owns its\n * translations and loads them here, so a host passes a value it already has\n * and never handles media copy. That also keeps the load lazy — the host\n * imports nothing from this package's i18n, so nothing is fetched until this\n * modal mounts.\n */\n locale?: string;\n /**\n * Resolved UI theme (`\"light\"` / `\"dark\"`) for the overlay chrome.\n *\n * A plain string rather than the host's ref: props are reactive, so the\n * computed below tracks a host theme toggle without this package depending on\n * how the host stores it.\n */\n uiTheme?: string;\n /** Forwarded on list / create / importFromUrl when a template is loaded. */\n templateId?: string;\n /**\n * Failures from the storage provider. The composable already reports\n * them here; a host that wants them on its own `onError` passes that\n * function through. Standalone `init()` has no such config, so this\n * stays optional.\n */\n onError?: (error: Error) => void;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"select\", item: MediaAsset): void;\n}>();\n\nconst translations = ref<MediaTranslations | null>(null);\nwatch(\n () => props.locale,\n async (locale) => {\n translations.value = await loadMediaTranslations(locale ?? \"en\");\n },\n { immediate: true },\n);\n\nprovide(TRANSLATIONS_KEY, translations);\n\nconst tplUiTheme = computed(() => props.uiTheme);\nprovide(UI_THEME_KEY, tplUiTheme);\n\nconst popoverTargetRef = toRef(() => props.popoverTarget ?? null);\nprovide(POPOVER_TARGET_KEY, popoverTargetRef);\n\nprovide(\n UI_LOCALE_KEY,\n toRef(() => props.locale),\n);\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst chromeRef = ref<{\n nestedDialogOpen: boolean;\n handleEscape: () => boolean;\n} | null>(null);\n\nconst trapActive = computed(\n () =>\n props.visible &&\n translations.value !== null &&\n !chromeRef.value?.nestedDialogOpen,\n);\n\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key !== \"Escape\") {\n return;\n }\n if (chromeRef.value?.handleEscape()) {\n event.preventDefault();\n return;\n }\n emit(\"close\");\n}\nuseEventListener(document, \"keydown\", handleKeydown);\n\nfunction handleConfirm(item: MediaAsset): void {\n emit(\"select\", item);\n emit(\"close\");\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:duration-200\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:duration-150\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <!-- `translations` gates the subtree, not just this element's own labels:\n every descendant unwraps `TRANSLATIONS_KEY` at its own setup, so none\n may mount before the locale's strings have landed. -->\n <div\n v-if=\"visible && translations\"\n data-testid=\"media-library-modal\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl-media-overlay tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"emit('close')\"\n >\n <!-- Caps are percentages of the overlay, never viewport units. This\n overlay is `fixed; inset: 0`, which covers the viewport only while\n nothing traps it — an ancestor with `transform`, `filter`,\n `backdrop-filter`, `will-change: transform`, `contain: paint` or\n `container-type` becomes the containing block for fixed\n descendants, and this modal teleports into the editor's popover\n root, i.e. inside a consumer's markup. `inset: 0` then resolves to\n that ancestor's box while a viewport cap does not, and the panel\n overflows a container that usually also has `overflow: hidden`.\n Percentages resolve against whatever the overlay turned out to be.\n Locked by `tests/overlay-height-scope-audit.test.ts`.\n\n `width` / `height` are the untrapped size (900×650). They are not\n a fixed preference — `max-height` is what reins it in. -->\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-library-title\"\n data-testid=\"media-library-dialog\"\n class=\"tpl-media-modal tpl-scale-in tpl:flex tpl:flex-col tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n width: 900px;\n height: 650px;\n max-width: 100%;\n max-height: 90%;\n background-color: var(--tpl-bg-elevated);\n border: 1px solid var(--tpl-border);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <MediaLibraryChrome\n ref=\"chromeRef\"\n :provider=\"provider\"\n :accept=\"accept\"\n :template-id=\"templateId\"\n :on-error=\"onError\"\n :translations=\"translations\"\n show-close\n show-confirm\n confirm-test-id=\"media-confirm\"\n @close=\"emit('close')\"\n @confirm=\"handleConfirm\"\n />\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport MediaLibraryChrome from \"./MediaLibraryChrome.vue\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport { useEventListener } from \"@vueuse/core\";\nimport { useFocusTrap } from \"../composables/useFocusTrap\";\nimport { computed, provide, ref, toRef, watch } from \"vue\";\nimport {\n POPOVER_TARGET_KEY,\n TRANSLATIONS_KEY,\n UI_LOCALE_KEY,\n UI_THEME_KEY,\n} from \"../keys\";\nimport { loadMediaTranslations, type MediaTranslations } from \"../i18n\";\n\nconst props = defineProps<{\n visible: boolean;\n /**\n * Storage backend. **A prop, not an injection.** An injection would have\n * to agree with the host on key identity: a bare string never resolves\n * the `Symbol` a host provides, and the miss is silent. A prop is checked\n * at compile time.\n */\n provider: MediaProvider;\n accept?: MediaCategory[];\n popoverTarget?: HTMLElement | null;\n /**\n * BCP-47 locale for this package's own strings, defaulting to English.\n *\n * A locale rather than the strings themselves: media-library owns its\n * translations and loads them here, so a host passes a value it already has\n * and never handles media copy. That also keeps the load lazy — the host\n * imports nothing from this package's i18n, so nothing is fetched until this\n * modal mounts.\n */\n locale?: string;\n /**\n * Resolved UI theme (`\"light\"` / `\"dark\"`) for the overlay chrome.\n *\n * A plain string rather than the host's ref: props are reactive, so the\n * computed below tracks a host theme toggle without this package depending on\n * how the host stores it.\n */\n uiTheme?: string;\n /** Forwarded on list / create / importFromUrl when a template is loaded. */\n templateId?: string;\n /**\n * Failures from the storage provider. The composable already reports\n * them here; a host that wants them on its own `onError` passes that\n * function through. Standalone `init()` has no such config, so this\n * stays optional.\n */\n onError?: (error: Error) => void;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"select\", item: MediaAsset): void;\n}>();\n\nconst translations = ref<MediaTranslations | null>(null);\nwatch(\n () => props.locale,\n async (locale) => {\n translations.value = await loadMediaTranslations(locale ?? \"en\");\n },\n { immediate: true },\n);\n\nprovide(TRANSLATIONS_KEY, translations);\n\nconst tplUiTheme = computed(() => props.uiTheme);\nprovide(UI_THEME_KEY, tplUiTheme);\n\nconst popoverTargetRef = toRef(() => props.popoverTarget ?? null);\nprovide(POPOVER_TARGET_KEY, popoverTargetRef);\n\nprovide(\n UI_LOCALE_KEY,\n toRef(() => props.locale),\n);\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst chromeRef = ref<{\n nestedDialogOpen: boolean;\n handleEscape: () => boolean;\n} | null>(null);\n\nconst trapActive = computed(\n () =>\n props.visible &&\n translations.value !== null &&\n !chromeRef.value?.nestedDialogOpen,\n);\n\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key !== \"Escape\") {\n return;\n }\n if (chromeRef.value?.handleEscape()) {\n event.preventDefault();\n return;\n }\n emit(\"close\");\n}\nuseEventListener(document, \"keydown\", handleKeydown);\n\nfunction handleConfirm(item: MediaAsset): void {\n emit(\"select\", item);\n emit(\"close\");\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:duration-200\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:duration-150\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <!-- `translations` gates the subtree, not just this element's own labels:\n every descendant unwraps `TRANSLATIONS_KEY` at its own setup, so none\n may mount before the locale's strings have landed. -->\n <div\n v-if=\"visible && translations\"\n data-testid=\"media-library-modal\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl-media-overlay tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"emit('close')\"\n >\n <!-- Caps are percentages of the overlay, never viewport units. This\n overlay is `fixed; inset: 0`, which covers the viewport only while\n nothing traps it — an ancestor with `transform`, `filter`,\n `backdrop-filter`, `will-change: transform`, `contain: paint` or\n `container-type` becomes the containing block for fixed\n descendants, and this modal teleports into the editor's popover\n root, i.e. inside a consumer's markup. `inset: 0` then resolves to\n that ancestor's box while a viewport cap does not, and the panel\n overflows a container that usually also has `overflow: hidden`.\n Percentages resolve against whatever the overlay turned out to be.\n Locked by `tests/overlay-height-scope-audit.test.ts`.\n\n `width` / `height` are the untrapped size (900×650). They are not\n a fixed preference — `max-height` is what reins it in. -->\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-library-title\"\n data-testid=\"media-library-dialog\"\n class=\"tpl-media-modal tpl-scale-in tpl:flex tpl:flex-col tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n width: 900px;\n height: 650px;\n max-width: 100%;\n max-height: 90%;\n background-color: var(--tpl-bg-elevated);\n border: 1px solid var(--tpl-border);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <MediaLibraryChrome\n ref=\"chromeRef\"\n :provider=\"provider\"\n :accept=\"accept\"\n :template-id=\"templateId\"\n :on-error=\"onError\"\n :translations=\"translations\"\n show-close\n show-confirm\n confirm-test-id=\"media-confirm\"\n @close=\"emit('close')\"\n @confirm=\"handleConfirm\"\n />\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n"],"mappings":"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"}