@chenzy-design/core 0.2.1 → 0.4.0

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 (283) hide show
  1. package/dist/ai-chat-dialogue.d.ts +300 -0
  2. package/dist/ai-chat-dialogue.d.ts.map +1 -0
  3. package/dist/ai-chat-dialogue.js +699 -0
  4. package/dist/ai-chat-dialogue.js.map +1 -0
  5. package/dist/ai-chat-dialogue.test.d.ts +2 -0
  6. package/dist/ai-chat-dialogue.test.d.ts.map +1 -0
  7. package/dist/ai-chat-dialogue.test.js +338 -0
  8. package/dist/ai-chat-dialogue.test.js.map +1 -0
  9. package/dist/ai-chat-input.d.ts +191 -0
  10. package/dist/ai-chat-input.d.ts.map +1 -0
  11. package/dist/ai-chat-input.js +274 -0
  12. package/dist/ai-chat-input.js.map +1 -0
  13. package/dist/ai-chat-input.test.d.ts +2 -0
  14. package/dist/ai-chat-input.test.d.ts.map +1 -0
  15. package/dist/ai-chat-input.test.js +339 -0
  16. package/dist/ai-chat-input.test.js.map +1 -0
  17. package/dist/audio-player.d.ts +154 -0
  18. package/dist/audio-player.d.ts.map +1 -0
  19. package/dist/audio-player.js +254 -0
  20. package/dist/audio-player.js.map +1 -0
  21. package/dist/audio-player.test.d.ts +2 -0
  22. package/dist/audio-player.test.d.ts.map +1 -0
  23. package/dist/audio-player.test.js +275 -0
  24. package/dist/audio-player.test.js.map +1 -0
  25. package/dist/back-top.d.ts +4 -1
  26. package/dist/back-top.d.ts.map +1 -1
  27. package/dist/back-top.js +5 -2
  28. package/dist/back-top.js.map +1 -1
  29. package/dist/back-top.test.js +3 -2
  30. package/dist/back-top.test.js.map +1 -1
  31. package/dist/calendar.d.ts +132 -44
  32. package/dist/calendar.d.ts.map +1 -1
  33. package/dist/calendar.js +413 -70
  34. package/dist/calendar.js.map +1 -1
  35. package/dist/calendar.test.js +160 -76
  36. package/dist/calendar.test.js.map +1 -1
  37. package/dist/chat.d.ts +172 -0
  38. package/dist/chat.d.ts.map +1 -0
  39. package/dist/chat.js +210 -0
  40. package/dist/chat.js.map +1 -0
  41. package/dist/chat.test.d.ts +2 -0
  42. package/dist/chat.test.d.ts.map +1 -0
  43. package/dist/chat.test.js +181 -0
  44. package/dist/chat.test.js.map +1 -0
  45. package/dist/code-highlight.d.ts +30 -0
  46. package/dist/code-highlight.d.ts.map +1 -0
  47. package/dist/code-highlight.js +37 -0
  48. package/dist/code-highlight.js.map +1 -0
  49. package/dist/code-highlight.test.d.ts +2 -0
  50. package/dist/code-highlight.test.d.ts.map +1 -0
  51. package/dist/code-highlight.test.js +36 -0
  52. package/dist/code-highlight.test.js.map +1 -0
  53. package/dist/collapsible.d.ts +41 -0
  54. package/dist/collapsible.d.ts.map +1 -0
  55. package/dist/collapsible.js +33 -0
  56. package/dist/collapsible.js.map +1 -0
  57. package/dist/collapsible.test.d.ts +2 -0
  58. package/dist/collapsible.test.d.ts.map +1 -0
  59. package/dist/collapsible.test.js +57 -0
  60. package/dist/collapsible.test.js.map +1 -0
  61. package/dist/color-value.d.ts +60 -0
  62. package/dist/color-value.d.ts.map +1 -0
  63. package/dist/color-value.js +165 -0
  64. package/dist/color-value.js.map +1 -0
  65. package/dist/color-value.test.d.ts +2 -0
  66. package/dist/color-value.test.d.ts.map +1 -0
  67. package/dist/color-value.test.js +104 -0
  68. package/dist/color-value.test.js.map +1 -0
  69. package/dist/config-provider.d.ts +76 -37
  70. package/dist/config-provider.d.ts.map +1 -1
  71. package/dist/config-provider.js +107 -32
  72. package/dist/config-provider.js.map +1 -1
  73. package/dist/config-provider.test.js +123 -60
  74. package/dist/config-provider.test.js.map +1 -1
  75. package/dist/cropper.d.ts +164 -0
  76. package/dist/cropper.d.ts.map +1 -0
  77. package/dist/cropper.js +841 -0
  78. package/dist/cropper.js.map +1 -0
  79. package/dist/cropper.test.d.ts +2 -0
  80. package/dist/cropper.test.d.ts.map +1 -0
  81. package/dist/cropper.test.js +259 -0
  82. package/dist/cropper.test.js.map +1 -0
  83. package/dist/drag-move.d.ts +119 -0
  84. package/dist/drag-move.d.ts.map +1 -0
  85. package/dist/drag-move.js +237 -0
  86. package/dist/drag-move.js.map +1 -0
  87. package/dist/drag-move.test.d.ts +2 -0
  88. package/dist/drag-move.test.d.ts.map +1 -0
  89. package/dist/drag-move.test.js +304 -0
  90. package/dist/drag-move.test.js.map +1 -0
  91. package/dist/editable.d.ts +24 -4
  92. package/dist/editable.d.ts.map +1 -1
  93. package/dist/editable.js +18 -0
  94. package/dist/editable.js.map +1 -1
  95. package/dist/editable.test.js +55 -2
  96. package/dist/editable.test.js.map +1 -1
  97. package/dist/ellipsis.d.ts +13 -0
  98. package/dist/ellipsis.d.ts.map +1 -1
  99. package/dist/ellipsis.js +36 -0
  100. package/dist/ellipsis.js.map +1 -1
  101. package/dist/ellipsis.test.js +37 -1
  102. package/dist/ellipsis.test.js.map +1 -1
  103. package/dist/form-path.d.ts +19 -0
  104. package/dist/form-path.d.ts.map +1 -0
  105. package/dist/form-path.js +97 -0
  106. package/dist/form-path.js.map +1 -0
  107. package/dist/form-path.test.d.ts +2 -0
  108. package/dist/form-path.test.d.ts.map +1 -0
  109. package/dist/form-path.test.js +57 -0
  110. package/dist/form-path.test.js.map +1 -0
  111. package/dist/form-validate.d.ts +13 -0
  112. package/dist/form-validate.d.ts.map +1 -0
  113. package/dist/form-validate.js +204 -0
  114. package/dist/form-validate.js.map +1 -0
  115. package/dist/form.d.ts +91 -18
  116. package/dist/form.d.ts.map +1 -1
  117. package/dist/form.js +170 -115
  118. package/dist/form.js.map +1 -1
  119. package/dist/form.test.js +349 -76
  120. package/dist/form.test.js.map +1 -1
  121. package/dist/highlight.d.ts +42 -32
  122. package/dist/highlight.d.ts.map +1 -1
  123. package/dist/highlight.js +67 -41
  124. package/dist/highlight.js.map +1 -1
  125. package/dist/highlight.test.js +73 -32
  126. package/dist/highlight.test.js.map +1 -1
  127. package/dist/hotkeys.d.ts +151 -0
  128. package/dist/hotkeys.d.ts.map +1 -0
  129. package/dist/hotkeys.js +188 -0
  130. package/dist/hotkeys.js.map +1 -0
  131. package/dist/hotkeys.test.d.ts +2 -0
  132. package/dist/hotkeys.test.d.ts.map +1 -0
  133. package/dist/hotkeys.test.js +170 -0
  134. package/dist/hotkeys.test.js.map +1 -0
  135. package/dist/index.d.ts +33 -17
  136. package/dist/index.d.ts.map +1 -1
  137. package/dist/index.js +32 -13
  138. package/dist/index.js.map +1 -1
  139. package/dist/input.d.ts +34 -0
  140. package/dist/input.d.ts.map +1 -0
  141. package/dist/input.js +54 -0
  142. package/dist/input.js.map +1 -0
  143. package/dist/input.test.d.ts +2 -0
  144. package/dist/input.test.d.ts.map +1 -0
  145. package/dist/input.test.js +56 -0
  146. package/dist/input.test.js.map +1 -0
  147. package/dist/markdown-render.d.ts +49 -0
  148. package/dist/markdown-render.d.ts.map +1 -0
  149. package/dist/markdown-render.js +61 -0
  150. package/dist/markdown-render.js.map +1 -0
  151. package/dist/markdown-render.test.d.ts +2 -0
  152. package/dist/markdown-render.test.d.ts.map +1 -0
  153. package/dist/markdown-render.test.js +106 -0
  154. package/dist/markdown-render.test.js.map +1 -0
  155. package/dist/notification.d.ts +40 -31
  156. package/dist/notification.d.ts.map +1 -1
  157. package/dist/notification.js +86 -70
  158. package/dist/notification.js.map +1 -1
  159. package/dist/notification.test.js +62 -39
  160. package/dist/notification.test.js.map +1 -1
  161. package/dist/numeral.d.ts +32 -0
  162. package/dist/numeral.d.ts.map +1 -0
  163. package/dist/numeral.js +67 -0
  164. package/dist/numeral.js.map +1 -0
  165. package/dist/numeral.test.d.ts +2 -0
  166. package/dist/numeral.test.d.ts.map +1 -0
  167. package/dist/numeral.test.js +59 -0
  168. package/dist/numeral.test.js.map +1 -0
  169. package/dist/overflow-list.d.ts +9 -40
  170. package/dist/overflow-list.d.ts.map +1 -1
  171. package/dist/overflow-list.js +9 -70
  172. package/dist/overflow-list.js.map +1 -1
  173. package/dist/overflow-list.test.js +1 -122
  174. package/dist/overflow-list.test.js.map +1 -1
  175. package/dist/pagination.d.ts +17 -0
  176. package/dist/pagination.d.ts.map +1 -1
  177. package/dist/pagination.js +48 -0
  178. package/dist/pagination.js.map +1 -1
  179. package/dist/pagination.test.js +40 -1
  180. package/dist/pagination.test.js.map +1 -1
  181. package/dist/pincode.d.ts +79 -0
  182. package/dist/pincode.d.ts.map +1 -0
  183. package/dist/pincode.js +119 -0
  184. package/dist/pincode.js.map +1 -0
  185. package/dist/pincode.test.d.ts +2 -0
  186. package/dist/pincode.test.d.ts.map +1 -0
  187. package/dist/pincode.test.js +151 -0
  188. package/dist/pincode.test.js.map +1 -0
  189. package/dist/progress.d.ts +32 -30
  190. package/dist/progress.d.ts.map +1 -1
  191. package/dist/progress.js +257 -64
  192. package/dist/progress.js.map +1 -1
  193. package/dist/progress.test.js +50 -47
  194. package/dist/progress.test.js.map +1 -1
  195. package/dist/resizable.d.ts +200 -0
  196. package/dist/resizable.d.ts.map +1 -0
  197. package/dist/resizable.js +329 -0
  198. package/dist/resizable.js.map +1 -0
  199. package/dist/resizable.test.d.ts +2 -0
  200. package/dist/resizable.test.d.ts.map +1 -0
  201. package/dist/resizable.test.js +302 -0
  202. package/dist/resizable.test.js.map +1 -0
  203. package/dist/scroll-list.d.ts +69 -62
  204. package/dist/scroll-list.d.ts.map +1 -1
  205. package/dist/scroll-list.js +103 -144
  206. package/dist/scroll-list.js.map +1 -1
  207. package/dist/scroll-list.test.js +100 -110
  208. package/dist/scroll-list.test.js.map +1 -1
  209. package/dist/sidebar.d.ts +46 -0
  210. package/dist/sidebar.d.ts.map +1 -0
  211. package/dist/sidebar.js +65 -0
  212. package/dist/sidebar.js.map +1 -0
  213. package/dist/sidebar.test.d.ts +2 -0
  214. package/dist/sidebar.test.d.ts.map +1 -0
  215. package/dist/sidebar.test.js +88 -0
  216. package/dist/sidebar.test.js.map +1 -0
  217. package/dist/spin.d.ts +5 -5
  218. package/dist/spin.d.ts.map +1 -1
  219. package/dist/spin.js +13 -39
  220. package/dist/spin.js.map +1 -1
  221. package/dist/spin.test.js +2 -26
  222. package/dist/spin.test.js.map +1 -1
  223. package/dist/toast.d.ts +33 -30
  224. package/dist/toast.d.ts.map +1 -1
  225. package/dist/toast.js +77 -72
  226. package/dist/toast.js.map +1 -1
  227. package/dist/toast.test.js +63 -37
  228. package/dist/toast.test.js.map +1 -1
  229. package/dist/tree.d.ts +22 -5
  230. package/dist/tree.d.ts.map +1 -1
  231. package/dist/tree.js +61 -9
  232. package/dist/tree.js.map +1 -1
  233. package/dist/tree.test.js +72 -1
  234. package/dist/tree.test.js.map +1 -1
  235. package/dist/user-guide.d.ts +73 -0
  236. package/dist/user-guide.d.ts.map +1 -0
  237. package/dist/user-guide.js +95 -0
  238. package/dist/user-guide.js.map +1 -0
  239. package/dist/user-guide.test.d.ts +2 -0
  240. package/dist/user-guide.test.d.ts.map +1 -0
  241. package/dist/user-guide.test.js +109 -0
  242. package/dist/user-guide.test.js.map +1 -0
  243. package/dist/video-player.d.ts +196 -0
  244. package/dist/video-player.d.ts.map +1 -0
  245. package/dist/video-player.js +461 -0
  246. package/dist/video-player.js.map +1 -0
  247. package/dist/video-player.test.d.ts +2 -0
  248. package/dist/video-player.test.d.ts.map +1 -0
  249. package/dist/video-player.test.js +435 -0
  250. package/dist/video-player.test.js.map +1 -0
  251. package/package.json +17 -1
  252. package/dist/banner.d.ts +0 -19
  253. package/dist/banner.d.ts.map +0 -1
  254. package/dist/banner.js +0 -20
  255. package/dist/banner.js.map +0 -1
  256. package/dist/banner.test.d.ts +0 -2
  257. package/dist/banner.test.d.ts.map +0 -1
  258. package/dist/banner.test.js +0 -29
  259. package/dist/banner.test.js.map +0 -1
  260. package/dist/breakpoints.d.ts +0 -30
  261. package/dist/breakpoints.d.ts.map +0 -1
  262. package/dist/breakpoints.js +0 -45
  263. package/dist/breakpoints.js.map +0 -1
  264. package/dist/breakpoints.test.d.ts +0 -2
  265. package/dist/breakpoints.test.d.ts.map +0 -1
  266. package/dist/breakpoints.test.js +0 -46
  267. package/dist/breakpoints.test.js.map +0 -1
  268. package/dist/lottie-icon.d.ts +0 -88
  269. package/dist/lottie-icon.d.ts.map +0 -1
  270. package/dist/lottie-icon.js +0 -84
  271. package/dist/lottie-icon.js.map +0 -1
  272. package/dist/lottie-icon.test.d.ts +0 -2
  273. package/dist/lottie-icon.test.d.ts.map +0 -1
  274. package/dist/lottie-icon.test.js +0 -88
  275. package/dist/lottie-icon.test.js.map +0 -1
  276. package/dist/sider.d.ts +0 -26
  277. package/dist/sider.d.ts.map +0 -1
  278. package/dist/sider.js +0 -55
  279. package/dist/sider.js.map +0 -1
  280. package/dist/sider.test.d.ts +0 -2
  281. package/dist/sider.test.d.ts.map +0 -1
  282. package/dist/sider.test.js +0 -29
  283. package/dist/sider.test.js.map +0 -1
