chuijs 4.0.3 → 4.0.4

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 (258) hide show
  1. package/CHANGELOG.md +536 -0
  2. package/framework/appLayout/default/chui_app_layout.js +361 -303
  3. package/framework/appLayout/default/global_style.css +243 -89
  4. package/framework/appLayout/default/main_theme_style.css +188 -101
  5. package/framework/components/chui_accordion/accordion.js +17 -17
  6. package/framework/components/chui_accordion/styles.css +7 -7
  7. package/framework/components/chui_action_sheet/action_sheet.js +35 -32
  8. package/framework/components/chui_action_sheet/styles.css +10 -4
  9. package/framework/components/chui_actions_bar/actions_bar.js +338 -0
  10. package/framework/components/chui_actions_bar/styles.css +139 -0
  11. package/framework/components/chui_affix/affix.js +4 -4
  12. package/framework/components/chui_affix/styles.css +5 -0
  13. package/framework/components/chui_alert/alert.js +11 -5
  14. package/framework/components/chui_alert/styles.css +7 -0
  15. package/framework/components/chui_avatar/avatar.js +13 -13
  16. package/framework/components/chui_avatar/styles.css +1 -1
  17. package/framework/components/chui_avatar_group/avatar_group.js +4 -4
  18. package/framework/components/chui_avatar_group/styles.css +1 -1
  19. package/framework/components/chui_backtop/backtop.js +25 -2
  20. package/framework/components/chui_backtop/styles.css +21 -0
  21. package/framework/components/chui_badge/badge.js +15 -15
  22. package/framework/components/chui_badge/styles.css +6 -6
  23. package/framework/components/chui_banner/banner.js +222 -0
  24. package/framework/components/chui_banner/styles.css +170 -0
  25. package/framework/components/chui_breadcrumb/styles.css +1 -1
  26. package/framework/components/chui_button/button.js +24 -28
  27. package/framework/components/chui_button/styles.css +5 -5
  28. package/framework/components/chui_calendar/calendar.js +6 -6
  29. package/framework/components/chui_card/card.js +14 -14
  30. package/framework/components/chui_card_scroll/card_scroll.js +169 -0
  31. package/framework/components/chui_card_scroll/styles.css +86 -0
  32. package/framework/components/chui_chat/chat.js +73 -51
  33. package/framework/components/chui_chat/styles.css +18 -7
  34. package/framework/components/chui_clipboard_history/clipboard_history.js +25 -20
  35. package/framework/components/chui_clipboard_history/styles.css +10 -8
  36. package/framework/components/chui_code/code.js +8 -8
  37. package/framework/components/chui_code/styles.css +2 -2
  38. package/framework/components/chui_code_editor/code_editor.js +26 -26
  39. package/framework/components/chui_code_editor/styles.css +3 -3
  40. package/framework/components/chui_command_palette/command_palette.js +29 -29
  41. package/framework/components/chui_command_palette/styles.css +5 -5
  42. package/framework/components/chui_console/console.js +9 -9
  43. package/framework/components/chui_console/styles.css +5 -2
  44. package/framework/components/chui_content_block/content_block.js +2 -2
  45. package/framework/components/chui_context_menu/context_menu.js +162 -54
  46. package/framework/components/chui_context_menu/styles.css +82 -8
  47. package/framework/components/chui_custom_element/custom_element.js +11 -11
  48. package/framework/components/chui_data_grid/data_grid.js +268 -268
  49. package/framework/components/chui_data_grid/styles.css +14 -14
  50. package/framework/components/chui_descriptions/styles.css +1 -1
  51. package/framework/components/chui_details/details.js +12 -12
  52. package/framework/components/chui_details/styles.css +7 -7
  53. package/framework/components/chui_diff_viewer/diff_viewer.js +88 -88
  54. package/framework/components/chui_diff_viewer/styles.css +9 -9
  55. package/framework/components/chui_divider/divider.js +6 -6
  56. package/framework/components/chui_divider/styles.css +2 -2
  57. package/framework/components/chui_drawer/drawer.js +37 -14
  58. package/framework/components/chui_drawer/styles.css +18 -4
  59. package/framework/components/chui_drop_zone/drop_zone.js +6 -6
  60. package/framework/components/chui_drop_zone/styles.css +10 -6
  61. package/framework/components/chui_fieldset/fieldset.js +6 -6
  62. package/framework/components/chui_fieldset/styles.css +5 -5
  63. package/framework/components/chui_file_explorer/file_explorer.js +115 -115
  64. package/framework/components/chui_file_explorer/styles.css +6 -6
  65. package/framework/components/chui_form/form.js +11 -11
  66. package/framework/components/chui_gantt/gantt.js +41 -41
  67. package/framework/components/chui_gantt/styles.css +5 -5
  68. package/framework/components/chui_graphs/graphs.js +82 -46
  69. package/framework/components/chui_graphs/styles.css +26 -5
  70. package/framework/components/chui_hero/hero.js +63 -20
  71. package/framework/components/chui_hero/styles.css +176 -7
  72. package/framework/components/chui_html_block/html_block.js +5 -5
  73. package/framework/components/chui_html_block/styles.css +1 -1
  74. package/framework/components/chui_hx/styles.css +2 -2
  75. package/framework/components/chui_icons/icons.js +7 -7
  76. package/framework/components/chui_icons/styles.css +2 -1
  77. package/framework/components/chui_inputs/chui_check_box/styles.css +12 -3
  78. package/framework/components/chui_inputs/chui_chips/chips.js +92 -31
  79. package/framework/components/chui_inputs/chui_chips/styles.css +28 -5
  80. package/framework/components/chui_inputs/chui_color_picker/color_picker.js +20 -15
  81. package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
  82. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +58 -58
  83. package/framework/components/chui_inputs/chui_combo_box/styles.css +9 -9
  84. package/framework/components/chui_inputs/chui_date/date.js +93 -95
  85. package/framework/components/chui_inputs/chui_date/styles.css +8 -8
  86. package/framework/components/chui_inputs/chui_date_range/date_range.js +22 -22
  87. package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
  88. package/framework/components/chui_inputs/chui_datetime/datetime.js +12 -12
  89. package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
  90. package/framework/components/chui_inputs/chui_email/email.js +10 -10
  91. package/framework/components/chui_inputs/chui_email/styles.css +6 -6
  92. package/framework/components/chui_inputs/chui_file/file.js +8 -8
  93. package/framework/components/chui_inputs/chui_file/styles.css +12 -9
  94. package/framework/components/chui_inputs/chui_hotkey/hotkey.js +39 -39
  95. package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
  96. package/framework/components/chui_inputs/chui_masked/masked.js +44 -44
  97. package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
  98. package/framework/components/chui_inputs/chui_mention/mention.js +13 -13
  99. package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
  100. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +147 -137
  101. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +14 -8
  102. package/framework/components/chui_inputs/chui_number/number.js +42 -45
  103. package/framework/components/chui_inputs/chui_number/styles.css +6 -6
  104. package/framework/components/chui_inputs/chui_otp/otp.js +32 -32
  105. package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
  106. package/framework/components/chui_inputs/chui_password/password.js +30 -36
  107. package/framework/components/chui_inputs/chui_password/styles.css +7 -7
  108. package/framework/components/chui_inputs/chui_radio_button/styles.css +2 -2
  109. package/framework/components/chui_inputs/chui_rating/rating.js +28 -28
  110. package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
  111. package/framework/components/chui_inputs/chui_select_box/select_box.js +54 -54
  112. package/framework/components/chui_inputs/chui_select_box/styles.css +10 -10
  113. package/framework/components/chui_inputs/chui_slider/slider.js +14 -14
  114. package/framework/components/chui_inputs/chui_slider/styles.css +5 -4
  115. package/framework/components/chui_inputs/chui_text/styles.css +7 -7
  116. package/framework/components/chui_inputs/chui_text/text.js +13 -17
  117. package/framework/components/chui_inputs/chui_text_area/styles.css +8 -8
  118. package/framework/components/chui_inputs/chui_text_area/text_area.js +23 -23
  119. package/framework/components/chui_inputs/chui_time/styles.css +13 -17
  120. package/framework/components/chui_inputs/chui_time/time.js +35 -24
  121. package/framework/components/chui_inputs/chui_toggle/styles.css +10 -3
  122. package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
  123. package/framework/components/chui_json_editor/json_editor.js +87 -87
  124. package/framework/components/chui_json_editor/styles.css +5 -5
  125. package/framework/components/chui_json_viewer/json_viewer.js +80 -80
  126. package/framework/components/chui_json_viewer/styles.css +6 -6
  127. package/framework/components/chui_kanban/kanban.js +27 -27
  128. package/framework/components/chui_kanban/styles.css +8 -4
  129. package/framework/components/chui_kbd/styles.css +1 -1
  130. package/framework/components/chui_label/label.js +17 -17
  131. package/framework/components/chui_label/styles.css +1 -1
  132. package/framework/components/chui_link/link.js +9 -9
  133. package/framework/components/chui_link/styles.css +11 -4
  134. package/framework/components/chui_list/list.js +11 -11
  135. package/framework/components/chui_list/styles.css +2 -2
  136. package/framework/components/chui_markdown_editor/markdown_editor.js +66 -66
  137. package/framework/components/chui_markdown_editor/styles.css +5 -5
  138. package/framework/components/chui_media/audio.js +240 -246
  139. package/framework/components/chui_media/audio_styles.css +41 -23
  140. package/framework/components/chui_media/image.js +49 -46
  141. package/framework/components/chui_media/image_compare.js +35 -35
  142. package/framework/components/chui_media/image_compare_styles.css +5 -5
  143. package/framework/components/chui_media/image_styles.css +1 -1
  144. package/framework/components/chui_media/video.js +148 -148
  145. package/framework/components/chui_media/video_styles.css +27 -11
  146. package/framework/components/chui_menu_bar/menu_bar.js +54 -54
  147. package/framework/components/chui_menu_bar/styles.css +10 -10
  148. package/framework/components/chui_message_bar/message_bar.js +551 -0
  149. package/framework/components/chui_message_bar/styles.css +248 -0
  150. package/framework/components/chui_modal/modal.js +37 -13
  151. package/framework/components/chui_notification/notification.js +76 -54
  152. package/framework/components/chui_notification/notification_download.js +26 -81
  153. package/framework/components/chui_notification/notification_download_card.js +115 -0
  154. package/framework/components/chui_notification/notification_download_progress.css +2 -2
  155. package/framework/components/chui_notification/notification_download_progress.js +70 -108
  156. package/framework/components/chui_notification/notification_update.js +31 -31
  157. package/framework/components/chui_notification/status_plate.css +3 -3
  158. package/framework/components/chui_notification/styles.css +6 -1
  159. package/framework/components/chui_notification/styles_download.css +1 -1
  160. package/framework/components/chui_notification/styles_update.css +2 -2
  161. package/framework/components/chui_org_chart/org_chart.js +29 -29
  162. package/framework/components/chui_org_chart/styles.css +2 -2
  163. package/framework/components/chui_pagination/pagination.js +3 -3
  164. package/framework/components/chui_pagination/styles.css +1 -1
  165. package/framework/components/chui_paragraph/styles.css +2 -2
  166. package/framework/components/chui_popover/popover.js +29 -23
  167. package/framework/components/chui_popover/styles.css +1 -1
  168. package/framework/components/chui_popups/popups.js +124 -124
  169. package/framework/components/chui_popups/styles.css +5 -5
  170. package/framework/components/chui_print_button/print_button.js +15 -10
  171. package/framework/components/chui_progress_bar/progress_bar.js +34 -12
  172. package/framework/components/chui_progress_bar/styles.css +4 -4
  173. package/framework/components/chui_progress_ring/progress_ring.js +15 -15
  174. package/framework/components/chui_progress_ring/styles.css +1 -1
  175. package/framework/components/chui_qrcode/qrcode.js +12 -12
  176. package/framework/components/chui_query_builder/query_builder.js +46 -46
  177. package/framework/components/chui_query_builder/styles.css +4 -4
  178. package/framework/components/chui_radio_group/styles.css +1 -1
  179. package/framework/components/chui_result/result.js +15 -15
  180. package/framework/components/chui_result/styles.css +3 -3
  181. package/framework/components/chui_scheduler/scheduler.js +74 -74
  182. package/framework/components/chui_scheduler/styles.css +5 -5
  183. package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
  184. package/framework/components/chui_screen_spinner/styles.css +49 -0
  185. package/framework/components/chui_segmented_control/segmented_control.js +63 -6
  186. package/framework/components/chui_segmented_control/styles.css +32 -6
  187. package/framework/components/chui_settings/settings.js +45 -46
  188. package/framework/components/chui_settings/styles.css +4 -4
  189. package/framework/components/chui_shortcut_help/shortcut_help.js +32 -34
  190. package/framework/components/chui_sidebar_nav/sidebar_nav.js +98 -29
  191. package/framework/components/chui_sidebar_nav/styles.css +9 -3
  192. package/framework/components/chui_slideshow/slideshow.js +85 -85
  193. package/framework/components/chui_slideshow/styles.css +6 -6
  194. package/framework/components/chui_spinner/spinner.js +10 -10
  195. package/framework/components/chui_split_button/split_button.js +30 -12
  196. package/framework/components/chui_split_button/styles.css +21 -5
  197. package/framework/components/chui_splitter/splitter.js +11 -11
  198. package/framework/components/chui_splitter/styles.css +2 -2
  199. package/framework/components/chui_statistic/statistic.js +47 -47
  200. package/framework/components/chui_status_bar/styles.css +2 -2
  201. package/framework/components/chui_stepper/stepper.js +202 -31
  202. package/framework/components/chui_stepper/styles.css +79 -14
  203. package/framework/components/chui_sticky_bar/sticky_bar.js +44 -13
  204. package/framework/components/chui_sticky_bar/styles.css +10 -8
  205. package/framework/components/chui_tabs/chui_tabs.js +74 -26
  206. package/framework/components/chui_tabs/styles.css +40 -12
  207. package/framework/components/chui_text_editor/styles.css +7 -7
  208. package/framework/components/chui_text_editor/text_editor.js +56 -56
  209. package/framework/components/chui_text_editor/text_editor_panel.js +173 -174
  210. package/framework/components/chui_theme_toggle/theme_toggle.js +21 -5
  211. package/framework/components/chui_timeline/styles.css +3 -1
  212. package/framework/components/chui_toast_queue/styles.css +11 -11
  213. package/framework/components/chui_toast_queue/toast_queue.js +30 -27
  214. package/framework/components/chui_toolbar/styles.css +2 -2
  215. package/framework/components/chui_tooltip/styles.css +2 -2
  216. package/framework/components/chui_tooltip/tooltip.js +3 -3
  217. package/framework/components/chui_tour/styles.css +8 -8
  218. package/framework/components/chui_tour/tour.js +72 -72
  219. package/framework/components/chui_transfer/styles.css +6 -6
  220. package/framework/components/chui_transfer/transfer.js +93 -106
  221. package/framework/components/chui_tree_table/styles.css +4 -4
  222. package/framework/components/chui_tree_table/tree_table.js +45 -45
  223. package/framework/components/chui_tree_view/styles.css +10 -8
  224. package/framework/components/chui_tree_view/tree_view.js +185 -183
  225. package/framework/components/chui_user_bar/styles.css +145 -0
  226. package/framework/components/chui_user_bar/user_bar.js +402 -0
  227. package/framework/components/chui_user_list_bar/styles.css +173 -0
  228. package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
  229. package/framework/components/chui_watermark/watermark.js +17 -17
  230. package/framework/components/chui_webview/webview.js +9 -9
  231. package/framework/components/chui_write_bar/styles.css +172 -0
  232. package/framework/components/chui_write_bar/write_bar.js +320 -0
  233. package/framework/main_window.js +1234 -0
  234. package/framework/modules/chui_a11y/chui_a11y.js +78 -79
  235. package/framework/modules/chui_animations/animations.js +77 -15
  236. package/framework/modules/chui_animations/motion.js +120 -0
  237. package/framework/modules/chui_animations/styles.css +36 -3
  238. package/framework/modules/chui_functions.js +31 -27
  239. package/framework/modules/chui_highlight/styles.css +13 -10
  240. package/framework/modules/chui_i18n/chui_i18n.js +3 -3
  241. package/framework/modules/chui_i18n/locales/feedback.js +62 -0
  242. package/framework/modules/chui_i18n/locales/navigation.js +4 -0
  243. package/framework/modules/chui_json/chui_json.js +2 -2
  244. package/framework/modules/chui_logger/chui_logger.js +23 -8
  245. package/framework/modules/chui_page/page.js +10 -10
  246. package/framework/modules/chui_palette/palette.js +61 -0
  247. package/framework/modules/chui_qr/chui_qr.js +23 -23
  248. package/framework/modules/chui_scrollbar/chui_scrollbar.js +45 -45
  249. package/framework/modules/chui_scrollbar/styles.css +3 -3
  250. package/framework/modules/chui_services/chui_services.js +8 -8
  251. package/framework/modules/chui_services/desktop.js +77 -71
  252. package/framework/modules/chui_sortable/chui_sortable.js +17 -17
  253. package/framework/modules/chui_validate/chui_validate.js +3 -3
  254. package/framework/tray_bar/chui_menu_items.js +2 -2
  255. package/index.d.ts +266 -34
  256. package/index.js +20 -1168
  257. package/package.json +6 -4
  258. package/framework/components/telegram_bot/chui_telegram_bot.js +0 -191
