flet-box 0.0.0-stage → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -2
  3. package/bin/cli.js +394 -0
  4. package/bin/commands/buildAndroid.js +200 -0
  5. package/bin/commands/buildIOS.js +237 -0
  6. package/bin/commands/create.js +406 -0
  7. package/bin/commands/createBundle.js +375 -0
  8. package/bin/commands/createComponent.js +115 -0
  9. package/bin/commands/createScreen.js +161 -0
  10. package/bin/commands/killServer.js +107 -0
  11. package/bin/commands/package.js +131 -0
  12. package/bin/commands/release.js +189 -0
  13. package/bin/commands/runBundle.js +62 -0
  14. package/bin/commands/runCloudflare.js +89 -0
  15. package/bin/commands/runNative.js +109 -0
  16. package/bin/commands/runServer.js +390 -0
  17. package/bin/ota/loader.js +361 -0
  18. package/bin/utils/bundleConfig.js +178 -0
  19. package/bin/utils/capacitor.js +137 -0
  20. package/bin/utils/colors.js +215 -0
  21. package/bin/utils/helpers.js +80 -0
  22. package/bin/utils/spinner.js +96 -0
  23. package/bin/utils/templates.js +1824 -0
  24. package/bin/utils/version.js +108 -0
  25. package/package.json +58 -4
  26. package/src/animations/AnimatedBox.js +607 -0
  27. package/src/animations/AnimatedText.js +105 -0
  28. package/src/animations/MatrixRain.js +220 -0
  29. package/src/animations/ParallaxBox.js +369 -0
  30. package/src/animations/animate.js +332 -0
  31. package/src/animations/index.js +25 -0
  32. package/src/app.js +41 -0
  33. package/src/assets/icon-1024-maskable.png +0 -0
  34. package/src/assets/icon-192-maskable.png +0 -0
  35. package/src/assets/icon-192.png +0 -0
  36. package/src/assets/icon-512-maskable.png +0 -0
  37. package/src/assets/icon-512.png +0 -0
  38. package/src/assets/logo.png +0 -0
  39. package/src/core/App.js +44 -0
  40. package/src/core/hmr-client.js +142 -0
  41. package/src/core/index.js +34 -0
  42. package/src/core/pwa.js +100 -0
  43. package/src/core/runApp.js +430 -0
  44. package/src/create-index.sh +237 -0
  45. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  46. package/src/fonts/icons.css +23 -0
  47. package/src/index.d.ts +1930 -0
  48. package/src/index.js +288 -0
  49. package/src/navigations/AdaptiveScaffold.js +149 -0
  50. package/src/navigations/AppBar.js +331 -0
  51. package/src/navigations/BottomNavigation.js +298 -0
  52. package/src/navigations/CollapsibleSideBar.js +139 -0
  53. package/src/navigations/Drawer.js +333 -0
  54. package/src/navigations/DrawerItem.js +260 -0
  55. package/src/navigations/Router.js +475 -0
  56. package/src/navigations/Scaffold.js +537 -0
  57. package/src/navigations/Tabs.js +384 -0
  58. package/src/navigations/index.js +57 -0
  59. package/src/services/RamStore.js +213 -0
  60. package/src/services/Session.js +310 -0
  61. package/src/services/Storage.js +309 -0
  62. package/src/services/http.js +350 -0
  63. package/src/services/index.js +57 -0
  64. package/src/tools/animation.js +42 -0
  65. package/src/tools/array.js +95 -0
  66. package/src/tools/border.js +28 -0
  67. package/src/tools/clipboard.js +104 -0
  68. package/src/tools/color.js +329 -0
  69. package/src/tools/createList.js +215 -0
  70. package/src/tools/delay.js +55 -0
  71. package/src/tools/device.js +138 -0
  72. package/src/tools/dict.js +297 -0
  73. package/src/tools/dimensions.js +118 -0
  74. package/src/tools/filter.js +41 -0
  75. package/src/tools/flex.js +26 -0
  76. package/src/tools/gradient.js +34 -0
  77. package/src/tools/grid.js +57 -0
  78. package/src/tools/index.js +38 -0
  79. package/src/tools/mapList.js +107 -0
  80. package/src/tools/margin.js +93 -0
  81. package/src/tools/memo.js +60 -0
  82. package/src/tools/os.js +104 -0
  83. package/src/tools/padding.js +93 -0
  84. package/src/tools/print.js +69 -0
  85. package/src/tools/random.js +299 -0
  86. package/src/tools/ref.js +70 -0
  87. package/src/tools/rgba.js +4 -0
  88. package/src/tools/shadow.js +23 -0
  89. package/src/tools/string.js +85 -0
  90. package/src/tools/time.js +674 -0
  91. package/src/tools/transform.js +68 -0
  92. package/src/tools/transition.js +48 -0
  93. package/src/tools/useState.js +268 -0
  94. package/src/tools/uuid.js +42 -0
  95. package/src/utils/TextInputValidator.js +256 -0
  96. package/src/utils/composeUpdate.js +65 -0
  97. package/src/utils/getWidgetProps.js +35 -0
  98. package/src/utils/index.js +76 -0
  99. package/src/utils/markdownParser.js +368 -0
  100. package/src/utils/mediaTime.js +79 -0
  101. package/src/utils/navigation.js +263 -0
  102. package/src/utils/scriptInjector.js +73 -0
  103. package/src/utils/stackPosition.js +31 -0
  104. package/src/utils/stopWebRefresh.js +77 -0
  105. package/src/utils/styleInjector.js +38 -0
  106. package/src/utils/syntaxHighlight.js +339 -0
  107. package/src/utils/themes.js +347 -0
  108. package/src/utils/units.js +87 -0
  109. package/src/utils/visualEffects.js +169 -0
  110. package/src/widget-factory/addChildren.js +114 -0
  111. package/src/widget-factory/assignProps.js +256 -0
  112. package/src/widget-factory/createWidget.js +28 -0
  113. package/src/widget-factory/effects.js +132 -0
  114. package/src/widget-factory/index.js +7 -0
  115. package/src/widget-factory/lifecycle.js +210 -0
  116. package/src/widget-factory/makeParentable.js +89 -0
  117. package/src/widget-factory/pixTool.js +40 -0
  118. package/src/widget-factory/processProps.js +122 -0
  119. package/src/widget-factory/reactivity.js +98 -0
  120. package/src/widget-factory/remTool.js +34 -0
  121. package/src/widget-factory/stackPosition.js +87 -0
  122. package/src/widget-factory/stringTool.js +26 -0
  123. package/src/widget-factory/tools.js +249 -0
  124. package/src/widget-factory/translateProps.js +1049 -0
  125. package/src/widget-factory/warnings.js +284 -0
  126. package/src/widget-factory/widgetFactory.js +200 -0
  127. package/src/widgets/Accordion.js +562 -0
  128. package/src/widgets/AdMob.js +315 -0
  129. package/src/widgets/AdSense.js +268 -0
  130. package/src/widgets/AlertDialog.js +162 -0
  131. package/src/widgets/Audio.js +311 -0
  132. package/src/widgets/Avatar.js +210 -0
  133. package/src/widgets/Badge.js +189 -0
  134. package/src/widgets/BottomSheet.js +287 -0
  135. package/src/widgets/Button.js +247 -0
  136. package/src/widgets/Card.js +33 -0
  137. package/src/widgets/Carousel.js +301 -0
  138. package/src/widgets/Chart.js +632 -0
  139. package/src/widgets/Checkbox.js +106 -0
  140. package/src/widgets/Chip.js +153 -0
  141. package/src/widgets/CircularBar.js +352 -0
  142. package/src/widgets/CircularChart.js +549 -0
  143. package/src/widgets/CodeViewer.js +236 -0
  144. package/src/widgets/Column.js +29 -0
  145. package/src/widgets/Container.js +31 -0
  146. package/src/widgets/DataTable.js +257 -0
  147. package/src/widgets/Divider.js +93 -0
  148. package/src/widgets/DraggBox.js +115 -0
  149. package/src/widgets/Dropdown.js +505 -0
  150. package/src/widgets/DroppBox.js +180 -0
  151. package/src/widgets/FloatingActionButton.js +91 -0
  152. package/src/widgets/GridView.js +20 -0
  153. package/src/widgets/Icon.js +33 -0
  154. package/src/widgets/Image.js +15 -0
  155. package/src/widgets/Input.js +565 -0
  156. package/src/widgets/Inspector.js +254 -0
  157. package/src/widgets/InstallButton.js +110 -0
  158. package/src/widgets/ListTile.js +224 -0
  159. package/src/widgets/ListView.js +503 -0
  160. package/src/widgets/Markdown.js +1236 -0
  161. package/src/widgets/Modal.js +496 -0
  162. package/src/widgets/Pagination.js +195 -0
  163. package/src/widgets/ProgressBar.js +178 -0
  164. package/src/widgets/QRCode.js +467 -0
  165. package/src/widgets/Radio.js +132 -0
  166. package/src/widgets/Rating.js +261 -0
  167. package/src/widgets/Row.js +29 -0
  168. package/src/widgets/Skeleton.js +243 -0
  169. package/src/widgets/Slider.js +527 -0
  170. package/src/widgets/SnackBar.js +227 -0
  171. package/src/widgets/Stack.js +29 -0
  172. package/src/widgets/Stepper.js +296 -0
  173. package/src/widgets/Switch.js +102 -0
  174. package/src/widgets/Text.js +98 -0
  175. package/src/widgets/Tooltip.js +267 -0
  176. package/src/widgets/TreeView.js +370 -0
  177. package/src/widgets/Video.js +92 -0
  178. package/src/widgets/index.js +52 -0
