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.
- package/CHANGELOG.md +536 -0
- package/framework/appLayout/default/chui_app_layout.js +361 -303
- package/framework/appLayout/default/global_style.css +243 -89
- package/framework/appLayout/default/main_theme_style.css +188 -101
- package/framework/components/chui_accordion/accordion.js +17 -17
- package/framework/components/chui_accordion/styles.css +7 -7
- package/framework/components/chui_action_sheet/action_sheet.js +35 -32
- package/framework/components/chui_action_sheet/styles.css +10 -4
- package/framework/components/chui_actions_bar/actions_bar.js +338 -0
- package/framework/components/chui_actions_bar/styles.css +139 -0
- package/framework/components/chui_affix/affix.js +4 -4
- package/framework/components/chui_affix/styles.css +5 -0
- package/framework/components/chui_alert/alert.js +11 -5
- package/framework/components/chui_alert/styles.css +7 -0
- package/framework/components/chui_avatar/avatar.js +13 -13
- package/framework/components/chui_avatar/styles.css +1 -1
- package/framework/components/chui_avatar_group/avatar_group.js +4 -4
- package/framework/components/chui_avatar_group/styles.css +1 -1
- package/framework/components/chui_backtop/backtop.js +25 -2
- package/framework/components/chui_backtop/styles.css +21 -0
- package/framework/components/chui_badge/badge.js +15 -15
- package/framework/components/chui_badge/styles.css +6 -6
- package/framework/components/chui_banner/banner.js +222 -0
- package/framework/components/chui_banner/styles.css +170 -0
- package/framework/components/chui_breadcrumb/styles.css +1 -1
- package/framework/components/chui_button/button.js +24 -28
- package/framework/components/chui_button/styles.css +5 -5
- package/framework/components/chui_calendar/calendar.js +6 -6
- package/framework/components/chui_card/card.js +14 -14
- package/framework/components/chui_card_scroll/card_scroll.js +169 -0
- package/framework/components/chui_card_scroll/styles.css +86 -0
- package/framework/components/chui_chat/chat.js +73 -51
- package/framework/components/chui_chat/styles.css +18 -7
- package/framework/components/chui_clipboard_history/clipboard_history.js +25 -20
- package/framework/components/chui_clipboard_history/styles.css +10 -8
- package/framework/components/chui_code/code.js +8 -8
- package/framework/components/chui_code/styles.css +2 -2
- package/framework/components/chui_code_editor/code_editor.js +26 -26
- package/framework/components/chui_code_editor/styles.css +3 -3
- package/framework/components/chui_command_palette/command_palette.js +29 -29
- package/framework/components/chui_command_palette/styles.css +5 -5
- package/framework/components/chui_console/console.js +9 -9
- package/framework/components/chui_console/styles.css +5 -2
- package/framework/components/chui_content_block/content_block.js +2 -2
- package/framework/components/chui_context_menu/context_menu.js +162 -54
- package/framework/components/chui_context_menu/styles.css +82 -8
- package/framework/components/chui_custom_element/custom_element.js +11 -11
- package/framework/components/chui_data_grid/data_grid.js +268 -268
- package/framework/components/chui_data_grid/styles.css +14 -14
- package/framework/components/chui_descriptions/styles.css +1 -1
- package/framework/components/chui_details/details.js +12 -12
- package/framework/components/chui_details/styles.css +7 -7
- package/framework/components/chui_diff_viewer/diff_viewer.js +88 -88
- package/framework/components/chui_diff_viewer/styles.css +9 -9
- package/framework/components/chui_divider/divider.js +6 -6
- package/framework/components/chui_divider/styles.css +2 -2
- package/framework/components/chui_drawer/drawer.js +37 -14
- package/framework/components/chui_drawer/styles.css +18 -4
- package/framework/components/chui_drop_zone/drop_zone.js +6 -6
- package/framework/components/chui_drop_zone/styles.css +10 -6
- package/framework/components/chui_fieldset/fieldset.js +6 -6
- package/framework/components/chui_fieldset/styles.css +5 -5
- package/framework/components/chui_file_explorer/file_explorer.js +115 -115
- package/framework/components/chui_file_explorer/styles.css +6 -6
- package/framework/components/chui_form/form.js +11 -11
- package/framework/components/chui_gantt/gantt.js +41 -41
- package/framework/components/chui_gantt/styles.css +5 -5
- package/framework/components/chui_graphs/graphs.js +82 -46
- package/framework/components/chui_graphs/styles.css +26 -5
- package/framework/components/chui_hero/hero.js +63 -20
- package/framework/components/chui_hero/styles.css +176 -7
- package/framework/components/chui_html_block/html_block.js +5 -5
- package/framework/components/chui_html_block/styles.css +1 -1
- package/framework/components/chui_hx/styles.css +2 -2
- package/framework/components/chui_icons/icons.js +7 -7
- package/framework/components/chui_icons/styles.css +2 -1
- package/framework/components/chui_inputs/chui_check_box/styles.css +12 -3
- package/framework/components/chui_inputs/chui_chips/chips.js +92 -31
- package/framework/components/chui_inputs/chui_chips/styles.css +28 -5
- package/framework/components/chui_inputs/chui_color_picker/color_picker.js +20 -15
- package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
- package/framework/components/chui_inputs/chui_combo_box/combo_box.js +58 -58
- package/framework/components/chui_inputs/chui_combo_box/styles.css +9 -9
- package/framework/components/chui_inputs/chui_date/date.js +93 -95
- package/framework/components/chui_inputs/chui_date/styles.css +8 -8
- package/framework/components/chui_inputs/chui_date_range/date_range.js +22 -22
- package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
- package/framework/components/chui_inputs/chui_datetime/datetime.js +12 -12
- package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
- package/framework/components/chui_inputs/chui_email/email.js +10 -10
- package/framework/components/chui_inputs/chui_email/styles.css +6 -6
- package/framework/components/chui_inputs/chui_file/file.js +8 -8
- package/framework/components/chui_inputs/chui_file/styles.css +12 -9
- package/framework/components/chui_inputs/chui_hotkey/hotkey.js +39 -39
- package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
- package/framework/components/chui_inputs/chui_masked/masked.js +44 -44
- package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
- package/framework/components/chui_inputs/chui_mention/mention.js +13 -13
- package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
- package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +147 -137
- package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +14 -8
- package/framework/components/chui_inputs/chui_number/number.js +42 -45
- package/framework/components/chui_inputs/chui_number/styles.css +6 -6
- package/framework/components/chui_inputs/chui_otp/otp.js +32 -32
- package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
- package/framework/components/chui_inputs/chui_password/password.js +30 -36
- package/framework/components/chui_inputs/chui_password/styles.css +7 -7
- package/framework/components/chui_inputs/chui_radio_button/styles.css +2 -2
- package/framework/components/chui_inputs/chui_rating/rating.js +28 -28
- package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
- package/framework/components/chui_inputs/chui_select_box/select_box.js +54 -54
- package/framework/components/chui_inputs/chui_select_box/styles.css +10 -10
- package/framework/components/chui_inputs/chui_slider/slider.js +14 -14
- package/framework/components/chui_inputs/chui_slider/styles.css +5 -4
- package/framework/components/chui_inputs/chui_text/styles.css +7 -7
- package/framework/components/chui_inputs/chui_text/text.js +13 -17
- package/framework/components/chui_inputs/chui_text_area/styles.css +8 -8
- package/framework/components/chui_inputs/chui_text_area/text_area.js +23 -23
- package/framework/components/chui_inputs/chui_time/styles.css +13 -17
- package/framework/components/chui_inputs/chui_time/time.js +35 -24
- package/framework/components/chui_inputs/chui_toggle/styles.css +10 -3
- package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
- package/framework/components/chui_json_editor/json_editor.js +87 -87
- package/framework/components/chui_json_editor/styles.css +5 -5
- package/framework/components/chui_json_viewer/json_viewer.js +80 -80
- package/framework/components/chui_json_viewer/styles.css +6 -6
- package/framework/components/chui_kanban/kanban.js +27 -27
- package/framework/components/chui_kanban/styles.css +8 -4
- package/framework/components/chui_kbd/styles.css +1 -1
- package/framework/components/chui_label/label.js +17 -17
- package/framework/components/chui_label/styles.css +1 -1
- package/framework/components/chui_link/link.js +9 -9
- package/framework/components/chui_link/styles.css +11 -4
- package/framework/components/chui_list/list.js +11 -11
- package/framework/components/chui_list/styles.css +2 -2
- package/framework/components/chui_markdown_editor/markdown_editor.js +66 -66
- package/framework/components/chui_markdown_editor/styles.css +5 -5
- package/framework/components/chui_media/audio.js +240 -246
- package/framework/components/chui_media/audio_styles.css +41 -23
- package/framework/components/chui_media/image.js +49 -46
- package/framework/components/chui_media/image_compare.js +35 -35
- package/framework/components/chui_media/image_compare_styles.css +5 -5
- package/framework/components/chui_media/image_styles.css +1 -1
- package/framework/components/chui_media/video.js +148 -148
- package/framework/components/chui_media/video_styles.css +27 -11
- package/framework/components/chui_menu_bar/menu_bar.js +54 -54
- package/framework/components/chui_menu_bar/styles.css +10 -10
- package/framework/components/chui_message_bar/message_bar.js +551 -0
- package/framework/components/chui_message_bar/styles.css +248 -0
- package/framework/components/chui_modal/modal.js +37 -13
- package/framework/components/chui_notification/notification.js +76 -54
- package/framework/components/chui_notification/notification_download.js +26 -81
- package/framework/components/chui_notification/notification_download_card.js +115 -0
- package/framework/components/chui_notification/notification_download_progress.css +2 -2
- package/framework/components/chui_notification/notification_download_progress.js +70 -108
- package/framework/components/chui_notification/notification_update.js +31 -31
- package/framework/components/chui_notification/status_plate.css +3 -3
- package/framework/components/chui_notification/styles.css +6 -1
- package/framework/components/chui_notification/styles_download.css +1 -1
- package/framework/components/chui_notification/styles_update.css +2 -2
- package/framework/components/chui_org_chart/org_chart.js +29 -29
- package/framework/components/chui_org_chart/styles.css +2 -2
- package/framework/components/chui_pagination/pagination.js +3 -3
- package/framework/components/chui_pagination/styles.css +1 -1
- package/framework/components/chui_paragraph/styles.css +2 -2
- package/framework/components/chui_popover/popover.js +29 -23
- package/framework/components/chui_popover/styles.css +1 -1
- package/framework/components/chui_popups/popups.js +124 -124
- package/framework/components/chui_popups/styles.css +5 -5
- package/framework/components/chui_print_button/print_button.js +15 -10
- package/framework/components/chui_progress_bar/progress_bar.js +34 -12
- package/framework/components/chui_progress_bar/styles.css +4 -4
- package/framework/components/chui_progress_ring/progress_ring.js +15 -15
- package/framework/components/chui_progress_ring/styles.css +1 -1
- package/framework/components/chui_qrcode/qrcode.js +12 -12
- package/framework/components/chui_query_builder/query_builder.js +46 -46
- package/framework/components/chui_query_builder/styles.css +4 -4
- package/framework/components/chui_radio_group/styles.css +1 -1
- package/framework/components/chui_result/result.js +15 -15
- package/framework/components/chui_result/styles.css +3 -3
- package/framework/components/chui_scheduler/scheduler.js +74 -74
- package/framework/components/chui_scheduler/styles.css +5 -5
- package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
- package/framework/components/chui_screen_spinner/styles.css +49 -0
- package/framework/components/chui_segmented_control/segmented_control.js +63 -6
- package/framework/components/chui_segmented_control/styles.css +32 -6
- package/framework/components/chui_settings/settings.js +45 -46
- package/framework/components/chui_settings/styles.css +4 -4
- package/framework/components/chui_shortcut_help/shortcut_help.js +32 -34
- package/framework/components/chui_sidebar_nav/sidebar_nav.js +98 -29
- package/framework/components/chui_sidebar_nav/styles.css +9 -3
- package/framework/components/chui_slideshow/slideshow.js +85 -85
- package/framework/components/chui_slideshow/styles.css +6 -6
- package/framework/components/chui_spinner/spinner.js +10 -10
- package/framework/components/chui_split_button/split_button.js +30 -12
- package/framework/components/chui_split_button/styles.css +21 -5
- package/framework/components/chui_splitter/splitter.js +11 -11
- package/framework/components/chui_splitter/styles.css +2 -2
- package/framework/components/chui_statistic/statistic.js +47 -47
- package/framework/components/chui_status_bar/styles.css +2 -2
- package/framework/components/chui_stepper/stepper.js +202 -31
- package/framework/components/chui_stepper/styles.css +79 -14
- package/framework/components/chui_sticky_bar/sticky_bar.js +44 -13
- package/framework/components/chui_sticky_bar/styles.css +10 -8
- package/framework/components/chui_tabs/chui_tabs.js +74 -26
- package/framework/components/chui_tabs/styles.css +40 -12
- package/framework/components/chui_text_editor/styles.css +7 -7
- package/framework/components/chui_text_editor/text_editor.js +56 -56
- package/framework/components/chui_text_editor/text_editor_panel.js +173 -174
- package/framework/components/chui_theme_toggle/theme_toggle.js +21 -5
- package/framework/components/chui_timeline/styles.css +3 -1
- package/framework/components/chui_toast_queue/styles.css +11 -11
- package/framework/components/chui_toast_queue/toast_queue.js +30 -27
- package/framework/components/chui_toolbar/styles.css +2 -2
- package/framework/components/chui_tooltip/styles.css +2 -2
- package/framework/components/chui_tooltip/tooltip.js +3 -3
- package/framework/components/chui_tour/styles.css +8 -8
- package/framework/components/chui_tour/tour.js +72 -72
- package/framework/components/chui_transfer/styles.css +6 -6
- package/framework/components/chui_transfer/transfer.js +93 -106
- package/framework/components/chui_tree_table/styles.css +4 -4
- package/framework/components/chui_tree_table/tree_table.js +45 -45
- package/framework/components/chui_tree_view/styles.css +10 -8
- package/framework/components/chui_tree_view/tree_view.js +185 -183
- package/framework/components/chui_user_bar/styles.css +145 -0
- package/framework/components/chui_user_bar/user_bar.js +402 -0
- package/framework/components/chui_user_list_bar/styles.css +173 -0
- package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
- package/framework/components/chui_watermark/watermark.js +17 -17
- package/framework/components/chui_webview/webview.js +9 -9
- package/framework/components/chui_write_bar/styles.css +172 -0
- package/framework/components/chui_write_bar/write_bar.js +320 -0
- package/framework/main_window.js +1234 -0
- package/framework/modules/chui_a11y/chui_a11y.js +78 -79
- package/framework/modules/chui_animations/animations.js +77 -15
- package/framework/modules/chui_animations/motion.js +120 -0
- package/framework/modules/chui_animations/styles.css +36 -3
- package/framework/modules/chui_functions.js +31 -27
- package/framework/modules/chui_highlight/styles.css +13 -10
- package/framework/modules/chui_i18n/chui_i18n.js +3 -3
- package/framework/modules/chui_i18n/locales/feedback.js +62 -0
- package/framework/modules/chui_i18n/locales/navigation.js +4 -0
- package/framework/modules/chui_json/chui_json.js +2 -2
- package/framework/modules/chui_logger/chui_logger.js +23 -8
- package/framework/modules/chui_page/page.js +10 -10
- package/framework/modules/chui_palette/palette.js +61 -0
- package/framework/modules/chui_qr/chui_qr.js +23 -23
- package/framework/modules/chui_scrollbar/chui_scrollbar.js +45 -45
- package/framework/modules/chui_scrollbar/styles.css +3 -3
- package/framework/modules/chui_services/chui_services.js +8 -8
- package/framework/modules/chui_services/desktop.js +77 -71
- package/framework/modules/chui_sortable/chui_sortable.js +17 -17
- package/framework/modules/chui_validate/chui_validate.js +3 -3
- package/framework/tray_bar/chui_menu_items.js +2 -2
- package/index.d.ts +266 -34
- package/index.js +20 -1168
- package/package.json +6 -4
- 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 = "
|
|
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 = "
|
|
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 = "
|
|
16
|
-
const LINE_FALLBACK_COLOR = "
|
|
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 = "
|
|
28
|
-
const GAUGE_TRACK_COLOR = "
|
|
29
|
-
const GAUGE_THRESHOLD_COLORS = ["
|
|
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 = "
|
|
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
|
-
|
|
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 (
|
|
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
|
-
#
|
|
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
|
-
|
|
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.#
|
|
309
|
-
if (options.legend) this.#
|
|
324
|
+
this.#chuiPiechart.appendChild(this.#canvas);
|
|
325
|
+
if (options.legend) this.#chuiPiechart.appendChild(this.#buildLegend());
|
|
310
326
|
// Доступная альтернатива канвасу: та же таблица данных рядом с графиком
|
|
311
|
-
this.#
|
|
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
|
|
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.#
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
967
|
+
#showValue = true;
|
|
935
968
|
#size = GAUGE_SIZE;
|
|
936
969
|
#thickness = GAUGE_THICKNESS;
|
|
937
|
-
#
|
|
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.#
|
|
951
|
-
this.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
1148
|
-
const end = ((this.#
|
|
1149
|
-
|
|
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.#
|
|
1211
|
-
this.#
|
|
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(--
|
|
6
|
-
margin: var(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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
|
-
|
|
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.#
|
|
44
|
+
if (options.id !== undefined) this.#chuiHero.id = options.id;
|
|
26
45
|
const style = optionOr(options.style, undefined);
|
|
27
|
-
if (style !== undefined) this.#
|
|
28
|
-
if (options.width !== undefined) this.#
|
|
29
|
-
if (options.height !== undefined) this.#
|
|
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 (
|
|
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.#
|
|
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.#
|
|
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.#
|
|
71
|
+
if (known !== undefined) this.#chuiHero.classList.add(known);
|
|
53
72
|
}
|
|
54
73
|
setWidth(width = String()) {
|
|
55
|
-
this.#
|
|
74
|
+
this.#chuiHero.style.width = width;
|
|
56
75
|
}
|
|
57
76
|
setHeight(height = String()) {
|
|
58
|
-
this.#
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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.#
|
|
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.#
|
|
155
|
+
this.#chuiHero.appendChild(this.#stats);
|
|
113
156
|
}
|
|
114
157
|
return this.#stats;
|
|
115
158
|
}
|
|
116
159
|
// RENDER
|
|
117
160
|
set() {
|
|
118
|
-
return this.#
|
|
161
|
+
return this.#chuiHero;
|
|
119
162
|
}
|
|
120
163
|
}
|
|
121
164
|
|