@@ -1,19 +1,21 @@
1
1
  const { Notification } = require("../chui_notification/notification");
2
2
  const { setStyles, optionOr } = require("../../modules/chui_functions");
3
3
  const { I18n } = require("../../modules/chui_i18n/chui_i18n");
4
+ const { animate } = require("../../modules/chui_animations/motion");
5
+ const { paletteColor } = require("../../modules/chui_palette/palette");
4
6
 
5
7
  const STYLES_ID = "chUiJS_Canvas";
6
- const AXIS_COLOR = "#8e8e93";
8
+ const AXIS_COLOR = paletteColor("gray");
7
9
  const LABEL_FONT = "bold 14px Arial";
8
10
  const Y_AXIS_STEPS = 4;
9
11
  const PLOT_HEIGHT = 240;
10
12
  const CATEGORY_WIDTH = 150;
11
13
  const BAR_WIDTH = 75;
12
- const BAR_FALLBACK_COLOR = "#1676f3";
14
+ const BAR_FALLBACK_COLOR = paletteColor("blue");
13
15
  const PADDING = { top: 20, right: 20, bottom: 50, left: 50 };
14
16
  const PIE_SIZE = 400;
15
- const GRID_COLOR = "rgba(142, 142, 147, 0.25)";
16
- const LINE_FALLBACK_COLOR = "#1676f3";
17
+ const GRID_COLOR = paletteColor("gray", { alpha: 0.25 });
18
+ const LINE_FALLBACK_COLOR = paletteColor("blue");
17
19
  const LINE_POINT_RADIUS = 4;
