jodit-pro 4.12.40 → 4.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/config.js +1 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +30 -30
  5. package/es2015/jodit.js +25 -7
  6. package/es2015/jodit.min.js +5 -5
  7. package/es2015/plugins/ai-assistant-pro/ai-assistant-pro.css +1 -1
  8. package/es2015/plugins/ai-assistant-pro/ai-assistant-pro.js +1 -1
  9. package/es2015/plugins/ai-assistant-pro/ai-assistant-pro.min.js +1 -1
  10. package/es2015/plugins/autocomplete/autocomplete.css +1 -1
  11. package/es2015/plugins/autocomplete/autocomplete.js +1 -1
  12. package/es2015/plugins/autocomplete/autocomplete.min.js +1 -1
  13. package/es2015/plugins/backup/backup.css +1 -1
  14. package/es2015/plugins/backup/backup.js +1 -1
  15. package/es2015/plugins/backup/backup.min.js +1 -1
  16. package/es2015/plugins/button-generator/button-generator.css +1 -1
  17. package/es2015/plugins/button-generator/button-generator.js +1 -1
  18. package/es2015/plugins/button-generator/button-generator.min.js +1 -1
  19. package/es2015/plugins/change-case/change-case.js +1 -1
  20. package/es2015/plugins/change-case/change-case.min.js +1 -1
  21. package/es2015/plugins/color-picker/color-picker.css +1 -1
  22. package/es2015/plugins/color-picker/color-picker.js +1 -1
  23. package/es2015/plugins/color-picker/color-picker.min.js +1 -1
  24. package/es2015/plugins/emoji/emoji.css +1 -1
  25. package/es2015/plugins/emoji/emoji.js +1 -1
  26. package/es2015/plugins/emoji/emoji.min.js +1 -1
  27. package/es2015/plugins/export-docs/export-docs.js +1 -1
  28. package/es2015/plugins/export-docs/export-docs.min.js +1 -1
  29. package/es2015/plugins/finder/finder.css +47 -4
  30. package/es2015/plugins/finder/finder.js +48 -3
  31. package/es2015/plugins/finder/finder.min.css +1 -1
  32. package/es2015/plugins/finder/finder.min.js +4 -4
  33. package/es2015/plugins/google-maps/google-maps.css +1 -1
  34. package/es2015/plugins/google-maps/google-maps.js +1 -1
  35. package/es2015/plugins/google-maps/google-maps.min.js +1 -1
  36. package/es2015/plugins/google-search/google-search.js +1 -1
  37. package/es2015/plugins/google-search/google-search.min.js +1 -1
  38. package/es2015/plugins/highlight-signature/highlight-signature.js +1 -1
  39. package/es2015/plugins/highlight-signature/highlight-signature.min.js +1 -1
  40. package/es2015/plugins/iframe-editor/iframe-editor.css +1 -1
  41. package/es2015/plugins/iframe-editor/iframe-editor.js +1 -1
  42. package/es2015/plugins/iframe-editor/iframe-editor.min.js +1 -1
  43. package/es2015/plugins/image-editor-pro/image-editor-pro.css +52 -0
  44. package/es2015/plugins/image-editor-pro/image-editor-pro.js +3969 -0
  45. package/es2015/plugins/image-editor-pro/image-editor-pro.min.css +1 -0
  46. package/es2015/plugins/image-editor-pro/image-editor-pro.min.js +8 -0
  47. package/es2015/plugins/keyboard/keyboard.css +1 -1
  48. package/es2015/plugins/keyboard/keyboard.js +1 -1
  49. package/es2015/plugins/keyboard/keyboard.min.js +1 -1
  50. package/es2015/plugins/mobile-view/mobile-view.js +1 -1
  51. package/es2015/plugins/mobile-view/mobile-view.min.js +1 -1
  52. package/es2015/plugins/page-break/page-break.js +1 -1
  53. package/es2015/plugins/page-break/page-break.min.js +1 -1
  54. package/es2015/plugins/paste-code/paste-code.css +1 -1
  55. package/es2015/plugins/paste-code/paste-code.js +1 -1
  56. package/es2015/plugins/paste-code/paste-code.min.js +1 -1
  57. package/es2015/plugins/paste-from-word/paste-from-word.js +1 -1
  58. package/es2015/plugins/paste-from-word/paste-from-word.min.js +1 -1
  59. package/es2015/plugins/show-blocks/show-blocks.js +1 -1
  60. package/es2015/plugins/show-blocks/show-blocks.min.js +1 -1
  61. package/es2015/plugins/style/style.css +1 -1
  62. package/es2015/plugins/style/style.js +1 -1
  63. package/es2015/plugins/style/style.min.js +1 -1
  64. package/es2015/plugins/templates/templates.css +1 -1
  65. package/es2015/plugins/templates/templates.js +1 -1
  66. package/es2015/plugins/templates/templates.min.js +1 -1
  67. package/es2015/plugins/todo-list/todo-list.css +1 -1
  68. package/es2015/plugins/todo-list/todo-list.js +1 -1
  69. package/es2015/plugins/todo-list/todo-list.min.js +1 -1
  70. package/es2015/plugins/translate/translate.css +1 -1
  71. package/es2015/plugins/translate/translate.js +1 -1
  72. package/es2015/plugins/translate/translate.min.js +1 -1
  73. package/es2015/plugins/tune-block/tune-block.css +1 -1
  74. package/es2015/plugins/tune-block/tune-block.js +1 -1
  75. package/es2015/plugins/tune-block/tune-block.min.js +1 -1
  76. package/es2018/jodit.fat.min.css +1 -1
  77. package/es2018/jodit.fat.min.js +31 -31
  78. package/es2018/jodit.min.js +5 -5
  79. package/es2018/plugins/ai-assistant-pro/ai-assistant-pro.min.js +1 -1
  80. package/es2018/plugins/autocomplete/autocomplete.min.js +1 -1
  81. package/es2018/plugins/backup/backup.min.js +1 -1
  82. package/es2018/plugins/button-generator/button-generator.min.js +1 -1
  83. package/es2018/plugins/change-case/change-case.min.js +1 -1
  84. package/es2018/plugins/color-picker/color-picker.min.js +1 -1
  85. package/es2018/plugins/emoji/emoji.min.js +1 -1
  86. package/es2018/plugins/export-docs/export-docs.min.js +1 -1
  87. package/es2018/plugins/finder/finder.min.css +1 -1
  88. package/es2018/plugins/finder/finder.min.js +4 -4
  89. package/es2018/plugins/google-maps/google-maps.min.js +1 -1
  90. package/es2018/plugins/google-search/google-search.min.js +1 -1
  91. package/es2018/plugins/highlight-signature/highlight-signature.min.js +1 -1
  92. package/es2018/plugins/iframe-editor/iframe-editor.min.js +1 -1
  93. package/es2018/plugins/image-editor-pro/image-editor-pro.min.css +1 -0
  94. package/es2018/plugins/image-editor-pro/image-editor-pro.min.js +8 -0
  95. package/es2018/plugins/keyboard/keyboard.min.js +1 -1
  96. package/es2018/plugins/mobile-view/mobile-view.min.js +1 -1
  97. package/es2018/plugins/page-break/page-break.min.js +1 -1
  98. package/es2018/plugins/paste-code/paste-code.min.js +1 -1
  99. package/es2018/plugins/paste-from-word/paste-from-word.min.js +1 -1
  100. package/es2018/plugins/show-blocks/show-blocks.min.js +1 -1
  101. package/es2018/plugins/style/style.min.js +1 -1
  102. package/es2018/plugins/templates/templates.min.js +1 -1
  103. package/es2018/plugins/todo-list/todo-list.min.js +1 -1
  104. package/es2018/plugins/translate/translate.min.js +1 -1
  105. package/es2018/plugins/tune-block/tune-block.min.js +1 -1
  106. package/es2021/jodit.css +1 -1
  107. package/es2021/jodit.fat.min.css +1 -1
  108. package/es2021/jodit.fat.min.js +823 -39
  109. package/es2021/jodit.js +25 -7
  110. package/es2021/jodit.min.js +6 -6
  111. package/es2021/plugins/ai-assistant-pro/ai-assistant-pro.css +1 -1
  112. package/es2021/plugins/ai-assistant-pro/ai-assistant-pro.js +1 -1
  113. package/es2021/plugins/ai-assistant-pro/ai-assistant-pro.min.js +1 -1
  114. package/es2021/plugins/autocomplete/autocomplete.css +1 -1
  115. package/es2021/plugins/autocomplete/autocomplete.js +1 -1
  116. package/es2021/plugins/autocomplete/autocomplete.min.js +1 -1
  117. package/es2021/plugins/backup/backup.css +1 -1
  118. package/es2021/plugins/backup/backup.js +1 -1
  119. package/es2021/plugins/backup/backup.min.js +1 -1
  120. package/es2021/plugins/button-generator/button-generator.css +1 -1
  121. package/es2021/plugins/button-generator/button-generator.js +1 -1
  122. package/es2021/plugins/button-generator/button-generator.min.js +1 -1
  123. package/es2021/plugins/change-case/change-case.js +1 -1
  124. package/es2021/plugins/change-case/change-case.min.js +1 -1
  125. package/es2021/plugins/color-picker/color-picker.css +1 -1
  126. package/es2021/plugins/color-picker/color-picker.js +1 -1
  127. package/es2021/plugins/color-picker/color-picker.min.js +1 -1
  128. package/es2021/plugins/emoji/emoji.css +1 -1
  129. package/es2021/plugins/emoji/emoji.js +1 -1
  130. package/es2021/plugins/emoji/emoji.min.js +1 -1
  131. package/es2021/plugins/export-docs/export-docs.js +1 -1
  132. package/es2021/plugins/export-docs/export-docs.min.js +1 -1
  133. package/es2021/plugins/finder/finder.css +47 -4
  134. package/es2021/plugins/finder/finder.js +48 -3
  135. package/es2021/plugins/finder/finder.min.css +1 -1
  136. package/es2021/plugins/finder/finder.min.js +12 -12
  137. package/es2021/plugins/google-maps/google-maps.css +1 -1
  138. package/es2021/plugins/google-maps/google-maps.js +1 -1
  139. package/es2021/plugins/google-maps/google-maps.min.js +1 -1
  140. package/es2021/plugins/google-search/google-search.js +1 -1
  141. package/es2021/plugins/google-search/google-search.min.js +1 -1
  142. package/es2021/plugins/highlight-signature/highlight-signature.js +1 -1
  143. package/es2021/plugins/highlight-signature/highlight-signature.min.js +1 -1
  144. package/es2021/plugins/iframe-editor/iframe-editor.css +1 -1
  145. package/es2021/plugins/iframe-editor/iframe-editor.js +1 -1
  146. package/es2021/plugins/iframe-editor/iframe-editor.min.js +1 -1
  147. package/es2021/plugins/image-editor-pro/image-editor-pro.css +52 -0
  148. package/es2021/plugins/image-editor-pro/image-editor-pro.js +3870 -0
  149. package/es2021/plugins/image-editor-pro/image-editor-pro.min.css +1 -0
  150. package/es2021/plugins/image-editor-pro/image-editor-pro.min.js +792 -0
  151. package/es2021/plugins/keyboard/keyboard.css +1 -1
  152. package/es2021/plugins/keyboard/keyboard.js +1 -1
  153. package/es2021/plugins/keyboard/keyboard.min.js +1 -1
  154. package/es2021/plugins/mobile-view/mobile-view.js +1 -1
  155. package/es2021/plugins/mobile-view/mobile-view.min.js +1 -1
  156. package/es2021/plugins/page-break/page-break.js +1 -1
  157. package/es2021/plugins/page-break/page-break.min.js +1 -1
  158. package/es2021/plugins/paste-code/paste-code.css +1 -1
  159. package/es2021/plugins/paste-code/paste-code.js +1 -1
  160. package/es2021/plugins/paste-code/paste-code.min.js +1 -1
  161. package/es2021/plugins/paste-from-word/paste-from-word.js +1 -1
  162. package/es2021/plugins/paste-from-word/paste-from-word.min.js +1 -1
  163. package/es2021/plugins/show-blocks/show-blocks.js +1 -1
  164. package/es2021/plugins/show-blocks/show-blocks.min.js +1 -1
  165. package/es2021/plugins/style/style.css +1 -1
  166. package/es2021/plugins/style/style.js +1 -1
  167. package/es2021/plugins/style/style.min.js +1 -1
  168. package/es2021/plugins/templates/templates.css +1 -1
  169. package/es2021/plugins/templates/templates.js +1 -1
  170. package/es2021/plugins/templates/templates.min.js +1 -1
  171. package/es2021/plugins/todo-list/todo-list.css +1 -1
  172. package/es2021/plugins/todo-list/todo-list.js +1 -1
  173. package/es2021/plugins/todo-list/todo-list.min.js +1 -1
  174. package/es2021/plugins/translate/translate.css +1 -1
  175. package/es2021/plugins/translate/translate.js +1 -1
  176. package/es2021/plugins/translate/translate.min.js +1 -1
  177. package/es2021/plugins/tune-block/tune-block.css +1 -1
  178. package/es2021/plugins/tune-block/tune-block.js +1 -1
  179. package/es2021/plugins/tune-block/tune-block.min.js +1 -1
  180. package/es2021.en/jodit.css +1 -1
  181. package/es2021.en/jodit.fat.min.css +1 -1
  182. package/es2021.en/jodit.fat.min.js +865 -81
  183. package/es2021.en/jodit.js +25 -7
  184. package/es2021.en/jodit.min.js +6 -6
  185. package/es2021.en/plugins/ai-assistant-pro/ai-assistant-pro.css +1 -1
  186. package/es2021.en/plugins/ai-assistant-pro/ai-assistant-pro.js +1 -1
  187. package/es2021.en/plugins/ai-assistant-pro/ai-assistant-pro.min.js +1 -1
  188. package/es2021.en/plugins/autocomplete/autocomplete.css +1 -1
  189. package/es2021.en/plugins/autocomplete/autocomplete.js +1 -1
  190. package/es2021.en/plugins/autocomplete/autocomplete.min.js +1 -1
  191. package/es2021.en/plugins/backup/backup.css +1 -1
  192. package/es2021.en/plugins/backup/backup.js +1 -1
  193. package/es2021.en/plugins/backup/backup.min.js +1 -1
  194. package/es2021.en/plugins/button-generator/button-generator.css +1 -1
  195. package/es2021.en/plugins/button-generator/button-generator.js +1 -1
  196. package/es2021.en/plugins/button-generator/button-generator.min.js +1 -1
  197. package/es2021.en/plugins/change-case/change-case.js +1 -1
  198. package/es2021.en/plugins/change-case/change-case.min.js +1 -1
  199. package/es2021.en/plugins/color-picker/color-picker.css +1 -1
  200. package/es2021.en/plugins/color-picker/color-picker.js +1 -1
  201. package/es2021.en/plugins/color-picker/color-picker.min.js +1 -1
  202. package/es2021.en/plugins/emoji/emoji.css +1 -1
  203. package/es2021.en/plugins/emoji/emoji.js +1 -1
  204. package/es2021.en/plugins/emoji/emoji.min.js +1 -1
  205. package/es2021.en/plugins/export-docs/export-docs.js +1 -1
  206. package/es2021.en/plugins/export-docs/export-docs.min.js +1 -1
  207. package/es2021.en/plugins/finder/finder.css +47 -4
  208. package/es2021.en/plugins/finder/finder.js +48 -3
  209. package/es2021.en/plugins/finder/finder.min.css +1 -1
  210. package/es2021.en/plugins/finder/finder.min.js +4 -4
  211. package/es2021.en/plugins/google-maps/google-maps.css +1 -1
  212. package/es2021.en/plugins/google-maps/google-maps.js +1 -1
  213. package/es2021.en/plugins/google-maps/google-maps.min.js +1 -1
  214. package/es2021.en/plugins/google-search/google-search.js +1 -1
  215. package/es2021.en/plugins/google-search/google-search.min.js +1 -1
  216. package/es2021.en/plugins/highlight-signature/highlight-signature.js +1 -1
  217. package/es2021.en/plugins/highlight-signature/highlight-signature.min.js +1 -1
  218. package/es2021.en/plugins/iframe-editor/iframe-editor.css +1 -1
  219. package/es2021.en/plugins/iframe-editor/iframe-editor.js +1 -1
  220. package/es2021.en/plugins/iframe-editor/iframe-editor.min.js +1 -1
  221. package/es2021.en/plugins/image-editor-pro/image-editor-pro.css +52 -0
  222. package/es2021.en/plugins/image-editor-pro/image-editor-pro.js +3870 -0
  223. package/es2021.en/plugins/image-editor-pro/image-editor-pro.min.css +1 -0
  224. package/es2021.en/plugins/image-editor-pro/image-editor-pro.min.js +792 -0
  225. package/es2021.en/plugins/keyboard/keyboard.css +1 -1
  226. package/es2021.en/plugins/keyboard/keyboard.js +1 -1
  227. package/es2021.en/plugins/keyboard/keyboard.min.js +1 -1
  228. package/es2021.en/plugins/mobile-view/mobile-view.js +1 -1
  229. package/es2021.en/plugins/mobile-view/mobile-view.min.js +1 -1
  230. package/es2021.en/plugins/page-break/page-break.js +1 -1
  231. package/es2021.en/plugins/page-break/page-break.min.js +1 -1
  232. package/es2021.en/plugins/paste-code/paste-code.css +1 -1
  233. package/es2021.en/plugins/paste-code/paste-code.js +1 -1
  234. package/es2021.en/plugins/paste-code/paste-code.min.js +1 -1
  235. package/es2021.en/plugins/paste-from-word/paste-from-word.js +1 -1
  236. package/es2021.en/plugins/paste-from-word/paste-from-word.min.js +1 -1
  237. package/es2021.en/plugins/show-blocks/show-blocks.js +1 -1
  238. package/es2021.en/plugins/show-blocks/show-blocks.min.js +1 -1
  239. package/es2021.en/plugins/style/style.css +1 -1
  240. package/es2021.en/plugins/style/style.js +1 -1
  241. package/es2021.en/plugins/style/style.min.js +1 -1
  242. package/es2021.en/plugins/templates/templates.css +1 -1
  243. package/es2021.en/plugins/templates/templates.js +1 -1
  244. package/es2021.en/plugins/templates/templates.min.js +1 -1
  245. package/es2021.en/plugins/todo-list/todo-list.css +1 -1
  246. package/es2021.en/plugins/todo-list/todo-list.js +1 -1
  247. package/es2021.en/plugins/todo-list/todo-list.min.js +1 -1
  248. package/es2021.en/plugins/translate/translate.css +1 -1
  249. package/es2021.en/plugins/translate/translate.js +1 -1
  250. package/es2021.en/plugins/translate/translate.min.js +1 -1
  251. package/es2021.en/plugins/tune-block/tune-block.css +1 -1
  252. package/es2021.en/plugins/tune-block/tune-block.js +1 -1
  253. package/es2021.en/plugins/tune-block/tune-block.min.js +1 -1
  254. package/es5/jodit.css +2 -2
  255. package/es5/jodit.fat.min.css +1 -1
  256. package/es5/jodit.fat.min.js +2 -2
  257. package/es5/jodit.js +22 -6
  258. package/es5/jodit.min.css +2 -2
  259. package/es5/jodit.min.js +2 -2
  260. package/es5/plugins/ai-assistant-pro/ai-assistant-pro.css +1 -1
  261. package/es5/plugins/ai-assistant-pro/ai-assistant-pro.js +1 -1
  262. package/es5/plugins/ai-assistant-pro/ai-assistant-pro.min.js +1 -1
  263. package/es5/plugins/autocomplete/autocomplete.css +1 -1
  264. package/es5/plugins/autocomplete/autocomplete.js +1 -1
  265. package/es5/plugins/autocomplete/autocomplete.min.js +1 -1
  266. package/es5/plugins/backup/backup.css +1 -1
  267. package/es5/plugins/backup/backup.js +1 -1
  268. package/es5/plugins/backup/backup.min.js +1 -1
  269. package/es5/plugins/button-generator/button-generator.css +1 -1
  270. package/es5/plugins/button-generator/button-generator.js +1 -1
  271. package/es5/plugins/button-generator/button-generator.min.js +1 -1
  272. package/es5/plugins/change-case/change-case.js +1 -1
  273. package/es5/plugins/change-case/change-case.min.js +1 -1
  274. package/es5/plugins/color-picker/color-picker.css +1 -1
  275. package/es5/plugins/color-picker/color-picker.js +1 -1
  276. package/es5/plugins/color-picker/color-picker.min.js +1 -1
  277. package/es5/plugins/emoji/emoji.css +1 -1
  278. package/es5/plugins/emoji/emoji.js +1 -1
  279. package/es5/plugins/emoji/emoji.min.js +1 -1
  280. package/es5/plugins/export-docs/export-docs.js +1 -1
  281. package/es5/plugins/export-docs/export-docs.min.js +1 -1
  282. package/es5/plugins/finder/finder.css +47 -4
  283. package/es5/plugins/finder/finder.js +93 -28
  284. package/es5/plugins/finder/finder.min.css +1 -1
  285. package/es5/plugins/finder/finder.min.js +2 -2
  286. package/es5/plugins/google-maps/google-maps.css +1 -1
  287. package/es5/plugins/google-maps/google-maps.js +1 -1
  288. package/es5/plugins/google-maps/google-maps.min.js +1 -1
  289. package/es5/plugins/google-search/google-search.js +1 -1
  290. package/es5/plugins/google-search/google-search.min.js +1 -1
  291. package/es5/plugins/highlight-signature/highlight-signature.js +1 -1
  292. package/es5/plugins/highlight-signature/highlight-signature.min.js +1 -1
  293. package/es5/plugins/iframe-editor/iframe-editor.css +1 -1
  294. package/es5/plugins/iframe-editor/iframe-editor.js +1 -1
  295. package/es5/plugins/iframe-editor/iframe-editor.min.js +1 -1
  296. package/es5/plugins/image-editor-pro/image-editor-pro.css +52 -0
  297. package/es5/plugins/image-editor-pro/image-editor-pro.js +5469 -0
  298. package/es5/plugins/image-editor-pro/image-editor-pro.min.css +1 -0
  299. package/es5/plugins/image-editor-pro/image-editor-pro.min.js +8 -0
  300. package/es5/plugins/keyboard/keyboard.css +1 -1
  301. package/es5/plugins/keyboard/keyboard.js +1 -1
  302. package/es5/plugins/keyboard/keyboard.min.js +1 -1
  303. package/es5/plugins/mobile-view/mobile-view.js +1 -1
  304. package/es5/plugins/mobile-view/mobile-view.min.js +1 -1
  305. package/es5/plugins/page-break/page-break.js +1 -1
  306. package/es5/plugins/page-break/page-break.min.js +1 -1
  307. package/es5/plugins/paste-code/paste-code.css +1 -1
  308. package/es5/plugins/paste-code/paste-code.js +1 -1
  309. package/es5/plugins/paste-code/paste-code.min.js +1 -1
  310. package/es5/plugins/paste-from-word/paste-from-word.js +1 -1
  311. package/es5/plugins/paste-from-word/paste-from-word.min.js +1 -1
  312. package/es5/plugins/show-blocks/show-blocks.js +1 -1
  313. package/es5/plugins/show-blocks/show-blocks.min.js +1 -1
  314. package/es5/plugins/style/style.css +1 -1
  315. package/es5/plugins/style/style.js +1 -1
  316. package/es5/plugins/style/style.min.js +1 -1
  317. package/es5/plugins/templates/templates.css +1 -1
  318. package/es5/plugins/templates/templates.js +1 -1
  319. package/es5/plugins/templates/templates.min.js +1 -1
  320. package/es5/plugins/todo-list/todo-list.css +1 -1
  321. package/es5/plugins/todo-list/todo-list.js +1 -1
  322. package/es5/plugins/todo-list/todo-list.min.js +1 -1
  323. package/es5/plugins/translate/translate.css +1 -1
  324. package/es5/plugins/translate/translate.js +1 -1
  325. package/es5/plugins/translate/translate.min.js +1 -1
  326. package/es5/plugins/tune-block/tune-block.css +1 -1
  327. package/es5/plugins/tune-block/tune-block.js +1 -1
  328. package/es5/plugins/tune-block/tune-block.min.js +1 -1
  329. package/es5/polyfills.fat.min.js +1 -1
  330. package/es5/polyfills.js +1 -1
  331. package/es5/polyfills.min.js +1 -1
  332. package/esm/config.d.ts +9 -0
  333. package/esm/plugins/all.js +1 -1
  334. package/esm/plugins/finder/ui/lightboox/lightbox.js +2 -2
  335. package/esm/plugins/finder/ui/virtual-scroll/virtual-scroll.d.ts +11 -1
  336. package/esm/plugins/finder/ui/virtual-scroll/virtual-scroll.js +2 -2
  337. package/esm/plugins/image-editor-pro/adapter.d.ts +98 -0
  338. package/esm/plugins/image-editor-pro/adapter.js +1 -0
  339. package/esm/plugins/image-editor-pro/config.d.ts +17 -0
  340. package/esm/plugins/image-editor-pro/config.js +1 -0
  341. package/esm/plugins/image-editor-pro/image-editor-pro.d.ts +26 -0
  342. package/esm/plugins/image-editor-pro/image-editor-pro.js +1 -0
  343. package/package.json +2 -1
  344. package/types/config.d.ts +9 -0
  345. package/types/plugins/finder/ui/virtual-scroll/virtual-scroll.d.ts +11 -1
  346. package/types/plugins/image-editor-pro/adapter.d.ts +98 -0
  347. package/types/plugins/image-editor-pro/config.d.ts +17 -0
  348. package/types/plugins/image-editor-pro/image-editor-pro.d.ts +26 -0