@@ -0,0 +1,841 @@
1
+ /**
2
+ * createCropper — framework-agnostic geometry engine for the Cropper component.
3
+ * Ported 逐行 from Semi 的 semi-foundation/cropper/foundation.js (CropperFoundation).
4
+ * See specs/components/show/Cropper.spec.md §3.
5
+ *
6
+ * 纯几何/状态:容器/图片/裁切框坐标换算、8 角点 resize(自由 + aspectRatio 约束)、
7
+ * 裁切框拖拽、图片拖拽、滚轮缩放(min/maxZoom 钳制)、resize 重排、坐标→canvas。
8
+ * 所有 DOM 读取(container/img 尺寸、bounding rect)经 adapter 注入,
9
+ * document 级 mousemove/mouseup 绑定由 svelte 渲染层负责;本模块只算不碰 DOM 监听。
10
+ *
11
+ * 坐标系:以裁切容器左上角为原点,x 向右、y 向下。centerPoint 存的是几何中心。
12
+ */
13
+ /** 角点全集与圆形(round)时仅保留的 4 个边中点角点。 */
14
+ export const CROPPER_CORNERS = [
15
+ 'tl',
16
+ 'tm',
17
+ 'tr',
18
+ 'ml',
19
+ 'mr',
20
+ 'bl',
21
+ 'bm',
22
+ 'br',
23
+ ];
24
+ export const CROPPER_ROUND_CORNERS = ['tm', 'ml', 'mr', 'bm'];
25
+ export const CROPPER_SHAPES = ['rect', 'round', 'roundRect'];
26
+ /** getMiddle — 将 value 钳制到 [min, max]。(Semi cropper.utils.getMiddle) */
27
+ export function getMiddle(value, [min, max]) {
28
+ return Math.min(Math.max(value, min), max);
29
+ }
30
+ /**
31
+ * getAspectHW — 在给定 (width, height) 外接框内取满足 aspect(=w/h) 的最大内接矩形宽高。
32
+ * (Semi cropper.utils.getAspectHW)
33
+ */
34
+ export function getAspectHW(width, height, aspect) {
35
+ if (width / height > aspect) {
36
+ width = height * aspect;
37
+ }
38
+ else {
39
+ height = width / aspect;
40
+ }
41
+ return [width, height];
42
+ }
43
+ const DEFAULT_PROPS = {
44
+ defaultAspectRatio: 1,
45
+ fill: 'rgba(0, 0, 0, 0)',
46
+ maxZoom: 3,
47
+ minZoom: 0.1,
48
+ zoomStep: 0.1,
49
+ };
50
+ /** 每个方向的 resize 参数(自由 resize 分支用)。 */
51
+ export function getMoveParamByDir(dir) {
52
+ let paramX = 0;
53
+ let paramY = 0;
54
+ switch (dir) {
55
+ case 'tl':
56
+ paramX = -1;
57
+ paramY = -1;
58
+ break;
59
+ case 'tm':
60
+ paramY = -1;
61
+ break;
62
+ case 'tr':
63
+ paramX = 1;
64
+ paramY = -1;
65
+ break;
66
+ case 'ml':
67
+ paramX = -1;
68
+ break;
69
+ case 'mr':
70
+ paramX = 1;
71
+ break;
72
+ case 'bl':
73
+ paramX = -1;
74
+ paramY = 1;
75
+ break;
76
+ case 'bm':
77
+ paramY = 1;
78
+ break;
79
+ case 'br':
80
+ paramX = 1;
81
+ paramY = 1;
82
+ break;
83
+ default:
84
+ break;
85
+ }
86
+ return { paramX, paramY };
87
+ }
88
+ /**
89
+ * createCropperFoundation — 构造几何引擎实例。
90
+ * 状态经 `getState()` 读、`setState()` 写(渲染层将其桥接到框架响应式)。
91
+ */
92
+ export function createCropperFoundation(options) {
93
+ const { adapter, getState, setState } = options;
94
+ const getProps = () => ({ ...DEFAULT_PROPS, ...options.getProps() });
95
+ // ——— 非响应式内部簿记(Semi 中挂在 foundation 实例上的字段) ———
96
+ const containerData = { width: 0, height: 0 };
97
+ const imgData = { originalWidth: 0, originalHeight: 0, scale: 1 };
98
+ let boxMoveDir = '';
99
+ let boxMoveParam = { paramX: 0, paramY: 0 };
100
+ let rangeX = [0, 0];
101
+ let rangeY = [0, 0];
102
+ let initial = false;
103
+ let cropperBoxMoveStart = null;
104
+ let imgMoveStart = null;
105
+ let moveRange = { xMin: 0, xMax: 0, yMin: 0, yMax: 0 };
106
+ function init() {
107
+ const container = adapter.getContainer();
108
+ if (container) {
109
+ containerData.width = container.clientWidth;
110
+ containerData.height = container.clientHeight;
111
+ }
112
+ cropperBoxMoveStart = null;
113
+ }
114
+ // ——— resize 重排 ———
115
+ function getImgDataWhenResize(ratio) {
116
+ const { imgData: imgState } = getState();
117
+ const newImgData = {
118
+ width: imgState.width * ratio,
119
+ height: imgState.height * ratio,
120
+ centerPoint: {
121
+ x: imgState.centerPoint.x * ratio,
122
+ y: imgState.centerPoint.y * ratio,
123
+ },
124
+ };
125
+ imgData.scale *= ratio;
126
+ return newImgData;
127
+ }
128
+ function getCropperBoxWhenResize(ratio, newContainerData) {
129
+ const { cropperBox } = getState();
130
+ const { aspectRatio } = getProps();
131
+ const tempCropperBox = {
132
+ width: cropperBox.width * ratio,
133
+ height: cropperBox.height * ratio,
134
+ centerPoint: {
135
+ x: cropperBox.centerPoint.x * ratio,
136
+ y: cropperBox.centerPoint.y * ratio,
137
+ },
138
+ };
139
+ let xMin = tempCropperBox.centerPoint.x - tempCropperBox.width / 2;
140
+ let xMax = tempCropperBox.centerPoint.x + tempCropperBox.width / 2;
141
+ let yMin = tempCropperBox.centerPoint.y - tempCropperBox.height / 2;
142
+ let yMax = tempCropperBox.centerPoint.y + tempCropperBox.height / 2;
143
+ if (aspectRatio) {
144
+ if (xMax > newContainerData.width) {
145
+ xMax = newContainerData.width;
146
+ xMin =
147
+ tempCropperBox.width > newContainerData.width ? 0 : newContainerData.width - tempCropperBox.width;
148
+ tempCropperBox.width = xMax - xMin;
149
+ tempCropperBox.height = tempCropperBox.width / aspectRatio;
150
+ yMax = yMin + tempCropperBox.height;
151
+ }
152
+ if (yMax > newContainerData.height) {
153
+ yMax = newContainerData.height;
154
+ yMin =
155
+ tempCropperBox.height > newContainerData.height
156
+ ? 0
157
+ : newContainerData.height - tempCropperBox.height;
158
+ tempCropperBox.height = yMax - yMin;
159
+ tempCropperBox.width = tempCropperBox.height * aspectRatio;
160
+ xMax = xMin + tempCropperBox.width;
161
+ }
162
+ }
163
+ else {
164
+ if (xMax > newContainerData.width) {
165
+ xMax = newContainerData.width;
166
+ xMin =
167
+ tempCropperBox.width > newContainerData.width ? 0 : newContainerData.width - tempCropperBox.width;
168
+ }
169
+ if (yMax > newContainerData.height) {
170
+ yMax = newContainerData.height;
171
+ yMin =
172
+ tempCropperBox.height > newContainerData.height
173
+ ? 0
174
+ : newContainerData.height - tempCropperBox.height;
175
+ }
176
+ }
177
+ return {
178
+ width: xMax - xMin,
179
+ height: yMax - yMin,
180
+ centerPoint: {
181
+ x: (xMax + xMin) / 2,
182
+ y: (yMax + yMin) / 2,
183
+ },
184
+ };
185
+ }
186
+ function handleResize() {
187
+ const { loaded } = getState();
188
+ // Semi:首帧(观测器 observe 时的初始回调)跳过,避免用 0 尺寸重排。
189
+ if (!initial) {
190
+ initial = true;
191
+ return;
192
+ }
193
+ if (!loaded) {
194
+ return;
195
+ }
196
+ const container = adapter.getContainer();
197
+ if (!container)
198
+ return;
199
+ const newContainerData = {
200
+ width: container.clientWidth,
201
+ height: container.clientHeight,
202
+ };
203
+ if (containerData.width === 0)
204
+ return;
205
+ const ratio = newContainerData.width / containerData.width;
206
+ const newImgData = getImgDataWhenResize(ratio);
207
+ const newCropperBox = getCropperBoxWhenResize(ratio, newContainerData);
208
+ containerData.width = newContainerData.width;
209
+ containerData.height = newContainerData.height;
210
+ setState({ imgData: newImgData, cropperBox: newCropperBox });
211
+ }
212
+ // ——— 图片加载:计算初始适配缩放 + 裁切框初始尺寸 ———
213
+ function handleImageLoad(target) {
214
+ const { naturalWidth, naturalHeight } = target;
215
+ const container = adapter.getContainer();
216
+ // 容器尺寸以最新读取为准(首次加载时 init 可能早于布局稳定)。
217
+ if (container) {
218
+ containerData.width = container.clientWidth;
219
+ containerData.height = container.clientHeight;
220
+ }
221
+ const { width: containerWidth, height: containerHeight } = containerData;
222
+ imgData.originalWidth = naturalWidth;
223
+ imgData.originalHeight = naturalHeight;
224
+ let scale;
225
+ const newImgDataState = {
226
+ width: 0,
227
+ height: 0,
228
+ centerPoint: { x: 0, y: 0 },
229
+ };
230
+ if (naturalWidth / containerWidth > naturalHeight / containerHeight) {
231
+ scale = containerWidth / naturalWidth;
232
+ newImgDataState.width = containerWidth;
233
+ newImgDataState.height = naturalHeight * scale;
234
+ }
235
+ else {
236
+ scale = containerHeight / naturalHeight;
237
+ newImgDataState.width = naturalWidth * scale;
238
+ newImgDataState.height = containerHeight;
239
+ }
240
+ imgData.scale = scale;
241
+ newImgDataState.centerPoint = {
242
+ x: containerWidth / 2,
243
+ y: containerHeight / 2,
244
+ };
245
+ // 裁切框初始尺寸
246
+ const { defaultAspectRatio, aspectRatio } = getProps();
247
+ const calcAspect = aspectRatio || defaultAspectRatio;
248
+ const newCropperBoxState = {
249
+ width: 0,
250
+ height: 0,
251
+ centerPoint: { x: containerWidth / 2, y: containerHeight / 2 },
252
+ };
253
+ if (containerWidth / containerHeight > calcAspect) {
254
+ newCropperBoxState.width = containerHeight * calcAspect;
255
+ newCropperBoxState.height = containerHeight;
256
+ }
257
+ else {
258
+ newCropperBoxState.width = containerWidth;
259
+ newCropperBoxState.height = containerWidth / calcAspect;
260
+ }
261
+ setState({ imgData: newImgDataState, cropperBox: newCropperBoxState, loaded: true });
262
+ }
263
+ // ——— 滚轮缩放(以指针位置为缩放中心,钳制 min/maxZoom) ———
264
+ function handleWheel(e) {
265
+ e.preventDefault?.();
266
+ const { imgData: imgState, zoom: currZoom } = getState();
267
+ const { maxZoom, minZoom, zoomStep } = getProps();
268
+ let nextZoom;
269
+ if (e.deltaY < 0) {
270
+ // zoom in
271
+ if (currZoom + zoomStep <= maxZoom) {
272
+ nextZoom = Number((currZoom + zoomStep).toFixed(2));
273
+ }
274
+ }
275
+ else if (e.deltaY > 0) {
276
+ // zoom out
277
+ if (currZoom - zoomStep >= minZoom) {
278
+ nextZoom = Number((currZoom - zoomStep).toFixed(2));
279
+ }
280
+ }
281
+ if (nextZoom === undefined) {
282
+ return;
283
+ }
284
+ const container = adapter.getContainer();
285
+ if (!container)
286
+ return;
287
+ const boundingRect = container.getBoundingClientRect();
288
+ const offsetX = e.clientX - boundingRect.left;
289
+ const offsetY = e.clientY - boundingRect.top;
290
+ const scaleCenter = { x: offsetX, y: -offsetY };
291
+ const currentPoint = { ...imgState.centerPoint };
292
+ currentPoint.y = -currentPoint.y;
293
+ const newCenterPoint = {
294
+ x: ((currentPoint.x - scaleCenter.x) / currZoom) * nextZoom + scaleCenter.x,
295
+ y: -(((currentPoint.y - scaleCenter.y) / currZoom) * nextZoom + scaleCenter.y),
296
+ };
297
+ const newWidth = (imgState.width / currZoom) * nextZoom;
298
+ const newHeight = (imgState.height / currZoom) * nextZoom;
299
+ setState({
300
+ imgData: { width: newWidth, height: newHeight, centerPoint: newCenterPoint },
301
+ zoom: nextZoom,
302
+ });
303
+ adapter.notifyZoomChange(nextZoom);
304
+ }
305
+ // ——— 角点 resize:aspect 约束下的可移动范围 ———
306
+ function getRangeForAspectChange() {
307
+ const { cropperBox } = getState();
308
+ const { aspectRatio } = getProps();
309
+ const { width: containerWidth, height: containerHeight } = containerData;
310
+ if (!aspectRatio)
311
+ return;
312
+ let height;
313
+ let width;
314
+ const xMin = cropperBox.centerPoint.x - cropperBox.width / 2;
315
+ const xMax = cropperBox.centerPoint.x + cropperBox.width / 2;
316
+ const yMin = cropperBox.centerPoint.y - cropperBox.height / 2;
317
+ const yMax = cropperBox.centerPoint.y + cropperBox.height / 2;
318
+ switch (boxMoveDir) {
319
+ case 'tl':
320
+ height = yMax;
321
+ width = xMax;
322
+ [width, height] = getAspectHW(width, height, aspectRatio);
323
+ rangeX = [xMax - width, xMax];
324
+ rangeY = [yMax - height, yMax];
325
+ break;
326
+ case 'tm': {
327
+ height = yMax;
328
+ const leftHalfWidth = cropperBox.centerPoint.x;
329
+ const rightHalfWidth = containerWidth - cropperBox.centerPoint.x;
330
+ width = 2 * (leftHalfWidth < rightHalfWidth ? leftHalfWidth : rightHalfWidth);
331
+ [width, height] = getAspectHW(width, height, aspectRatio);
332
+ rangeX = [cropperBox.centerPoint.x - width / 2, cropperBox.centerPoint.x + width / 2];
333
+ rangeY = [yMax - height, yMax];
334
+ break;
335
+ }
336
+ case 'tr':
337
+ height = yMax;
338
+ width = containerWidth - xMin;
339
+ [width, height] = getAspectHW(width, height, aspectRatio);
340
+ rangeX = [xMin, xMin + width];
341
+ rangeY = [yMax - height, yMax];
342
+ break;
343
+ case 'ml': {
344
+ width = xMax;
345
+ const topHalfHeight = cropperBox.centerPoint.y;
346
+ const bottomHalfHeight = containerHeight - cropperBox.centerPoint.y;
347
+ height = 2 * (topHalfHeight < bottomHalfHeight ? topHalfHeight : bottomHalfHeight);
348
+ [width, height] = getAspectHW(width, height, aspectRatio);
349
+ rangeX = [xMax - width, xMax];
350
+ rangeY = [cropperBox.centerPoint.y - height / 2, cropperBox.centerPoint.y + height / 2];
351
+ break;
352
+ }
353
+ case 'mr': {
354
+ width = containerWidth - xMin;
355
+ const topHalfHeight = cropperBox.centerPoint.y;
356
+ const bottomHalfHeight = containerHeight - cropperBox.centerPoint.y;
357
+ height = 2 * (topHalfHeight < bottomHalfHeight ? topHalfHeight : bottomHalfHeight);
358
+ [width, height] = getAspectHW(width, height, aspectRatio);
359
+ rangeX = [xMin, xMin + width];
360
+ rangeY = [cropperBox.centerPoint.y - height / 2, cropperBox.centerPoint.y + height / 2];
361
+ break;
362
+ }
363
+ case 'bl':
364
+ height = containerHeight - yMin;
365
+ width = xMax;
366
+ [width, height] = getAspectHW(width, height, aspectRatio);
367
+ rangeX = [xMax - width, xMax];
368
+ rangeY = [yMin, yMin + height];
369
+ break;
370
+ case 'bm': {
371
+ height = containerHeight - yMin;
372
+ const leftHalfWidth = cropperBox.centerPoint.x;
373
+ const rightHalfWidth = containerWidth - cropperBox.centerPoint.x;
374
+ width = 2 * (leftHalfWidth < rightHalfWidth ? leftHalfWidth : rightHalfWidth);
375
+ [width, height] = getAspectHW(width, height, aspectRatio);
376
+ rangeX = [cropperBox.centerPoint.x - width / 2, cropperBox.centerPoint.x + width / 2];
377
+ rangeY = [yMin, yMin + height];
378
+ break;
379
+ }
380
+ case 'br':
381
+ height = containerHeight - yMin;
382
+ width = containerWidth - xMin;
383
+ [width, height] = getAspectHW(width, height, aspectRatio);
384
+ rangeX = [xMin, xMin + width];
385
+ rangeY = [yMin, yMin + height];
386
+ break;
387
+ default:
388
+ break;
389
+ }
390
+ }
391
+ /** 角点 mousedown:记录方向、算参数与可移动范围。渲染层随后绑 document 事件。 */
392
+ function handleCornerMouseDown(dir) {
393
+ boxMoveDir = dir;
394
+ boxMoveParam = getMoveParamByDir(dir);
395
+ const { aspectRatio } = getProps();
396
+ if (aspectRatio) {
397
+ getRangeForAspectChange();
398
+ }
399
+ else {
400
+ rangeX = [0, containerData.width];
401
+ rangeY = [0, containerData.height];
402
+ }
403
+ }
404
+ function changeDir() {
405
+ if (boxMoveDir.includes('t')) {
406
+ boxMoveDir = boxMoveDir.replace('t', 'b');
407
+ }
408
+ else if (boxMoveDir.includes('b')) {
409
+ boxMoveDir = boxMoveDir.replace('b', 't');
410
+ }
411
+ if (boxMoveDir.includes('l')) {
412
+ boxMoveDir = boxMoveDir.replace('l', 'r');
413
+ }
414
+ else if (boxMoveDir.includes('r')) {
415
+ boxMoveDir = boxMoveDir.replace('r', 'l');
416
+ }
417
+ }
418
+ /** aspectRatio 约束下的角点 resize。clientX/Y 为鼠标坐标。 */
419
+ function handleCornerAspectMouseMove(clientX, clientY) {
420
+ const { cropperBox } = getState();
421
+ const { aspectRatio } = getProps();
422
+ const container = adapter.getContainer();
423
+ if (!container || !aspectRatio)
424
+ return;
425
+ const boundingRect = container.getBoundingClientRect();
426
+ const newCropperBoxPos = {
427
+ width: cropperBox.width,
428
+ height: cropperBox.height,
429
+ centerPoint: { ...cropperBox.centerPoint },
430
+ };
431
+ let offsetX = 0;
432
+ let offsetY = 0;
433
+ if (boxMoveDir === 'ml' || boxMoveDir === 'mr') {
434
+ offsetX = getMiddle(clientX - boundingRect.left, rangeX);
435
+ }
436
+ else {
437
+ offsetY = getMiddle(clientY - boundingRect.top, rangeY);
438
+ }
439
+ switch (boxMoveDir) {
440
+ case 'tl':
441
+ newCropperBoxPos.height = rangeY[1] - offsetY;
442
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
443
+ newCropperBoxPos.centerPoint = {
444
+ x: rangeX[1] - newCropperBoxPos.width / 2,
445
+ y: rangeY[1] - newCropperBoxPos.height / 2,
446
+ };
447
+ break;
448
+ case 'tm':
449
+ newCropperBoxPos.height = rangeY[1] - offsetY;
450
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
451
+ newCropperBoxPos.centerPoint = {
452
+ x: cropperBox.centerPoint.x,
453
+ y: rangeY[1] - newCropperBoxPos.height / 2,
454
+ };
455
+ break;
456
+ case 'tr':
457
+ newCropperBoxPos.height = rangeY[1] - offsetY;
458
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
459
+ newCropperBoxPos.centerPoint = {
460
+ x: rangeX[0] + newCropperBoxPos.width / 2,
461
+ y: rangeY[1] - newCropperBoxPos.height / 2,
462
+ };
463
+ break;
464
+ case 'ml':
465
+ newCropperBoxPos.width = rangeX[1] - offsetX;
466
+ newCropperBoxPos.height = newCropperBoxPos.width / aspectRatio;
467
+ newCropperBoxPos.centerPoint = {
468
+ x: rangeX[1] - newCropperBoxPos.width / 2,
469
+ y: cropperBox.centerPoint.y,
470
+ };
471
+ break;
472
+ case 'mr':
473
+ newCropperBoxPos.width = offsetX - rangeX[0];
474
+ newCropperBoxPos.height = newCropperBoxPos.width / aspectRatio;
475
+ newCropperBoxPos.centerPoint = {
476
+ x: rangeX[0] + newCropperBoxPos.width / 2,
477
+ y: cropperBox.centerPoint.y,
478
+ };
479
+ break;
480
+ case 'bl':
481
+ newCropperBoxPos.height = offsetY - rangeY[0];
482
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
483
+ newCropperBoxPos.centerPoint = {
484
+ x: rangeX[1] - newCropperBoxPos.width / 2,
485
+ y: rangeY[0] + newCropperBoxPos.height / 2,
486
+ };
487
+ break;
488
+ case 'bm':
489
+ newCropperBoxPos.height = offsetY - rangeY[0];
490
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
491
+ newCropperBoxPos.centerPoint = {
492
+ x: cropperBox.centerPoint.x,
493
+ y: rangeY[0] + newCropperBoxPos.height / 2,
494
+ };
495
+ break;
496
+ case 'br':
497
+ newCropperBoxPos.height = offsetY - rangeY[0];
498
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
499
+ newCropperBoxPos.centerPoint = {
500
+ x: rangeX[0] + newCropperBoxPos.width / 2,
501
+ y: rangeY[0] + newCropperBoxPos.height / 2,
502
+ };
503
+ break;
504
+ default:
505
+ break;
506
+ }
507
+ // 收缩到 0 时反向翻转方向,允许跨过对边继续拉伸。
508
+ if (newCropperBoxPos.height === 0 && newCropperBoxPos.width === 0) {
509
+ changeDir();
510
+ getRangeForAspectChange();
511
+ }
512
+ setState({ cropperBox: newCropperBoxPos });
513
+ }
514
+ /** 自由(无 aspectRatio)角点 resize。 */
515
+ function handleCornerMouseMove(clientX, clientY) {
516
+ const { cropperBox } = getState();
517
+ const container = adapter.getContainer();
518
+ if (!container)
519
+ return;
520
+ const boundingRect = container.getBoundingClientRect();
521
+ const offsetX = getMiddle(clientX - boundingRect.left, rangeX);
522
+ const offsetY = getMiddle(clientY - boundingRect.top, rangeY);
523
+ const newCropperBoxPos = {
524
+ width: cropperBox.width,
525
+ height: cropperBox.height,
526
+ centerPoint: { x: cropperBox.centerPoint.x, y: cropperBox.centerPoint.y },
527
+ };
528
+ const { paramX, paramY } = boxMoveParam;
529
+ if (paramX) {
530
+ const x = cropperBox.centerPoint.x + (paramX * cropperBox.width) / 2;
531
+ newCropperBoxPos.width = cropperBox.width + paramX * (offsetX - x);
532
+ if (newCropperBoxPos.width < 0) {
533
+ newCropperBoxPos.width = -newCropperBoxPos.width;
534
+ boxMoveParam.paramX = -paramX;
535
+ }
536
+ newCropperBoxPos.centerPoint.x = offsetX - (boxMoveParam.paramX * newCropperBoxPos.width) / 2;
537
+ }
538
+ if (paramY) {
539
+ const y = cropperBox.centerPoint.y + (paramY * cropperBox.height) / 2;
540
+ newCropperBoxPos.height = cropperBox.height + paramY * (offsetY - y);
541
+ if (newCropperBoxPos.height < 0) {
542
+ newCropperBoxPos.height = -newCropperBoxPos.height;
543
+ boxMoveParam.paramY = -paramY;
544
+ }
545
+ newCropperBoxPos.centerPoint.y = offsetY - (boxMoveParam.paramY * newCropperBoxPos.height) / 2;
546
+ }
547
+ setState({ cropperBox: newCropperBoxPos });
548
+ }
549
+ /** 角点拖拽移动的统一入口,按当前是否 aspect 约束分派。 */
550
+ function handleCornerMove(clientX, clientY) {
551
+ const { aspectRatio } = getProps();
552
+ if (aspectRatio) {
553
+ handleCornerAspectMouseMove(clientX, clientY);
554
+ }
555
+ else {
556
+ handleCornerMouseMove(clientX, clientY);
557
+ }
558
+ }
559
+ function handleCornerMouseUp() {
560
+ boxMoveParam = { paramX: 0, paramY: 0 };
561
+ }
562
+ // ——— 裁切框整体拖拽 ———
563
+ function handleCropperBoxMouseDown(clientX, clientY) {
564
+ const { cropperBox } = getState();
565
+ const container = adapter.getContainer();
566
+ if (!container)
567
+ return;
568
+ const boundingRect = container.getBoundingClientRect();
569
+ cropperBoxMoveStart = { x: clientX, y: clientY };
570
+ moveRange = {
571
+ xMin: cropperBox.width / 2,
572
+ xMax: boundingRect.width - cropperBox.width / 2,
573
+ yMin: cropperBox.height / 2,
574
+ yMax: boundingRect.height - cropperBox.height / 2,
575
+ };
576
+ }
577
+ function handleCropperBoxMouseMove(clientX, clientY) {
578
+ if (!cropperBoxMoveStart)
579
+ return;
580
+ const { cropperBox } = getState();
581
+ const offsetX = clientX - cropperBoxMoveStart.x;
582
+ const offsetY = clientY - cropperBoxMoveStart.y;
583
+ const newCenterPointX = getMiddle(cropperBox.centerPoint.x + offsetX, [
584
+ moveRange.xMin,
585
+ moveRange.xMax,
586
+ ]);
587
+ const newCenterPointY = getMiddle(cropperBox.centerPoint.y + offsetY, [
588
+ moveRange.yMin,
589
+ moveRange.yMax,
590
+ ]);
591
+ cropperBoxMoveStart = { x: clientX, y: clientY };
592
+ setState({
593
+ cropperBox: {
594
+ width: cropperBox.width,
595
+ height: cropperBox.height,
596
+ centerPoint: { x: newCenterPointX, y: newCenterPointY },
597
+ },
598
+ });
599
+ }
600
+ function handleCropperBoxMouseUp() {
601
+ if (!cropperBoxMoveStart)
602
+ return;
603
+ cropperBoxMoveStart = null;
604
+ }
605
+ // ——— 遮罩层(图片)拖拽 ———
606
+ function handleImgMoveStart(clientX, clientY) {
607
+ imgMoveStart = { x: clientX, y: clientY };
608
+ }
609
+ function handleImgMove(clientX, clientY) {
610
+ if (!imgMoveStart)
611
+ return;
612
+ const { imgData: imgState } = getState();
613
+ const offsetX = clientX - imgMoveStart.x;
614
+ const offsetY = clientY - imgMoveStart.y;
615
+ imgMoveStart = { x: clientX, y: clientY };
616
+ setState({
617
+ imgData: {
618
+ width: imgState.width,
619
+ height: imgState.height,
620
+ centerPoint: {
621
+ x: imgState.centerPoint.x + offsetX,
622
+ y: imgState.centerPoint.y + offsetY,
623
+ },
624
+ },
625
+ });
626
+ }
627
+ function handleImgMoveUp() {
628
+ if (!imgMoveStart)
629
+ return;
630
+ imgMoveStart = null;
631
+ }
632
+ // ——— 受控 rotate / zoom 变化的几何重算(映射 Semi getDerivedStateFromProps) ———
633
+ /**
634
+ * 由渲染层在 rotate/zoom prop 变化时调用,围绕裁切框中心旋转/缩放图片中心点。
635
+ * 返回新的 imgData(渲染层写回 state)。不含 loaded 判定(渲染层已判)。
636
+ */
637
+ function computeControlledImgData(nextRotate, nextZoom) {
638
+ const { rotate, zoom, imgData: imgState, cropperBox } = getState();
639
+ let nextWidth = imgState.width;
640
+ let nextHeight = imgState.height;
641
+ let nextImgCenter = { ...imgState.centerPoint };
642
+ if (nextRotate !== rotate) {
643
+ const rotateCenter = { x: cropperBox.centerPoint.x, y: -cropperBox.centerPoint.y };
644
+ const imgCenter = { x: imgState.centerPoint.x, y: -imgState.centerPoint.y };
645
+ const angle = ((nextRotate - rotate) * Math.PI) / 180;
646
+ nextImgCenter = {
647
+ x: (imgCenter.x - rotateCenter.x) * Math.cos(angle) +
648
+ (imgCenter.y - rotateCenter.y) * Math.sin(angle) +
649
+ rotateCenter.x,
650
+ y: -(-(imgCenter.x - rotateCenter.x) * Math.sin(angle) +
651
+ (imgCenter.y - rotateCenter.y) * Math.cos(angle) +
652
+ rotateCenter.y),
653
+ };
654
+ }
655
+ if (nextZoom !== zoom) {
656
+ const scaleCenter = { x: cropperBox.centerPoint.x, y: -cropperBox.centerPoint.y };
657
+ const currentImgCenter = { x: nextImgCenter.x, y: -nextImgCenter.y };
658
+ nextWidth = (imgState.width / zoom) * nextZoom;
659
+ nextHeight = (imgState.height / zoom) * nextZoom;
660
+ nextImgCenter = {
661
+ x: ((currentImgCenter.x - scaleCenter.x) / zoom) * nextZoom + scaleCenter.x,
662
+ y: -(((currentImgCenter.y - scaleCenter.y) / zoom) * nextZoom + scaleCenter.y),
663
+ };
664
+ }
665
+ return { width: nextWidth, height: nextHeight, centerPoint: nextImgCenter };
666
+ }
667
+ // ——— 裁切结果 canvas ———
668
+ /**
669
+ * 依当前裁切框/图片/rotate/zoom 生成裁切结果 canvas。
670
+ * 逐行移植 Semi getCropperCanvas:先绘旋转后的整图到中间 canvas,
671
+ * 再按裁切框相对位置 getImageData/putImageData 到结果 canvas,超界用 fill 填充。
672
+ * 需要 DOM canvas 2D 上下文,jsdom 下不可用(渲染层测试对此 skip)。
673
+ */
674
+ function getCropperCanvas() {
675
+ const { cropperBox, imgData: imgState, rotate, zoom } = getState();
676
+ const { fill } = getProps();
677
+ const canvas = document.createElement('canvas');
678
+ const ctx = canvas.getContext('2d');
679
+ const img = adapter.getImg();
680
+ if (!ctx || !img) {
681
+ return canvas;
682
+ }
683
+ const angle = (rotate * Math.PI) / 180;
684
+ const sine = Math.abs(Math.sin(angle));
685
+ const cosine = Math.abs(Math.cos(angle));
686
+ const imgWidth = imgData.originalWidth;
687
+ const imgHeight = imgData.originalHeight;
688
+ const containerWidth = imgWidth * cosine + imgHeight * sine;
689
+ const containerHeight = imgHeight * cosine + imgWidth * sine;
690
+ const cropperContainerWidth = containerWidth * zoom * imgData.scale;
691
+ const cropperContainerHeight = containerHeight * zoom * imgData.scale;
692
+ const cropperContainerTop = imgState.centerPoint.y - cropperContainerHeight / 2;
693
+ const cropperContainerLeft = imgState.centerPoint.x - cropperContainerWidth / 2;
694
+ const cropperBoxLeft = cropperBox.centerPoint.x - cropperBox.width / 2;
695
+ const cropperBoxTop = cropperBox.centerPoint.y - cropperBox.height / 2;
696
+ const realZoom = zoom * imgData.scale;
697
+ const relativeCropLeft = (cropperBoxLeft - cropperContainerLeft) / realZoom;
698
+ const relativeCropTop = (cropperBoxTop - cropperContainerTop) / realZoom;
699
+ const relativeWidth = cropperBox.width / realZoom;
700
+ const relativeHeight = cropperBox.height / realZoom;
701
+ const relativeCropRight = relativeCropLeft + relativeWidth;
702
+ const relativeCropBottom = relativeCropTop + relativeHeight;
703
+ if (relativeCropRight < 0 ||
704
+ relativeCropBottom < 0 ||
705
+ relativeCropLeft > containerWidth ||
706
+ relativeCropTop > containerHeight) {
707
+ const emptyCanvas = document.createElement('canvas');
708
+ const emptyCtx = emptyCanvas.getContext('2d');
709
+ emptyCanvas.width = relativeWidth;
710
+ emptyCanvas.height = relativeHeight;
711
+ if (emptyCtx) {
712
+ emptyCtx.fillStyle = fill;
713
+ emptyCtx.fillRect(0, 0, relativeWidth, relativeHeight);
714
+ }
715
+ return emptyCanvas;
716
+ }
717
+ canvas.width = containerWidth;
718
+ canvas.height = containerHeight;
719
+ ctx.fillStyle = fill;
720
+ ctx.fillRect(0, 0, containerWidth, containerHeight);
721
+ const halfWidth = containerWidth / 2;
722
+ const halfHeight = containerHeight / 2;
723
+ ctx.translate(halfWidth, halfHeight);
724
+ ctx.rotate((rotate * Math.PI) / 180);
725
+ ctx.translate(-halfWidth, -halfHeight);
726
+ const imgX = (containerWidth - imgWidth) / 2;
727
+ const imgY = (containerHeight - imgHeight) / 2;
728
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight, imgX, imgY, imgWidth, imgHeight);
729
+ const canvas2 = document.createElement('canvas');
730
+ const ctx2 = canvas2.getContext('2d');
731
+ let realLeft = relativeCropLeft;
732
+ let realTop = relativeCropTop;
733
+ let realWidth = relativeWidth;
734
+ let realHeight = relativeHeight;
735
+ if (relativeCropLeft < 0) {
736
+ realLeft = 0;
737
+ }
738
+ if (relativeCropTop < 0) {
739
+ realTop = 0;
740
+ }
741
+ if (relativeCropRight > containerWidth) {
742
+ realWidth = containerWidth - realLeft;
743
+ }
744
+ else if (relativeCropLeft < 0) {
745
+ realWidth = relativeCropRight;
746
+ }
747
+ if (relativeCropBottom > containerHeight) {
748
+ realHeight = containerHeight - realTop;
749
+ }
750
+ else if (relativeCropTop < 0) {
751
+ realHeight = relativeCropBottom;
752
+ }
753
+ const imgDataResult = ctx.getImageData(realLeft, realTop, realWidth, realHeight);
754
+ canvas2.width = relativeWidth;
755
+ canvas2.height = relativeHeight;
756
+ if (ctx2) {
757
+ ctx2.fillStyle = fill;
758
+ ctx2.fillRect(0, 0, relativeWidth, relativeHeight);
759
+ ctx2.putImageData(imgDataResult, relativeCropLeft < 0 ? -relativeCropLeft : 0, relativeCropTop < 0 ? -relativeCropTop : 0);
760
+ }
761
+ return canvas2;
762
+ }
763
+ /**
764
+ * 预览尺寸/变换计算(映射 Semi updatePreview 的纯几何部分)。
765
+ * 输入预览容器初始尺寸 + 当前图片/裁切框态,返回预览 img 与容器的样式尺寸。
766
+ */
767
+ function computePreview(previewContainerInitSize, props) {
768
+ const { cropperBox } = getState();
769
+ let zoom;
770
+ const { width: containerWidth, height: containerHeight } = previewContainerInitSize;
771
+ let previewWidth = containerWidth;
772
+ let previewHeight = containerHeight;
773
+ if (previewWidth < previewHeight) {
774
+ zoom = containerWidth / cropperBox.width;
775
+ const tempHeight = zoom * cropperBox.height;
776
+ if (tempHeight > containerHeight) {
777
+ zoom = containerHeight / cropperBox.height;
778
+ previewWidth = zoom * cropperBox.width;
779
+ }
780
+ else {
781
+ previewHeight = tempHeight;
782
+ }
783
+ }
784
+ else {
785
+ zoom = containerHeight / cropperBox.height;
786
+ const tempWidth = zoom * cropperBox.width;
787
+ if (tempWidth > containerWidth) {
788
+ zoom = containerWidth / cropperBox.width;
789
+ previewHeight = zoom * cropperBox.height;
790
+ }
791
+ else {
792
+ previewWidth = tempWidth;
793
+ }
794
+ }
795
+ return {
796
+ imgWidth: props.width * zoom,
797
+ imgHeight: props.height * zoom,
798
+ translateX: props.translateX * zoom,
799
+ translateY: props.translateY * zoom,
800
+ rotate: props.rotate,
801
+ containerWidth: previewWidth,
802
+ containerHeight: previewHeight,
803
+ };
804
+ }
805
+ return {
806
+ // 状态读取(供测试/渲染层查内部簿记)
807
+ getContainerData: () => ({ ...containerData }),
808
+ getImageMeta: () => ({ ...imgData }),
809
+ getBoxMoveDir: () => boxMoveDir,
810
+ getRangeX: () => [...rangeX],
811
+ getRangeY: () => [...rangeY],
812
+ // 生命周期
813
+ init,
814
+ // resize
815
+ handleResize,
816
+ getImgDataWhenResize,
817
+ getCropperBoxWhenResize,
818
+ // 加载 / 缩放
819
+ handleImageLoad,
820
+ handleWheel,
821
+ // 角点 resize
822
+ handleCornerMouseDown,
823
+ handleCornerMove,
824
+ handleCornerMouseUp,
825
+ getRangeForAspectChange,
826
+ // 裁切框拖拽
827
+ handleCropperBoxMouseDown,
828
+ handleCropperBoxMouseMove,
829
+ handleCropperBoxMouseUp,
830
+ // 图片拖拽
831
+ handleImgMoveStart,
832
+ handleImgMove,
833
+ handleImgMoveUp,
834
+ // 受控变换
835
+ computeControlledImgData,
836
+ // 结果 / 预览
837
+ getCropperCanvas,
838
+ computePreview,
839
+ };
840
+ }
841
+ //# sourceMappingURL=cropper.js.map