18
20
  const POINT_HOVER_DISTANCE = 30;
19
21
  const SCATTER_PLOT_WIDTH = 460;
@@ -24,9 +26,9 @@ const GAUGE_THICKNESS = 20;
24
26
  const GAUGE_START_ANGLE = 135;
25
27
  const GAUGE_SWEEP = 270;
26
28
  const GAUGE_TICKS = 6;
27
- const GAUGE_FALLBACK_COLOR = "#1676f3";
28
- const GAUGE_TRACK_COLOR = "rgba(142, 142, 147, 0.25)";
29
- const GAUGE_THRESHOLD_COLORS = ["#34c759", "#ff9f0a", "#ff3b30"];
29
+ const GAUGE_FALLBACK_COLOR = paletteColor("blue");
30
+ const GAUGE_TRACK_COLOR = paletteColor("gray", { alpha: 0.25 });
31
+ const GAUGE_THRESHOLD_COLORS = [paletteColor("green"), paletteColor("orange"), paletteColor("red")];
30
32
  const SPARKLINE_WIDTH = 160;
31
33
  const SPARKLINE_HEIGHT = 40;
32
34
  const SPARKLINE_INSET = 3;
@@ -42,7 +44,7 @@ const HEATMAP_GAP = 4;
42
44
  const HEATMAP_LABEL_WIDTH = 70;
