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