@@ -0,0 +1,549 @@
1
+ /**
2
+ * @file CircularChart.js
3
+ * @description Pie/donut chart drawn on canvas with legend support.
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+ import { colors } from "../utils/themes.js";
7
+
8
+ export const CircularChart = (props) => {
9
+ let {
10
+ data = [],
11
+ size = null,
12
+ strokeWidth = 20,
13
+ innerRadius = null,
14
+ rounded = false,
15
+ cornerRadius = 0,
16
+ startAngle = -Math.PI / 2,
17
+ endAngle = null,
18
+ animate = true,
19
+ animationDuration = 1000,
20
+ onComplete,
21
+ centerContent = null,
22
+ showLabels = false,
23
+ labelSize = 12,
24
+ labelColor = colors.text,
25
+ onClick = null,
26
+ onHover = null,
27
+ defaultColors = [
28
+ colors.primary,
29
+ colors.secondary,
30
+ colors.success,
31
+ colors.warning,
32
+ colors.danger,
33
+ colors.info,
34
+ ],
35
+ ref,
36
+ shadowBlur = 0,
37
+ shadowColor = "rgba(0,0,0,0.2)",
38
+ glow = false,
39
+ glowColor = null,
40
+ semiCircle = null,
41
+ borderRadius = 0,
42
+ borderColor = null,
43
+ borderWidth = 0,
44
+ sliceBorderWidth = 0,
45
+ sliceBorderColor = "#ffffff",
46
+ ...rest
47
+ } = props;
48
+
49
+ if (!Array.isArray(data)) data = [];
50
+
51
+ // Configure angles for semi-circle
52
+ if (semiCircle) {
53
+ switch (semiCircle) {
54
+ case "top":
55
+ startAngle = -Math.PI;
56
+ endAngle = 0;
57
+ break;
58
+ case "bottom":
59
+ startAngle = 0;
60
+ endAngle = Math.PI;
61
+ break;
62
+ case "left":
63
+ startAngle = -Math.PI / 2;
64
+ endAngle = Math.PI / 2;
65
+ break;
66
+ case "right":
67
+ startAngle = Math.PI / 2;
68
+ endAngle = (3 * Math.PI) / 2;
69
+ break;
70
+ }
71
+ }
72
+
73
+ let finalStartAngle = startAngle;
74
+ let finalEndAngle = endAngle !== null ? endAngle : startAngle + Math.PI * 2;
75
+
76
+ let finalInnerRadius = innerRadius;
77
+ if (finalInnerRadius === null && strokeWidth > 0) {
78
+ const defaultRadius = size ? size / 2 : 100;
79
+ finalInnerRadius = defaultRadius - strokeWidth;
80
+ }
81
+
82
+ const isSemi = semiCircle !== null;
83
+ const diameter = size || 200;
84
+ const radius = diameter / 2;
85
+ let canvasWidth = diameter;
86
+ let canvasHeight = diameter;
87
+ if (isSemi) {
88
+ switch (semiCircle) {
89
+ case "top":
90
+ canvasHeight = radius;
91
+ break;
92
+ case "bottom":
93
+ canvasHeight = radius;
94
+ break;
95
+ case "left":
96
+ canvasWidth = radius;
97
+ break;
98
+ case "right":
99
+ canvasWidth = radius;
100
+ break;
101
+ }
102
+ }
103
+
104
+ let canvas = null;
105
+ let ctx = null;
106
+ let container = null;
107
+ let animationId = null;
108
+ let currentData = [];
109
+ let startTime = null;
110
+ let resizeObserver = null;
111
+ let currentWidth = canvasWidth;
112
+ let currentHeight = canvasHeight;
113
+ let hoveredIndex = -1;
114
+
115
+ const isFixedSize = size !== null && size !== undefined;
116
+
117
+ const updateCanvasSize = () => {
118
+ if (!canvas) return;
119
+ if (isFixedSize) {
120
+ canvas.width = canvasWidth;
121
+ canvas.height = canvasHeight;
122
+ currentWidth = canvasWidth;
123
+ currentHeight = canvasHeight;
124
+ } else {
125
+ const rect = container.getBoundingClientRect();
126
+ let w = rect.width;
127
+ if (w <= 0) w = 200;
128
+ if (isSemi) {
129
+ if (semiCircle === "top" || semiCircle === "bottom") {
130
+ canvas.width = w;
131
+ canvas.height = w / 2;
132
+ } else {
133
+ canvas.width = w / 2;
134
+ canvas.height = w;
135
+ }
136
+ } else {
137
+ canvas.width = w;
138
+ canvas.height = w;
139
+ }
140
+ currentWidth = canvas.width;
141
+ currentHeight = canvas.height;
142
+ }
143
+ };
144
+
145
+ const getCenterX = () => {
146
+ if (!isSemi) return currentWidth / 2;
147
+ switch (semiCircle) {
148
+ case "left":
149
+ return currentWidth;
150
+ case "right":
151
+ return 0;
152
+ default:
153
+ return currentWidth / 2;
154
+ }
155
+ };
156
+ const getCenterY = () => {
157
+ if (!isSemi) return currentHeight / 2;
158
+ switch (semiCircle) {
159
+ case "top":
160
+ return currentHeight;
161
+ case "bottom":
162
+ return 0;
163
+ default:
164
+ return currentHeight / 2;
165
+ }
166
+ };
167
+ const getRadiusVal = () => Math.min(currentWidth, currentHeight);
168
+
169
+ const computeAngles = (values) => {
170
+ if (!values || values.length === 0) return [];
171
+ const total = values.reduce((sum, v) => sum + (v || 0), 0);
172
+ if (total === 0) return [];
173
+ let start = finalStartAngle;
174
+ const slices = [];
175
+ for (let i = 0; i < values.length; i++) {
176
+ const angle = (values[i] / total) * (finalEndAngle - finalStartAngle);
177
+ slices.push({
178
+ start,
179
+ end: start + angle,
180
+ value: values[i] || 0,
181
+ original: data[i] || { value: values[i] },
182
+ });
183
+ start += angle;
184
+ }
185
+ return slices;
186
+ };
187
+
188
+ let currentSlices = [];
189
+
190
+ const getColor = (index, originalColor) => {
191
+ if (originalColor) return originalColor;
192
+ return defaultColors[index % defaultColors.length];
193
+ };
194
+
195
+ // Draw each segment with rounded ends (each one with its own color)
196
+ const drawRoundedSegment = (slice, index) => {
197
+ const r = getRadiusVal() / 2;
198
+ const innerR = finalInnerRadius !== null ? finalInnerRadius : 0;
199
+ const cx = getCenterX();
200
+ const cy = getCenterY();
201
+ const start = slice.start;
202
+ const end = slice.end;
203
+ const color = getColor(index, slice.original?.color);
204
+
205
+ // For donut with cornerRadius > 0: draw as a rounded stroke on both ends
206
+ if (cornerRadius > 0 && innerR > 0) {
207
+ const thickness = r - innerR;
208
+ const midRadius = (r + innerR) / 2;
209
+ ctx.save();
210
+ ctx.beginPath();
211
+ ctx.arc(cx, cy, midRadius, start, end);
212
+ ctx.lineWidth = thickness;
213
+ ctx.strokeStyle = color;
214
+ ctx.lineCap = "round"; // Rounds both ends of the segment
215
+ ctx.stroke();
216
+ ctx.restore();
217
+ } else {
218
+ // Normal mode (fill)
219
+ ctx.save();
220
+ ctx.beginPath();
221
+ if (innerR > 0) {
222
+ ctx.arc(cx, cy, r, start, end);
223
+ ctx.arc(cx, cy, innerR, end, start, true);
224
+ ctx.closePath();
225
+ } else {
226
+ ctx.moveTo(cx, cy);
227
+ ctx.arc(cx, cy, r, start, end);
228
+ ctx.closePath();
229
+ }
230
+ ctx.fillStyle = color;
231
+ ctx.fill();
232
+ ctx.restore();
233
+ }
234
+
235
+ // Border between segments (optional)
236
+ if (sliceBorderWidth > 0 && sliceBorderColor) {
237
+ ctx.save();
238
+ ctx.strokeStyle = sliceBorderColor;
239
+ ctx.lineWidth = sliceBorderWidth;
240
+ ctx.beginPath();
241
+ if (innerR > 0) {
242
+ ctx.arc(cx, cy, r, start, end);
243
+ ctx.arc(cx, cy, innerR, end, start, true);
244
+ ctx.closePath();
245
+ } else {
246
+ ctx.moveTo(cx, cy);
247
+ ctx.arc(cx, cy, r, start, end);
248
+ ctx.closePath();
249
+ }
250
+ ctx.stroke();
251
+ ctx.restore();
252
+ }
253
+
254
+ // Rounded for pie chart (rounded ends)
255
+ if (rounded && !innerR) {
256
+ const midAngle = (start + end) / 2;
257
+ const x = cx + r * Math.cos(midAngle);
258
+ const y = cy + r * Math.sin(midAngle);
259
+ ctx.beginPath();
260
+ ctx.arc(x, y, r * 0.05, 0, Math.PI * 2);
261
+ ctx.fillStyle = color;
262
+ ctx.fill();
263
+ }
264
+
265
+ // Labels
266
+ if (showLabels && slice.value > 0) {
267
+ const midAngle = (start + end) / 2;
268
+ const labelRadius = innerR > 0 ? (innerR + r) / 2 : r * 0.7;
269
+ const x = cx + labelRadius * Math.cos(midAngle);
270
+ const y = cy + labelRadius * Math.sin(midAngle);
271
+ ctx.font = `${labelSize}px system-ui`;
272
+ ctx.fillStyle = labelColor;
273
+ ctx.shadowBlur = 0;
274
+ const percent = Math.round(
275
+ (slice.value / (slice.end - slice.start)) * 100,
276
+ );
277
+ ctx.fillText(slice.original?.label || `${percent}%`, x, y);
278
+ }
279
+ };
280
+
281
+ const drawBackground = () => {
282
+ if (finalInnerRadius > 0) {
283
+ const r = getRadiusVal() / 2;
284
+ const cx = getCenterX();
285
+ const cy = getCenterY();
286
+ ctx.beginPath();
287
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
288
+ ctx.fillStyle = colors.gray200;
289
+ ctx.fill();
290
+ }
291
+ };
292
+
293
+ const drawCenterContent = () => {
294
+ if (!centerContent) return;
295
+ let cx = getCenterX();
296
+ let cy = getCenterY();
297
+ if (isSemi) {
298
+ const r = getRadiusVal() / 2;
299
+ switch (semiCircle) {
300
+ case "top":
301
+ cy = cy - r * 0.3;
302
+ break;
303
+ case "bottom":
304
+ cy = cy + r * 0.3;
305
+ break;
306
+ case "left":
307
+ cx = cx - r * 0.3;
308
+ break;
309
+ case "right":
310
+ cx = cx + r * 0.3;
311
+ break;
312
+ }
313
+ }
314
+ if (typeof centerContent === "string") {
315
+ ctx.font = `${Math.min(currentWidth * 0.1, 24)}px system-ui`;
316
+ ctx.fillStyle = colors.text;
317
+ ctx.textAlign = "center";
318
+ ctx.textBaseline = "middle";
319
+ ctx.fillText(centerContent, cx, cy);
320
+ }
321
+ };
322
+
323
+ const draw = () => {
324
+ if (!ctx || !canvas) return;
325
+ ctx.clearRect(0, 0, currentWidth, currentHeight);
326
+ drawBackground();
327
+ if (currentSlices && currentSlices.length > 0) {
328
+ currentSlices.forEach((slice, idx) => drawRoundedSegment(slice, idx));
329
+ }
330
+ ctx.shadowBlur = 0;
331
+ drawCenterContent();
332
+ };
333
+
334
+ const updateAnimation = (timestamp) => {
335
+ if (!ctx || !canvas) return;
336
+ if (!startTime) {
337
+ startTime = timestamp;
338
+ requestAnimationFrame(updateAnimation);
339
+ return;
340
+ }
341
+ const elapsed = timestamp - startTime;
342
+ const progress = Math.min(1, elapsed / animationDuration);
343
+
344
+ if (data && data.length > 0) {
345
+ for (let i = 0; i < data.length; i++) {
346
+ const targetValue = data[i]?.value || 0;
347
+ currentData[i] = targetValue * progress;
348
+ }
349
+ }
350
+ currentSlices = computeAngles(currentData);
351
+ draw();
352
+
353
+ if (progress < 1 && animate) {
354
+ animationId = requestAnimationFrame(updateAnimation);
355
+ } else {
356
+ if (data && data.length > 0) {
357
+ for (let i = 0; i < data.length; i++) {
358
+ currentData[i] = data[i]?.value || 0;
359
+ }
360
+ }
361
+ currentSlices = computeAngles(currentData);
362
+ draw();
363
+ if (onComplete) onComplete();
364
+ animationId = null;
365
+ }
366
+ };
367
+
368
+ const startAnimation = () => {
369
+ if (animationId) cancelAnimationFrame(animationId);
370
+ if (!ctx || !canvas) return;
371
+ if (data && data.length > 0) {
372
+ currentData = data.map((d) => 0);
373
+ } else {
374
+ currentData = [];
375
+ }
376
+ currentSlices = computeAngles(currentData);
377
+
378
+ if (animate && data && data.length > 0) {
379
+ startTime = null;
380
+ animationId = requestAnimationFrame(updateAnimation);
381
+ } else {
382
+ if (data && data.length > 0) {
383
+ for (let i = 0; i < data.length; i++) {
384
+ currentData[i] = data[i]?.value || 0;
385
+ }
386
+ }
387
+ currentSlices = computeAngles(currentData);
388
+ draw();
389
+ }
390
+ };
391
+
392
+ const resizeCanvas = () => {
393
+ if (!canvas) return;
394
+ updateCanvasSize();
395
+ startAnimation();
396
+ };
397
+
398
+ const getSliceIndex = (x, y) => {
399
+ if (!currentSlices || currentSlices.length === 0) return -1;
400
+ const cx = getCenterX();
401
+ const cy = getCenterY();
402
+ const dx = x - cx;
403
+ const dy = y - cy;
404
+ const distance = Math.hypot(dx, dy);
405
+ const r = getRadiusVal() / 2;
406
+ if (distance > r) return -1;
407
+ if (finalInnerRadius > 0 && distance < finalInnerRadius) return -1;
408
+ let angle = Math.atan2(dy, dx);
409
+ if (angle < 0) angle += Math.PI * 2;
410
+ let adjusted = angle - finalStartAngle;
411
+ if (adjusted < 0) adjusted += Math.PI * 2;
412
+ for (let i = 0; i < currentSlices.length; i++) {
413
+ const slice = currentSlices[i];
414
+ let start = slice.start - finalStartAngle;
415
+ if (start < 0) start += Math.PI * 2;
416
+ let end = slice.end - finalStartAngle;
417
+ if (end < 0) end += Math.PI * 2;
418
+ if (adjusted >= start - 0.01 && adjusted <= end + 0.01) return i;
419
+ }
420
+ return -1;
421
+ };
422
+
423
+ const handleCanvasClick = (e) => {
424
+ if (!onClick || !canvas) return;
425
+ const rect = canvas.getBoundingClientRect();
426
+ const scaleX = canvas.width / rect.width;
427
+ const scaleY = canvas.height / rect.height;
428
+ const mouseX = (e.clientX - rect.left) * scaleX;
429
+ const mouseY = (e.clientY - rect.top) * scaleY;
430
+ const idx = getSliceIndex(mouseX, mouseY);
431
+ if (idx !== -1 && currentSlices[idx]) {
432
+ onClick(currentSlices[idx].original, idx);
433
+ }
434
+ };
435
+
436
+ const handleCanvasMousemove = (e) => {
437
+ if (!onHover && !glow) return;
438
+ if (!canvas) return;
439
+ const rect = canvas.getBoundingClientRect();
440
+ const scaleX = canvas.width / rect.width;
441
+ const scaleY = canvas.height / rect.height;
442
+ const mouseX = (e.clientX - rect.left) * scaleX;
443
+ const mouseY = (e.clientY - rect.top) * scaleY;
444
+ const idx = getSliceIndex(mouseX, mouseY);
445
+ if (hoveredIndex !== idx) {
446
+ hoveredIndex = idx;
447
+ if (onHover)
448
+ onHover(idx !== -1 ? currentSlices[idx]?.original : null, idx);
449
+ draw();
450
+ }
451
+ };
452
+
453
+ const initWidget = () => {
454
+ container = WidgetFactory({
455
+ tag: "div",
456
+ widgetName: "CircularChart",
457
+ display: isFixedSize ? "inline-block" : "block",
458
+ width: isFixedSize ? `${canvasWidth}px` : "100%",
459
+ height: isFixedSize ? `${canvasHeight}px` : "auto",
460
+ position: "relative",
461
+ overflow: "hidden",
462
+ borderRadius:
463
+ typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius,
464
+ border:
465
+ borderWidth > 0 && borderColor
466
+ ? `${borderWidth}px solid ${borderColor}`
467
+ : "none",
468
+ ...rest,
469
+ });
470
+
471
+ // Remove old canvas if it exists
472
+ const oldCanvas = container.querySelector("canvas");
473
+ if (oldCanvas) oldCanvas.remove();
474
+
475
+ canvas = document.createElement("canvas");
476
+ canvas.style.display = "block";
477
+ canvas.style.width = "100%";
478
+ canvas.style.height = "100%";
479
+ canvas.addEventListener("click", handleCanvasClick);
480
+ canvas.addEventListener("mousemove", handleCanvasMousemove);
481
+ canvas.addEventListener("mouseleave", () => {
482
+ if (hoveredIndex !== -1) {
483
+ hoveredIndex = -1;
484
+ if (onHover) onHover(null, -1);
485
+ draw();
486
+ }
487
+ });
488
+
489
+ container.appendChild(canvas);
490
+ ctx = canvas.getContext("2d");
491
+ updateCanvasSize();
492
+
493
+ if (!isFixedSize && typeof ResizeObserver !== "undefined") {
494
+ resizeObserver = new ResizeObserver(() => resizeCanvas());
495
+ resizeObserver.observe(container);
496
+ } else if (!isFixedSize) {
497
+ window.addEventListener("resize", resizeCanvas);
498
+ }
499
+
500
+ startAnimation();
501
+ if (ref && typeof ref === "function") ref(canvas);
502
+ };
503
+
504
+ initWidget();
505
+
506
+ const updateData = (newData, animateUpdate = true) => {
507
+ if (!Array.isArray(newData)) return;
508
+ data = newData;
509
+ animate = animateUpdate;
510
+ startAnimation();
511
+ };
512
+
513
+ const redraw = () => {
514
+ updateCanvasSize();
515
+ startAnimation();
516
+ };
517
+
518
+ const setCornerRadius = (newRadius) => {
519
+ cornerRadius = newRadius;
520
+ if (currentSlices && currentSlices.length > 0) {
521
+ draw();
522
+ } else if (data && data.length > 0) {
523
+ startAnimation();
524
+ } else {
525
+ draw();
526
+ }
527
+ };
528
+
529
+ container.updateData = updateData;
530
+ container.redraw = redraw;
531
+ container.setCornerRadius = setCornerRadius;
532
+
533
+ container.onUnmount(() => {
534
+ if (animationId) cancelAnimationFrame(animationId);
535
+ if (resizeObserver) resizeObserver?.disconnect();
536
+ else window.removeEventListener("resize", resizeCanvas);
537
+ if (canvas) {
538
+ canvas.removeEventListener("click", handleCanvasClick);
539
+ canvas.removeEventListener("mousemove", handleCanvasMousemove);
540
+ canvas.remove();
541
+ }
542
+ if (container && container.parentNode)
543
+ container.parentNode.removeChild(container);
544
+ });
545
+
546
+ return container;
547
+ };
548
+
549
+ export default CircularChart;