43
45
  const HEATMAP_LABEL_HEIGHT = 24;
44
46
  const HEATMAP_PADDING = 10;
45
- const HEATMAP_EMPTY_COLOR = "rgba(142, 142, 147, 0.18)";
47
+ const HEATMAP_EMPTY_COLOR = paletteColor("gray", { alpha: 0.18 });
46
48
  // Самая бледная клетка с данными: пустая заливка остаётся светлее
47
49
  const HEATMAP_ALPHA_MIN = 0.2;
48
50
 
@@ -139,16 +141,31 @@ function createTooltip(container) {
139
141
  element: tooltip,
140
142
  show(text, x, y) {
141
143
  tooltip.innerText = text;
144
+ // Незавершённый уход отменяется: подсказка снова на месте
145
+ tooltip.classList.remove("chui_chart_tooltip_leaving");
142
146
  tooltip.style.display = "block";
143
147
  tooltip.style.left = Math.round(x) + "px";
144
148
  tooltip.style.top = Math.round(y) + "px";
145
149
  },
146
150
  hide() {
147
- tooltip.style.display = "none";
151
+ // Уход — движением: подсказка гаснет, а узел прячется по концу кадра
152
+ if (getComputedStyle(tooltip).display === "none") return;
153
+ if (tooltip.classList.contains("chui_chart_tooltip_leaving")) return;
154
+ tooltip.classList.add("chui_chart_tooltip_leaving");
155
+ const done = (event) => {
156
+ if (event.target !== tooltip) return;
157
+ tooltip.removeEventListener("animationend", done);
158
+ // Показ за время ухода — узел остаётся на месте
159
+ if (tooltip.classList.contains("chui_chart_tooltip_leaving") === false) return;
160
+ tooltip.classList.remove("chui_chart_tooltip_leaving");
161
+ tooltip.style.display = "none";
162
+ };
163
+ tooltip.addEventListener("animationend", done);
148
164
  },
149
165
  };
150
166
  }
151
167
 