@@ -0,0 +1,3969 @@
1
+ /*!
2
+ * jodit-pro - PRO Version of Jodit Editor
3
+ * Author: Chupurnov Valerii <chupurnov@gmail.com>
4
+ * Version: v4.13.1
5
+ * Url: https://xdsoft.net/jodit/pro/
6
+ * License(s): SEE LICENSE IN LICENSE.md
7
+ */
8
+
9
+ "use strict";
10
+ (function webpackUniversalModuleDefinition(root, factory) {
11
+ if(typeof exports === 'object' && typeof module === 'object')
12
+ module.exports = factory();
13
+ else if(typeof define === 'function' && define.amd)
14
+ define([], factory);
15
+ else {
16
+ var a = factory();
17
+ for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
18
+ }
19
+ })(self, function() {
20
+ return (self["webpackChunkjodit_pro"] = self["webpackChunkjodit_pro"] || []).push([[918],{
21
+
22
+ /***/ 76542:
23
+ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
24
+
25
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
26
+ /* harmony export */ ProImageEditor: function() { return /* binding */ ProImageEditor; }
27
+ /* harmony export */ });
28
+ /* harmony import */ var _swc_helpers_async_to_generator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(77688);
29
+ /* harmony import */ var _swc_helpers_define_property__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(82749);
30
+ /* harmony import */ var _jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(85013);
31
+ /* harmony import */ var jodit_esm_config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6441);
32
+ /* harmony import */ var jodit_esm_core_component__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(91907);
33
+ /* harmony import */ var jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(10830);
34
+ /* harmony import */ var jodit_esm_modules_dialog_dialog__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(78584);
35
+ /*!
36
+ * Jodit Editor PRO (https://xdsoft.net/jodit/)
37
+ * See LICENSE.md in the project root for license information.
38
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net/jodit/pro/
39
+ */
40
+
41
+
42
+
43
+
44
+
45
+
46
+ /**
47
+ * PRO replacement for Jodit's core image editor, backed by the standalone
48
+ * `@jodit/image-editor` package (crop, resize, rotate, flip, filters, finetune,
49
+ * text annotations).
50
+ *
51
+ * It is registered as `Jodit.modules.ImageEditor`, so **every** path that opens
52
+ * the image editor — the finder pencil, Image properties → Edit, a direct
53
+ * `getInstance('ImageEditor')` — instantiates it via `getInstance`'s module
54
+ * fallback (the event fires on the *file browser's* emitter, which is why an
55
+ * `jodit.e` listener never worked).
56
+ *
57
+ * `@jodit/image-editor` is bundled as a dependency (it is part of the PRO
58
+ * editor), so the class is imported directly.
59
+ *
60
+ * The core editor speaks a server "resize/crop the original file" language; this
61
+ * one produces a fully edited `Blob`. So on save it uploads the blob through the
62
+ * connector's `imageSave` action (overwriting the source file), then reports
63
+ * `action: 'saved'` to the core's save callback — which skips the server
64
+ * resize/crop and runs only the `success`/`onSuccess` machinery, reusing
65
+ * finder's refresh and image-properties' `<img>` swap without reimplementing
66
+ * either.
67
+ */ class ProImageEditor extends jodit_esm_core_component__WEBPACK_IMPORTED_MODULE_4__.ViewComponent {
68
+ className() {
69
+ return 'ImageEditorPro';
70
+ }
71
+ open(url, save) {
72
+ return (0,_swc_helpers_async_to_generator__WEBPACK_IMPORTED_MODULE_0__._)(function*() {
73
+ const view = this.j;
74
+ const options = jodit_esm_config__WEBPACK_IMPORTED_MODULE_3__.Config.defaultOptions.imageEditorPro;
75
+ const container = view.c.div('jodit-image-editor-pro');
76
+ container.style.width = '100%';
77
+ container.style.height = '100%';
78
+ const dialog = new jodit_esm_modules_dialog_dialog__WEBPACK_IMPORTED_MODULE_6__.Dialog({
79
+ language: view.o.language
80
+ });
81
+ dialog.setSize(options.dialog.width, options.dialog.height);
82
+ dialog.setContent(container);
83
+ dialog.open();
84
+ let editor = null;
85
+ let disposeToolbar = null;
86
+ const cleanup = ()=>{
87
+ disposeToolbar === null || disposeToolbar === void 0 ? void 0 : disposeToolbar();
88
+ disposeToolbar = null;
89
+ editor === null || editor === void 0 ? void 0 : editor.destroy();
90
+ editor = null;
91
+ dialog.close();
92
+ };
93
+ dialog.e.on('beforeClose', ()=>{
94
+ disposeToolbar === null || disposeToolbar === void 0 ? void 0 : disposeToolbar();
95
+ disposeToolbar = null;
96
+ editor === null || editor === void 0 ? void 0 : editor.destroy();
97
+ editor = null;
98
+ });
99
+ editor = new _jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.ImageEditor({
100
+ container,
101
+ // The whole top bar is rebuilt as a native Jodit toolbar in the
102
+ // dialog header (see `mountToolbar`), so the built-in one is hidden.
103
+ // Start in `loading` so the editor shows its spinner overlay while
104
+ // the source image travels through the connector below.
105
+ state: {
106
+ showToolbar: false,
107
+ status: 'loading'
108
+ },
109
+ onSave: (edited)=>{
110
+ void this.persist(editor, url, undefined, edited, save, cleanup);
111
+ },
112
+ onSaveAs: (edited)=>{
113
+ this.saveAs(editor, url, edited, save, cleanup);
114
+ }
115
+ });
116
+ disposeToolbar = this.mountToolbar(dialog, editor);
117
+ // Load the image separately so the editor always opens, even if the
118
+ // source image can't be fetched (CORS, network, a deleted file …).
119
+ try {
120
+ const blob = yield this.loadImage(url);
121
+ yield editor.fromBlob(blob);
122
+ } catch (error) {
123
+ editor === null || editor === void 0 ? void 0 : editor.update({
124
+ status: 'error',
125
+ error: error instanceof Error ? error.message : String(error)
126
+ });
127
+ this.error(error);
128
+ }
129
+ }).call(this);
130
+ }
131
+ /**
132
+ * Rebuilds the editor's top bar as a native Jodit toolbar in the dialog
133
+ * header — Save / Save as, dimensions, zoom, reset / undo / redo — driven by
134
+ * the `@jodit/image-editor` public API (`save()`, `saveAs()`, `update(...)`)
135
+ * and kept in sync by subscribing to the editor store. Returns a disposer.
136
+ */ mountToolbar(dialog, editor) {
137
+ const view = this.j;
138
+ // The floppy icon matches the free (core) image editor's Save buttons;
139
+ // while an export is in flight the spinner replaces the icon (same
140
+ // footprint), so the button doesn't jump.
141
+ const save = (0,jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__.Button)(view, {
142
+ icon: {
143
+ name: 'save'
144
+ },
145
+ text: view.i18n('Save')
146
+ }, 'primary');
147
+ const saveAs = (0,jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__.Button)(view, {
148
+ icon: {
149
+ name: 'save'
150
+ },
151
+ name: 'save-as',
152
+ text: view.i18n('Save as')
153
+ });
154
+ const size = view.c.span('jodit-image-editor-pro__size');
155
+ const zoomOut = (0,jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__.Button)(view, _jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.ICONS.minus);
156
+ const zoom = view.c.span('jodit-image-editor-pro__zoom');
157
+ const zoomIn = (0,jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__.Button)(view, _jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.ICONS.plus);
158
+ const reset = (0,jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__.Button)(view, 'update', view.i18n('Reset'));
159
+ const undo = (0,jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__.Button)(view, 'undo', view.i18n('Undo'));
160
+ const redo = (0,jodit_esm_core_ui_button_button_button__WEBPACK_IMPORTED_MODULE_5__.Button)(view, 'redo', view.i18n('Redo'));
161
+ zoomOut.setState({
162
+ tooltip: 'Zoom out'
163
+ });
164
+ zoomIn.setState({
165
+ tooltip: 'Zoom in'
166
+ });
167
+ // Which of the two save buttons started the in-flight export — the
168
+ // spinner is shown on that one while `status` is `exporting`.
169
+ let pending = null;
170
+ save.onAction(()=>{
171
+ pending = save;
172
+ void editor.save();
173
+ });
174
+ saveAs.onAction(()=>{
175
+ pending = saveAs;
176
+ void editor.saveAs();
177
+ });
178
+ zoomOut.onAction(()=>editor.update({
179
+ zoom: editor.state.zoom / 1.2
180
+ }));
181
+ zoomIn.onAction(()=>editor.update({
182
+ zoom: editor.state.zoom * 1.2
183
+ }));
184
+ reset.onAction(()=>editor.reset());
185
+ undo.onAction(()=>editor.update({
186
+ history: {
187
+ step: -1
188
+ }
189
+ }));
190
+ redo.onAction(()=>editor.update({
191
+ history: {
192
+ step: 1
193
+ }
194
+ }));
195
+ const group = (...children)=>{
196
+ const el = view.c.div('jodit-image-editor-pro__group');
197
+ el.style.display = 'flex';
198
+ el.style.alignItems = 'center';
199
+ el.style.gap = '4px';
200
+ children.forEach((child)=>el.appendChild(child));
201
+ return el;
202
+ };
203
+ const bar = view.c.div('jodit-image-editor-pro__toolbar');
204
+ bar.style.display = 'flex';
205
+ bar.style.alignItems = 'center';
206
+ bar.style.gap = '12px';
207
+ bar.style.width = '100%';
208
+ size.style.opacity = '0.75';
209
+ zoom.style.minWidth = '3.5em';
210
+ zoom.style.textAlign = 'center';
211
+ bar.appendChild(group(save.container, saveAs.container));
212
+ bar.appendChild(group(size, zoomOut.container, zoom, zoomIn.container));
213
+ const actions = group(reset.container, undo.container, redo.container);
214
+ actions.style.marginLeft = 'auto';
215
+ bar.appendChild(actions);
216
+ dialog.setHeader(bar);
217
+ const busyClass = 'jodit-image-editor-pro__button_busy';
218
+ const sync = (state)=>{
219
+ const output = _jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.selectors.selectOutputSize(state);
220
+ // `exporting` covers both the blob export and the connector upload
221
+ // (persist() keeps the status set until the dialog closes).
222
+ const exporting = state.status === 'exporting';
223
+ const busy = exporting || state.status === 'loading';
224
+ // `busyVisible` (@jodit/image-editor ≥ 0.2.4, `spinnerDelay`)
225
+ // debounces the spinner so a fast save never flashes the button;
226
+ // on older builds the field is absent — fall back to `exporting`.
227
+ const busyVisible = state.busyVisible;
228
+ const spinning = exporting && (busyVisible !== null && busyVisible !== void 0 ? busyVisible : true);
229
+ if (!exporting) {
230
+ pending = null;
231
+ }
232
+ save.setState({
233
+ disabled: !state.source || busy
234
+ });
235
+ saveAs.setState({
236
+ disabled: !state.source || busy
237
+ });
238
+ save.container.classList.toggle(busyClass, spinning && pending !== saveAs);
239
+ saveAs.container.classList.toggle(busyClass, spinning && pending === saveAs);
240
+ reset.setState({
241
+ disabled: !_jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.selectors.selectIsDirty(state)
242
+ });
243
+ undo.setState({
244
+ disabled: !_jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.selectors.selectCanUndo(state)
245
+ });
246
+ redo.setState({
247
+ disabled: !_jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.selectors.selectCanRedo(state)
248
+ });
249
+ size.textContent = output ? `${output.width} × ${output.height}` : '';
250
+ // Show the *effective* on-screen scale (fit-to-viewport × zoom), the
251
+ // same value the editor's own zoom widget reports — not the raw
252
+ // `state.zoom`, which is 100% even when a huge image is shrunk to fit.
253
+ const fit = _jodit_image_editor__WEBPACK_IMPORTED_MODULE_2__.selectors.selectViewportFit(state);
254
+ const percent = fit ? Math.round(fit.scale * 100) : Math.round(state.zoom * 100);
255
+ zoom.textContent = `${percent}%`;
256
+ };
257
+ sync(editor.state);
258
+ const unsubscribe = editor.store.subscribe(sync);
259
+ return ()=>{
260
+ unsubscribe();
261
+ [
262
+ save,
263
+ saveAs,
264
+ zoomOut,
265
+ zoomIn,
266
+ reset,
267
+ undo,
268
+ redo
269
+ ].forEach((btn)=>btn.destruct());
270
+ };
271
+ }
272
+ /**
273
+ * "Save as" — prompt for a new file name, then persist the edited blob under
274
+ * it (mirrors the core editor's Save-as UX and its `onSave(name, …)` call).
275
+ */ saveAs(editor, url, blob, save, cleanup) {
276
+ this.j.prompt('Enter new name', 'Save in new file', (name)=>{
277
+ const trimmed = (name || '').trim();
278
+ if (!trimmed) {
279
+ this.j.alert('The name should not be empty');
280
+ return false;
281
+ }
282
+ void this.persist(editor, url, trimmed, blob, save, cleanup);
283
+ });
284
+ }
285
+ /**
286
+ * Persists the edited blob. With `newName` it writes a new file ("Save as");
287
+ * without it, it overwrites the source ("Save"). Either way it then reuses
288
+ * the core's `save()` success/onSuccess wiring (finder refresh, in-content
289
+ * `<img>` swap) with a no-op resize of the resulting file.
290
+ */ persist(editor, url, newName, blob, save, cleanup) {
291
+ return (0,_swc_helpers_async_to_generator__WEBPACK_IMPORTED_MODULE_0__._)(function*() {
292
+ // `toBlob()` has already flipped the status back to `ready`; keep the
293
+ // toolbar locked (spinner on the save button) while the edited bytes
294
+ // travel to the server. No reset needed — every branch below closes
295
+ // the dialog via `cleanup()`.
296
+ editor === null || editor === void 0 ? void 0 : editor.update({
297
+ status: 'exporting'
298
+ });
299
+ try {
300
+ const { w, h } = yield this.blobSize(blob);
301
+ // 1. Write the edited bytes (overwrite the source, or a new file).
302
+ const newPath = yield this.uploadEdited(url, blob, newName);
303
+ // 2. The file is already persisted — `action: 'saved'` makes the
304
+ // core skip its server resize/crop (which 404s when the source
305
+ // came from image-properties) and only run the success wiring:
306
+ // finder refreshes, in-content edits swap the <img>.
307
+ //
308
+ // A core older than 4.12.34 doesn't know 'saved' and would route
309
+ // it into a server *crop* (failing the whole save — the finder
310
+ // list then never refreshes). Fall back to the legacy no-op
311
+ // resize of the already-edited file there.
312
+ const done = ()=>{
313
+ cleanup();
314
+ this.j.e.fire('update.filebrowser');
315
+ };
316
+ save(newName, this.coreSupportsSaved() ? {
317
+ action: 'saved',
318
+ box: {
319
+ w,
320
+ h
321
+ },
322
+ newPath: newPath || undefined
323
+ } : {
324
+ action: 'resize',
325
+ box: {
326
+ w,
327
+ h
328
+ }
329
+ }, done, (error)=>{
330
+ cleanup();
331
+ this.error(error);
332
+ });
333
+ } catch (error) {
334
+ cleanup();
335
+ this.error(error);
336
+ }
337
+ }).call(this);
338
+ }
339
+ /**
340
+ * Uploads the edited blob through the connector's `imageSave` action and
341
+ * returns the saved file's new path (`''` for older connectors that don't
342
+ * report it).
343
+ */ uploadEdited(url, blob, newName) {
344
+ return (0,_swc_helpers_async_to_generator__WEBPACK_IMPORTED_MODULE_0__._)(function*() {
345
+ var _ref;
346
+ var _data_data;
347
+ const { source, path, name } = yield this.j.dataProvider.getPathByUrl(url);
348
+ const connectorUrl = this.connectorUrl();
349
+ if (!connectorUrl) {
350
+ throw new Error('No connector configured for imageSave');
351
+ }
352
+ const fileName = String(newName || name || 'image.png');
353
+ const form = new FormData();
354
+ form.append('action', 'imageSave');
355
+ form.append('source', String(source !== null && source !== void 0 ? source : ''));
356
+ form.append('path', String(path !== null && path !== void 0 ? path : ''));
357
+ form.append('name', fileName);
358
+ form.append('files[0]', blob, fileName);
359
+ const sep = connectorUrl.includes('?') ? '&' : '?';
360
+ const response = yield fetch(`${connectorUrl}${sep}action=imageSave`, {
361
+ method: 'POST',
362
+ body: form,
363
+ credentials: 'include'
364
+ });
365
+ const data = yield response.json();
366
+ if (!data.success) {
367
+ throw new Error(data.message || 'Failed to save the edited image');
368
+ }
369
+ return (_ref = (_data_data = data.data) === null || _data_data === void 0 ? void 0 : _data_data.newPath) !== null && _ref !== void 0 ? _ref : '';
370
+ }).call(this);
371
+ }
372
+ /**
373
+ * `ImageEditorActionBox.action: 'saved'` appeared in jodit 4.12.34 — an
374
+ * older core routes unknown actions into a server crop, which fails the
375
+ * save. Detected from the host view's version.
376
+ */ coreSupportsSaved() {
377
+ const version = this.j.getVersion ? this.j.getVersion() : '';
378
+ const [major = 0, minor = 0, patch = 0] = version.split('.').map((part)=>parseInt(part, 10) || 0);
379
+ return major > 4 || major === 4 && (minor > 12 || minor === 12 && patch >= 34);
380
+ }
381
+ connectorUrl() {
382
+ var _this_j_o_ajax, _this_j_o_uploader;
383
+ // `url` may be a function (dynamic endpoint); we only use a static string.
384
+ const ajax = (_this_j_o_ajax = this.j.o.ajax) === null || _this_j_o_ajax === void 0 ? void 0 : _this_j_o_ajax.url;
385
+ const uploader = (_this_j_o_uploader = this.j.o.uploader) === null || _this_j_o_uploader === void 0 ? void 0 : _this_j_o_uploader.url;
386
+ if (typeof ajax === 'string' && ajax) {
387
+ return ajax;
388
+ }
389
+ if (typeof uploader === 'string' && uploader) {
390
+ return uploader;
391
+ }
392
+ return '';
393
+ }
394
+ loadImage(url) {
395
+ return (0,_swc_helpers_async_to_generator__WEBPACK_IMPORTED_MODULE_0__._)(function*() {
396
+ // Prefer the connector's `imageLoad` action: the raw file host usually
397
+ // serves images without CORS headers, but the connector API is
398
+ // CORS-enabled (it's what powers the file browser), so this works from a
399
+ // different origin.
400
+ const connectorUrl = this.connectorUrl();
401
+ if (connectorUrl) {
402
+ try {
403
+ return yield this.loadViaConnector(connectorUrl, url);
404
+ } catch (unused) {
405
+ // Fall back to a direct fetch (same-origin hosts, older
406
+ // connectors without `imageLoad`).
407
+ }
408
+ }
409
+ // Cache-buster (same trick as the core editor's `_tst`): the file is
410
+ // edited in place at a stable URL, so browser/CDN caches (e.g. a
411
+ // Cloudflare edge HIT) would otherwise serve the pre-edit bytes.
412
+ const sep = url.includes('?') ? '&' : '?';
413
+ const response = yield fetch(`${url}${sep}_tst=${Date.now()}`, {
414
+ credentials: 'include'
415
+ });
416
+ if (!response.ok) {
417
+ throw new Error(`Failed to load image (${response.status})`);
418
+ }
419
+ return response.blob();
420
+ }).call(this);
421
+ }
422
+ loadViaConnector(connectorUrl, url) {
423
+ return (0,_swc_helpers_async_to_generator__WEBPACK_IMPORTED_MODULE_0__._)(function*() {
424
+ var _data_data;
425
+ const { source, path, name } = yield this.j.dataProvider.getPathByUrl(url);
426
+ const form = new FormData();
427
+ form.append('action', 'imageLoad');
428
+ form.append('source', String(source !== null && source !== void 0 ? source : ''));
429
+ form.append('path', String(path !== null && path !== void 0 ? path : ''));
430
+ form.append('name', String(name !== null && name !== void 0 ? name : ''));
431
+ const sep = connectorUrl.includes('?') ? '&' : '?';
432
+ const response = yield fetch(`${connectorUrl}${sep}action=imageLoad`, {
433
+ method: 'POST',
434
+ body: form,
435
+ credentials: 'include'
436
+ });
437
+ const data = yield response.json();
438
+ if (!data.success || !((_data_data = data.data) === null || _data_data === void 0 ? void 0 : _data_data.content)) {
439
+ throw new Error(data.message || 'Failed to load the image');
440
+ }
441
+ // `content` is a data URL — decoding it never hits CORS.
442
+ return (yield fetch(data.data.content)).blob();
443
+ }).call(this);
444
+ }
445
+ blobSize(blob) {
446
+ return (0,_swc_helpers_async_to_generator__WEBPACK_IMPORTED_MODULE_0__._)(function*() {
447
+ const bitmap = yield createImageBitmap(blob);
448
+ const size = {
449
+ w: bitmap.width,
450
+ h: bitmap.height
451
+ };
452
+ bitmap.close();
453
+ return size;
454
+ })();
455
+ }
456
+ error(error) {
457
+ this.j.e.fire('errorMessage', error.message);
458
+ }
459
+ constructor(...args){
460
+ super(...args), /** Marker for callers/tests. */ (0,_swc_helpers_define_property__WEBPACK_IMPORTED_MODULE_1__._)(this, "isProImageEditor", true);
461
+ }
462
+ }
463
+
464
+
465
+ /***/ }),
466
+
467
+ /***/ 45719:
468
+ /***/ (function(__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) {
469
+
470
+ /* harmony import */ var jodit_esm_config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6441);
471
+ /*!
472
+ * Jodit Editor PRO (https://xdsoft.net/jodit/)
473
+ * See LICENSE.md in the project root for license information.
474
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net/jodit/pro/
475
+ */
476
+ jodit_esm_config__WEBPACK_IMPORTED_MODULE_0__.Config.prototype.imageEditorPro = {
477
+ dialog: {
478
+ width: '90%',
479
+ height: '90%'
480
+ }
481
+ };
482
+
483
+
484
+ /***/ }),
485
+
486
+ /***/ 98270:
487
+ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
488
+
489
+ __webpack_require__.r(__webpack_exports__);
490
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
491
+ /* harmony export */ imageEditorPro: function() { return /* binding */ imageEditorPro; }
492
+ /* harmony export */ });
493
+ /* harmony import */ var _swc_helpers_define_property__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(82749);
494
+ /* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45719);
495
+ /* harmony import */ var _adapter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(76542);
496
+ /* harmony import */ var jodit_esm_core_plugin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(50911);
497
+ /* harmony import */ var jodit_pro_jodit_pro__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2692);
498
+ /*!
499
+ * Jodit Editor PRO (https://xdsoft.net/jodit/)
500
+ * See LICENSE.md in the project root for license information.
501
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net/jodit/pro/
502
+ */
503
+
504
+
505
+
506
+
507
+
508
+ /**
509
+ * Replaces Jodit's core image editor with the standalone `@jodit/image-editor`
510
+ * (crop, resize, rotate, flip, filters, finetune, text annotations).
511
+ *
512
+ * The replacement is done at the module registry level — `Jodit.modules.ImageEditor`
513
+ * — because the editor is instantiated via `getInstance('ImageEditor')` on the
514
+ * **file browser's** own event emitter (the finder pencil, Image properties →
515
+ * Edit), not on `jodit.e`. Overriding the module makes every entry point resolve
516
+ * to {@link ProImageEditor} through `getInstance`'s fallback.
517
+ */ class imageEditorPro extends jodit_esm_core_plugin__WEBPACK_IMPORTED_MODULE_3__.Plugin {
518
+ /** @override */ afterInit(_jodit) {
519
+ // Global, idempotent: once a licensed PRO editor initialises, the core
520
+ // image editor is replaced everywhere (matching how `finder` replaces
521
+ // the file browser for the whole build). The cast bridges two
522
+ // duck-compatible classes: callers only ever `new` it and call
523
+ // `open(url, save)`, but the core's nominal `typeof ImageEditor`
524
+ // (a private static) makes them structurally unrelated.
525
+ jodit_pro_jodit_pro__WEBPACK_IMPORTED_MODULE_4__.JoditPro.modules.ImageEditor = _adapter__WEBPACK_IMPORTED_MODULE_2__.ProImageEditor;
526
+ }
527
+ /** @override */ beforeDestruct(_jodit) {}
528
+ }
529
+ /** @override */ (0,_swc_helpers_define_property__WEBPACK_IMPORTED_MODULE_0__._)(imageEditorPro, "requires", [
530
+ 'license'
531
+ ]);
532
+ jodit_pro_jodit_pro__WEBPACK_IMPORTED_MODULE_4__.JoditPro.plugins.add('imageEditorPro', imageEditorPro);
533
+
534
+
535
+ /***/ }),
536
+
537
+ /***/ 85013:
538
+ /***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) {
539
+
540
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
541
+ /* harmony export */ ICONS: function() { return /* binding */ ICONS; },
542
+ /* harmony export */ ImageEditor: function() { return /* binding */ ImageEditor; },
543
+ /* harmony export */ selectors: function() { return /* binding */ selectors; }
544
+ /* harmony export */ });
545
+ /* unused harmony exports CanvasImageCodec, CoalescingScheduler, DEFAULT_FONTS, DEFAULT_PALETTE, DomHost, ImageProcessor, LocaleRegistry, MemoryHost, Renderer, Store, SyncScheduler, TOKENS, ToolRegistry, addAnnotation, adjustments, applyAspectRatio, applyFilter, button, centerRect, clamp, clampRectToBounds, cloneRaster, colorPicker, createBuiltinTools, createDefaultFocus, createDefaultProcessor, createIdentityDesign, createInitialState, createMicrotaskScheduler, createRafScheduler, createRaster, createTextAnnotation, createTranslator, crop, cropCenter, defaultCanvasFactory, execute, findAnnotation, fitInViewport, fitScale, fitWithinLimits, flattenOnBackground, flip, fontSelect, getFilter, h, icon, init, interpolate, isIdentity, listFilters, lockedResize, moveCrop, moveCropFree, numberField, parseHexColor, pointerAngle, positionGrid, registerFilter, removeAnnotation, renderApp, resize, resizeCrop, resizeRotatedCrop, rotate90, rotatedCorner, roundRect, sampleRotatedRect, segmented, selectiveBlur, slider, smoothstep, solidRaster, text, updateAnnotation */
546
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
547
+ try {
548
+ var info = gen[key](arg);
549
+ var value = info.value;
550
+ } catch (error) {
551
+ reject(error);
552
+ return;
553
+ }
554
+ if (info.done) {
555
+ resolve(value);
556
+ } else {
557
+ Promise.resolve(value).then(_next, _throw);
558
+ }
559
+ }
560
+ function _async_to_generator(fn) {
561
+ return function() {
562
+ var self = this, args = arguments;
563
+ return new Promise(function(resolve, reject) {
564
+ var gen = fn.apply(self, args);
565
+ function _next(value) {
566
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
567
+ }
568
+ function _throw(err) {
569
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
570
+ }
571
+ _next(undefined);
572
+ });
573
+ };
574
+ }
575
+ function _define_property(obj, key, value) {
576
+ if (key in obj) {
577
+ Object.defineProperty(obj, key, {
578
+ value: value,
579
+ enumerable: true,
580
+ configurable: true,
581
+ writable: true
582
+ });
583
+ } else {
584
+ obj[key] = value;
585
+ }
586
+ return obj;
587
+ }
588
+ function _object_spread(target) {
589
+ for(var i = 1; i < arguments.length; i++){
590
+ var source = arguments[i] != null ? arguments[i] : {};
591
+ var ownKeys = Object.keys(source);
592
+ if (typeof Object.getOwnPropertySymbols === "function") {
593
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
594
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
595
+ }));
596
+ }
597
+ ownKeys.forEach(function(key) {
598
+ _define_property(target, key, source[key]);
599
+ });
600
+ }
601
+ return target;
602
+ }
603
+ function ownKeys(object, enumerableOnly) {
604
+ var keys = Object.keys(object);
605
+ if (Object.getOwnPropertySymbols) {
606
+ var symbols = Object.getOwnPropertySymbols(object);
607
+ if (enumerableOnly) {
608
+ symbols = symbols.filter(function(sym) {
609
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
610
+ });
611
+ }
612
+ keys.push.apply(keys, symbols);
613
+ }
614
+ return keys;
615
+ }
616
+ function _object_spread_props(target, source) {
617
+ source = source != null ? source : {};
618
+ if (Object.getOwnPropertyDescriptors) {
619
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
620
+ } else {
621
+ ownKeys(Object(source)).forEach(function(key) {
622
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
623
+ });
624
+ }
625
+ return target;
626
+ }
627
+ (function() {
628
+ 'use strict';
629
+ try {
630
+ if (typeof document != 'undefined') {
631
+ var elementStyle = document.createElement('style');
632
+ elementStyle.appendChild(document.createTextNode("/*\n * Jodit Image Editor — design system & layout.\n *\n * Everything is scoped under `.jie` so the stylesheet can be injected into any\n * page without leaking. Tokens are CSS custom properties; switching `data-theme`\n * re-themes the whole tree. PostCSS handles nesting + autoprefixing.\n */\n\n.jie {\n /* palette */\n --jie-accent: #5b5fc7;\n --jie-accent-soft: #eceefb;\n --jie-bg: #ffffff;\n --jie-surface: #ffffff;\n --jie-stage: #f3f4f8;\n --jie-border: #e6e8ef;\n --jie-text: #1f2430;\n --jie-text-dim: #8a90a2;\n --jie-danger: #e0533d;\n --jie-shadow: 0 1px 2px rgba(20, 24, 40, 0.06), 0 8px 24px rgba(20, 24, 40, 0.06);\n\n /* sizing scale — one system for every control */\n --jie-control-h: 38px; /* shared height: buttons, inputs, selects, color, slider row */\n --jie-control-px: 12px; /* horizontal padding inside controls */\n --jie-icon: 20px; /* inline icon size (rail icons override to 24) */\n --jie-radius: 12px; /* cards / panels */\n --jie-radius-sm: 8px; /* controls */\n --jie-gap: 16px; /* between groups */\n --jie-gap-sm: 8px; /* between controls in a row */\n --jie-font-sm: 13px;\n --jie-font-md: 14px;\n --jie-rail-w: 92px;\n --jie-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 420px;\n box-sizing: border-box;\n font-family: var(--jie-font);\n color: var(--jie-text);\n background: var(--jie-bg);\n border: 1px solid var(--jie-border);\n border-radius: var(--jie-radius);\n overflow: hidden;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.jie *,.jie *::before,.jie *::after {\n box-sizing: border-box;\n }\n\n.jie[data-theme='dark'] {\n --jie-accent: #8a8df0;\n --jie-accent-soft: #2a2d44;\n --jie-bg: #14161f;\n --jie-surface: #1b1e2a;\n --jie-stage: #0f1118;\n --jie-border: #2a2e3d;\n --jie-text: #e7e9f3;\n --jie-text-dim: #8b91a7;\n --jie-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.4);\n }\n\n/* ---- top bar ------------------------------------------------------------ */\n.jie-topbar {\n display: flex;\n align-items: center;\n gap: var(--jie-gap);\n padding: 12px 16px;\n border-bottom: 1px solid var(--jie-border);\n background: var(--jie-surface);\n}\n\n.jie-topbar__save {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.jie-topbar__meta {\n display: flex;\n align-items: center;\n gap: 12px;\n margin: 0 auto;\n color: var(--jie-text-dim);\n font-size: 14px;\n}\n\n.jie-zoom {\n display: flex;\n align-items: center;\n gap: 6px;\n font-variant-numeric: tabular-nums;\n}\n\n.jie-topbar__actions {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n/* ---- buttons ------------------------------------------------------------ */\n.jie-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: var(--jie-control-h);\n min-width: var(--jie-control-h); /* single-char buttons (B / I) stay square */\n padding: 0 var(--jie-control-px);\n font: inherit;\n font-size: var(--jie-font-md);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: var(--jie-text);\n background: transparent;\n border: 1px solid transparent;\n border-radius: var(--jie-radius-sm);\n cursor: pointer;\n transition:\n background 0.12s ease,\n color 0.12s ease,\n border-color 0.12s ease;\n}\n.jie-btn:hover:not(:disabled) {\n background: var(--jie-accent-soft);\n }\n.jie-btn:disabled {\n opacity: 0.4;\n cursor: default;\n }\n/* inline icons are smaller than the standalone rail icons */\n.jie-btn .jie-icon {\n width: var(--jie-icon);\n height: var(--jie-icon);\n }\n\n.jie-btn--primary {\n color: #fff;\n background: var(--jie-accent);\n padding-inline: 18px;\n}\n\n.jie-btn--primary:hover:not(:disabled) {\n background: color-mix(in srgb, var(--jie-accent) 88%, #000);\n }\n\n.jie-btn--icon {\n width: var(--jie-control-h);\n padding: 0;\n color: var(--jie-text-dim);\n}\n\n.jie-btn--icon:hover:not(:disabled) {\n color: var(--jie-text);\n }\n\n.jie-btn--active {\n color: var(--jie-accent);\n background: var(--jie-accent-soft);\n}\n\n/* text-style modifiers (the B / I annotation toggles) */\n.jie-btn--bold {\n font-weight: 800;\n}\n.jie-btn--italic {\n font-style: italic;\n font-family: Georgia, 'Times New Roman', serif;\n}\n\n.jie-icon {\n display: inline-flex;\n width: 24px;\n height: 24px;\n}\n\n.jie-icon svg {\n width: 100%;\n height: 100%;\n }\n\n/* ---- body / rail -------------------------------------------------------- */\n.jie-body {\n display: flex;\n flex: 1;\n min-height: 0;\n}\n\n.jie-rail {\n display: flex;\n flex-direction: column;\n gap: 6px;\n width: var(--jie-rail-w);\n padding: 14px 10px;\n border-right: 1px solid var(--jie-border);\n background: var(--jie-surface);\n overflow-y: auto;\n}\n\n.jie-tab {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 12px 6px;\n font-size: 12px;\n font-weight: 600;\n color: var(--jie-text-dim);\n background: transparent;\n border: 0;\n border-radius: var(--jie-radius-sm);\n cursor: pointer;\n transition:\n background 0.12s ease,\n color 0.12s ease;\n}\n\n.jie-tab:hover {\n background: var(--jie-accent-soft);\n }\n\n.jie-tab[aria-selected='true'] {\n color: var(--jie-accent);\n background: var(--jie-accent-soft);\n }\n\n/* ---- stage / preview ---------------------------------------------------- */\n.jie-stage {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n background: var(--jie-bg);\n}\n\n.jie-canvas-wrap {\n position: relative;\n flex: 1;\n margin: 16px;\n border-radius: var(--jie-radius);\n background: var(--jie-stage);\n overflow: hidden;\n touch-action: none;\n}\n\n.jie-canvas {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* ---- transient status (loading / saving / error) ------------------------- */\n\n.jie-spinner {\n display: inline-block;\n flex: 0 0 auto;\n width: var(--jie-icon);\n height: var(--jie-icon);\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: jie-spin 0.8s linear infinite;\n}\n\n@keyframes jie-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* Covers the canvas while the image is loading, or shows the load error. */\n.jie-overlay {\n position: absolute;\n inset: 0;\n z-index: 2;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--jie-gap-sm);\n font-size: var(--jie-font-md);\n color: var(--jie-text-dim);\n background: var(--jie-stage);\n}\n.jie-overlay .jie-spinner {\n width: 32px;\n height: 32px;\n border-width: 3px;\n color: var(--jie-accent);\n }\n\n.jie-overlay--error {\n color: var(--jie-danger);\n}\n\n.jie-overlay__detail {\n max-width: 80%;\n font-size: var(--jie-font-sm);\n text-align: center;\n color: var(--jie-text-dim);\n}\n\n/* crop overlay */\n.jie-crop {\n position: absolute;\n border: 1.5px dashed var(--jie-accent);\n box-shadow: 0 0 0 100vmax var(--jie-scrim, rgba(17, 24, 39, 0.45));\n transform-origin: center;\n cursor: move;\n}\n\n/*\n * Rotation knob, centred *above* the frame and connected by a short stem. The\n * preview keeps `VIEWPORT_PADDING` around the image, so the knob has room and is\n * never clipped — even for a full-image crop.\n */\n.jie-crop__rotate {\n position: absolute;\n left: 50%;\n top: -34px;\n width: 18px;\n height: 18px;\n margin-left: -9px;\n background: var(--jie-bg);\n border: 2px solid var(--jie-accent);\n border-radius: 50%;\n box-shadow: 0 1px 3px rgba(20, 24, 40, 0.3);\n cursor: grab;\n touch-action: none;\n\n /* stem runs from the knob's bottom (-16px) toward the frame, stopping ~5px\n short of the edge so it never touches/overlaps the dashed border */\n}\n.jie-crop__rotate::before {\n content: '';\n position: absolute;\n left: 50%;\n top: 18px;\n width: 2px;\n height: 11px;\n margin-left: -1px;\n background: var(--jie-accent);\n }\n.jie-crop__rotate:active {\n cursor: grabbing;\n }\n\n.jie-crop__handle {\n position: absolute;\n width: 14px;\n height: 14px;\n background: var(--jie-bg);\n border: 2px solid var(--jie-accent);\n border-radius: 50%;\n}\n\n.jie-crop__handle[data-h='nw'] {\n top: -7px;\n left: -7px;\n cursor: nwse-resize;\n }\n\n.jie-crop__handle[data-h='ne'] {\n top: -7px;\n right: -7px;\n cursor: nesw-resize;\n }\n\n.jie-crop__handle[data-h='sw'] {\n bottom: -7px;\n left: -7px;\n cursor: nesw-resize;\n }\n\n.jie-crop__handle[data-h='se'] {\n bottom: -7px;\n right: -7px;\n cursor: nwse-resize;\n }\n\n/* ---- bottom panel ------------------------------------------------------- */\n.jie-panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n align-items: center;\n padding: 14px 16px 20px;\n border-top: 1px solid var(--jie-border);\n background: var(--jie-surface);\n}\n\n/* a row of equal-height controls, centred on a shared baseline */\n.jie-toolrow {\n display: flex;\n flex-wrap: wrap;\n gap: var(--jie-gap-sm);\n align-items: center;\n justify-content: center;\n}\n\n/* a row of labelled (label-above) fields; inputs and buttons share a height,\n so aligning their bottoms keeps everything tidy */\n.jie-fieldrow {\n display: flex;\n flex-wrap: wrap;\n gap: var(--jie-gap);\n align-items: flex-end;\n justify-content: center;\n}\n\n.jie-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: var(--jie-font-sm);\n color: var(--jie-text-dim);\n}\n\n.jie-input,\n.jie-select {\n height: var(--jie-control-h);\n width: 120px;\n padding: 0 var(--jie-control-px);\n font: inherit;\n font-size: var(--jie-font-md);\n color: var(--jie-text);\n background: var(--jie-bg);\n border: 1px solid var(--jie-border);\n border-radius: var(--jie-radius-sm);\n}\n\n:is(.jie-input,.jie-select):focus {\n outline: 2px solid var(--jie-accent-soft);\n border-color: var(--jie-accent);\n }\n\n/* native colour input, normalised to the control height as a rounded swatch */\n/* colour picker: a swatch button that opens a popover of preset swatches */\n.jie-colorpicker {\n position: relative;\n display: inline-flex;\n}\n\n/* the trigger swatch (shows the current colour via inline `background`) */\n.jie-color {\n width: var(--jie-control-h);\n height: var(--jie-control-h);\n padding: 0;\n border: 1px solid var(--jie-border);\n border-radius: var(--jie-radius-sm);\n box-shadow: inset 0 0 0 2px var(--jie-bg);\n cursor: pointer;\n}\n\n.jie-popover__backdrop {\n position: fixed;\n inset: 0;\n z-index: 50;\n}\n\n.jie-popover {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n z-index: 51;\n padding: 10px;\n background: var(--jie-surface);\n border: 1px solid var(--jie-border);\n border-radius: var(--jie-radius);\n box-shadow: var(--jie-shadow);\n}\n\n.jie-swatches {\n display: grid;\n grid-template-columns: repeat(6, 1fr);\n gap: 8px;\n}\n\n/* 3×3 position anchor grid */\n.jie-posgrid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 2px;\n padding: 3px;\n background: var(--jie-bg);\n border: 1px solid var(--jie-border);\n border-radius: var(--jie-radius-sm);\n}\n\n.jie-posgrid__cell {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n color: var(--jie-text-dim);\n background: transparent;\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n}\n\n.jie-posgrid__cell .jie-icon {\n width: 16px;\n height: 16px;\n }\n\n.jie-posgrid__cell:hover {\n background: var(--jie-accent-soft);\n color: var(--jie-text);\n }\n\n.jie-posgrid__cell[aria-pressed='true'] {\n background: var(--jie-accent);\n color: #fff;\n }\n\n/* font selector */\n.jie-fontselect {\n position: relative;\n display: inline-flex;\n}\n\n.jie-fontselect__trigger {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: var(--jie-control-h);\n min-width: 130px;\n padding: 0 10px;\n font: inherit;\n font-size: var(--jie-font-md);\n color: var(--jie-text);\n background: var(--jie-bg);\n border: 1px solid var(--jie-border);\n border-radius: var(--jie-radius-sm);\n cursor: pointer;\n}\n\n.jie-fontselect__trigger > span {\n flex: 1;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n.jie-fontselect__trigger .jie-icon {\n width: 16px;\n height: 16px;\n color: var(--jie-text-dim);\n flex: none;\n }\n\n.jie-fontlist {\n min-width: 180px;\n max-height: 260px;\n overflow-y: auto;\n padding: 6px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.jie-fontlist__item {\n padding: 8px 10px;\n font-size: 15px;\n text-align: left;\n color: var(--jie-text);\n background: transparent;\n border: 0;\n border-radius: var(--jie-radius-sm);\n cursor: pointer;\n}\n\n.jie-fontlist__item:hover {\n background: var(--jie-accent-soft);\n }\n\n.jie-fontlist__item[aria-pressed='true'] {\n color: var(--jie-accent);\n background: var(--jie-accent-soft);\n }\n\n.jie-swatch {\n width: 28px;\n height: 28px;\n padding: 0;\n border: 1px solid var(--jie-border);\n border-radius: var(--jie-radius-sm);\n box-shadow: inset 0 0 0 2px var(--jie-surface);\n cursor: pointer;\n transition:\n transform 0.1s ease,\n box-shadow 0.1s ease;\n}\n\n.jie-swatch:hover {\n transform: scale(1.08);\n }\n\n.jie-swatch[aria-pressed='true'] {\n box-shadow:\n inset 0 0 0 2px var(--jie-surface),\n 0 0 0 2px var(--jie-accent);\n }\n\n/* slider — the whole control occupies the shared height and centres its track */\n.jie-slider {\n display: flex;\n align-items: center;\n gap: 10px;\n height: var(--jie-control-h);\n width: clamp(180px, 28vw, 300px);\n}\n\n.jie-slider__label {\n font-size: var(--jie-font-sm);\n color: var(--jie-text-dim);\n white-space: nowrap;\n}\n\n.jie-slider__value {\n min-width: 2.5ch;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n}\n\n/*\n * Fully custom range so it looks identical across evergreen browsers (the\n * native `accent-color` only themes the colour, not the track/thumb shapes).\n * The filled portion is driven by `--jie-range-fill` (set per render by the\n * slider component) on WebKit, and by the native `::-moz-range-progress` on\n * Firefox.\n */\n.jie-range {\n --jie-range-fill: 50%;\n appearance: none;\n flex: 1;\n min-width: 0;\n height: 16px; /* hit area */\n background: transparent;\n cursor: pointer;\n}\n.jie-range::-webkit-slider-runnable-track {\n height: 4px;\n border-radius: 999px;\n background: linear-gradient(\n to right,\n var(--jie-accent) var(--jie-range-fill),\n var(--jie-border) var(--jie-range-fill)\n );\n }\n.jie-range::-webkit-slider-thumb {\n appearance: none;\n margin-top: -6px; /* centre the 16px thumb on the 4px track */\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--jie-accent);\n border: 2px solid var(--jie-bg);\n box-shadow: 0 1px 3px rgba(20, 24, 40, 0.25);\n }\n.jie-range::-moz-range-track {\n height: 4px;\n border-radius: 999px;\n background: var(--jie-border);\n }\n.jie-range::-moz-range-progress {\n height: 4px;\n border-radius: 999px;\n background: var(--jie-accent);\n }\n.jie-range::-moz-range-thumb {\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--jie-accent);\n border: 2px solid var(--jie-bg);\n box-shadow: 0 1px 3px rgba(20, 24, 40, 0.25);\n }\n.jie-range:focus-visible {\n outline: none;\n }\n.jie-range:focus-visible::-webkit-slider-thumb {\n box-shadow: 0 0 0 4px var(--jie-accent-soft);\n }\n.jie-range:focus-visible::-moz-range-thumb {\n box-shadow: 0 0 0 4px var(--jie-accent-soft);\n }\n\n/* filter thumbnails */\n.jie-thumbs {\n display: flex;\n gap: 12px;\n width: 100%;\n padding-bottom: 4px;\n overflow-x: auto;\n justify-content: flex-start;\n}\n\n.jie-thumb {\n display: flex;\n flex-direction: column;\n gap: 6px;\n align-items: center;\n padding: 4px;\n font-size: 12px;\n color: var(--jie-text-dim);\n background: transparent;\n border: 0;\n cursor: pointer;\n}\n\n.jie-thumb canvas,.jie-thumb .jie-thumb__swatch {\n width: 76px;\n height: 56px;\n border-radius: var(--jie-radius-sm);\n border: 2px solid transparent;\n object-fit: cover;\n background: var(--jie-stage);\n }\n\n.jie-thumb[aria-pressed='true'] {\n color: var(--jie-accent);\n }\n\n.jie-thumb[aria-pressed='true'] canvas,.jie-thumb[aria-pressed='true'] .jie-thumb__swatch {\n border-color: var(--jie-accent);\n }\n\n/* ---- responsive / mobile ------------------------------------------------ */\n@media (max-width: 720px) {\n .jie {\n --jie-rail-w: 100%;\n }\n\n .jie-topbar__meta {\n display: none;\n }\n\n .jie-body {\n flex-direction: column-reverse;\n }\n\n .jie-rail {\n flex-direction: row;\n width: 100%;\n border-right: 0;\n border-top: 1px solid var(--jie-border);\n overflow-x: auto;\n }\n\n .jie-tab {\n flex: 0 0 auto;\n min-width: 72px;\n }\n\n .jie-canvas-wrap {\n margin: 10px;\n }\n\n .jie-input {\n width: 100px;\n }\n}\n\n@media (hover: none) {\n .jie-crop__handle {\n width: 22px;\n height: 22px;\n }\n}"));
633
+ document.head.appendChild(elementStyle);
634
+ }
635
+ } catch (e) {
636
+ console.error('vite-plugin-css-injected-by-js', e);
637
+ }
638
+ })();
639
+ const DEFAULT_FONTS = Object.freeze([
640
+ {
641
+ label: "Arial",
642
+ value: "Arial, Helvetica, sans-serif"
643
+ },
644
+ {
645
+ label: "Helvetica",
646
+ value: "Helvetica, Arial, sans-serif"
647
+ },
648
+ {
649
+ label: "Verdana",
650
+ value: "Verdana, Geneva, sans-serif"
651
+ },
652
+ {
653
+ label: "Tahoma",
654
+ value: "Tahoma, Geneva, sans-serif"
655
+ },
656
+ {
657
+ label: "Trebuchet MS",
658
+ value: '"Trebuchet MS", Helvetica, sans-serif'
659
+ },
660
+ {
661
+ label: "Times New Roman",
662
+ value: '"Times New Roman", Times, serif'
663
+ },
664
+ {
665
+ label: "Georgia",
666
+ value: 'Georgia, "Times New Roman", serif'
667
+ },
668
+ {
669
+ label: "Courier New",
670
+ value: '"Courier New", Courier, monospace'
671
+ },
672
+ {
673
+ label: "Impact",
674
+ value: "Impact, Haettenschweiler, sans-serif"
675
+ }
676
+ ]);
677
+ const IDENTITY_FINETUNE = Object.freeze({
678
+ brightness: 0,
679
+ contrast: 0,
680
+ saturation: 0,
681
+ warmth: 0,
682
+ blur: 0
683
+ });
684
+ const DEFAULT_PALETTE = Object.freeze([
685
+ "#ffffff",
686
+ "#1a1a1a",
687
+ "#9e9689",
688
+ "#4a90e2",
689
+ "#54b98a",
690
+ "#f2c94c",
691
+ "#eb5757",
692
+ "#a29bfe",
693
+ "#8b5cf6",
694
+ "#f178b6",
695
+ "#f2994a",
696
+ "#b8e454",
697
+ "#7ec8f0",
698
+ "#5b6470",
699
+ "#d8d2c8",
700
+ "#eeeeee"
701
+ ]);
702
+ function createIdentityDesign() {
703
+ return {
704
+ flip: {
705
+ horizontal: false,
706
+ vertical: false
707
+ },
708
+ rotate: 0,
709
+ angle: 0,
710
+ crop: null,
711
+ resize: null,
712
+ finetune: _object_spread({}, IDENTITY_FINETUNE),
713
+ filter: "original",
714
+ annotations: [],
715
+ focus: null
716
+ };
717
+ }
718
+ function createInitialState() {
719
+ return {
720
+ source: null,
721
+ status: "idle",
722
+ busyVisible: false,
723
+ error: null,
724
+ activeTab: "adjust",
725
+ activeTool: "crop",
726
+ zoom: 1,
727
+ history: {
728
+ entries: [
729
+ createIdentityDesign()
730
+ ],
731
+ index: 0
732
+ },
733
+ selectedAnnotationId: null,
734
+ theme: "light",
735
+ showToolbar: true,
736
+ locale: "en",
737
+ minCropSize: 8,
738
+ minResizeSize: 1,
739
+ palette: [
740
+ ...DEFAULT_PALETTE
741
+ ],
742
+ fonts: DEFAULT_FONTS.map((f)=>_object_spread({}, f)),
743
+ activePopover: null,
744
+ viewport: null
745
+ };
746
+ }
747
+ const MAX_HISTORY = 100;
748
+ function present(history) {
749
+ const value = history.entries[history.index];
750
+ if (value === void 0) {
751
+ throw new RangeError(`history index ${history.index} is out of bounds`);
752
+ }
753
+ return value;
754
+ }
755
+ function canUndo(history) {
756
+ return history.index > 0;
757
+ }
758
+ function canRedo(history) {
759
+ return history.index < history.entries.length - 1;
760
+ }
761
+ function commit(history, next) {
762
+ const kept = history.entries.slice(0, history.index + 1);
763
+ kept.push(next);
764
+ const overflow = Math.max(0, kept.length - MAX_HISTORY);
765
+ const entries = overflow > 0 ? kept.slice(overflow) : kept;
766
+ return {
767
+ entries,
768
+ index: entries.length - 1
769
+ };
770
+ }
771
+ function replacePresent(history, next) {
772
+ const entries = history.entries.slice();
773
+ entries[history.index] = next;
774
+ return {
775
+ entries,
776
+ index: history.index
777
+ };
778
+ }
779
+ function goTo(history, index) {
780
+ const clamped = clamp$1(index, 0, history.entries.length - 1);
781
+ return clamped === history.index ? history : _object_spread_props(_object_spread({}, history), {
782
+ index: clamped
783
+ });
784
+ }
785
+ function step(history, delta) {
786
+ return goTo(history, history.index + delta);
787
+ }
788
+ function clamp$1(value, min, max) {
789
+ return Math.min(Math.max(value, min), max);
790
+ }
791
+ function reduce(state, patch) {
792
+ let next = state;
793
+ if (patch.activeTab !== void 0) next = set(next, {
794
+ activeTab: patch.activeTab
795
+ });
796
+ if (patch.activeTool !== void 0) next = set(next, {
797
+ activeTool: patch.activeTool
798
+ });
799
+ if (patch.zoom !== void 0) next = set(next, {
800
+ zoom: sanitizeZoom(patch.zoom)
801
+ });
802
+ if (patch.theme !== void 0) next = set(next, {
803
+ theme: patch.theme
804
+ });
805
+ if (patch.showToolbar !== void 0) {
806
+ next = set(next, {
807
+ showToolbar: patch.showToolbar
808
+ });
809
+ }
810
+ if (patch.locale !== void 0) next = set(next, {
811
+ locale: patch.locale
812
+ });
813
+ if (patch.minCropSize !== void 0) {
814
+ next = set(next, {
815
+ minCropSize: Math.max(1, patch.minCropSize)
816
+ });
817
+ }
818
+ if (patch.minResizeSize !== void 0) {
819
+ next = set(next, {
820
+ minResizeSize: Math.max(1, patch.minResizeSize)
821
+ });
822
+ }
823
+ if (patch.palette !== void 0) next = set(next, {
824
+ palette: patch.palette
825
+ });
826
+ if (patch.fonts !== void 0) next = set(next, {
827
+ fonts: patch.fonts
828
+ });
829
+ if (patch.activePopover !== void 0) next = set(next, {
830
+ activePopover: patch.activePopover
831
+ });
832
+ if (patch.status !== void 0) {
833
+ next = set(next, {
834
+ status: patch.status
835
+ });
836
+ if (patch.status !== "exporting" && next.busyVisible) {
837
+ next = set(next, {
838
+ busyVisible: false
839
+ });
840
+ }
841
+ }
842
+ if (patch.busyVisible !== void 0) next = set(next, {
843
+ busyVisible: patch.busyVisible
844
+ });
845
+ if (patch.error !== void 0) next = set(next, {
846
+ error: patch.error
847
+ });
848
+ if (patch.source !== void 0) next = set(next, {
849
+ source: patch.source
850
+ });
851
+ if (patch.viewport !== void 0) next = set(next, {
852
+ viewport: patch.viewport
853
+ });
854
+ if (patch.selectedAnnotationId !== void 0) {
855
+ next = set(next, {
856
+ selectedAnnotationId: patch.selectedAnnotationId
857
+ });
858
+ }
859
+ if (patch.resetHistory) {
860
+ next = set(next, {
861
+ history: {
862
+ entries: [
863
+ createIdentityDesign()
864
+ ],
865
+ index: 0
866
+ }
867
+ });
868
+ }
869
+ if (patch.resetDesign) {
870
+ next = set(next, {
871
+ history: commit(next.history, createIdentityDesign())
872
+ });
873
+ }
874
+ if (patch.design !== void 0) {
875
+ const merged = mergeDesign(present(next.history), patch.design);
876
+ const history = patch.commit === false ? replacePresent(next.history, merged) : commit(next.history, merged);
877
+ next = set(next, {
878
+ history
879
+ });
880
+ }
881
+ if (patch.history !== void 0) {
882
+ const nav = patch.history;
883
+ const history = "step" in nav ? step(next.history, nav.step) : goTo(next.history, nav.index);
884
+ next = set(next, {
885
+ history
886
+ });
887
+ }
888
+ return next;
889
+ }
890
+ function mergeDesign(base, patch) {
891
+ return _object_spread_props(_object_spread({}, base, patch), {
892
+ flip: patch.flip ? _object_spread({}, base.flip, patch.flip) : base.flip,
893
+ finetune: patch.finetune ? _object_spread({}, base.finetune, patch.finetune) : base.finetune,
894
+ rotate: patch.rotate !== void 0 ? normalizeRotation(patch.rotate) : base.rotate
895
+ });
896
+ }
897
+ function normalizeRotation(deg) {
898
+ return (Math.round(deg / 90) * 90 % 360 + 360) % 360;
899
+ }
900
+ function sanitizeZoom(zoom) {
901
+ if (!Number.isFinite(zoom) || zoom <= 0) return 1;
902
+ return Math.min(Math.max(zoom, 0.01), 64);
903
+ }
904
+ function set(state, partial) {
905
+ for (const key of Object.keys(partial)){
906
+ if (state[key] !== partial[key]) {
907
+ return _object_spread({}, state, partial);
908
+ }
909
+ }
910
+ return state;
911
+ }
912
+ class CoalescingScheduler {
913
+ request(task) {
914
+ this.pending = task;
915
+ if (this.handle === null) {
916
+ this.handle = this.schedule(()=>this.flush());
917
+ }
918
+ }
919
+ flush() {
920
+ if (this.handle !== null) {
921
+ this.clear(this.handle);
922
+ this.handle = null;
923
+ }
924
+ const task = this.pending;
925
+ this.pending = null;
926
+ if (task) task();
927
+ }
928
+ cancel() {
929
+ if (this.handle !== null) {
930
+ this.clear(this.handle);
931
+ this.handle = null;
932
+ }
933
+ this.pending = null;
934
+ }
935
+ constructor(schedule, clear){
936
+ _define_property(this, "pending", null);
937
+ _define_property(this, "handle", null);
938
+ this.schedule = schedule;
939
+ this.clear = clear;
940
+ }
941
+ }
942
+ function createRafScheduler() {
943
+ if (typeof requestAnimationFrame !== "function") {
944
+ return new CoalescingScheduler((cb)=>setTimeout(cb, 16), (h)=>clearTimeout(h));
945
+ }
946
+ const clear = (h)=>{
947
+ const handle = h;
948
+ cancelAnimationFrame(handle.raf);
949
+ if (handle.timer !== null) {
950
+ clearTimeout(handle.timer);
951
+ handle.timer = null;
952
+ }
953
+ };
954
+ const schedule = (cb)=>{
955
+ const handle = {
956
+ raf: 0,
957
+ timer: null
958
+ };
959
+ const run = ()=>{
960
+ clear(handle);
961
+ cb();
962
+ };
963
+ handle.raf = requestAnimationFrame(run);
964
+ handle.timer = setTimeout(run, 100);
965
+ return handle;
966
+ };
967
+ return new CoalescingScheduler(schedule, clear);
968
+ }
969
+ function createMicrotaskScheduler() {
970
+ let cancelled = false;
971
+ const schedule = (cb)=>{
972
+ cancelled = false;
973
+ queueMicrotask(()=>{
974
+ if (!cancelled) cb();
975
+ });
976
+ return 1;
977
+ };
978
+ const clear = ()=>{
979
+ cancelled = true;
980
+ };
981
+ return new CoalescingScheduler(schedule, clear);
982
+ }
983
+ class SyncScheduler {
984
+ request(task) {
985
+ this.pending = task;
986
+ this.flush();
987
+ }
988
+ flush() {
989
+ const task = this.pending;
990
+ this.pending = null;
991
+ if (task) task();
992
+ }
993
+ cancel() {
994
+ this.pending = null;
995
+ }
996
+ constructor(){
997
+ _define_property(this, "pending", null);
998
+ }
999
+ }
1000
+ class Store {
1001
+ getState() {
1002
+ return this.state;
1003
+ }
1004
+ /**
1005
+ * Apply a patch. If the reducer produced a new state reference, schedule a
1006
+ * (coalesced) notification. Returns the resulting state for convenience.
1007
+ */ update(patch) {
1008
+ const next = this.reducer(this.state, patch);
1009
+ if (next !== this.state) {
1010
+ this.state = next;
1011
+ this.scheduler.request(()=>this.notify());
1012
+ }
1013
+ return this.state;
1014
+ }
1015
+ /** Subscribe to state changes. The listener is *not* called immediately. */ subscribe(listener) {
1016
+ this.listeners.add(listener);
1017
+ return ()=>{
1018
+ this.listeners.delete(listener);
1019
+ };
1020
+ }
1021
+ /** Force any pending batched notification to run now. */ flush() {
1022
+ this.scheduler.flush();
1023
+ }
1024
+ /** Drop listeners and cancel pending work. */ destroy() {
1025
+ this.scheduler.cancel();
1026
+ this.listeners.clear();
1027
+ }
1028
+ notify() {
1029
+ if (this.notifying) return;
1030
+ this.notifying = true;
1031
+ try {
1032
+ for (const listener of this.listeners)listener(this.state);
1033
+ } finally{
1034
+ this.notifying = false;
1035
+ }
1036
+ }
1037
+ constructor(options = {}){
1038
+ var _options_initialState, _options_reducer, _options_scheduler;
1039
+ _define_property(this, "state", void 0);
1040
+ _define_property(this, "reducer", void 0);
1041
+ _define_property(this, "scheduler", void 0);
1042
+ _define_property(this, "listeners", /* @__PURE__ */ new Set());
1043
+ _define_property(this, "notifying", false);
1044
+ this.state = (_options_initialState = options.initialState) !== null && _options_initialState !== void 0 ? _options_initialState : createInitialState();
1045
+ this.reducer = (_options_reducer = options.reducer) !== null && _options_reducer !== void 0 ? _options_reducer : reduce;
1046
+ this.scheduler = (_options_scheduler = options.scheduler) !== null && _options_scheduler !== void 0 ? _options_scheduler : new SyncScheduler();
1047
+ }
1048
+ }
1049
+ function clamp(value, min, max) {
1050
+ return Math.min(Math.max(value, min), max);
1051
+ }
1052
+ function roundRect(rect) {
1053
+ const x = Math.round(rect.x);
1054
+ const y = Math.round(rect.y);
1055
+ return {
1056
+ x,
1057
+ y,
1058
+ width: Math.round(rect.x + rect.width) - x,
1059
+ height: Math.round(rect.y + rect.height) - y
1060
+ };
1061
+ }
1062
+ function clampRectToBounds(rect, bounds) {
1063
+ const width = clamp(rect.width, 1, bounds.width);
1064
+ const height = clamp(rect.height, 1, bounds.height);
1065
+ const x = clamp(rect.x, 0, bounds.width - width);
1066
+ const y = clamp(rect.y, 0, bounds.height - height);
1067
+ return {
1068
+ x,
1069
+ y,
1070
+ width,
1071
+ height
1072
+ };
1073
+ }
1074
+ function applyAspectRatio(rect, ratio) {
1075
+ if (ratio === null || !Number.isFinite(ratio) || ratio <= 0) return rect;
1076
+ const cx = rect.x + rect.width / 2;
1077
+ const cy = rect.y + rect.height / 2;
1078
+ const area = rect.width * rect.height;
1079
+ const height = Math.sqrt(area / ratio);
1080
+ const width = height * ratio;
1081
+ return {
1082
+ x: cx - width / 2,
1083
+ y: cy - height / 2,
1084
+ width,
1085
+ height
1086
+ };
1087
+ }
1088
+ function fitScale(content, container, allowUpscale = true) {
1089
+ const scale = Math.min(container.width / content.width, container.height / content.height);
1090
+ return allowUpscale ? scale : Math.min(scale, 1);
1091
+ }
1092
+ function lockedResize(source, next, locked, min = 1) {
1093
+ const ratio = source.width / source.height;
1094
+ if (!locked) {
1095
+ var _next_width, _next_height;
1096
+ return {
1097
+ width: Math.max(min, Math.round((_next_width = next.width) !== null && _next_width !== void 0 ? _next_width : source.width)),
1098
+ height: Math.max(min, Math.round((_next_height = next.height) !== null && _next_height !== void 0 ? _next_height : source.height))
1099
+ };
1100
+ }
1101
+ if (next.width !== void 0) {
1102
+ const width = Math.max(min, Math.round(next.width));
1103
+ return {
1104
+ width,
1105
+ height: Math.max(min, Math.round(width / ratio))
1106
+ };
1107
+ }
1108
+ if (next.height !== void 0) {
1109
+ const height = Math.max(min, Math.round(next.height));
1110
+ return {
1111
+ width: Math.max(min, Math.round(height * ratio)),
1112
+ height
1113
+ };
1114
+ }
1115
+ return _object_spread({}, source);
1116
+ }
1117
+ function fitWithinLimits(w, h, limits) {
1118
+ let scale = 1;
1119
+ if (limits.maxSize && limits.maxSize > 0) {
1120
+ scale = Math.min(scale, limits.maxSize / Math.max(w, h));
1121
+ }
1122
+ if (limits.maxPixels && limits.maxPixels > 0) {
1123
+ scale = Math.min(scale, Math.sqrt(limits.maxPixels / (w * h)));
1124
+ }
1125
+ return Math.min(1, scale);
1126
+ }
1127
+ function fitInViewport(content, container) {
1128
+ const scale = Math.min(container.width / content.width, container.height / content.height) || 0;
1129
+ const width = content.width * scale;
1130
+ const height = content.height * scale;
1131
+ return {
1132
+ scale,
1133
+ width,
1134
+ height,
1135
+ offsetX: (container.width - width) / 2,
1136
+ offsetY: (container.height - height) / 2
1137
+ };
1138
+ }
1139
+ function centerRect(size, bounds) {
1140
+ return {
1141
+ x: (bounds.width - size.width) / 2,
1142
+ y: (bounds.height - size.height) / 2,
1143
+ width: size.width,
1144
+ height: size.height
1145
+ };
1146
+ }
1147
+ function selectDesign(state) {
1148
+ return present(state.history);
1149
+ }
1150
+ function selectIsCropping(state) {
1151
+ return state.activeTab === "adjust" && state.activeTool === "crop";
1152
+ }
1153
+ function selectPreviewDesign(state) {
1154
+ const design = selectDesign(state);
1155
+ return selectIsCropping(state) ? _object_spread_props(_object_spread({}, design), {
1156
+ crop: null,
1157
+ angle: 0
1158
+ }) : design;
1159
+ }
1160
+ function selectCanUndo(state) {
1161
+ return canUndo(state.history);
1162
+ }
1163
+ function selectCanRedo(state) {
1164
+ return canRedo(state.history);
1165
+ }
1166
+ function selectIsDirty(state) {
1167
+ return state.history.index > 0 || state.history.entries.length > 1;
1168
+ }
1169
+ function selectOrientedSize(state) {
1170
+ if (!state.source) return null;
1171
+ const design = selectDesign(state);
1172
+ const { width, height } = state.source;
1173
+ const swap = design.rotate === 90 || design.rotate === 270;
1174
+ return swap ? {
1175
+ width: height,
1176
+ height: width
1177
+ } : {
1178
+ width,
1179
+ height
1180
+ };
1181
+ }
1182
+ function selectOutputSize(state) {
1183
+ const design = selectDesign(state);
1184
+ if (design.resize) return design.resize;
1185
+ if (design.crop) {
1186
+ return {
1187
+ width: Math.round(design.crop.width),
1188
+ height: Math.round(design.crop.height)
1189
+ };
1190
+ }
1191
+ return selectOrientedSize(state);
1192
+ }
1193
+ function selectPreviewContentSize(state) {
1194
+ return selectIsCropping(state) ? selectOrientedSize(state) : selectOutputSize(state);
1195
+ }
1196
+ const VIEWPORT_PADDING = 44;
1197
+ function selectViewportFit(state) {
1198
+ const content = selectPreviewContentSize(state);
1199
+ if (!content || !state.viewport) return null;
1200
+ const avail = {
1201
+ width: Math.max(1, state.viewport.width - VIEWPORT_PADDING * 2),
1202
+ height: Math.max(1, state.viewport.height - VIEWPORT_PADDING * 2)
1203
+ };
1204
+ const base = fitInViewport(content, avail);
1205
+ const scale = base.scale * state.zoom;
1206
+ const width = content.width * scale;
1207
+ const height = content.height * scale;
1208
+ return {
1209
+ scale,
1210
+ width,
1211
+ height,
1212
+ offsetX: (state.viewport.width - width) / 2,
1213
+ offsetY: (state.viewport.height - height) / 2,
1214
+ content
1215
+ };
1216
+ }
1217
+ function selectCropAngle(state) {
1218
+ return selectDesign(state).angle;
1219
+ }
1220
+ function selectEffectiveCrop(state) {
1221
+ const design = selectDesign(state);
1222
+ if (design.crop) return design.crop;
1223
+ const size = selectOrientedSize(state);
1224
+ return size ? {
1225
+ x: 0,
1226
+ y: 0,
1227
+ width: size.width,
1228
+ height: size.height
1229
+ } : null;
1230
+ }
1231
+ const selectors = /*#__PURE__*/ Object.freeze(/*#__PURE__*/ Object.defineProperty({
1232
+ __proto__: null,
1233
+ VIEWPORT_PADDING,
1234
+ selectCanRedo,
1235
+ selectCanUndo,
1236
+ selectCropAngle,
1237
+ selectDesign,
1238
+ selectEffectiveCrop,
1239
+ selectIsCropping,
1240
+ selectIsDirty,
1241
+ selectOrientedSize,
1242
+ selectOutputSize,
1243
+ selectPreviewContentSize,
1244
+ selectPreviewDesign,
1245
+ selectViewportFit
1246
+ }, Symbol.toStringTag, {
1247
+ value: 'Module'
1248
+ }));
1249
+ const DEFAULT_MIN_CROP = 8;
1250
+ function moveCrop(rect, dx, dy, bounds) {
1251
+ return clampRectToBounds(_object_spread_props(_object_spread({}, rect), {
1252
+ x: rect.x + dx,
1253
+ y: rect.y + dy
1254
+ }), bounds);
1255
+ }
1256
+ function resizeCrop(rect, handle, dx, dy, bounds, ratio = null, min = DEFAULT_MIN_CROP) {
1257
+ if (handle === "move") return moveCrop(rect, dx, dy, bounds);
1258
+ const left = handle === "nw" || handle === "sw";
1259
+ const top = handle === "nw" || handle === "ne";
1260
+ let { x, y, width, height } = rect;
1261
+ if (left) {
1262
+ const nx = clamp(x + dx, 0, x + width - min);
1263
+ width += x - nx;
1264
+ x = nx;
1265
+ } else {
1266
+ width = clamp(width + dx, min, bounds.width - x);
1267
+ }
1268
+ if (top) {
1269
+ const ny = clamp(y + dy, 0, y + height - min);
1270
+ height += y - ny;
1271
+ y = ny;
1272
+ } else {
1273
+ height = clamp(height + dy, min, bounds.height - y);
1274
+ }
1275
+ let next = {
1276
+ x,
1277
+ y,
1278
+ width,
1279
+ height
1280
+ };
1281
+ if (ratio !== null && ratio > 0) next = enforceRatio(next, handle, ratio);
1282
+ return clampRectToBounds(next, bounds);
1283
+ }
1284
+ function enforceRatio(rect, handle, ratio) {
1285
+ const height = rect.width / ratio;
1286
+ const anchorTop = handle === "sw" || handle === "se";
1287
+ const y = anchorTop ? rect.y : rect.y + rect.height - height;
1288
+ return _object_spread_props(_object_spread({}, rect), {
1289
+ height,
1290
+ y
1291
+ });
1292
+ }
1293
+ const DEG = Math.PI / 180;
1294
+ const SIGNS = {
1295
+ nw: [
1296
+ -1,
1297
+ -1
1298
+ ],
1299
+ ne: [
1300
+ 1,
1301
+ -1
1302
+ ],
1303
+ sw: [
1304
+ -1,
1305
+ 1
1306
+ ],
1307
+ se: [
1308
+ 1,
1309
+ 1
1310
+ ]
1311
+ };
1312
+ function cropCenter(rect) {
1313
+ return {
1314
+ x: rect.x + rect.width / 2,
1315
+ y: rect.y + rect.height / 2
1316
+ };
1317
+ }
1318
+ function rotatedCorner(rect, angleDeg, sx, sy) {
1319
+ const c = cropCenter(rect);
1320
+ const a = angleDeg * DEG;
1321
+ const cos = Math.cos(a);
1322
+ const sin = Math.sin(a);
1323
+ const hx = sx * rect.width / 2;
1324
+ const hy = sy * rect.height / 2;
1325
+ return {
1326
+ x: c.x + hx * cos - hy * sin,
1327
+ y: c.y + hx * sin + hy * cos
1328
+ };
1329
+ }
1330
+ function pointerAngle(cx, cy, px, py) {
1331
+ return Math.atan2(py - cy, px - cx) / DEG;
1332
+ }
1333
+ function moveCropFree(rect, dx, dy) {
1334
+ return _object_spread_props(_object_spread({}, rect), {
1335
+ x: rect.x + dx,
1336
+ y: rect.y + dy
1337
+ });
1338
+ }
1339
+ function resizeRotatedCrop(rect, angleDeg, handle, dx, dy, min = DEFAULT_MIN_CROP) {
1340
+ if (handle === "move") return moveCropFree(rect, dx, dy);
1341
+ const [sx, sy] = SIGNS[handle];
1342
+ const dragged = rotatedCorner(rect, angleDeg, sx, sy);
1343
+ const fixed = rotatedCorner(rect, angleDeg, -sx, -sy);
1344
+ const nd = {
1345
+ x: dragged.x + dx,
1346
+ y: dragged.y + dy
1347
+ };
1348
+ const cx = (nd.x + fixed.x) / 2;
1349
+ const cy = (nd.y + fixed.y) / 2;
1350
+ const a = angleDeg * DEG;
1351
+ const ux = Math.cos(a);
1352
+ const uy = Math.sin(a);
1353
+ const diffx = nd.x - fixed.x;
1354
+ const diffy = nd.y - fixed.y;
1355
+ const width = Math.max(min, Math.abs(diffx * ux + diffy * uy));
1356
+ const height = Math.max(min, Math.abs(diffx * -uy + diffy * ux));
1357
+ return {
1358
+ x: cx - width / 2,
1359
+ y: cy - height / 2,
1360
+ width,
1361
+ height
1362
+ };
1363
+ }
1364
+ const CHANNELS = 4;
1365
+ function createRaster(width, height, data) {
1366
+ assertPositiveInt(width, "width");
1367
+ assertPositiveInt(height, "height");
1368
+ const expected = width * height * CHANNELS;
1369
+ if (data && data.length !== expected) {
1370
+ throw new RangeError(`data length ${data.length} does not match ${width}x${height} (${expected})`);
1371
+ }
1372
+ return {
1373
+ width,
1374
+ height,
1375
+ data: data !== null && data !== void 0 ? data : new Uint8ClampedArray(expected)
1376
+ };
1377
+ }
1378
+ function solidRaster(width, height, [r, g, b, a]) {
1379
+ const img = createRaster(width, height);
1380
+ for(let i = 0; i < img.data.length; i += CHANNELS){
1381
+ img.data[i] = r;
1382
+ img.data[i + 1] = g;
1383
+ img.data[i + 2] = b;
1384
+ img.data[i + 3] = a;
1385
+ }
1386
+ return img;
1387
+ }
1388
+ function cloneRaster(img) {
1389
+ return createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
1390
+ }
1391
+ function indexOf(img, x, y) {
1392
+ return (y * img.width + x) * CHANNELS;
1393
+ }
1394
+ function assertPositiveInt(value, name) {
1395
+ if (!Number.isInteger(value) || value <= 0) {
1396
+ throw new RangeError(`${name} must be a positive integer, got ${value}`);
1397
+ }
1398
+ }
1399
+ function flip(img, axis) {
1400
+ const out = createRaster(img.width, img.height);
1401
+ const { width, height } = img;
1402
+ for(let y = 0; y < height; y++){
1403
+ for(let x = 0; x < width; x++){
1404
+ const sx = axis === "horizontal" ? width - 1 - x : x;
1405
+ const sy = axis === "vertical" ? height - 1 - y : y;
1406
+ copyPixel(img, indexOf(img, sx, sy), out, indexOf(out, x, y));
1407
+ }
1408
+ }
1409
+ return out;
1410
+ }
1411
+ function rotate90(img, degrees) {
1412
+ const turns = (Math.round(degrees / 90) % 4 + 4) % 4;
1413
+ if (turns === 0) return createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
1414
+ const { width: w, height: h } = img;
1415
+ const swap = turns === 1 || turns === 3;
1416
+ const out = createRaster(swap ? h : w, swap ? w : h);
1417
+ for(let y = 0; y < h; y++){
1418
+ for(let x = 0; x < w; x++){
1419
+ let dx;
1420
+ let dy;
1421
+ if (turns === 1) {
1422
+ dx = h - 1 - y;
1423
+ dy = x;
1424
+ } else if (turns === 2) {
1425
+ dx = w - 1 - x;
1426
+ dy = h - 1 - y;
1427
+ } else {
1428
+ dx = y;
1429
+ dy = w - 1 - x;
1430
+ }
1431
+ copyPixel(img, indexOf(img, x, y), out, indexOf(out, dx, dy));
1432
+ }
1433
+ }
1434
+ return out;
1435
+ }
1436
+ function crop(img, rect) {
1437
+ const safe = clampRectToBounds(roundRect(rect), img);
1438
+ const out = createRaster(safe.width, safe.height);
1439
+ for(let y = 0; y < safe.height; y++){
1440
+ const srcStart = indexOf(img, safe.x, safe.y + y);
1441
+ const dstStart = indexOf(out, 0, y);
1442
+ out.data.set(img.data.subarray(srcStart, srcStart + safe.width * CHANNELS), dstStart);
1443
+ }
1444
+ return out;
1445
+ }
1446
+ function resize(img, size) {
1447
+ const width = Math.max(1, Math.round(size.width));
1448
+ const height = Math.max(1, Math.round(size.height));
1449
+ if (width === img.width && height === img.height) {
1450
+ return createRaster(width, height, new Uint8ClampedArray(img.data));
1451
+ }
1452
+ let cur = img;
1453
+ while(cur.width >= width * 2)cur = halveWidth(cur);
1454
+ while(cur.height >= height * 2)cur = halveHeight(cur);
1455
+ if (cur.width === width && cur.height === height) {
1456
+ return createRaster(width, height, new Uint8ClampedArray(cur.data));
1457
+ }
1458
+ return bilinearResize(cur, width, height);
1459
+ }
1460
+ function halveWidth(img) {
1461
+ const w = Math.ceil(img.width / 2);
1462
+ const out = createRaster(w, img.height);
1463
+ for(let y = 0; y < img.height; y++){
1464
+ for(let x = 0; x < w; x++){
1465
+ const i0 = indexOf(img, 2 * x, y);
1466
+ const i1 = indexOf(img, Math.min(2 * x + 1, img.width - 1), y);
1467
+ const di = indexOf(out, x, y);
1468
+ for(let c = 0; c < CHANNELS; c++)out.data[di + c] = (img.data[i0 + c] + img.data[i1 + c]) / 2;
1469
+ }
1470
+ }
1471
+ return out;
1472
+ }
1473
+ function halveHeight(img) {
1474
+ const h = Math.ceil(img.height / 2);
1475
+ const out = createRaster(img.width, h);
1476
+ for(let y = 0; y < h; y++){
1477
+ for(let x = 0; x < img.width; x++){
1478
+ const i0 = indexOf(img, x, 2 * y);
1479
+ const i1 = indexOf(img, x, Math.min(2 * y + 1, img.height - 1));
1480
+ const di = indexOf(out, x, y);
1481
+ for(let c = 0; c < CHANNELS; c++)out.data[di + c] = (img.data[i0 + c] + img.data[i1 + c]) / 2;
1482
+ }
1483
+ }
1484
+ return out;
1485
+ }
1486
+ function bilinearResize(img, width, height) {
1487
+ const out = createRaster(width, height);
1488
+ const sx = img.width / width;
1489
+ const sy = img.height / height;
1490
+ for(let y = 0; y < height; y++){
1491
+ const fy = (y + 0.5) * sy - 0.5;
1492
+ const y0 = Math.floor(fy);
1493
+ const wy = fy - y0;
1494
+ const y0c = clampIndex(y0, img.height);
1495
+ const y1c = clampIndex(y0 + 1, img.height);
1496
+ for(let x = 0; x < width; x++){
1497
+ const fx = (x + 0.5) * sx - 0.5;
1498
+ const x0 = Math.floor(fx);
1499
+ const wx = fx - x0;
1500
+ const x0c = clampIndex(x0, img.width);
1501
+ const x1c = clampIndex(x0 + 1, img.width);
1502
+ const i00 = indexOf(img, x0c, y0c);
1503
+ const i10 = indexOf(img, x1c, y0c);
1504
+ const i01 = indexOf(img, x0c, y1c);
1505
+ const i11 = indexOf(img, x1c, y1c);
1506
+ const di = indexOf(out, x, y);
1507
+ for(let c = 0; c < CHANNELS; c++){
1508
+ const top = lerp(img.data[i00 + c], img.data[i10 + c], wx);
1509
+ const bottom = lerp(img.data[i01 + c], img.data[i11 + c], wx);
1510
+ out.data[di + c] = lerp(top, bottom, wy);
1511
+ }
1512
+ }
1513
+ }
1514
+ return out;
1515
+ }
1516
+ function sampleRotatedRect(img, rect, angleDeg) {
1517
+ const w = Math.max(1, Math.round(rect.width));
1518
+ const h = Math.max(1, Math.round(rect.height));
1519
+ const out = createRaster(w, h);
1520
+ const cx = rect.x + rect.width / 2;
1521
+ const cy = rect.y + rect.height / 2;
1522
+ const rad = angleDeg * Math.PI / 180;
1523
+ const cos = Math.cos(rad);
1524
+ const sin = Math.sin(rad);
1525
+ for(let oy = 0; oy < h; oy++){
1526
+ const ly = oy + 0.5 - h / 2;
1527
+ for(let ox = 0; ox < w; ox++){
1528
+ const lx = ox + 0.5 - w / 2;
1529
+ const sx = cx + lx * cos - ly * sin - 0.5;
1530
+ const sy = cy + lx * sin + ly * cos - 0.5;
1531
+ sampleBilinear(img, sx, sy, out, indexOf(out, ox, oy));
1532
+ }
1533
+ }
1534
+ return out;
1535
+ }
1536
+ function sampleBilinear(img, fx, fy, out, di) {
1537
+ if (fx <= -1 || fy <= -1 || fx >= img.width || fy >= img.height) {
1538
+ out.data[di] = 0;
1539
+ out.data[di + 1] = 0;
1540
+ out.data[di + 2] = 0;
1541
+ out.data[di + 3] = 0;
1542
+ return;
1543
+ }
1544
+ const x0 = Math.floor(fx);
1545
+ const y0 = Math.floor(fy);
1546
+ const wx = fx - x0;
1547
+ const wy = fy - y0;
1548
+ const x0c = clampIndex(x0, img.width);
1549
+ const x1c = clampIndex(x0 + 1, img.width);
1550
+ const y0c = clampIndex(y0, img.height);
1551
+ const y1c = clampIndex(y0 + 1, img.height);
1552
+ const i00 = indexOf(img, x0c, y0c);
1553
+ const i10 = indexOf(img, x1c, y0c);
1554
+ const i01 = indexOf(img, x0c, y1c);
1555
+ const i11 = indexOf(img, x1c, y1c);
1556
+ for(let c = 0; c < CHANNELS; c++){
1557
+ const top = lerp(img.data[i00 + c], img.data[i10 + c], wx);
1558
+ const bottom = lerp(img.data[i01 + c], img.data[i11 + c], wx);
1559
+ out.data[di + c] = lerp(top, bottom, wy);
1560
+ }
1561
+ }
1562
+ function copyPixel(src, si, dst, di) {
1563
+ dst.data[di] = src.data[si];
1564
+ dst.data[di + 1] = src.data[si + 1];
1565
+ dst.data[di + 2] = src.data[si + 2];
1566
+ dst.data[di + 3] = src.data[si + 3];
1567
+ }
1568
+ function clampIndex(value, length) {
1569
+ return value < 0 ? 0 : value >= length ? length - 1 : value;
1570
+ }
1571
+ function lerp(a, b, t) {
1572
+ return a + (b - a) * t;
1573
+ }
1574
+ const REC_601 = {
1575
+ r: 0.299,
1576
+ g: 0.587,
1577
+ b: 0.114
1578
+ };
1579
+ function mapRgb(img, fn) {
1580
+ const out = createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
1581
+ for(let i = 0; i < out.data.length; i += CHANNELS){
1582
+ out.data[i] = fn(out.data[i], 0);
1583
+ out.data[i + 1] = fn(out.data[i + 1], 1);
1584
+ out.data[i + 2] = fn(out.data[i + 2], 2);
1585
+ }
1586
+ return out;
1587
+ }
1588
+ function brightness(img, amount) {
1589
+ if (amount === 0) return clone(img);
1590
+ const delta = amount / 100 * 255;
1591
+ return mapRgb(img, (v1)=>v1 + delta);
1592
+ }
1593
+ function contrast(img, amount) {
1594
+ if (amount === 0) return clone(img);
1595
+ const c = amount / 100 * 255;
1596
+ const factor = 259 * (c + 255) / (255 * (259 - c));
1597
+ return mapRgb(img, (v1)=>factor * (v1 - 128) + 128);
1598
+ }
1599
+ function saturation(img, amount) {
1600
+ if (amount === 0) return clone(img);
1601
+ const s = 1 + amount / 100;
1602
+ const out = createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
1603
+ for(let i = 0; i < out.data.length; i += CHANNELS){
1604
+ const r = out.data[i];
1605
+ const g = out.data[i + 1];
1606
+ const b = out.data[i + 2];
1607
+ const lum = REC_601.r * r + REC_601.g * g + REC_601.b * b;
1608
+ out.data[i] = lum + (r - lum) * s;
1609
+ out.data[i + 1] = lum + (g - lum) * s;
1610
+ out.data[i + 2] = lum + (b - lum) * s;
1611
+ }
1612
+ return out;
1613
+ }
1614
+ function warmth(img, amount) {
1615
+ if (amount === 0) return clone(img);
1616
+ const delta = amount / 100 * 40;
1617
+ return mapRgb(img, (v1, ch)=>ch === 0 ? v1 + delta : ch === 2 ? v1 - delta : v1);
1618
+ }
1619
+ function blur(img, amount, maxRadius = 20) {
1620
+ const radius = Math.round(Math.max(0, Math.min(100, amount)) / 100 * maxRadius);
1621
+ if (radius <= 0) return clone(img);
1622
+ return boxBlurPass(boxBlurPass(img, radius, "horizontal"), radius, "vertical");
1623
+ }
1624
+ function applyFinetune(img, finetune) {
1625
+ let out = img;
1626
+ out = brightness(out, finetune.brightness);
1627
+ out = contrast(out, finetune.contrast);
1628
+ out = saturation(out, finetune.saturation);
1629
+ out = warmth(out, finetune.warmth);
1630
+ out = blur(out, finetune.blur);
1631
+ return out;
1632
+ }
1633
+ function boxBlurPass(img, radius, axis) {
1634
+ const out = createRaster(img.width, img.height);
1635
+ const { width, height, data } = img;
1636
+ const horizontal = axis === "horizontal";
1637
+ const outer = horizontal ? height : width;
1638
+ const inner = horizontal ? width : height;
1639
+ const window1 = radius * 2 + 1;
1640
+ for(let o = 0; o < outer; o++){
1641
+ for(let c = 0; c < CHANNELS; c++){
1642
+ let sum = 0;
1643
+ for(let k = -radius; k <= radius; k++){
1644
+ sum += sampleChannel(data, width, height, horizontal, o, k, c);
1645
+ }
1646
+ for(let i = 0; i < inner; i++){
1647
+ const di = (horizontal ? o * width + i : i * width + o) * CHANNELS + c;
1648
+ out.data[di] = sum / window1;
1649
+ const leaving = sampleChannel(data, width, height, horizontal, o, i - radius, c);
1650
+ const entering = sampleChannel(data, width, height, horizontal, o, i + radius + 1, c);
1651
+ sum += entering - leaving;
1652
+ }
1653
+ }
1654
+ }
1655
+ return out;
1656
+ }
1657
+ function sampleChannel(data, width, height, horizontal, outer, inner, channel) {
1658
+ const limit = (horizontal ? width : height) - 1;
1659
+ const i = inner < 0 ? 0 : inner > limit ? limit : inner;
1660
+ const idx = (horizontal ? outer * width + i : i * width + outer) * CHANNELS + channel;
1661
+ return data[idx];
1662
+ }
1663
+ function clone(img) {
1664
+ return createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
1665
+ }
1666
+ const adjustments = /*#__PURE__*/ Object.freeze(/*#__PURE__*/ Object.defineProperty({
1667
+ __proto__: null,
1668
+ applyFinetune,
1669
+ blur,
1670
+ brightness,
1671
+ contrast,
1672
+ saturation,
1673
+ warmth
1674
+ }, Symbol.toStringTag, {
1675
+ value: 'Module'
1676
+ }));
1677
+ function perPixel(img, fn) {
1678
+ const out = createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
1679
+ for(let i = 0; i < out.data.length; i += CHANNELS){
1680
+ const [r, g, b] = fn(out.data[i], out.data[i + 1], out.data[i + 2]);
1681
+ out.data[i] = r;
1682
+ out.data[i + 1] = g;
1683
+ out.data[i + 2] = b;
1684
+ }
1685
+ return out;
1686
+ }
1687
+ const original = (img)=>createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
1688
+ const invert = (img)=>perPixel(img, (r, g, b)=>[
1689
+ 255 - r,
1690
+ 255 - g,
1691
+ 255 - b
1692
+ ]);
1693
+ const grayscale = (img)=>perPixel(img, (r, g, b)=>{
1694
+ const l = 0.299 * r + 0.587 * g + 0.114 * b;
1695
+ return [
1696
+ l,
1697
+ l,
1698
+ l
1699
+ ];
1700
+ });
1701
+ const sepia = (img)=>perPixel(img, (r, g, b)=>[
1702
+ 0.393 * r + 0.769 * g + 0.189 * b,
1703
+ 0.349 * r + 0.686 * g + 0.168 * b,
1704
+ 0.272 * r + 0.534 * g + 0.131 * b
1705
+ ]);
1706
+ const solarize = (img)=>perPixel(img, (r, g, b)=>[
1707
+ r < 128 ? r : 255 - r,
1708
+ g < 128 ? g : 255 - g,
1709
+ b < 128 ? b : 255 - b
1710
+ ]);
1711
+ const clarendon = (img)=>warmth(saturation(contrast(img, 20), 25), -10);
1712
+ const gingham = (img)=>warmth(saturation(contrast(img, -10), -15), 12);
1713
+ const REGISTRY = /* @__PURE__ */ new Map();
1714
+ function registerFilter(def) {
1715
+ REGISTRY.set(def.id, def);
1716
+ return ()=>{
1717
+ if (REGISTRY.get(def.id) === def) REGISTRY.delete(def.id);
1718
+ };
1719
+ }
1720
+ function getFilter(id) {
1721
+ return REGISTRY.get(id);
1722
+ }
1723
+ function listFilters() {
1724
+ return [
1725
+ ...REGISTRY.values()
1726
+ ];
1727
+ }
1728
+ function applyFilter(img, id) {
1729
+ const def = REGISTRY.get(id);
1730
+ return def ? def.apply(img) : original(img);
1731
+ }
1732
+ const BUILT_INS = [
1733
+ {
1734
+ id: "original",
1735
+ label: "Original",
1736
+ apply: original
1737
+ },
1738
+ {
1739
+ id: "invert",
1740
+ label: "Invert",
1741
+ apply: invert
1742
+ },
1743
+ {
1744
+ id: "grayscale",
1745
+ label: "Black & White",
1746
+ apply: grayscale
1747
+ },
1748
+ {
1749
+ id: "sepia",
1750
+ label: "Sepia",
1751
+ apply: sepia
1752
+ },
1753
+ {
1754
+ id: "solarize",
1755
+ label: "Solarize",
1756
+ apply: solarize
1757
+ },
1758
+ {
1759
+ id: "clarendon",
1760
+ label: "Clarendon",
1761
+ apply: clarendon
1762
+ },
1763
+ {
1764
+ id: "gingham",
1765
+ label: "Gingham",
1766
+ apply: gingham
1767
+ }
1768
+ ];
1769
+ BUILT_INS.forEach(registerFilter);
1770
+ function toImageData(raster) {
1771
+ return new ImageData(raster.data, raster.width, raster.height);
1772
+ }
1773
+ function createTranslator(messages = {}, fallback = {}) {
1774
+ return (message, params)=>{
1775
+ var _ref, _messages_message;
1776
+ const template = (_ref = (_messages_message = messages[message]) !== null && _messages_message !== void 0 ? _messages_message : fallback[message]) !== null && _ref !== void 0 ? _ref : message;
1777
+ return params ? interpolate(template, params) : template;
1778
+ };
1779
+ }
1780
+ function interpolate(template, params) {
1781
+ return template.replace(/\{(\w+)\}/g, (whole, key)=>key in params ? String(params[key]) : whole);
1782
+ }
1783
+ class LocaleRegistry {
1784
+ /** Add (or merge into) a locale. Returns a disposer restoring the prior state. */ register(locale) {
1785
+ const prev = this.locales.get(locale.id);
1786
+ this.locales.set(locale.id, _object_spread({}, prev !== null && prev !== void 0 ? prev : {}, locale.messages));
1787
+ return ()=>{
1788
+ if (prev) this.locales.set(locale.id, prev);
1789
+ else this.locales.delete(locale.id);
1790
+ };
1791
+ }
1792
+ has(id) {
1793
+ return this.locales.has(id);
1794
+ }
1795
+ get(id) {
1796
+ return this.locales.get(id);
1797
+ }
1798
+ list() {
1799
+ return [
1800
+ ...this.locales.keys()
1801
+ ];
1802
+ }
1803
+ /** Build a translator for `id`; unknown ids yield the identity (English). */ translator(id) {
1804
+ var _this_locales_get;
1805
+ return createTranslator((_this_locales_get = this.locales.get(id)) !== null && _this_locales_get !== void 0 ? _this_locales_get : {});
1806
+ }
1807
+ constructor(){
1808
+ _define_property(this, "locales", /* @__PURE__ */ new Map());
1809
+ }
1810
+ }
1811
+ const DOM_PROPERTIES = /* @__PURE__ */ new Set([
1812
+ "value",
1813
+ "checked",
1814
+ "disabled",
1815
+ "selected",
1816
+ "innerHTML"
1817
+ ]);
1818
+ function h(tag, props = {}, children = []) {
1819
+ return {
1820
+ kind: "element",
1821
+ tag,
1822
+ key: props.key,
1823
+ props,
1824
+ children: normalizeChildren(children)
1825
+ };
1826
+ }
1827
+ function text(value) {
1828
+ return {
1829
+ kind: "text",
1830
+ text: String(value)
1831
+ };
1832
+ }
1833
+ function normalizeChildren(children) {
1834
+ const list = Array.isArray(children) ? children : [
1835
+ children
1836
+ ];
1837
+ const out = [];
1838
+ for (const child of list){
1839
+ if (child === null || child === void 0 || child === false || child === true) continue;
1840
+ out.push(typeof child === "object" ? child : text(child));
1841
+ }
1842
+ return out;
1843
+ }
1844
+ function isElement(node) {
1845
+ return node.kind === "element";
1846
+ }
1847
+ const EMPTY_PROPS = {};
1848
+ class Renderer {
1849
+ /** Render `next` as the single managed child of the container. */ render(next) {
1850
+ if (this.prev === null) {
1851
+ const node = this.mount(next);
1852
+ this.host.insert(this.container, node, null);
1853
+ } else if (sameVNode(this.prev, next)) {
1854
+ this.patch(this.prev, next);
1855
+ } else {
1856
+ const node = this.mount(next);
1857
+ const old = this.prev.node;
1858
+ this.host.insert(this.container, node, this.host.nextSibling(old));
1859
+ this.host.remove(this.container, old);
1860
+ }
1861
+ this.prev = next;
1862
+ }
1863
+ /** Detach the managed subtree. */ unmount() {
1864
+ if (this.prev) {
1865
+ this.host.remove(this.container, this.prev.node);
1866
+ this.prev = null;
1867
+ }
1868
+ }
1869
+ mount(vnode) {
1870
+ if (!isElement(vnode)) {
1871
+ const node = this.host.createText(vnode.text);
1872
+ vnode.node = node;
1873
+ return node;
1874
+ }
1875
+ const el = this.host.createElement(vnode.tag);
1876
+ vnode.node = el;
1877
+ this.applyProps(el, EMPTY_PROPS, vnode.props);
1878
+ for (const child of vnode.children){
1879
+ this.host.insert(el, this.mount(child), null);
1880
+ }
1881
+ return el;
1882
+ }
1883
+ patch(oldV, newV) {
1884
+ const node = oldV.node;
1885
+ newV.node = node;
1886
+ if (!isElement(newV) || !isElement(oldV)) {
1887
+ if (!isElement(newV) && newV.text !== oldV.text) {
1888
+ this.host.setText(node, newV.text);
1889
+ }
1890
+ return node;
1891
+ }
1892
+ this.applyProps(node, oldV.props, newV.props);
1893
+ this.patchChildren(node, oldV.children, newV.children);
1894
+ return node;
1895
+ }
1896
+ // --- props ---------------------------------------------------------------
1897
+ applyProps(node, oldProps, newProps) {
1898
+ var _oldProps_style, _newProps_style, _oldProps_on, _newProps_on;
1899
+ this.applyClass(node, oldProps.class, newProps.class);
1900
+ this.applyStyle(node, (_oldProps_style = oldProps.style) !== null && _oldProps_style !== void 0 ? _oldProps_style : {}, (_newProps_style = newProps.style) !== null && _newProps_style !== void 0 ? _newProps_style : {});
1901
+ this.applyEvents(node, (_oldProps_on = oldProps.on) !== null && _oldProps_on !== void 0 ? _oldProps_on : {}, (_newProps_on = newProps.on) !== null && _newProps_on !== void 0 ? _newProps_on : {});
1902
+ for (const name of Object.keys(newProps)){
1903
+ if (isReserved(name)) continue;
1904
+ const value = newProps[name];
1905
+ if (value === oldProps[name]) continue;
1906
+ this.applyAttr(node, name, value);
1907
+ }
1908
+ for (const name of Object.keys(oldProps)){
1909
+ if (isReserved(name) || name in newProps) continue;
1910
+ if (DOM_PROPERTIES.has(name)) this.host.setProperty(node, name, "");
1911
+ else this.host.removeAttribute(node, name);
1912
+ }
1913
+ }
1914
+ applyAttr(node, name, value) {
1915
+ if (DOM_PROPERTIES.has(name)) {
1916
+ this.host.setProperty(node, name, value);
1917
+ } else if (value === false || value === null || value === void 0) {
1918
+ this.host.removeAttribute(node, name);
1919
+ } else {
1920
+ this.host.setAttribute(node, name, value === true ? "" : stringifyAttr(value));
1921
+ }
1922
+ }
1923
+ applyClass(node, oldClass, newClass) {
1924
+ if (oldClass === newClass) return;
1925
+ if (newClass) this.host.setAttribute(node, "class", newClass);
1926
+ else this.host.removeAttribute(node, "class");
1927
+ }
1928
+ applyStyle(node, oldStyle, newStyle) {
1929
+ for (const prop of Object.keys(newStyle)){
1930
+ if (newStyle[prop] !== oldStyle[prop]) this.host.setStyle(node, prop, String(newStyle[prop]));
1931
+ }
1932
+ for (const prop of Object.keys(oldStyle)){
1933
+ if (!(prop in newStyle)) this.host.removeStyle(node, prop);
1934
+ }
1935
+ }
1936
+ applyEvents(node, oldOn, newOn) {
1937
+ let map = this.events.get(node);
1938
+ for (const type of Object.keys(oldOn)){
1939
+ if (!(type in newOn) && (map === null || map === void 0 ? void 0 : map.has(type))) {
1940
+ this.host.removeEventListener(node, type, map.get(type).wrapper);
1941
+ map.delete(type);
1942
+ }
1943
+ }
1944
+ for (const type of Object.keys(newOn)){
1945
+ const handler = newOn[type];
1946
+ const existing = map === null || map === void 0 ? void 0 : map.get(type);
1947
+ if (existing) {
1948
+ existing.handler = handler;
1949
+ continue;
1950
+ }
1951
+ if (!map) {
1952
+ map = /* @__PURE__ */ new Map();
1953
+ this.events.set(node, map);
1954
+ }
1955
+ const binding = {
1956
+ handler,
1957
+ wrapper: (event)=>binding.handler(event)
1958
+ };
1959
+ map.set(type, binding);
1960
+ this.host.addEventListener(node, type, binding.wrapper);
1961
+ }
1962
+ }
1963
+ // --- keyed children diff (snabbdom four-pointer algorithm) ---------------
1964
+ patchChildren(parent, oldCh, newCh) {
1965
+ let oldStart = 0;
1966
+ let oldEnd = oldCh.length - 1;
1967
+ let newStart = 0;
1968
+ let newEnd = newCh.length - 1;
1969
+ let keyMap = null;
1970
+ while(oldStart <= oldEnd && newStart <= newEnd){
1971
+ const os = oldCh[oldStart];
1972
+ const oe = oldCh[oldEnd];
1973
+ const ns = newCh[newStart];
1974
+ const ne = newCh[newEnd];
1975
+ if (!sameVNode(os, ns)) ;
1976
+ else {
1977
+ this.patch(os, ns);
1978
+ oldStart++;
1979
+ newStart++;
1980
+ continue;
1981
+ }
1982
+ if (sameVNode(oe, ne)) {
1983
+ this.patch(oe, ne);
1984
+ oldEnd--;
1985
+ newEnd--;
1986
+ continue;
1987
+ }
1988
+ if (sameVNode(os, ne)) {
1989
+ this.patch(os, ne);
1990
+ this.host.insert(parent, os.node, this.host.nextSibling(oe.node));
1991
+ oldStart++;
1992
+ newEnd--;
1993
+ continue;
1994
+ }
1995
+ if (sameVNode(oe, ns)) {
1996
+ this.patch(oe, ns);
1997
+ this.host.insert(parent, oe.node, os.node);
1998
+ oldEnd--;
1999
+ newStart++;
2000
+ continue;
2001
+ }
2002
+ keyMap !== null && keyMap !== void 0 ? keyMap : keyMap = buildKeyMap(oldCh, oldStart, oldEnd);
2003
+ const movedIndex = ns.key !== void 0 ? keyMap.get(ns.key) : void 0;
2004
+ if (movedIndex === void 0) {
2005
+ this.host.insert(parent, this.mount(ns), os.node);
2006
+ } else {
2007
+ const moved = oldCh[movedIndex];
2008
+ this.patch(moved, ns);
2009
+ moved.node = void 0;
2010
+ this.host.insert(parent, ns.node, os.node);
2011
+ }
2012
+ newStart++;
2013
+ }
2014
+ if (oldStart > oldEnd) {
2015
+ var _ref;
2016
+ var _newCh_;
2017
+ const anchor = (_ref = (_newCh_ = newCh[newEnd + 1]) === null || _newCh_ === void 0 ? void 0 : _newCh_.node) !== null && _ref !== void 0 ? _ref : null;
2018
+ for(let i = newStart; i <= newEnd; i++){
2019
+ this.host.insert(parent, this.mount(newCh[i]), anchor);
2020
+ }
2021
+ } else if (newStart > newEnd) {
2022
+ for(let i = oldStart; i <= oldEnd; i++){
2023
+ const node = oldCh[i].node;
2024
+ if (node) this.host.remove(parent, node);
2025
+ }
2026
+ }
2027
+ }
2028
+ constructor(host, container){
2029
+ _define_property(this, "events", /* @__PURE__ */ new WeakMap());
2030
+ _define_property(this, "prev", null);
2031
+ this.host = host;
2032
+ this.container = container;
2033
+ }
2034
+ }
2035
+ function buildKeyMap(children, start, end) {
2036
+ const map = /* @__PURE__ */ new Map();
2037
+ for(let i = start; i <= end; i++){
2038
+ const key = children[i].key;
2039
+ if (key !== void 0) map.set(key, i);
2040
+ }
2041
+ return map;
2042
+ }
2043
+ function sameVNode(a, b) {
2044
+ if (a.kind !== b.kind) return false;
2045
+ if (a.kind === "text") return true;
2046
+ return a.tag === b.tag && a.key === b.key;
2047
+ }
2048
+ function isReserved(name) {
2049
+ return name === "key" || name === "class" || name === "style" || name === "on";
2050
+ }
2051
+ function stringifyAttr(value) {
2052
+ var _JSON_stringify;
2053
+ if (typeof value === "string") return value;
2054
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
2055
+ return String(value);
2056
+ }
2057
+ return (_JSON_stringify = JSON.stringify(value)) !== null && _JSON_stringify !== void 0 ? _JSON_stringify : "";
2058
+ }
2059
+ class DomHost {
2060
+ createElement(tag) {
2061
+ return this.doc.createElement(tag);
2062
+ }
2063
+ createText(value) {
2064
+ return this.doc.createTextNode(value);
2065
+ }
2066
+ setText(node, value) {
2067
+ node.textContent = value;
2068
+ }
2069
+ insert(parent, node, anchor) {
2070
+ parent.insertBefore(node, anchor);
2071
+ }
2072
+ remove(parent, node) {
2073
+ if (node.parentNode === parent) parent.removeChild(node);
2074
+ }
2075
+ parent(node) {
2076
+ return node.parentNode;
2077
+ }
2078
+ nextSibling(node) {
2079
+ return node.nextSibling;
2080
+ }
2081
+ setAttribute(node, name, value) {
2082
+ node.setAttribute(name, value);
2083
+ }
2084
+ removeAttribute(node, name) {
2085
+ node.removeAttribute(name);
2086
+ }
2087
+ setProperty(node, name, value) {
2088
+ node[name] = value;
2089
+ }
2090
+ setStyle(node, prop, value) {
2091
+ node.style.setProperty(prop, value);
2092
+ }
2093
+ removeStyle(node, prop) {
2094
+ node.style.removeProperty(prop);
2095
+ }
2096
+ addEventListener(node, type, handler) {
2097
+ node.addEventListener(type, handler);
2098
+ }
2099
+ removeEventListener(node, type, handler) {
2100
+ node.removeEventListener(type, handler);
2101
+ }
2102
+ constructor(doc = document){
2103
+ this.doc = doc;
2104
+ }
2105
+ }
2106
+ class ToolRegistry {
2107
+ register(tool) {
2108
+ var _tool_order;
2109
+ this.tools.set(tool.id, tool);
2110
+ this.order.set(tool.id, (_tool_order = tool.order) !== null && _tool_order !== void 0 ? _tool_order : ToolRegistry.UNORDERED_BASE + this.seq++);
2111
+ return ()=>{
2112
+ if (this.tools.get(tool.id) === tool) {
2113
+ this.tools.delete(tool.id);
2114
+ this.order.delete(tool.id);
2115
+ }
2116
+ };
2117
+ }
2118
+ get(id) {
2119
+ return this.tools.get(id);
2120
+ }
2121
+ has(id) {
2122
+ return this.tools.has(id);
2123
+ }
2124
+ /** Tools sorted by their `order`, ties broken by registration sequence. */ list() {
2125
+ return [
2126
+ ...this.tools.values()
2127
+ ].sort((a, b)=>{
2128
+ var _this_order_get, _this_order_get1;
2129
+ return ((_this_order_get = this.order.get(a.id)) !== null && _this_order_get !== void 0 ? _this_order_get : 0) - ((_this_order_get1 = this.order.get(b.id)) !== null && _this_order_get1 !== void 0 ? _this_order_get1 : 0);
2130
+ });
2131
+ }
2132
+ /** The id to fall back to when the active tab is unknown. */ first() {
2133
+ return this.list()[0];
2134
+ }
2135
+ constructor(){
2136
+ _define_property(this, "tools", /* @__PURE__ */ new Map());
2137
+ _define_property(this, "seq", 0);
2138
+ _define_property(this, "order", /* @__PURE__ */ new Map());
2139
+ }
2140
+ }
2141
+ /** Unordered tools sort after every explicitly-ordered one, by arrival. */ _define_property(ToolRegistry, "UNORDERED_BASE", 1e6);
2142
+ function icon(svg) {
2143
+ return h("span", {
2144
+ class: "jie-icon",
2145
+ innerHTML: svg
2146
+ });
2147
+ }
2148
+ function spinner() {
2149
+ return h("span", {
2150
+ class: "jie-spinner"
2151
+ });
2152
+ }
2153
+ function button(props) {
2154
+ var _props_disabled, _ref, _props_title;
2155
+ const classes = [
2156
+ "jie-btn"
2157
+ ];
2158
+ if (props.variant === "primary") classes.push("jie-btn--primary");
2159
+ if (props.variant === "icon") classes.push("jie-btn--icon");
2160
+ if (props.active) classes.push("jie-btn--active");
2161
+ if (props.busy) classes.push("jie-btn--busy");
2162
+ if (props.className) classes.push(props.className);
2163
+ const children = [];
2164
+ if (props.busy) children.push(spinner());
2165
+ else if (props.icon) children.push(icon(props.icon));
2166
+ if (props.label) children.push(h("span", {}, props.label));
2167
+ return h("button", {
2168
+ key: props.key,
2169
+ class: classes.join(" "),
2170
+ type: "button",
2171
+ disabled: (_props_disabled = props.disabled) !== null && _props_disabled !== void 0 ? _props_disabled : false,
2172
+ title: (_ref = (_props_title = props.title) !== null && _props_title !== void 0 ? _props_title : props.label) !== null && _ref !== void 0 ? _ref : "",
2173
+ on: {
2174
+ click: props.onClick
2175
+ }
2176
+ }, children);
2177
+ }
2178
+ function slider(props) {
2179
+ var _props_step;
2180
+ const onStart = props.onStart;
2181
+ const on = {
2182
+ input: (e)=>props.onInput(Number(e.target.value))
2183
+ };
2184
+ if (onStart) on.pointerdown = ()=>onStart();
2185
+ const span = props.max - props.min || 1;
2186
+ const fill = Math.min(100, Math.max(0, (props.value - props.min) / span * 100));
2187
+ return h("div", {
2188
+ class: "jie-slider"
2189
+ }, [
2190
+ h("span", {
2191
+ class: "jie-slider__label"
2192
+ }, props.label),
2193
+ h("input", {
2194
+ class: "jie-range",
2195
+ type: "range",
2196
+ min: String(props.min),
2197
+ max: String(props.max),
2198
+ step: String((_props_step = props.step) !== null && _props_step !== void 0 ? _props_step : 1),
2199
+ value: String(props.value),
2200
+ style: {
2201
+ "--jie-range-fill": `${fill}%`
2202
+ },
2203
+ on
2204
+ }),
2205
+ h("span", {
2206
+ class: "jie-slider__value"
2207
+ }, String(Math.round(props.value)))
2208
+ ]);
2209
+ }
2210
+ function numberField(props) {
2211
+ return h("label", {
2212
+ class: "jie-field"
2213
+ }, [
2214
+ h("span", {}, props.label),
2215
+ h("span", {
2216
+ style: {
2217
+ display: "flex",
2218
+ "align-items": "center",
2219
+ gap: "6px"
2220
+ }
2221
+ }, [
2222
+ h("input", {
2223
+ class: "jie-input",
2224
+ type: "number",
2225
+ value: String(props.value),
2226
+ on: {
2227
+ change: (e)=>{
2228
+ const v1 = Number(e.target.value);
2229
+ if (Number.isFinite(v1)) props.onChange(v1);
2230
+ }
2231
+ }
2232
+ }),
2233
+ props.suffix ? h("span", {}, props.suffix) : null
2234
+ ])
2235
+ ]);
2236
+ }
2237
+ function segmented(options, active, onSelect) {
2238
+ return h("div", {
2239
+ class: "jie-toolrow"
2240
+ }, options.map((opt)=>button({
2241
+ key: opt.id,
2242
+ label: opt.label,
2243
+ icon: opt.icon,
2244
+ active: opt.id === active,
2245
+ onClick: ()=>onSelect(opt.id)
2246
+ })));
2247
+ }
2248
+ const svg = (body)=>`<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
2249
+ const ICONS = {
2250
+ adjust: svg('<path d="M4 8h2M9 8h11M4 16h11M18 16h2"/><circle cx="7.5" cy="8" r="1.6"/><circle cx="16.5" cy="16" r="1.6"/>'),
2251
+ finetune: svg('<path d="M4 7h12M4 12h16M4 17h8"/><circle cx="18" cy="7" r="1.6"/><circle cx="12" cy="17" r="1.6"/>'),
2252
+ filters: svg('<circle cx="9" cy="9" r="5"/><circle cx="15" cy="9" r="5"/><circle cx="12" cy="15" r="5"/>'),
2253
+ watermark: svg('<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 15l5-4 4 3 3-2 6 4"/><circle cx="8.5" cy="9.5" r="1.4"/>'),
2254
+ annotate: svg('<path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/>'),
2255
+ resize: svg('<path d="M9 3H5a2 2 0 0 0-2 2v4M21 15v4a2 2 0 0 1-2 2h-4"/><path d="M8 16l8-8M16 8h-4M16 8v4"/>'),
2256
+ crop: svg('<path d="M6 2v14a2 2 0 0 0 2 2h14"/><path d="M2 6h14a2 2 0 0 1 2 2v14"/>'),
2257
+ rotate: svg('<path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/>'),
2258
+ flipX: svg('<path d="M12 3v18"/><path d="M8 7 4 12l4 5Z"/><path d="M16 7l4 5-4 5Z"/>'),
2259
+ flipY: svg('<path d="M3 12h18"/><path d="M7 8 12 4l5 4Z"/><path d="M7 16l5 4 5-4Z"/>'),
2260
+ undo: svg('<path d="M9 7 4 12l5 5"/><path d="M4 12h11a5 5 0 0 1 0 10h-1"/>'),
2261
+ redo: svg('<path d="m15 7 5 5-5 5"/><path d="M20 12H9a5 5 0 0 0 0 10h1"/>'),
2262
+ reset: svg('<path d="M3 12a9 9 0 1 1 2.6 6.3"/><path d="M3 21v-5h5"/>'),
2263
+ trash: svg('<path d="M4 7h16M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2M6 7l1 13a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-13M10 11v6M14 11v6"/>'),
2264
+ plus: svg('<path d="M12 5v14M5 12h14"/>'),
2265
+ minus: svg('<path d="M5 12h14"/>'),
2266
+ zoomIn: svg('<circle cx="10.5" cy="10.5" r="7.5"/><path d="M21 21l-5.2-5.2M10.5 7.5v6M7.5 10.5h6"/>'),
2267
+ zoomOut: svg('<circle cx="10.5" cy="10.5" r="7.5"/><path d="M21 21l-5.2-5.2M7.5 10.5h6"/>'),
2268
+ // 3×3 position anchors (arrows point to the corner/edge)
2269
+ posTopLeft: svg('<path d="M17 17 7 7M7 13V7h6"/>'),
2270
+ posTop: svg('<path d="M12 19V6M7 11l5-5 5 5"/>'),
2271
+ posTopRight: svg('<path d="M7 17 17 7M11 7h6v6"/>'),
2272
+ posLeft: svg('<path d="M19 12H6M11 7l-5 5 5 5"/>'),
2273
+ posCenter: svg('<circle cx="12" cy="12" r="6.5"/><circle cx="12" cy="12" r="2" fill="currentColor" stroke="none"/>'),
2274
+ posRight: svg('<path d="M5 12h13M13 7l5 5-5 5"/>'),
2275
+ posBottomLeft: svg('<path d="M17 7 7 17M7 11v6h6"/>'),
2276
+ posBottom: svg('<path d="M12 5v13M7 13l5 5 5-5"/>'),
2277
+ posBottomRight: svg('<path d="M7 7l10 10M17 11v6h-6"/>'),
2278
+ font: svg('<path d="M4 20 9.5 6h1L16 20M6 15h8M17 20l3-9h.5l3 9M18 17h4"/>'),
2279
+ focus: svg('<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/>'),
2280
+ lock: svg('<rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>'),
2281
+ unlock: svg('<rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 7.5-2"/>'),
2282
+ text: svg('<path d="M5 5h14M12 5v14M9 19h6"/>'),
2283
+ brightness: svg('<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19"/>'),
2284
+ contrast: svg('<circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18Z" fill="currentColor" stroke="none"/>'),
2285
+ blur: svg('<path d="M12 3s6 6.5 6 11a6 6 0 0 1-12 0c0-4.5 6-11 6-11Z"/>'),
2286
+ book: svg('<path d="M4 5a2 2 0 0 1 2-2h5v18H6a2 2 0 0 1-2-2Z"/><path d="M20 5a2 2 0 0 0-2-2h-5v18h5a2 2 0 0 0 2-2Z"/>')
2287
+ };
2288
+ const adjustTool = {
2289
+ id: "adjust",
2290
+ label: "Adjust",
2291
+ icon: ICONS.adjust,
2292
+ order: 0,
2293
+ defaultTool: "crop",
2294
+ renderPanel ({ state, update, t }) {
2295
+ const design = selectDesign(state);
2296
+ const cropping = selectIsCropping(state);
2297
+ return h("div", {
2298
+ class: "jie-toolrow"
2299
+ }, [
2300
+ button({
2301
+ label: t("Crop"),
2302
+ icon: ICONS.crop,
2303
+ active: cropping,
2304
+ onClick: ()=>update({
2305
+ activeTab: "adjust",
2306
+ activeTool: cropping ? null : "crop"
2307
+ })
2308
+ }),
2309
+ button({
2310
+ label: t("Rotate"),
2311
+ icon: ICONS.rotate,
2312
+ onClick: ()=>update({
2313
+ design: {
2314
+ rotate: design.rotate + 90
2315
+ }
2316
+ })
2317
+ }),
2318
+ button({
2319
+ label: t("Flip X"),
2320
+ icon: ICONS.flipX,
2321
+ onClick: ()=>update({
2322
+ design: {
2323
+ flip: {
2324
+ horizontal: !design.flip.horizontal
2325
+ }
2326
+ }
2327
+ })
2328
+ }),
2329
+ button({
2330
+ label: t("Flip Y"),
2331
+ icon: ICONS.flipY,
2332
+ onClick: ()=>update({
2333
+ design: {
2334
+ flip: {
2335
+ vertical: !design.flip.vertical
2336
+ }
2337
+ }
2338
+ })
2339
+ })
2340
+ ]);
2341
+ }
2342
+ };
2343
+ const FIELDS = [
2344
+ {
2345
+ id: "brightness",
2346
+ label: "Brightness",
2347
+ icon: ICONS.brightness,
2348
+ min: -100,
2349
+ max: 100
2350
+ },
2351
+ {
2352
+ id: "contrast",
2353
+ label: "Contrast",
2354
+ icon: ICONS.contrast,
2355
+ min: -100,
2356
+ max: 100
2357
+ },
2358
+ {
2359
+ id: "saturation",
2360
+ label: "Saturation",
2361
+ icon: ICONS.filters,
2362
+ min: -100,
2363
+ max: 100
2364
+ },
2365
+ {
2366
+ id: "blur",
2367
+ label: "Blur",
2368
+ icon: ICONS.blur,
2369
+ min: 0,
2370
+ max: 100
2371
+ },
2372
+ {
2373
+ id: "warmth",
2374
+ label: "Warmth",
2375
+ icon: ICONS.brightness,
2376
+ min: -100,
2377
+ max: 100
2378
+ }
2379
+ ];
2380
+ function activeField(activeTool) {
2381
+ var _FIELDS_find;
2382
+ return (_FIELDS_find = FIELDS.find((f)=>f.id === activeTool)) !== null && _FIELDS_find !== void 0 ? _FIELDS_find : FIELDS[0];
2383
+ }
2384
+ const finetuneTool = {
2385
+ id: "finetune",
2386
+ label: "Finetune",
2387
+ icon: ICONS.finetune,
2388
+ order: 1,
2389
+ defaultTool: "brightness",
2390
+ renderPanel ({ state, update, t }) {
2391
+ const field = activeField(state.activeTool);
2392
+ const finetune = selectDesign(state).finetune;
2393
+ const value = finetune[field.id];
2394
+ const setValue = (v1, commit)=>{
2395
+ const patch = {
2396
+ [field.id]: v1
2397
+ };
2398
+ update({
2399
+ design: {
2400
+ finetune: patch
2401
+ },
2402
+ commit
2403
+ });
2404
+ };
2405
+ return h("div", {
2406
+ class: "jie-panel-stack",
2407
+ style: {
2408
+ display: "contents"
2409
+ }
2410
+ }, [
2411
+ slider({
2412
+ label: t(field.label),
2413
+ value,
2414
+ min: field.min,
2415
+ max: field.max,
2416
+ onStart: ()=>setValue(value, true),
2417
+ onInput: (v1)=>setValue(v1, false)
2418
+ }),
2419
+ segmented(FIELDS.map((f)=>({
2420
+ id: f.id,
2421
+ label: t(f.label),
2422
+ icon: f.icon
2423
+ })), field.id, (id)=>update({
2424
+ activeTab: "finetune",
2425
+ activeTool: id
2426
+ }))
2427
+ ]);
2428
+ }
2429
+ };
2430
+ const filtersTool = {
2431
+ id: "filters",
2432
+ label: "Filters",
2433
+ icon: ICONS.filters,
2434
+ order: 2,
2435
+ renderPanel ({ state, update, t }) {
2436
+ const active = selectDesign(state).filter;
2437
+ return h("div", {
2438
+ class: "jie-thumbs"
2439
+ }, listFilters().map((f)=>h("button", {
2440
+ key: f.id,
2441
+ class: "jie-thumb",
2442
+ type: "button",
2443
+ "aria-pressed": String(f.id === active),
2444
+ on: {
2445
+ click: ()=>update({
2446
+ design: {
2447
+ filter: f.id
2448
+ }
2449
+ })
2450
+ }
2451
+ }, [
2452
+ // The editor paints a live filtered preview onto this canvas after
2453
+ // render (see ImageEditor#paintFilterThumbs).
2454
+ h("canvas", {
2455
+ class: "jie-thumb__swatch",
2456
+ "data-jie-filter-thumb": f.id
2457
+ }),
2458
+ h("span", {}, t(f.label))
2459
+ ])));
2460
+ }
2461
+ };
2462
+ function createDefaultFocus() {
2463
+ return {
2464
+ shape: "radial",
2465
+ x: 0.5,
2466
+ y: 0.5,
2467
+ radius: 0.28,
2468
+ angle: 0,
2469
+ amount: 60
2470
+ };
2471
+ }
2472
+ function selectiveBlur(img, focus) {
2473
+ if (focus.amount <= 0) return createRaster(img.width, img.height, new Uint8ClampedArray(img.data));
2474
+ const blurred = blur(img, focus.amount, 32);
2475
+ const { width: w, height: h } = img;
2476
+ const out = createRaster(w, h);
2477
+ const minDim = Math.min(w, h);
2478
+ const cx = focus.x * w;
2479
+ const cy = focus.y * h;
2480
+ const sharpR = focus.radius * minDim;
2481
+ const feather = Math.max(minDim * 0.06, sharpR * 0.9);
2482
+ const rad = focus.angle * Math.PI / 180;
2483
+ const nx = -Math.sin(rad);
2484
+ const ny = Math.cos(rad);
2485
+ for(let y = 0; y < h; y++){
2486
+ for(let x = 0; x < w; x++){
2487
+ const dx = x - cx;
2488
+ const dy = y - cy;
2489
+ const dist = focus.shape === "radial" ? Math.hypot(dx, dy) : Math.abs(dx * nx + dy * ny);
2490
+ const t = smoothstep(sharpR, sharpR + feather, dist);
2491
+ const i = indexOf(out, x, y);
2492
+ for(let c = 0; c < CHANNELS; c++){
2493
+ out.data[i + c] = img.data[i + c] + (blurred.data[i + c] - img.data[i + c]) * t;
2494
+ }
2495
+ }
2496
+ }
2497
+ return out;
2498
+ }
2499
+ function smoothstep(e0, e1, v1) {
2500
+ if (e1 <= e0) return v1 >= e1 ? 1 : 0;
2501
+ const t = Math.min(1, Math.max(0, (v1 - e0) / (e1 - e0)));
2502
+ return t * t * (3 - 2 * t);
2503
+ }
2504
+ const focusTool = {
2505
+ id: "focus",
2506
+ label: "Focus",
2507
+ icon: ICONS.focus,
2508
+ order: 3,
2509
+ defaultTool: "radial",
2510
+ renderPanel ({ state, update, t }) {
2511
+ var _selectDesign_focus;
2512
+ const focus = (_selectDesign_focus = selectDesign(state).focus) !== null && _selectDesign_focus !== void 0 ? _selectDesign_focus : createDefaultFocus();
2513
+ const enabled = selectDesign(state).focus !== null;
2514
+ const set = (patch, commit)=>update({
2515
+ design: {
2516
+ focus: _object_spread({}, focus, patch)
2517
+ },
2518
+ commit
2519
+ });
2520
+ return h("div", {
2521
+ class: "jie-panel-stack",
2522
+ style: {
2523
+ display: "contents"
2524
+ }
2525
+ }, [
2526
+ h("div", {
2527
+ class: "jie-toolrow"
2528
+ }, [
2529
+ slider({
2530
+ label: t("Intensity"),
2531
+ value: focus.amount,
2532
+ min: 0,
2533
+ max: 100,
2534
+ onStart: ()=>set({
2535
+ amount: focus.amount
2536
+ }, true),
2537
+ onInput: (v1)=>set({
2538
+ amount: v1
2539
+ }, false)
2540
+ }),
2541
+ slider({
2542
+ label: t("Radius"),
2543
+ value: Math.round(focus.radius * 100),
2544
+ min: 5,
2545
+ max: 90,
2546
+ onStart: ()=>set({
2547
+ radius: focus.radius
2548
+ }, true),
2549
+ onInput: (v1)=>set({
2550
+ radius: v1 / 100
2551
+ }, false)
2552
+ }),
2553
+ enabled ? button({
2554
+ variant: "icon",
2555
+ icon: ICONS.trash,
2556
+ title: t("Reset"),
2557
+ onClick: ()=>update({
2558
+ design: {
2559
+ focus: null
2560
+ }
2561
+ })
2562
+ }) : null
2563
+ ]),
2564
+ segmented([
2565
+ {
2566
+ id: "radial",
2567
+ label: t("Radial")
2568
+ },
2569
+ {
2570
+ id: "linear",
2571
+ label: t("Linear")
2572
+ }
2573
+ ], focus.shape, (id)=>set({
2574
+ shape: id
2575
+ }, true))
2576
+ ]);
2577
+ }
2578
+ };
2579
+ function colorPicker(props) {
2580
+ var _props_title;
2581
+ const children = [
2582
+ h("button", {
2583
+ class: "jie-color",
2584
+ type: "button",
2585
+ title: (_props_title = props.title) !== null && _props_title !== void 0 ? _props_title : "Colour",
2586
+ style: {
2587
+ background: props.value
2588
+ },
2589
+ "aria-haspopup": "true",
2590
+ "aria-expanded": String(props.open),
2591
+ on: {
2592
+ click: ()=>props.onToggle()
2593
+ }
2594
+ })
2595
+ ];
2596
+ if (props.open) {
2597
+ children.push(h("div", {
2598
+ class: "jie-popover__backdrop",
2599
+ on: {
2600
+ click: ()=>props.onToggle()
2601
+ }
2602
+ }), h("div", {
2603
+ class: "jie-popover jie-swatches",
2604
+ role: "listbox"
2605
+ }, props.palette.map((color)=>h("button", {
2606
+ key: color,
2607
+ class: "jie-swatch",
2608
+ type: "button",
2609
+ title: color,
2610
+ style: {
2611
+ background: color
2612
+ },
2613
+ "aria-pressed": String(color.toLowerCase() === props.value.toLowerCase()),
2614
+ on: {
2615
+ click: ()=>props.onPick(color)
2616
+ }
2617
+ }))));
2618
+ }
2619
+ return h("div", {
2620
+ class: "jie-colorpicker"
2621
+ }, children);
2622
+ }
2623
+ function fontSelect(props) {
2624
+ var _props_title, _ref;
2625
+ const current = props.fonts.find((f)=>f.value === props.value);
2626
+ const children = [
2627
+ h("button", {
2628
+ class: "jie-fontselect__trigger",
2629
+ type: "button",
2630
+ title: (_props_title = props.title) !== null && _props_title !== void 0 ? _props_title : "Font",
2631
+ "aria-haspopup": "listbox",
2632
+ "aria-expanded": String(props.open),
2633
+ on: {
2634
+ click: ()=>props.onToggle()
2635
+ }
2636
+ }, [
2637
+ icon(ICONS.font),
2638
+ h("span", {
2639
+ style: {
2640
+ "font-family": props.value
2641
+ }
2642
+ }, (_ref = current === null || current === void 0 ? void 0 : current.label) !== null && _ref !== void 0 ? _ref : "Font"),
2643
+ icon(ICONS.book)
2644
+ ])
2645
+ ];
2646
+ if (props.open) {
2647
+ children.push(h("div", {
2648
+ class: "jie-popover__backdrop",
2649
+ on: {
2650
+ click: ()=>props.onToggle()
2651
+ }
2652
+ }), h("div", {
2653
+ class: "jie-popover jie-fontlist",
2654
+ role: "listbox"
2655
+ }, props.fonts.map((f)=>h("button", {
2656
+ key: f.value,
2657
+ class: "jie-fontlist__item",
2658
+ type: "button",
2659
+ "aria-pressed": String(f.value === props.value),
2660
+ style: {
2661
+ "font-family": f.value
2662
+ },
2663
+ on: {
2664
+ click: ()=>props.onPick(f.value)
2665
+ }
2666
+ }, f.label))));
2667
+ }
2668
+ return h("div", {
2669
+ class: "jie-fontselect"
2670
+ }, children);
2671
+ }
2672
+ const CELLS = [
2673
+ {
2674
+ align: "left",
2675
+ valign: "top",
2676
+ icon: ICONS.posTopLeft
2677
+ },
2678
+ {
2679
+ align: "center",
2680
+ valign: "top",
2681
+ icon: ICONS.posTop
2682
+ },
2683
+ {
2684
+ align: "right",
2685
+ valign: "top",
2686
+ icon: ICONS.posTopRight
2687
+ },
2688
+ {
2689
+ align: "left",
2690
+ valign: "middle",
2691
+ icon: ICONS.posLeft
2692
+ },
2693
+ {
2694
+ align: "center",
2695
+ valign: "middle",
2696
+ icon: ICONS.posCenter
2697
+ },
2698
+ {
2699
+ align: "right",
2700
+ valign: "middle",
2701
+ icon: ICONS.posRight
2702
+ },
2703
+ {
2704
+ align: "left",
2705
+ valign: "bottom",
2706
+ icon: ICONS.posBottomLeft
2707
+ },
2708
+ {
2709
+ align: "center",
2710
+ valign: "bottom",
2711
+ icon: ICONS.posBottom
2712
+ },
2713
+ {
2714
+ align: "right",
2715
+ valign: "bottom",
2716
+ icon: ICONS.posBottomRight
2717
+ }
2718
+ ];
2719
+ function positionGrid(props) {
2720
+ return h("div", {
2721
+ class: "jie-posgrid",
2722
+ role: "group"
2723
+ }, CELLS.map((cell)=>h("button", {
2724
+ key: `${cell.valign}-${cell.align}`,
2725
+ class: "jie-posgrid__cell",
2726
+ type: "button",
2727
+ "data-pos": `${cell.valign}-${cell.align}`,
2728
+ "aria-pressed": String(cell.align === props.align && cell.valign === props.valign),
2729
+ on: {
2730
+ click: ()=>props.onPick(cell.align, cell.valign)
2731
+ }
2732
+ }, [
2733
+ icon(cell.icon)
2734
+ ])));
2735
+ }
2736
+ function createIdFactory(prefix = "id") {
2737
+ let n = 0;
2738
+ return ()=>`${prefix}-${++n}`;
2739
+ }
2740
+ const uid = createIdFactory("jie");
2741
+ function createTextAnnotation(id, overrides = {}) {
2742
+ return _object_spread({
2743
+ id,
2744
+ type: "text",
2745
+ text: "Text",
2746
+ x: 0.5,
2747
+ y: 0.5,
2748
+ fontSize: 0.06,
2749
+ fontFamily: DEFAULT_FONTS[0].value,
2750
+ color: "#eb5757",
2751
+ bold: false,
2752
+ italic: false,
2753
+ align: "center",
2754
+ valign: "middle"
2755
+ }, overrides);
2756
+ }
2757
+ function addAnnotation(list, ann) {
2758
+ return [
2759
+ ...list,
2760
+ ann
2761
+ ];
2762
+ }
2763
+ function removeAnnotation(list, id) {
2764
+ return list.filter((a)=>a.id !== id);
2765
+ }
2766
+ function findAnnotation(list, id) {
2767
+ if (id === null) return void 0;
2768
+ return list.find((a)=>a.id === id);
2769
+ }
2770
+ function updateAnnotation(list, id, patch) {
2771
+ return list.map((a)=>a.id === id ? _object_spread({}, a, patch) : a);
2772
+ }
2773
+ const COLOR_POPOVER = "text-color";
2774
+ const FONT_POPOVER = "text-font";
2775
+ const PAD = 0.04;
2776
+ function anchorXY(align, valign) {
2777
+ return {
2778
+ x: align === "left" ? PAD : align === "right" ? 1 - PAD : 0.5,
2779
+ y: valign === "top" ? PAD : valign === "bottom" ? 1 - PAD : 0.5
2780
+ };
2781
+ }
2782
+ const annotateTool = {
2783
+ id: "annotate",
2784
+ label: "Text",
2785
+ icon: ICONS.text,
2786
+ order: 4,
2787
+ defaultTool: "text",
2788
+ renderPanel ({ state, update, t }) {
2789
+ const design = selectDesign(state);
2790
+ const text = design.annotations[0];
2791
+ if (!text) {
2792
+ const addText = ()=>{
2793
+ const ann = createTextAnnotation(uid());
2794
+ update({
2795
+ design: {
2796
+ annotations: [
2797
+ ann
2798
+ ]
2799
+ },
2800
+ selectedAnnotationId: ann.id
2801
+ });
2802
+ };
2803
+ return h("div", {
2804
+ class: "jie-toolrow"
2805
+ }, [
2806
+ button({
2807
+ label: t("Add text"),
2808
+ icon: ICONS.text,
2809
+ variant: "primary",
2810
+ onClick: addText
2811
+ })
2812
+ ]);
2813
+ }
2814
+ const patch = (p)=>update({
2815
+ design: {
2816
+ annotations: updateAnnotation(design.annotations, text.id, p)
2817
+ }
2818
+ });
2819
+ return h("div", {
2820
+ class: "jie-toolrow"
2821
+ }, [
2822
+ h("input", {
2823
+ class: "jie-input",
2824
+ type: "text",
2825
+ value: text.text,
2826
+ on: {
2827
+ input: (e)=>patch({
2828
+ text: e.target.value
2829
+ })
2830
+ }
2831
+ }),
2832
+ fontSelect({
2833
+ value: text.fontFamily,
2834
+ fonts: state.fonts,
2835
+ title: t("Font"),
2836
+ open: state.activePopover === FONT_POPOVER,
2837
+ onToggle: ()=>update({
2838
+ activePopover: state.activePopover === FONT_POPOVER ? null : FONT_POPOVER
2839
+ }),
2840
+ onPick: (fontFamily)=>{
2841
+ patch({
2842
+ fontFamily
2843
+ });
2844
+ update({
2845
+ activePopover: null
2846
+ });
2847
+ }
2848
+ }),
2849
+ colorPicker({
2850
+ value: text.color,
2851
+ palette: state.palette,
2852
+ title: t("Colour"),
2853
+ open: state.activePopover === COLOR_POPOVER,
2854
+ onToggle: ()=>update({
2855
+ activePopover: state.activePopover === COLOR_POPOVER ? null : COLOR_POPOVER
2856
+ }),
2857
+ onPick: (color)=>{
2858
+ patch({
2859
+ color
2860
+ });
2861
+ update({
2862
+ activePopover: null
2863
+ });
2864
+ }
2865
+ }),
2866
+ button({
2867
+ label: "B",
2868
+ className: "jie-btn--bold",
2869
+ active: text.bold,
2870
+ onClick: ()=>patch({
2871
+ bold: !text.bold
2872
+ })
2873
+ }),
2874
+ button({
2875
+ label: "I",
2876
+ className: "jie-btn--italic",
2877
+ active: text.italic,
2878
+ onClick: ()=>patch({
2879
+ italic: !text.italic
2880
+ })
2881
+ }),
2882
+ slider({
2883
+ label: t("Size"),
2884
+ value: Math.round(text.fontSize * 100),
2885
+ min: 1,
2886
+ max: 40,
2887
+ onInput: (v1)=>patch({
2888
+ fontSize: v1 / 100
2889
+ })
2890
+ }),
2891
+ positionGrid({
2892
+ align: text.align,
2893
+ valign: text.valign,
2894
+ onPick: (align, valign)=>patch(_object_spread({
2895
+ align,
2896
+ valign
2897
+ }, anchorXY(align, valign)))
2898
+ }),
2899
+ button({
2900
+ variant: "icon",
2901
+ icon: ICONS.trash,
2902
+ title: t("Delete"),
2903
+ onClick: ()=>update({
2904
+ design: {
2905
+ annotations: removeAnnotation(design.annotations, text.id)
2906
+ },
2907
+ selectedAnnotationId: null
2908
+ })
2909
+ })
2910
+ ]);
2911
+ }
2912
+ };
2913
+ const resizeTool = {
2914
+ id: "resize",
2915
+ label: "Resize",
2916
+ icon: ICONS.resize,
2917
+ order: 5,
2918
+ renderPanel ({ state, update, t }) {
2919
+ const size = selectOutputSize(state);
2920
+ if (!size) return null;
2921
+ const locked = state.activeTool !== "unlocked";
2922
+ const apply = (next)=>update({
2923
+ design: {
2924
+ resize: lockedResize(size, next, locked, state.minResizeSize)
2925
+ }
2926
+ });
2927
+ return h("div", {
2928
+ class: "jie-fieldrow"
2929
+ }, [
2930
+ numberField({
2931
+ label: t("Width"),
2932
+ value: size.width,
2933
+ suffix: t("px"),
2934
+ onChange: (w)=>apply({
2935
+ width: w
2936
+ })
2937
+ }),
2938
+ button({
2939
+ variant: "icon",
2940
+ icon: locked ? ICONS.lock : ICONS.unlock,
2941
+ title: locked ? t("Aspect ratio locked") : t("Aspect ratio unlocked"),
2942
+ onClick: ()=>update({
2943
+ activeTab: "resize",
2944
+ activeTool: locked ? "unlocked" : "locked"
2945
+ })
2946
+ }),
2947
+ numberField({
2948
+ label: t("Height"),
2949
+ value: size.height,
2950
+ suffix: t("px"),
2951
+ onChange: (hgt)=>apply({
2952
+ height: hgt
2953
+ })
2954
+ }),
2955
+ button({
2956
+ variant: "icon",
2957
+ icon: ICONS.reset,
2958
+ title: t("Reset size"),
2959
+ onClick: ()=>update({
2960
+ design: {
2961
+ resize: null
2962
+ }
2963
+ })
2964
+ })
2965
+ ]);
2966
+ }
2967
+ };
2968
+ function createBuiltinTools() {
2969
+ return [
2970
+ adjustTool,
2971
+ finetuneTool,
2972
+ filtersTool,
2973
+ focusTool,
2974
+ annotateTool,
2975
+ resizeTool
2976
+ ];
2977
+ }
2978
+ function execute(source, design) {
2979
+ let img = source;
2980
+ if (design.flip.horizontal) img = flip(img, "horizontal");
2981
+ if (design.flip.vertical) img = flip(img, "vertical");
2982
+ if (design.rotate !== 0) img = rotate90(img, design.rotate);
2983
+ if (design.angle !== 0) {
2984
+ var _design_crop;
2985
+ const rect = (_design_crop = design.crop) !== null && _design_crop !== void 0 ? _design_crop : {
2986
+ x: 0,
2987
+ y: 0,
2988
+ width: img.width,
2989
+ height: img.height
2990
+ };
2991
+ img = sampleRotatedRect(img, rect, design.angle);
2992
+ } else if (design.crop) {
2993
+ img = crop(img, design.crop);
2994
+ }
2995
+ if (design.resize) img = resize(img, design.resize);
2996
+ img = applyFinetune(img, design.finetune);
2997
+ img = applyFilter(img, design.filter);
2998
+ if (design.focus) img = selectiveBlur(img, design.focus);
2999
+ return img;
3000
+ }
3001
+ function isIdentity(design) {
3002
+ return !design.flip.horizontal && !design.flip.vertical && design.rotate === 0 && design.angle === 0 && design.crop === null && design.resize === null && design.filter === "original" && design.focus === null && design.annotations.length === 0 && design.finetune.brightness === 0 && design.finetune.contrast === 0 && design.finetune.saturation === 0 && design.finetune.warmth === 0 && design.finetune.blur === 0;
3003
+ }
3004
+ function flattenOnBackground(img, [br, bg, bb]) {
3005
+ const out = createRaster(img.width, img.height);
3006
+ for(let i = 0; i < img.data.length; i += CHANNELS){
3007
+ const a = img.data[i + 3] / 255;
3008
+ const ia = 1 - a;
3009
+ out.data[i] = img.data[i] * a + br * ia;
3010
+ out.data[i + 1] = img.data[i + 1] * a + bg * ia;
3011
+ out.data[i + 2] = img.data[i + 2] * a + bb * ia;
3012
+ out.data[i + 3] = 255;
3013
+ }
3014
+ return out;
3015
+ }
3016
+ function parseHexColor(css) {
3017
+ const hex = css.trim().replace(/^#/, "");
3018
+ if (/^[0-9a-f]{3}$/i.test(hex)) {
3019
+ return [
3020
+ expand(hex[0]),
3021
+ expand(hex[1]),
3022
+ expand(hex[2])
3023
+ ];
3024
+ }
3025
+ if (/^[0-9a-f]{6}$/i.test(hex)) {
3026
+ return [
3027
+ byte(hex, 0),
3028
+ byte(hex, 2),
3029
+ byte(hex, 4)
3030
+ ];
3031
+ }
3032
+ return [
3033
+ 255,
3034
+ 255,
3035
+ 255
3036
+ ];
3037
+ }
3038
+ function expand(nibble) {
3039
+ return parseInt(nibble + nibble, 16);
3040
+ }
3041
+ function byte(hex, at) {
3042
+ return parseInt(hex.slice(at, at + 2), 16);
3043
+ }
3044
+ const ALPHALESS = /image\/(jpe?g|bmp)/i;
3045
+ function defaultCanvasFactory(width, height) {
3046
+ if (typeof document === "undefined") {
3047
+ throw new Error("No DOM available: provide a CanvasFactory to the codec");
3048
+ }
3049
+ const canvas = document.createElement("canvas");
3050
+ canvas.width = width;
3051
+ canvas.height = height;
3052
+ return {
3053
+ get width () {
3054
+ return canvas.width;
3055
+ },
3056
+ set width (v){
3057
+ canvas.width = v;
3058
+ },
3059
+ get height () {
3060
+ return canvas.height;
3061
+ },
3062
+ set height (v){
3063
+ canvas.height = v;
3064
+ },
3065
+ getContext: (t)=>canvas.getContext(t),
3066
+ toBlobAsync: (type, quality)=>new Promise((resolve, reject)=>{
3067
+ canvas.toBlob((blob)=>blob ? resolve(blob) : reject(new Error("canvas.toBlob returned null")), type, quality);
3068
+ })
3069
+ };
3070
+ }
3071
+ class CanvasImageCodec {
3072
+ decode(blob) {
3073
+ return _async_to_generator(function*() {
3074
+ const bitmap = yield this.toBitmap(blob);
3075
+ const scale = fitWithinLimits(bitmap.width, bitmap.height, {
3076
+ maxSize: this.maxSize,
3077
+ maxPixels: this.maxPixels
3078
+ });
3079
+ const w = Math.max(1, Math.round(bitmap.width * scale));
3080
+ const h = Math.max(1, Math.round(bitmap.height * scale));
3081
+ const canvas = this.createCanvas(w, h);
3082
+ const ctx = this.context(canvas);
3083
+ ctx.imageSmoothingEnabled = true;
3084
+ ctx.imageSmoothingQuality = "high";
3085
+ ctx.drawImage(bitmap, 0, 0, w, h);
3086
+ if ("close" in bitmap && typeof bitmap.close === "function") bitmap.close();
3087
+ const imageData = ctx.getImageData(0, 0, w, h);
3088
+ return createRaster(imageData.width, imageData.height, imageData.data);
3089
+ }).call(this);
3090
+ }
3091
+ encode(_0) {
3092
+ return _async_to_generator(function*(raster, options = {}) {
3093
+ const { type = "image/png", quality = 0.92, background } = options;
3094
+ const flat = background !== void 0 || ALPHALESS.test(type) ? flattenOnBackground(raster, parseHexColor(background !== null && background !== void 0 ? background : "#ffffff")) : raster;
3095
+ const canvas = this.createCanvas(flat.width, flat.height);
3096
+ const ctx = this.context(canvas);
3097
+ ctx.putImageData(toImageData(flat), 0, 0);
3098
+ return canvas.toBlobAsync(type, quality);
3099
+ }).apply(this, arguments);
3100
+ }
3101
+ context(canvas) {
3102
+ const ctx = canvas.getContext("2d");
3103
+ if (!ctx) throw new Error("Unable to acquire a 2D canvas context");
3104
+ return ctx;
3105
+ }
3106
+ toBitmap(blob) {
3107
+ return _async_to_generator(function*() {
3108
+ if (typeof createImageBitmap === "function") {
3109
+ return createImageBitmap(blob, {
3110
+ imageOrientation: "from-image"
3111
+ });
3112
+ }
3113
+ return new Promise((resolve, reject)=>{
3114
+ const url = URL.createObjectURL(blob);
3115
+ const img = new Image();
3116
+ img.style.imageOrientation = "from-image";
3117
+ img.onload = ()=>{
3118
+ URL.revokeObjectURL(url);
3119
+ resolve(img);
3120
+ };
3121
+ img.onerror = ()=>{
3122
+ URL.revokeObjectURL(url);
3123
+ reject(new Error("Failed to decode image blob"));
3124
+ };
3125
+ img.src = url;
3126
+ });
3127
+ })();
3128
+ }
3129
+ constructor(createCanvas = defaultCanvasFactory, limits = {}){
3130
+ var _limits_maxPixels;
3131
+ _define_property(this, "maxPixels", void 0);
3132
+ _define_property(this, "maxSize", void 0);
3133
+ this.createCanvas = createCanvas;
3134
+ this.maxPixels = (_limits_maxPixels = limits.maxPixels) !== null && _limits_maxPixels !== void 0 ? _limits_maxPixels : 16777216;
3135
+ this.maxSize = limits.maxSize;
3136
+ }
3137
+ }
3138
+ function resolveText(ann, size) {
3139
+ const fontPx = Math.max(1, Math.round(ann.fontSize * size.height));
3140
+ const weight = ann.bold ? "bold" : "normal";
3141
+ const slant = ann.italic ? "italic" : "normal";
3142
+ return {
3143
+ text: ann.text,
3144
+ x: Math.round(ann.x * size.width),
3145
+ y: Math.round(ann.y * size.height),
3146
+ fontPx,
3147
+ font: `${slant} ${weight} ${fontPx}px ${ann.fontFamily}`,
3148
+ color: ann.color,
3149
+ align: ann.align,
3150
+ baseline: ann.valign
3151
+ };
3152
+ }
3153
+ function isText(ann) {
3154
+ return ann.type === "text";
3155
+ }
3156
+ function compositeAnnotations(createCanvas, raster, annotations) {
3157
+ if (annotations.length === 0) return raster;
3158
+ const canvas = createCanvas(raster.width, raster.height);
3159
+ const ctx = canvas.getContext("2d");
3160
+ if (!ctx) throw new Error("Unable to acquire a 2D canvas context");
3161
+ ctx.putImageData(toImageData(raster), 0, 0);
3162
+ for (const ann of annotations){
3163
+ if (!isText(ann)) continue;
3164
+ const r = resolveText(ann, raster);
3165
+ ctx.font = r.font;
3166
+ ctx.fillStyle = r.color;
3167
+ ctx.textAlign = r.align;
3168
+ ctx.textBaseline = r.baseline;
3169
+ drawWrapped(ctx, r);
3170
+ }
3171
+ const out = ctx.getImageData(0, 0, raster.width, raster.height);
3172
+ return createRaster(out.width, out.height, out.data);
3173
+ }
3174
+ function drawWrapped(ctx, r) {
3175
+ const lines = r.text.split("\n");
3176
+ const lineHeight = r.fontPx * 1.2;
3177
+ const span = (lines.length - 1) * lineHeight;
3178
+ const startY = r.baseline === "middle" ? r.y - span / 2 : r.baseline === "bottom" ? r.y - span : r.y;
3179
+ lines.forEach((line, i)=>ctx.fillText(line, r.x, startY + i * lineHeight));
3180
+ }
3181
+ class ImageProcessor {
3182
+ /** Decode a blob into the editor's working raster. */ decode(blob) {
3183
+ return this.codec.decode(blob);
3184
+ }
3185
+ /** Apply a full design: pure pipeline first, then annotation compositing. */ render(source, design) {
3186
+ const pixels = execute(source, design);
3187
+ return compositeAnnotations(this.createCanvas, pixels, design.annotations);
3188
+ }
3189
+ /** Render a design and encode the result to a blob. */ toBlob(source, design, options) {
3190
+ return _async_to_generator(function*() {
3191
+ return this.codec.encode(this.render(source, design), options);
3192
+ }).call(this);
3193
+ }
3194
+ constructor(deps){
3195
+ _define_property(this, "codec", void 0);
3196
+ _define_property(this, "createCanvas", void 0);
3197
+ this.codec = deps.codec;
3198
+ this.createCanvas = deps.createCanvas;
3199
+ }
3200
+ }
3201
+ function createDefaultProcessor(createCanvas = defaultCanvasFactory, limits) {
3202
+ return new ImageProcessor({
3203
+ codec: new CanvasImageCodec(createCanvas, limits),
3204
+ createCanvas
3205
+ });
3206
+ }
3207
+ function renderApp(state, ctx) {
3208
+ return h("div", {
3209
+ class: "jie",
3210
+ "data-theme": state.theme
3211
+ }, [
3212
+ state.showToolbar ? renderTopBar(state, ctx) : null,
3213
+ h("div", {
3214
+ class: "jie-body"
3215
+ }, [
3216
+ renderRail(state, ctx),
3217
+ renderStage(state, ctx)
3218
+ ])
3219
+ ]);
3220
+ }
3221
+ function renderTopBar(state, ctx) {
3222
+ const size = selectOutputSize(state);
3223
+ const fit = selectViewportFit(state);
3224
+ const percent = fit ? Math.round(fit.scale * 100) : Math.round(state.zoom * 100);
3225
+ const t = ctx.t;
3226
+ const busy = state.status === "loading" || state.status === "exporting";
3227
+ const spinning = state.status === "exporting" && state.busyVisible;
3228
+ return h("header", {
3229
+ class: "jie-topbar"
3230
+ }, [
3231
+ h("div", {
3232
+ class: "jie-topbar__save"
3233
+ }, [
3234
+ button({
3235
+ label: t("Save"),
3236
+ variant: "primary",
3237
+ onClick: ()=>ctx.onSave(),
3238
+ disabled: !state.source || busy,
3239
+ busy: spinning
3240
+ }),
3241
+ button({
3242
+ label: t("Save as"),
3243
+ onClick: ()=>ctx.onSaveAs(),
3244
+ disabled: !state.source || busy,
3245
+ busy: spinning
3246
+ })
3247
+ ]),
3248
+ h("div", {
3249
+ class: "jie-topbar__meta"
3250
+ }, [
3251
+ size ? h("span", {}, `${size.width} x ${size.height} px`) : null,
3252
+ icon(ICONS.book),
3253
+ h("div", {
3254
+ class: "jie-zoom"
3255
+ }, [
3256
+ button({
3257
+ variant: "icon",
3258
+ icon: ICONS.zoomOut,
3259
+ title: t("Zoom out"),
3260
+ onClick: ()=>zoom(ctx, state, 1 / 1.2)
3261
+ }),
3262
+ h("span", {}, `${percent}%`),
3263
+ button({
3264
+ variant: "icon",
3265
+ icon: ICONS.zoomIn,
3266
+ title: t("Zoom in"),
3267
+ onClick: ()=>zoom(ctx, state, 1.2)
3268
+ })
3269
+ ])
3270
+ ]),
3271
+ h("div", {
3272
+ class: "jie-topbar__actions"
3273
+ }, [
3274
+ button({
3275
+ variant: "icon",
3276
+ icon: ICONS.reset,
3277
+ title: t("Reset"),
3278
+ disabled: !selectIsDirty(state),
3279
+ onClick: ()=>ctx.onReset()
3280
+ }),
3281
+ button({
3282
+ variant: "icon",
3283
+ icon: ICONS.undo,
3284
+ title: t("Undo"),
3285
+ disabled: !selectCanUndo(state),
3286
+ onClick: ()=>ctx.update({
3287
+ history: {
3288
+ step: -1
3289
+ }
3290
+ })
3291
+ }),
3292
+ button({
3293
+ variant: "icon",
3294
+ icon: ICONS.redo,
3295
+ title: t("Redo"),
3296
+ disabled: !selectCanRedo(state),
3297
+ onClick: ()=>ctx.update({
3298
+ history: {
3299
+ step: 1
3300
+ }
3301
+ })
3302
+ })
3303
+ ])
3304
+ ]);
3305
+ }
3306
+ function renderRail(state, ctx) {
3307
+ return h("nav", {
3308
+ class: "jie-rail"
3309
+ }, ctx.tools.map((tool)=>h("button", {
3310
+ key: tool.id,
3311
+ class: "jie-tab",
3312
+ type: "button",
3313
+ "aria-selected": String(state.activeTab === tool.id),
3314
+ on: {
3315
+ click: ()=>{
3316
+ var _tool_defaultTool;
3317
+ return ctx.update({
3318
+ activeTab: tool.id,
3319
+ activeTool: (_tool_defaultTool = tool.defaultTool) !== null && _tool_defaultTool !== void 0 ? _tool_defaultTool : null
3320
+ });
3321
+ }
3322
+ }
3323
+ }, [
3324
+ icon(tool.icon),
3325
+ h("span", {}, ctx.t(tool.label))
3326
+ ])));
3327
+ }
3328
+ function renderStage(state, ctx) {
3329
+ var _ctx_tools_find, _ref;
3330
+ const active = (_ctx_tools_find = ctx.tools.find((tool)=>tool.id === state.activeTab)) !== null && _ctx_tools_find !== void 0 ? _ctx_tools_find : ctx.tools[0];
3331
+ const panel = (_ref = active === null || active === void 0 ? void 0 : active.renderPanel({
3332
+ state,
3333
+ update: ctx.update,
3334
+ t: ctx.t
3335
+ })) !== null && _ref !== void 0 ? _ref : null;
3336
+ return h("div", {
3337
+ class: "jie-stage"
3338
+ }, [
3339
+ h("div", {
3340
+ class: "jie-canvas-wrap",
3341
+ "data-jie-canvas-wrap": ""
3342
+ }, [
3343
+ // The editor paints pixels onto this canvas imperatively after each render.
3344
+ h("canvas", {
3345
+ class: "jie-canvas",
3346
+ "data-jie-canvas": ""
3347
+ }),
3348
+ renderCropOverlay(state, ctx),
3349
+ renderStatusOverlay(state, ctx)
3350
+ ]),
3351
+ panel ? h("div", {
3352
+ class: "jie-panel"
3353
+ }, [
3354
+ panel
3355
+ ]) : null
3356
+ ]);
3357
+ }
3358
+ function renderStatusOverlay(state, ctx) {
3359
+ if (state.status === "loading") {
3360
+ return h("div", {
3361
+ class: "jie-overlay",
3362
+ "data-jie-loading": ""
3363
+ }, [
3364
+ spinner()
3365
+ ]);
3366
+ }
3367
+ if (state.status === "error" && state.error) {
3368
+ return h("div", {
3369
+ class: "jie-overlay jie-overlay--error",
3370
+ "data-jie-error": ""
3371
+ }, [
3372
+ h("span", {}, ctx.t("Failed to load the image")),
3373
+ h("span", {
3374
+ class: "jie-overlay__detail"
3375
+ }, state.error)
3376
+ ]);
3377
+ }
3378
+ return null;
3379
+ }
3380
+ function renderCropOverlay(state, ctx) {
3381
+ if (!selectIsCropping(state)) return null;
3382
+ const fit = selectViewportFit(state);
3383
+ const crop = selectEffectiveCrop(state);
3384
+ if (!fit || !crop) return null;
3385
+ const angle = selectCropAngle(state);
3386
+ const style = {
3387
+ left: `${fit.offsetX + crop.x * fit.scale}px`,
3388
+ top: `${fit.offsetY + crop.y * fit.scale}px`,
3389
+ width: `${crop.width * fit.scale}px`,
3390
+ height: `${crop.height * fit.scale}px`,
3391
+ transform: `rotate(${angle}deg)`
3392
+ };
3393
+ const handles = [
3394
+ "nw",
3395
+ "ne",
3396
+ "sw",
3397
+ "se"
3398
+ ];
3399
+ return h("div", {
3400
+ class: "jie-crop",
3401
+ style,
3402
+ on: {
3403
+ pointerdown: (e)=>ctx.beginCropDrag("move", e)
3404
+ }
3405
+ }, [
3406
+ // rotation knob, centred above the frame
3407
+ h("div", {
3408
+ key: "rotate",
3409
+ class: "jie-crop__rotate",
3410
+ title: "Rotate",
3411
+ on: {
3412
+ pointerdown: (e)=>{
3413
+ e.stopPropagation();
3414
+ ctx.beginCropRotate(e);
3415
+ }
3416
+ }
3417
+ }),
3418
+ ...handles.map((handle)=>h("div", {
3419
+ key: handle,
3420
+ class: "jie-crop__handle",
3421
+ "data-h": handle,
3422
+ on: {
3423
+ pointerdown: (e)=>{
3424
+ e.stopPropagation();
3425
+ ctx.beginCropDrag(handle, e);
3426
+ }
3427
+ }
3428
+ }))
3429
+ ]);
3430
+ }
3431
+ function zoom(ctx, state, factor) {
3432
+ ctx.update({
3433
+ zoom: state.zoom * factor
3434
+ });
3435
+ }
3436
+ function paintPreview(canvas, raster, viewport, fit) {
3437
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
3438
+ canvas.width = Math.max(1, Math.round(viewport.width * dpr));
3439
+ canvas.height = Math.max(1, Math.round(viewport.height * dpr));
3440
+ const ctx = canvas.getContext("2d");
3441
+ if (!ctx) return;
3442
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
3443
+ ctx.clearRect(0, 0, viewport.width, viewport.height);
3444
+ const off = document.createElement("canvas");
3445
+ off.width = raster.width;
3446
+ off.height = raster.height;
3447
+ const offCtx = off.getContext("2d");
3448
+ if (!offCtx) return;
3449
+ offCtx.putImageData(toImageData(raster), 0, 0);
3450
+ ctx.imageSmoothingEnabled = true;
3451
+ ctx.imageSmoothingQuality = "high";
3452
+ ctx.drawImage(off, fit.offsetX, fit.offsetY, fit.width, fit.height);
3453
+ }
3454
+ class ImageEditor {
3455
+ /** The current immutable state snapshot. */ get state() {
3456
+ return this.store.getState();
3457
+ }
3458
+ /** The one universal mutation entry point. Returns `this` for chaining. */ update(patch) {
3459
+ this.store.update(patch);
3460
+ return this;
3461
+ }
3462
+ /**
3463
+ * Programmatically trigger **Save** — exports the current design and invokes
3464
+ * the `onSave` handler (and fires a `jie:save` DOM event). Lets a host that
3465
+ * hides the built-in toolbar (`showToolbar: false`) drive saving from its own
3466
+ * UI. No-op until an image is loaded.
3467
+ */ save() {
3468
+ return _async_to_generator(function*() {
3469
+ return this.handleSave();
3470
+ }).call(this);
3471
+ }
3472
+ /** Programmatically trigger **Save as** — like {@link save} but for `onSaveAs`. */ saveAs() {
3473
+ return _async_to_generator(function*() {
3474
+ return this.handleSaveAs();
3475
+ }).call(this);
3476
+ }
3477
+ /**
3478
+ * Reset every edit back to the original (behind the configured `confirm`
3479
+ * gate). Equivalent to the built-in Reset button.
3480
+ */ reset() {
3481
+ this.handleReset();
3482
+ return this;
3483
+ }
3484
+ /** Apply an extension. Returns `this`. */ use(plugin) {
3485
+ const dispose = plugin.setup(this.api());
3486
+ if (typeof dispose === "function") this.disposers.push(dispose);
3487
+ return this;
3488
+ }
3489
+ /** Load an image blob; resets the design and re-fits the preview. */ fromBlob(blob) {
3490
+ return _async_to_generator(function*() {
3491
+ this.store.update({
3492
+ status: "loading",
3493
+ error: null
3494
+ });
3495
+ try {
3496
+ const raster = yield this.processor.decode(blob);
3497
+ if (this.destroyed) return this;
3498
+ this.source = raster;
3499
+ this.previewSource = downscale(raster, this.previewMaxSize);
3500
+ this.thumbSource = downscale(raster, 160);
3501
+ this.thumbVersion++;
3502
+ this.store.update({
3503
+ source: {
3504
+ width: raster.width,
3505
+ height: raster.height,
3506
+ name: blob instanceof File ? blob.name : null,
3507
+ mimeType: blob.type || "image/png"
3508
+ },
3509
+ resetHistory: true,
3510
+ // a freshly loaded image starts with a clean, empty history
3511
+ status: "ready"
3512
+ });
3513
+ } catch (error) {
3514
+ this.store.update({
3515
+ status: "error",
3516
+ error: error instanceof Error ? error.message : String(error)
3517
+ });
3518
+ }
3519
+ return this;
3520
+ }).call(this);
3521
+ }
3522
+ /** Render the current design at full resolution and encode it to a blob. */ toBlob(options) {
3523
+ return _async_to_generator(function*() {
3524
+ if (!this.source) throw new Error("No image loaded — call fromBlob() first");
3525
+ const design = selectDesign(this.store.getState());
3526
+ this.store.update({
3527
+ status: "exporting"
3528
+ });
3529
+ try {
3530
+ if (design.annotations.length > 0 && typeof document !== "undefined" && document.fonts) {
3531
+ yield document.fonts.ready;
3532
+ }
3533
+ return yield this.processor.toBlob(this.source, design, options);
3534
+ } finally{
3535
+ this.store.update({
3536
+ status: "ready"
3537
+ });
3538
+ }
3539
+ }).call(this);
3540
+ }
3541
+ /** Tear down listeners, observers, and the rendered tree. */ destroy() {
3542
+ var _this_dragCleanup, _this, _this_resizeObserver;
3543
+ if (this.destroyed) return;
3544
+ this.destroyed = true;
3545
+ this.clearBusyTimer();
3546
+ (_this_dragCleanup = (_this = this).dragCleanup) === null || _this_dragCleanup === void 0 ? void 0 : _this_dragCleanup.call(_this);
3547
+ (_this_resizeObserver = this.resizeObserver) === null || _this_resizeObserver === void 0 ? void 0 : _this_resizeObserver.disconnect();
3548
+ for (const dispose of this.disposers.splice(0))dispose();
3549
+ this.renderer.unmount();
3550
+ this.store.destroy();
3551
+ }
3552
+ // --- internals -----------------------------------------------------------
3553
+ api() {
3554
+ return {
3555
+ registerTool: (tool)=>this.registry.register(tool),
3556
+ registerFilter: (def)=>registerFilter(def),
3557
+ registerLocale: (locale)=>this.locales.register(locale),
3558
+ getState: ()=>this.store.getState(),
3559
+ update: (patch)=>this.store.update(patch)
3560
+ };
3561
+ }
3562
+ /** Translator for the currently active locale. */ translator() {
3563
+ return this.locales.translator(this.store.getState().locale);
3564
+ }
3565
+ appContext() {
3566
+ return {
3567
+ update: (patch)=>this.store.update(patch),
3568
+ tools: this.registry.list(),
3569
+ t: this.translator(),
3570
+ onSave: ()=>void this.handleSave(),
3571
+ onSaveAs: ()=>void this.handleSaveAs(),
3572
+ onReset: ()=>this.handleReset(),
3573
+ beginCropDrag: (handle, event)=>this.beginCropDrag(handle, event),
3574
+ beginCropRotate: (event)=>this.beginCropRotate(event)
3575
+ };
3576
+ }
3577
+ /** Reset every edit, behind a confirmation prompt. */ handleReset() {
3578
+ if (!selectIsDirty(this.store.getState())) return;
3579
+ if (this.confirm(this.translator()("Reset all changes? You can still undo afterwards."))) {
3580
+ this.store.update({
3581
+ resetDesign: true
3582
+ });
3583
+ }
3584
+ }
3585
+ render() {
3586
+ if (this.destroyed) return;
3587
+ this.renderer.render(renderApp(this.store.getState(), this.appContext()));
3588
+ this.ensureViewportObserver();
3589
+ this.paint();
3590
+ this.paintFilterThumbs();
3591
+ }
3592
+ handleSave() {
3593
+ return _async_to_generator(function*() {
3594
+ var _this_onSaveCb, _this;
3595
+ if (!this.canExport()) return;
3596
+ const blob = yield this.toBlob();
3597
+ (_this_onSaveCb = (_this = this).onSaveCb) === null || _this_onSaveCb === void 0 ? void 0 : _this_onSaveCb.call(_this, blob, this);
3598
+ this.container.dispatchEvent(new CustomEvent("jie:save", {
3599
+ detail: {
3600
+ blob
3601
+ },
3602
+ bubbles: true
3603
+ }));
3604
+ }).call(this);
3605
+ }
3606
+ handleSaveAs() {
3607
+ return _async_to_generator(function*() {
3608
+ var _this_onSaveAsCb, _this;
3609
+ if (!this.canExport()) return;
3610
+ const blob = yield this.toBlob();
3611
+ (_this_onSaveAsCb = (_this = this).onSaveAsCb) === null || _this_onSaveAsCb === void 0 ? void 0 : _this_onSaveAsCb.call(_this, blob, this);
3612
+ this.container.dispatchEvent(new CustomEvent("jie:saveas", {
3613
+ detail: {
3614
+ blob
3615
+ },
3616
+ bubbles: true
3617
+ }));
3618
+ }).call(this);
3619
+ }
3620
+ /** Saving needs a loaded image and no export/load already in flight. */ canExport() {
3621
+ const { status } = this.store.getState();
3622
+ return this.source !== null && status !== "exporting" && status !== "loading";
3623
+ }
3624
+ /**
3625
+ * Debounce the Save-button spinner: it only appears once an export has been
3626
+ * in flight for `spinnerDelay` ms, so a fast save never flashes the button.
3627
+ * The timer merely *schedules* the `busyVisible` patch (never updates the
3628
+ * store from inside a notification); the reducer clears the flag whenever
3629
+ * `status` leaves `'exporting'`.
3630
+ */ syncBusyTimer(state) {
3631
+ if (state.status !== "exporting") {
3632
+ this.clearBusyTimer();
3633
+ return;
3634
+ }
3635
+ if (state.busyVisible || this.busyTimer !== null) return;
3636
+ this.busyTimer = setTimeout(()=>{
3637
+ this.busyTimer = null;
3638
+ if (!this.destroyed && this.store.getState().status === "exporting") {
3639
+ this.store.update({
3640
+ busyVisible: true
3641
+ });
3642
+ }
3643
+ }, this.spinnerDelay);
3644
+ }
3645
+ clearBusyTimer() {
3646
+ if (this.busyTimer !== null) {
3647
+ clearTimeout(this.busyTimer);
3648
+ this.busyTimer = null;
3649
+ }
3650
+ }
3651
+ ensureViewportObserver() {
3652
+ var _this_resizeObserver;
3653
+ const wrap = this.container.querySelector("[data-jie-canvas-wrap]");
3654
+ if (!wrap || wrap === this.observedWrap) return;
3655
+ (_this_resizeObserver = this.resizeObserver) === null || _this_resizeObserver === void 0 ? void 0 : _this_resizeObserver.disconnect();
3656
+ this.observedWrap = wrap;
3657
+ if (typeof ResizeObserver === "undefined") {
3658
+ this.measureViewport(wrap);
3659
+ return;
3660
+ }
3661
+ this.resizeObserver = new ResizeObserver(()=>this.measureViewport(wrap));
3662
+ this.resizeObserver.observe(wrap);
3663
+ this.measureViewport(wrap);
3664
+ }
3665
+ measureViewport(wrap) {
3666
+ const { clientWidth, clientHeight } = wrap;
3667
+ const current = this.store.getState().viewport;
3668
+ if (clientWidth <= 0 || clientHeight <= 0) return;
3669
+ if (current && current.width === clientWidth && current.height === clientHeight) return;
3670
+ this.store.update({
3671
+ viewport: {
3672
+ width: clientWidth,
3673
+ height: clientHeight
3674
+ }
3675
+ });
3676
+ }
3677
+ /**
3678
+ * Paint the preview canvas — with two layers of memoisation so dragging the
3679
+ * crop box never re-runs the (potentially expensive) filter pipeline:
3680
+ *
3681
+ * 1. The rendered raster is cached by the pixel-affecting design. While
3682
+ * cropping, `selectPreviewDesign` drops the crop, so every pointer move
3683
+ * yields the *same* key → the filter is computed once, not per frame.
3684
+ * 2. The canvas blit is skipped when neither the raster nor the viewport/fit
3685
+ * changed. The crop overlay is plain DOM, so it keeps tracking the pointer
3686
+ * smoothly regardless.
3687
+ */ paint() {
3688
+ const state = this.store.getState();
3689
+ const canvas = this.container.querySelector("[data-jie-canvas]");
3690
+ if (!canvas || !this.previewSource || !state.viewport) return;
3691
+ const fit = selectViewportFit(state);
3692
+ if (!fit) return;
3693
+ const design = selectPreviewDesign(state);
3694
+ const rasterKey = `${this.thumbVersion}:${JSON.stringify(design)}`;
3695
+ if (rasterKey !== this.cachedRasterKey || !this.cachedRaster) {
3696
+ this.cachedRaster = this.processor.render(this.previewSource, design);
3697
+ this.cachedRasterKey = rasterKey;
3698
+ }
3699
+ const paintKey = `${rasterKey}@${state.viewport.width}x${state.viewport.height}:${fit.scale}:${fit.offsetX},${fit.offsetY}`;
3700
+ if (paintKey === this.lastPaintKey) return;
3701
+ this.lastPaintKey = paintKey;
3702
+ paintPreview(canvas, this.cachedRaster, state.viewport, fit);
3703
+ }
3704
+ /**
3705
+ * Paint each filter-strip thumbnail with the live filtered image. Thumbnails
3706
+ * are keyed by `version:filterId` so they paint once per image (cheap) and
3707
+ * repaint when a new picture is loaded.
3708
+ */ paintFilterThumbs() {
3709
+ const thumb = this.thumbSource;
3710
+ if (!thumb) return;
3711
+ const canvases = this.container.querySelectorAll("canvas[data-jie-filter-thumb]");
3712
+ for (const canvas of canvases){
3713
+ const id = canvas.dataset.jieFilterThumb;
3714
+ if (!id) continue;
3715
+ const key = `${this.thumbVersion}:${id}`;
3716
+ if (canvas.dataset.key === key) continue;
3717
+ const raster = applyFilter(thumb, id);
3718
+ canvas.width = raster.width;
3719
+ canvas.height = raster.height;
3720
+ const ctx = canvas.getContext("2d");
3721
+ if (!ctx) continue;
3722
+ ctx.putImageData(toImageData(raster), 0, 0);
3723
+ canvas.dataset.key = key;
3724
+ }
3725
+ }
3726
+ beginCropDrag(handle, event) {
3727
+ const startState = this.store.getState();
3728
+ const fit = selectViewportFit(startState);
3729
+ const startCrop = selectEffectiveCrop(startState);
3730
+ const oriented = selectOrientedSize(startState);
3731
+ if (!fit || !startCrop || !oriented) return;
3732
+ const angle = selectCropAngle(startState);
3733
+ const min = startState.minCropSize;
3734
+ const startX = event.clientX;
3735
+ const startY = event.clientY;
3736
+ this.runPointerGesture(event, (e)=>{
3737
+ const dx = (e.clientX - startX) / fit.scale;
3738
+ const dy = (e.clientY - startY) / fit.scale;
3739
+ return {
3740
+ crop: angle !== 0 ? resizeRotatedCrop(startCrop, angle, handle, dx, dy, min) : resizeCrop(startCrop, handle, dx, dy, oriented, null, min)
3741
+ };
3742
+ });
3743
+ }
3744
+ beginCropRotate(event) {
3745
+ var _this_observedWrap;
3746
+ const startState = this.store.getState();
3747
+ const fit = selectViewportFit(startState);
3748
+ const crop = selectEffectiveCrop(startState);
3749
+ const wrap = (_this_observedWrap = this.observedWrap) !== null && _this_observedWrap !== void 0 ? _this_observedWrap : this.container.querySelector("[data-jie-canvas-wrap]");
3750
+ if (!fit || !crop || !wrap) return;
3751
+ const box = wrap.getBoundingClientRect();
3752
+ const center = cropCenter(crop);
3753
+ const cx = box.left + fit.offsetX + center.x * fit.scale;
3754
+ const cy = box.top + fit.offsetY + center.y * fit.scale;
3755
+ const startAngle = selectCropAngle(startState);
3756
+ const grabbed = pointerAngle(cx, cy, event.clientX, event.clientY);
3757
+ this.runPointerGesture(event, (e)=>{
3758
+ let angle = startAngle + (pointerAngle(cx, cy, e.clientX, e.clientY) - grabbed);
3759
+ if (Math.abs(angle) < 2) angle = 0;
3760
+ return {
3761
+ angle: Math.round(angle * 10) / 10
3762
+ };
3763
+ });
3764
+ }
3765
+ /**
3766
+ * Shared pointer-drag loop: `compute` maps each move to a design patch. The
3767
+ * first move opens a history step, the rest replace it (`commit: false`), so
3768
+ * the whole gesture is a single undo.
3769
+ */ runPointerGesture(event, compute) {
3770
+ event.preventDefault();
3771
+ let committed = false;
3772
+ const onMove = (e)=>{
3773
+ this.store.update({
3774
+ design: compute(e),
3775
+ commit: !committed
3776
+ });
3777
+ committed = true;
3778
+ };
3779
+ const onUp = ()=>{
3780
+ window.removeEventListener("pointermove", onMove);
3781
+ window.removeEventListener("pointerup", onUp);
3782
+ this.dragCleanup = null;
3783
+ };
3784
+ window.addEventListener("pointermove", onMove);
3785
+ window.addEventListener("pointerup", onUp);
3786
+ this.dragCleanup = onUp;
3787
+ }
3788
+ constructor(props){
3789
+ var _props_processor, _props_previewMaxSize, _props_spinnerDelay, _props_confirm, _props_locales, _props_scheduler, _props_tools, _props_plugins;
3790
+ _define_property(this, "store", void 0);
3791
+ _define_property(this, "container", void 0);
3792
+ _define_property(this, "renderer", void 0);
3793
+ _define_property(this, "registry", new ToolRegistry());
3794
+ _define_property(this, "locales", new LocaleRegistry());
3795
+ _define_property(this, "processor", void 0);
3796
+ _define_property(this, "previewMaxSize", void 0);
3797
+ _define_property(this, "spinnerDelay", void 0);
3798
+ /** Pending "show the busy spinner" timer — see {@link syncBusyTimer}. */ _define_property(this, "busyTimer", null);
3799
+ _define_property(this, "onSaveCb", void 0);
3800
+ _define_property(this, "onSaveAsCb", void 0);
3801
+ _define_property(this, "confirm", void 0);
3802
+ _define_property(this, "source", null);
3803
+ _define_property(this, "previewSource", null);
3804
+ /** Tiny raster used to paint the filter strip thumbnails. */ _define_property(this, "thumbSource", null);
3805
+ /** Bumped on each image load so thumbnails repaint for the new picture. */ _define_property(this, "thumbVersion", 0);
3806
+ /** Memoised preview render — see {@link paint}. */ _define_property(this, "cachedRaster", null);
3807
+ _define_property(this, "cachedRasterKey", "");
3808
+ _define_property(this, "lastPaintKey", "");
3809
+ _define_property(this, "disposers", []);
3810
+ _define_property(this, "resizeObserver", null);
3811
+ _define_property(this, "observedWrap", null);
3812
+ _define_property(this, "dragCleanup", null);
3813
+ _define_property(this, "destroyed", false);
3814
+ this.container = resolveContainer(props.container);
3815
+ this.processor = (_props_processor = props.processor) !== null && _props_processor !== void 0 ? _props_processor : createDefaultProcessor(void 0, {
3816
+ maxPixels: props.maxSourcePixels,
3817
+ maxSize: props.maxSourceSize
3818
+ });
3819
+ this.previewMaxSize = (_props_previewMaxSize = props.previewMaxSize) !== null && _props_previewMaxSize !== void 0 ? _props_previewMaxSize : 1600;
3820
+ this.spinnerDelay = Math.max(0, (_props_spinnerDelay = props.spinnerDelay) !== null && _props_spinnerDelay !== void 0 ? _props_spinnerDelay : 250);
3821
+ this.onSaveCb = props.onSave;
3822
+ this.onSaveAsCb = props.onSaveAs;
3823
+ this.confirm = (_props_confirm = props.confirm) !== null && _props_confirm !== void 0 ? _props_confirm : (message)=>typeof window !== "undefined" ? window.confirm(message) : true;
3824
+ for (const locale of (_props_locales = props.locales) !== null && _props_locales !== void 0 ? _props_locales : [])this.locales.register(locale);
3825
+ this.store = new Store({
3826
+ scheduler: (_props_scheduler = props.scheduler) !== null && _props_scheduler !== void 0 ? _props_scheduler : createRafScheduler(),
3827
+ initialState: _object_spread({}, createInitialState(), props.state, props.locale ? {
3828
+ locale: props.locale
3829
+ } : {}, props.minCropSize !== void 0 ? {
3830
+ minCropSize: Math.max(1, props.minCropSize)
3831
+ } : {}, props.minResizeSize !== void 0 ? {
3832
+ minResizeSize: Math.max(1, props.minResizeSize)
3833
+ } : {}, props.palette ? {
3834
+ palette: props.palette
3835
+ } : {}, props.fonts ? {
3836
+ fonts: props.fonts
3837
+ } : {})
3838
+ });
3839
+ for (const tool of (_props_tools = props.tools) !== null && _props_tools !== void 0 ? _props_tools : createBuiltinTools())this.registry.register(tool);
3840
+ for (const plugin of (_props_plugins = props.plugins) !== null && _props_plugins !== void 0 ? _props_plugins : [])this.use(plugin);
3841
+ this.renderer = new Renderer(new DomHost(document), this.container);
3842
+ this.disposers.push(this.store.subscribe(()=>this.render()));
3843
+ this.disposers.push(this.store.subscribe((state)=>this.syncBusyTimer(state)));
3844
+ this.render();
3845
+ if (props.image) void this.fromBlob(props.image);
3846
+ }
3847
+ }
3848
+ function resolveContainer(container) {
3849
+ if (typeof container !== "string") return container;
3850
+ const el = document.querySelector(container);
3851
+ if (!el) throw new Error(`Container not found: ${container}`);
3852
+ return el;
3853
+ }
3854
+ function downscale(raster, maxSize) {
3855
+ const longest = Math.max(raster.width, raster.height);
3856
+ if (longest <= maxSize) return raster;
3857
+ const scale = maxSize / longest;
3858
+ return resize(raster, {
3859
+ width: Math.round(raster.width * scale),
3860
+ height: Math.round(raster.height * scale)
3861
+ });
3862
+ }
3863
+ function init(props) {
3864
+ return new ImageEditor(props);
3865
+ }
3866
+ class MemoryHost {
3867
+ createElement(tag) {
3868
+ return this.node("element", {
3869
+ tag
3870
+ });
3871
+ }
3872
+ createText(value) {
3873
+ return this.node("text", {
3874
+ text: value
3875
+ });
3876
+ }
3877
+ setText(node, value) {
3878
+ node.text = value;
3879
+ }
3880
+ insert(parent, node, anchor) {
3881
+ if (node.parent) this.remove(node.parent, node);
3882
+ const at = anchor ? parent.children.indexOf(anchor) : -1;
3883
+ if (at === -1) parent.children.push(node);
3884
+ else parent.children.splice(at, 0, node);
3885
+ node.parent = parent;
3886
+ }
3887
+ remove(parent, node) {
3888
+ const at = parent.children.indexOf(node);
3889
+ if (at !== -1) parent.children.splice(at, 1);
3890
+ node.parent = null;
3891
+ }
3892
+ parent(node) {
3893
+ return node.parent;
3894
+ }
3895
+ nextSibling(node) {
3896
+ var _node_parent_children_;
3897
+ if (!node.parent) return null;
3898
+ const at = node.parent.children.indexOf(node);
3899
+ return (_node_parent_children_ = node.parent.children[at + 1]) !== null && _node_parent_children_ !== void 0 ? _node_parent_children_ : null;
3900
+ }
3901
+ setAttribute(node, name, value) {
3902
+ node.attrs.set(name, value);
3903
+ }
3904
+ removeAttribute(node, name) {
3905
+ node.attrs.delete(name);
3906
+ }
3907
+ setProperty(node, name, value) {
3908
+ node.props.set(name, value);
3909
+ }
3910
+ setStyle(node, prop, value) {
3911
+ node.style.set(prop, value);
3912
+ }
3913
+ removeStyle(node, prop) {
3914
+ node.style.delete(prop);
3915
+ }
3916
+ addEventListener(node, type, handler) {
3917
+ var _node_listeners_get;
3918
+ const set = (_node_listeners_get = node.listeners.get(type)) !== null && _node_listeners_get !== void 0 ? _node_listeners_get : /* @__PURE__ */ new Set();
3919
+ set.add(handler);
3920
+ node.listeners.set(type, set);
3921
+ }
3922
+ removeEventListener(node, type, handler) {
3923
+ var _node_listeners_get;
3924
+ (_node_listeners_get = node.listeners.get(type)) === null || _node_listeners_get === void 0 ? void 0 : _node_listeners_get.delete(handler);
3925
+ }
3926
+ /** Fire every listener bound for `type` on `node` (test convenience). */ dispatch(node, type, event) {
3927
+ var _node_listeners_get;
3928
+ for (const handler of (_node_listeners_get = node.listeners.get(type)) !== null && _node_listeners_get !== void 0 ? _node_listeners_get : [])handler(event);
3929
+ }
3930
+ /** Serialise the subtree to a compact HTML-like string for assertions. */ serialize(node) {
3931
+ var _node_text;
3932
+ if (node.type === "text") return (_node_text = node.text) !== null && _node_text !== void 0 ? _node_text : "";
3933
+ const attrs = [
3934
+ ...node.attrs
3935
+ ].map(([k, v1])=>` ${k}="${v1}"`).join("");
3936
+ const inner = node.children.map((c)=>this.serialize(c)).join("");
3937
+ return `<${node.tag}${attrs}>${inner}</${node.tag}>`;
3938
+ }
3939
+ node(type, partial) {
3940
+ return _object_spread({
3941
+ type,
3942
+ attrs: /* @__PURE__ */ new Map(),
3943
+ props: /* @__PURE__ */ new Map(),
3944
+ style: /* @__PURE__ */ new Map(),
3945
+ listeners: /* @__PURE__ */ new Map(),
3946
+ children: [],
3947
+ parent: null
3948
+ }, partial);
3949
+ }
3950
+ }
3951
+ const TOKENS = {
3952
+ /** Crop overlay accent — matches `--jie-accent`. */ accent: "#5b5fc7",
3953
+ /** Dimmed scrim drawn over the cropped-out region. */ scrim: "rgba(17, 24, 39, 0.55)",
3954
+ /** Corner handle radius, in screen pixels. */ handleRadius: 7,
3955
+ /** Canvas backdrop behind the image. */ canvasBackground: "#f3f4f8"
3956
+ };
3957
+ //# sourceMappingURL=jodit-image-editor.js.map
3958
+
3959
+
3960
+ /***/ })
3961
+
3962
+ },
3963
+ /******/ function(__webpack_require__) { // webpackRuntimeModules
3964
+ /******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
3965
+ /******/ var __webpack_exports__ = (__webpack_exec__(98270));
3966
+ /******/ return __webpack_exports__;
3967
+ /******/ }
3968
+ ]);
3969
+ });