168
+ /** Уменьшенное движение: диаграмма строится сразу, без роста */
152
169
  class BarGraph {
153
170
  #canvas = document.createElement("canvas");
154
171
  #main = document.createElement("chui_bar_graph");
@@ -162,13 +179,13 @@ class BarGraph {
162
179
  this.#options = options;
163
180
  this.#main.appendChild(this.#canvas);
164
181
  this.#tooltip = createTooltip(this.#main);
165
- this.#draw();
182
+ animate((growth) => this.#draw(growth));
166
183
  this.#bindPointer();
167
184
  // Доступная альтернатива канвасу: та же таблица данных рядом с графиком
168
185
  this.#main.appendChild(buildDataTable(this.#options.data || {}));
169
186
  }
170
187
 
171
- #draw() {
188
+ #draw(growth = 1) {
172
189
  const data = this.#options.data || {};
173
190
  const colors = this.#options.colors || [];
174
191
  const labels = Object.keys(data);
@@ -181,10 +198,10 @@ class BarGraph {
181
198
  const ctx = prepareCanvas(this.#canvas, cssWidth, cssHeight);
182
199
 
183
200
  drawAxesGrid(ctx, plotWidth, scale);
184
- this.#drawBars(ctx, labels, values, scale, colors);
201
+ this.#drawBars(ctx, labels, values, scale, colors, growth);
185
202
  }
186
203
 
187
- #drawBars(ctx, labels, values, scale, colors) {
204
+ #drawBars(ctx, labels, values, scale, colors, growth = 1) {
188
205
  const bottomY = PADDING.top + PLOT_HEIGHT;
189
206
  this.#bars = [];
190
207
  ctx.textAlign = "center";
@@ -192,7 +209,7 @@ class BarGraph {
192
209
  for (let i = 0; i < labels.length; i++) {
193
210
  // Отрицательное значение — это ноль на оси, которая начинается с нуля:
194
211
  // иначе столбец уходил бы вниз, на подписи категорий (как у RadarGraph и HeatmapGraph)
195
- const barHeight = Math.max(0, (values[i] / scale.max) * PLOT_HEIGHT);
212
+ const barHeight = Math.max(0, (values[i] / scale.max) * PLOT_HEIGHT * growth);
196
213
  const x = PADDING.left + i * CATEGORY_WIDTH + (CATEGORY_WIDTH - BAR_WIDTH) / 2;
197
214
  const bar = { index: i, x: x, y: bottomY - barHeight, w: BAR_WIDTH, h: barHeight };
198
215
  this.#bars.push(bar);
@@ -282,7 +299,7 @@ class BarGraph {
282
299
  const rect = this.#canvas.getBoundingClientRect();
283
300
  const x = event.clientX - rect.left;
284
301
  const y = event.clientY - rect.top;
285
- for (let bar of this.#bars) {
302
+ for (const bar of this.#bars) {
286
303
  if (x >= bar.x && x <= bar.x + bar.w && y >= bar.y && y <= bar.y + bar.h) return bar;
287
304
  }
288
305
  return null;
@@ -294,24 +311,28 @@ class BarGraph {
294
311
  }
295
312
 
296
313
  class PieGraph {
297
- #chui_piechart = document.createElement("chui_piechart");
314
+ #chuiPiechart = document.createElement("chui_piechart");
298
315
  #canvas = document.createElement("canvas");
299
316
  #options = undefined;
300
317
 
301
318
  constructor(options = {}) {
302
319
  setStyles(__dirname + "/styles.css", STYLES_ID);
303
320
  this.#options = options;
304
- const ctx = prepareCanvas(this.#canvas, PIE_SIZE, PIE_SIZE);
305
- this.#drawSlices(ctx);
321
+ animate((growth) => this.#draw(growth));
306
322
  this.#canvas.setAttribute("role", "img");
307
323
  this.#canvas.setAttribute("aria-label", describeData(I18n.t("graphics.pieChart"), options.data || {}));
308
- this.#chui_piechart.appendChild(this.#canvas);
309
- if (options.legend) this.#chui_piechart.appendChild(this.#buildLegend());
324
+ this.#chuiPiechart.appendChild(this.#canvas);
325
+ if (options.legend) this.#chuiPiechart.appendChild(this.#buildLegend());
310
326
  // Доступная альтернатива канвасу: та же таблица данных рядом с графиком
311
- this.#chui_piechart.appendChild(buildDataTable(options.data || {}));
327
+ this.#chuiPiechart.appendChild(buildDataTable(options.data || {}));
328
+ }
329
+
330
+ #draw(growth = 1) {
331
+ const ctx = prepareCanvas(this.#canvas, PIE_SIZE, PIE_SIZE);
332
+ this.#drawSlices(ctx, growth);
312
333
  }
313
334
 
314
- #drawSlices(ctx) {
335
+ #drawSlices(ctx, growth = 1) {
315
336
  const data = this.#options.data || {};
316
337
  const colors = this.#options.colors || [];
317
338
  const labels = Object.keys(data);
@@ -322,12 +343,17 @@ class PieGraph {
322
343
  if (total <= 0) return;
323
344
 
324
345
  const center = PIE_SIZE / 2;
346
+ // Построение: секторы прочерчиваются по кругу. Подписи процентов встают на место,
347
+ // когда круг замкнулся, — иначе они ехали бы вслед за незакрытым сектором
348
+ const limit = 2 * Math.PI * growth;
325
349
  let startAngle = 0;
326
350
  for (let i = 0; i < labels.length; i++) {
327
351
  const value = values[i];
328
- const sliceAngle = (2 * Math.PI * value) / total;
352
+ const remainder = limit - startAngle;
353
+ if (remainder <= 0) break;
354
+ const sliceAngle = Math.min((2 * Math.PI * value) / total, remainder);
329
355
  drawPieSlice(ctx, center, center, center, startAngle, startAngle + sliceAngle, colors[i % colors.length]);
330
- this.#drawPercentage(ctx, center, startAngle, sliceAngle, value, total);
356
+ if (growth >= 1) this.#drawPercentage(ctx, center, startAngle, sliceAngle, value, total);
331
357
  startAngle += sliceAngle;
332
358
  }
333
359
  this.#drawDoughnutHole(ctx, center);
@@ -368,7 +394,7 @@ class PieGraph {
368
394
  }
369
395
 
370
396
  set() {
371
- return this.#chui_piechart;
397
+ return this.#chuiPiechart;
372
398
  }
373
399
  }
374
400
 
@@ -385,7 +411,7 @@ class LineGraph {
385
411
  this.#options = options;
386
412
  this.#main.appendChild(this.#canvas);
387
413
  this.#tooltip = createTooltip(this.#main);
388
- this.#draw();
414
+ animate((growth) => this.#draw(growth));
389
415
  this.#bindPointer();
390
416
  // Доступная альтернатива канвасу
391
417
  this.#main.appendChild(buildDataTable(options.data || {}));
@@ -395,7 +421,7 @@ class LineGraph {
395
421
  return (this.#options.colors || [])[0] || LINE_FALLBACK_COLOR;
396
422
  }
397
423
 
398
- #draw() {
424
+ #draw(growth = 1) {
399
425
  const data = this.#options.data || {};
400
426
  const labels = Object.keys(data);
401
427
  const values = labels.map((label) => Number(data[label]) || 0);
@@ -416,6 +442,11 @@ class LineGraph {
416
442
  }));
417
443
 
418
444
  // Заливка под линией (AreaGraph): градиент только для hex-цветов
445
+ // Построение: линия прочерчивается слева направо — поле приоткрывается по мере роста
446
+ ctx.save();
447
+ ctx.beginPath();
448
+ ctx.rect(0, 0, PADDING.left + plotWidth * growth, cssHeight);
449
+ ctx.clip();
419
450
  if (this.#options.area === true && this.#points.length > 0) {
420
451
  ctx.fillStyle = this.#color;
421
452
  if (this.#color.startsWith("#")) {
@@ -461,6 +492,7 @@ class LineGraph {
461
492
  ctx.textBaseline = "top";
462
493
  ctx.fillText(labels[point.index], point.x, bottomY + 10);
463
494
  }
495
+ ctx.restore();
464
496
  }
465
497
 
466
498
  #bindPointer() {
@@ -603,7 +635,7 @@ class ScatterGraph {
603
635
  this.#series = ScatterGraph.#normalize(options.series, options.colors);
604
636
  this.#main.appendChild(this.#canvas);
605
637
  this.#tooltip = createTooltip(this.#main);
606
- this.#draw();
638
+ animate((growth) => this.#draw(growth));
607
639
  // Легенда нужна только когда серий больше одной: у одной подпись есть в подсказке
608
640
  if (options.legend !== false && this.#series.length > 1) {
609
641
  this.#main.appendChild(
@@ -669,7 +701,7 @@ class ScatterGraph {
669
701
  return rows;
670
702
  }
671
703
 
672
- #draw() {
704
+ #draw(growth = 1) {
673
705
  const points = this.#series.reduce((list, serie) => list.concat(serie.points), []);
674
706
  const xs = points.map((point) => point.x);
675
707
  const ys = points.map((point) => point.y);
@@ -784,7 +816,8 @@ class ScatterGraph {
784
816
  });
785
817
  ctx.fillStyle = serie.color;
786
818
  ctx.beginPath();
787
- ctx.arc(x, y, SCATTER_POINT_RADIUS, 0, 2 * Math.PI);
819
+ // Точки растут из нуля: появление серии читается, как её набор
820
+ ctx.arc(x, y, Math.max(0.5, SCATTER_POINT_RADIUS * growth), 0, 2 * Math.PI);
788
821
  ctx.fill();
789
822
  if (index === this.#active) {
790
823
  ctx.strokeStyle = serie.color;
@@ -922,7 +955,7 @@ class Gauge {
922
955
 
923
956
  #root = document.createElement("chui_gauge");
924
957
  #canvas = document.createElement("canvas");
925
- #value_text = document.createElement("gauge_value");
958
+ #valueText = document.createElement("gauge_value");
926
959
  #status = document.createElement("chui_gauge_status");
927
960
  #id = undefined;
928
961
  #value = 0;
@@ -931,10 +964,10 @@ class Gauge {
931
964
  #unit = "";
932
965
  #label = I18n.t("graphics.gauge");
933
966
  #format = undefined;
934
- #show_value = true;
967
+ #showValue = true;
935
968
  #size = GAUGE_SIZE;
936
969
  #thickness = GAUGE_THICKNESS;
937
- #start_angle = GAUGE_START_ANGLE;
970
+ #startAngle = GAUGE_START_ANGLE;
938
971
  #sweep = GAUGE_SWEEP;
939
972
  #ticks = GAUGE_TICKS;
940
973
  #color = undefined;
@@ -947,13 +980,13 @@ class Gauge {
947
980
  this.#root.id = this.#id;
948
981
  this.#root.setAttribute("role", "meter");
949
982
  this.#canvas.setAttribute("aria-hidden", "true");
950
- this.#value_text.className = "gauge_value";
951
- this.#value_text.setAttribute("aria-hidden", "true");
983
+ this.#valueText.className = "gauge_value";
984
+ this.#valueText.setAttribute("aria-hidden", "true");
952
985
  this.#status.className = "gauge_status chui_visually_hidden";
953
986
  this.#status.setAttribute("role", "status");
954
987
  this.#status.setAttribute("aria-live", "polite");
955
988
  this.#root.appendChild(this.#canvas);
956
- this.#root.appendChild(this.#value_text);
989
+ this.#root.appendChild(this.#valueText);
957
990
  this.#root.appendChild(this.#status);
958
991
  if (options.min !== undefined) this.#min = Number(options.min) || 0;
959
992
  if (options.max !== undefined) this.#max = Number(options.max) || 0;
@@ -961,18 +994,20 @@ class Gauge {
961
994
  if (options.unit !== undefined) this.#unit = String(options.unit);
962
995
  if (options.size !== undefined) this.#size = Number(options.size) || GAUGE_SIZE;
963
996
  if (options.thickness !== undefined) this.#thickness = Number(options.thickness) || GAUGE_THICKNESS;
964
- if (options.startAngle !== undefined) this.#start_angle = Number(options.startAngle) || 0;
997
+ if (options.startAngle !== undefined) this.#startAngle = Number(options.startAngle) || 0;
965
998
  if (options.sweep !== undefined) this.#sweep = Number(options.sweep) || GAUGE_SWEEP;
966
999
  if (options.ticks !== undefined) this.#ticks = Math.max(1, Number(options.ticks) || GAUGE_TICKS);
967
1000
  if (options.color !== undefined) this.#color = String(options.color);
968
1001
  this.#format = options.format;
969
- this.#show_value = options.showValue !== false;
1002
+ this.#showValue = options.showValue !== false;
970
1003
  this.setLabel(optionOr(options.label, I18n.t("graphics.gauge")));
971
1004
  if (options.thresholds !== undefined)
972
1005
  this.#thresholds = Gauge.#normalizeThresholds(options.thresholds, this.#max);
973
1006
  // Значение ставим последним: оно уже знает про min/max и пороги
974
1007
  this.#value = this.#min;
975
1008
  this.setValue(options.value === undefined ? this.#min : options.value, true);
1009
+ // Построение: дуга идёт от начала шкалы к значению
1010
+ animate((growth) => this.#render(growth));
976
1011
  }
977
1012
 
978
1013
  // GET
@@ -1031,7 +1066,7 @@ class Gauge {
1031
1066
  }
1032
1067
 
1033
1068
  isValueVisible() {
1034
- return this.#show_value;
1069
+ return this.#showValue;
1035
1070
  }
1036
1071
 
1037
1072
  // SET
@@ -1101,7 +1136,7 @@ class Gauge {
1101
1136
 
1102
1137
  /** Показывать ли значение в центре шкалы */
1103
1138
  setShowValue(visible = Boolean()) {
1104
- this.#show_value = visible === true;
1139
+ this.#showValue = visible === true;
1105
1140
  return this.#render();
1106
1141
  }
1107
1142
 
@@ -1143,10 +1178,11 @@ class Gauge {
1143
1178
  return this.#color === undefined ? GAUGE_FALLBACK_COLOR : this.#color;
1144
1179
  }
1145
1180
 
1146
- #render() {
1147
- const start = (this.#start_angle * Math.PI) / 180;
1148
- const end = ((this.#start_angle + this.#sweep) * Math.PI) / 180;
1149
- const ratio = Math.max(0, Math.min(1, this.getRatio()));
1181
+ #render(growth = 1) {
1182
+ const start = (this.#startAngle * Math.PI) / 180;
1183
+ const end = ((this.#startAngle + this.#sweep) * Math.PI) / 180;
1184
+ // Рост — только у построения: смена значения и настроек рисуется полной дугой
1185
+ const ratio = Math.max(0, Math.min(1, this.getRatio())) * growth;
1150
1186
  const color = this.#zoneColor();
1151
1187
  // Размер шкалы виден и подписи значения: её кегль считается от него в CSS
1152
1188
  this.#root.style.setProperty("--gauge_size", `${this.#size}px`);
@@ -1207,8 +1243,8 @@ class Gauge {
1207
1243
  ctx.arc(center, center, 5, 0, 2 * Math.PI);
1208
1244
  ctx.fill();
1209
1245
 
1210
- this.#value_text.hidden = !this.#show_value;
1211
- this.#value_text.innerText = this.#show_value ? this.getValueText() : "";
1246
+ this.#valueText.hidden = !this.#showValue;
1247
+ this.#valueText.innerText = this.#showValue ? this.getValueText() : "";
1212
1248
  this.#root.setAttribute("aria-valuemin", String(this.#min));
1213
1249
  this.#root.setAttribute("aria-valuemax", String(this.#max));
1214
1250
  this.#root.setAttribute("aria-valuenow", String(this.#value));
@@ -2,8 +2,8 @@ canvas {
2
2
  background: var(--element_background_2);
3
3
  height: max-content;
4
4
  width: max-content;
5
- border-radius: var(--border_radius);
6
- margin: var(--margin);
5
+ border-radius: var(--radius_sm);
6
+ margin: var(--block_margin);
7
7
  }
8
8
 
9
9
  /* Курсор «можно нажать» ставит компонент классом, когда вешает обработчик указателя:
@@ -19,6 +19,27 @@ chui_bar_graph {
19
19
  flex-wrap: wrap;
20
20
  }
21
21
 
22
+ /* Построение графика читается появлением: полотно встаёт на место, а не вспыхивает.
23
+ Рост столбцов и прорисовку линии кадром не задать — их рисует канвас по данным,
24
+ для этого кадр должен считаться в самом рисовании (следующий шаг для графиков) */
25
+ chui_bar_graph,
26
+ chui_line_graph,
27
+ chui_piechart,
28
+ chui_scatter_graph,
29
+ chui_gauge {
30
+ animation: chui_appear_up var(--motion_base) var(--motion_ease);
31
+ }
32
+
33
+ /* Подсказка графика: появляется и гаснет движения — иначе она перескакивала бы с
34
+ одного столбца на другой рывком (кадр ухода общий, время — быстрое) */
35
+ chui_chart_tooltip {
36
+ animation: chui_appear_up var(--motion_fast) var(--motion_ease);
37
+ }
38
+
39
+ .chui_chart_tooltip_leaving {
40
+ animation: chui_leave_up var(--motion_fast) var(--motion_ease) forwards;
41
+ }
42
+
22
43
  /* Обёртка линейного графика и графика с заливкой */
23
44
  chui_line_graph {
24
45
  position: relative;
@@ -33,7 +54,7 @@ chui_chart_tooltip {
33
54
  z-index: 2;
34
55
  transform: translate(-50%, -130%);
35
56
  padding: var(--control_padding);
36
- border-radius: var(--border_radius);
57
+ border-radius: var(--radius_sm);
37
58
  background: var(--header_background);
38
59
  backdrop-filter: var(--global_blur);
39
60
  color: var(--text_color);
@@ -65,13 +86,13 @@ legend_color {
65
86
  width: 20px;
66
87
  height: 20px;
67
88
  flex-shrink: 0;
68
- border-radius: var(--border_radius);
89
+ border-radius: var(--radius_sm);
69
90
  }
70
91
 
71
92
  legend_label {
72
93
  color: var(--text_color);
73
94
  font-weight: 400;
74
- margin: var(--margin);
95
+ margin: var(--block_margin);
75
96
  }
76
97
 
77
98
  /* Обёртка диаграммы рассеяния: канвас, легенда серий и скрытая таблица данных */
@@ -1,8 +1,27 @@
1
1
  const { Icon } = require("../chui_icons/icons");
2
2
  const { optionOr } = require("../../modules/chui_functions");
3
+ const { animate, tokenTime } = require("../../modules/chui_animations/motion");
3
4
 
5
+ /**
6
+ * Шапка страницы: значок, заголовок, описание и строка статистики.
7
+ *
8
+ * Подложка — нейтральная заливка с свечением акцентов, сверху акцентная линия, а знак
9
+ * залит градиентом двух акцентов палитры: цвет не заведён мимо таблицы, поэтому обе схемы
10
+ * работают сами. Плитки статистики берут акценты по кругу (`STAT_ACCENTS`) — ряд читается
11
+ * цветным без настройки каждой плитки; цвет идёт заливкой, рамкой и полосой, а число —
12
+ * цветом темы, иначе акцентный текст на тинте дал бы контраст ниже 3.
13
+ *
14
+ * Движение — по системе движения: шапка приходит кадром появления, линия сверху
15
+ * прочерчивается, знак встаёт с перелётом и выпускает кольцо, заголовок → описание → плитки
16
+ * идут по очереди, полосы плиток растут, числа догоняют значения, а замыкает блик по подложке.
17
+ * Все кадры одиночные: у шапки нет задачи ждать — непрерывное движение в ней было бы
18
+ * обещанием процесса, которого нет. При уменьшенном движении всё сжимается общим правилом темы.
19
+ */
4
20
  class Hero {
5
- #chui_hero = document.createElement("chui_hero");
21
+ /** Акценты плиток по кругу: порядок задаёт сам компонент, а не вызывающий код */
22
+ static STAT_ACCENTS = ["blue", "purple", "green", "orange"];
23
+
24
+ #chuiHero = document.createElement("chui_hero");
6
25
  #head;
7
26
  #badge;
8
27
  #titles;
@@ -22,19 +41,19 @@ class Hero {
22
41
  }
23
42
  ) {
24
43
  require("../../modules/chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_Hero");
25
- if (options.id !== undefined) this.#chui_hero.id = options.id;
44
+ if (options.id !== undefined) this.#chuiHero.id = options.id;
26
45
  const style = optionOr(options.style, undefined);
27
- if (style !== undefined) this.#chui_hero.classList.add(style);
28
- if (options.width !== undefined) this.#chui_hero.style.width = options.width;
29
- if (options.height !== undefined) this.#chui_hero.style.height = options.height;
46
+ if (style !== undefined) this.#chuiHero.classList.add(style);
47
+ if (options.width !== undefined) this.#chuiHero.style.width = options.width;
48
+ if (options.height !== undefined) this.#chuiHero.style.height = options.height;
30
49
  if (options.icon !== undefined) this.setIcon(options.icon);
31
50
  if (options.title !== undefined) this.setTitle(options.title);
32
51
  if (options.description !== undefined) this.setDescription(options.description);
33
- if (options.stats !== undefined) for (let stat of options.stats) this.addStat(stat.value, stat.label);
52
+ if (options.stats !== undefined) for (const stat of options.stats) this.addStat(stat.value, stat.label);
34
53
  }
35
54
  // GET
36
55
  getId() {
37
- return this.#chui_hero.id;
56
+ return this.#chuiHero.id;
38
57
  }
39
58
  getTitle() {
40
59
  return this.#title === undefined ? "" : this.#title.innerText;
@@ -44,18 +63,18 @@ class Hero {
44
63
  }
45
64
  // SET
46
65
  setId(id = String()) {
47
- this.#chui_hero.id = id;
66
+ this.#chuiHero.id = id;
48
67
  }
49
68
  // Пустая строка — «не задана»: `classList.add("")` бросает исключение
50
69
  setStyle(style = String()) {
51
70
  const known = optionOr(style, undefined);
52
- if (known !== undefined) this.#chui_hero.classList.add(known);
71
+ if (known !== undefined) this.#chuiHero.classList.add(known);
53
72
  }
54
73
  setWidth(width = String()) {
55
- this.#chui_hero.style.width = width;
74
+ this.#chuiHero.style.width = width;
56
75
  }
57
76
  setHeight(height = String()) {
58
- this.#chui_hero.style.height = height;
77
+ this.#chuiHero.style.height = height;
59
78
  }
60
79
  setIcon(icon = String()) {
61
80
  if (this.#badge === undefined) {
@@ -80,13 +99,37 @@ class Hero {
80
99
  }
81
100
  // FUNCTIONS
82
101
  addStat(value = String(), label = String()) {
83
- let stat = document.createElement("hero_stat");
84
- let stat_value = document.createElement("hero_stat_value");
85
- let stat_label = document.createElement("hero_stat_label");
86
- stat_value.innerText = value;
87
- stat_label.innerText = label;
88
- stat.append(stat_value, stat_label);
102
+ const stat = document.createElement("hero_stat");
103
+ const statValue = document.createElement("hero_stat_value");
104
+ const statLabel = document.createElement("hero_stat_label");
105
+ // Акцент — по числу уже стоящих плиток: ряд разукрашивается сам, без настройки.
106
+ // Тот же номер даёт ступеньку задержки в CSS (`--hero_stat_index`)
107
+ const index = this.#statsNode().children.length;
108
+ stat.dataset.accent = Hero.STAT_ACCENTS[index % Hero.STAT_ACCENTS.length];
109
+ stat.style.setProperty("--hero_stat_index", String(index));
110
+ statValue.innerText = value;
111
+ statLabel.innerText = label;
112
+ stat.append(statValue, statLabel);
89
113
  this.#statsNode().appendChild(stat);
114
+ this.#countUp(statValue, value, index);
115
+ }
116
+
117
+ /**
118
+ * Число догоняет значение. Шаг считает общий движок движения (`animate` в
119
+ * `chui_animations/motion`), поэтому время — токен темы, а при уменьшенном движении значение
120
+ * встаёт сразу. Плитка появляется со ступенькой, и счёт начинается той же ступенькой,
121
+ * иначе число менялось бы в уже стоящей плитке. Значение, которое не разбирается как число
122
+ * («12 %», «1,2 тыс.» или текст), остаётся как есть.
123
+ */
124
+ #countUp(node, value, index) {
125
+ const normalized = String(value).trim().replace(/\s/g, "").replace(",", ".");
126
+ const target = Number(normalized);
127
+ if (normalized === "" || Number.isFinite(target) === false) return;
128
+ // Знаков после запятой столько же, сколько в значении: целое не показываем с десятыми
129
+ const decimals = normalized.includes(".") ? normalized.split(".")[1].length : 0;
130
+ const frame = (growth) => (node.innerText = (target * growth).toFixed(decimals));
131
+ node.innerText = (0).toFixed(decimals);
132
+ setTimeout(() => animate(frame), tokenTime("--motion_base") + tokenTime("--motion_fast") * index);
90
133
  }
91
134
  clearStats() {
92
135
  this.#statsNode().innerHTML = "";
@@ -95,7 +138,7 @@ class Hero {
95
138
  #headNode() {
96
139
  if (this.#head === undefined) {
97
140
  this.#head = document.createElement("hero_head");
98
- this.#chui_hero.prepend(this.#head);
141
+ this.#chuiHero.prepend(this.#head);
99
142
  }
100
143
  return this.#head;
101
144
  }
@@ -109,13 +152,13 @@ class Hero {
109
152
  #statsNode() {
110
153
  if (this.#stats === undefined) {
111
154
  this.#stats = document.createElement("hero_stats");
112
- this.#chui_hero.appendChild(this.#stats);
155
+ this.#chuiHero.appendChild(this.#stats);
113
156
  }
114
157
  return this.#stats;
115
158
  }
116
159
  // RENDER
117
160
  set() {
118
- return this.#chui_hero;
161
+ return this.#chuiHero;
119
162
  }
120
163
  }
121
164