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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Общий ритм для тех, кто считает кадры сам: построение диаграмм, догоняющее значение,
|
|
3
|
+
* счётчики. Время и кривую берём из темы — те же токены, что у переходов и кадров CSS,
|
|
4
|
+
* поэтому движение на канвасе и в стилях читается одним ритмом, а не двумя.
|
|
5
|
+
*
|
|
6
|
+
* ```javascript
|
|
7
|
+
* const { animate } = require("../../modules/chui_animations/motion");
|
|
8
|
+
* animate((growth) => this.#draw(growth)); // рисуем полотно по мере роста
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Уменьшенное движение: движение не запускаем, результат ставим сразу */
|
|
13
|
+
function reducedMotion() {
|
|
14
|
+
return (
|
|
15
|
+
typeof window !== "undefined" &&
|
|
16
|
+
typeof window.matchMedia === "function" &&
|
|
17
|
+
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Время токена движения в миллисекундах: `--motion_base` → 250. Своё число здесь не годится —
|
|
23
|
+
* время живёт в теме, и его правка должна менять всё движение сразу.
|
|
24
|
+
*/
|
|
25
|
+
function tokenTime(name = "--motion_base") {
|
|
26
|
+
const source =
|
|
27
|
+
typeof window !== "undefined" && typeof window.getComputedStyle === "function"
|
|
28
|
+
? window.getComputedStyle(document.documentElement).getPropertyValue(name)
|
|
29
|
+
: "";
|
|
30
|
+
const seconds = Number.parseFloat(source);
|
|
31
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : 250;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Торможение к покою — приближение кривой `--motion_ease`: движение заканчивается мягко */
|
|
35
|
+
function easeOut(t) {
|
|
36
|
+
return 1 - Math.pow(1 - t, 3);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Кадры движения: `step(0…1)` вызывается на каждом кадре до конца времени, затем один раз
|
|
41
|
+
* с единицей — так последний кадр всегда полный. Возвращает остановку: её зовут, когда
|
|
42
|
+
* началось новое движение по тому же значению (иначе два кадра спорили бы за результат).
|
|
43
|
+
* При уменьшенном движении и там, где кадров нет (jsdom без `pretendToBeVisual`), шаг
|
|
44
|
+
* выполняется сразу с единицей.
|
|
45
|
+
*/
|
|
46
|
+
function animate(step, options = {}) {
|
|
47
|
+
const duration = options.duration === undefined ? tokenTime() : options.duration;
|
|
48
|
+
if (typeof requestAnimationFrame !== "function" || reducedMotion() || !(duration > 0)) {
|
|
49
|
+
step(1);
|
|
50
|
+
return () => {};
|
|
51
|
+
}
|
|
52
|
+
const now = () => (typeof performance === "undefined" ? Date.now() : performance.now());
|
|
53
|
+
const start = now();
|
|
54
|
+
let stopped = false;
|
|
55
|
+
const frame = (time) => {
|
|
56
|
+
if (stopped) return;
|
|
57
|
+
const t = Math.min(1, (time - start) / duration);
|
|
58
|
+
step(options.linear === true ? t : easeOut(t));
|
|
59
|
+
if (t < 1) requestAnimationFrame(frame);
|
|
60
|
+
};
|
|
61
|
+
requestAnimationFrame(frame);
|
|
62
|
+
return () => {
|
|
63
|
+
stopped = true;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Ждём, пока запрос сменит значение: событие приходит асинхронно, кадров на это не хватает */
|
|
68
|
+
function waitForFlip(media, before) {
|
|
69
|
+
if (media.matches !== before) return Promise.resolve();
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const done = () => {
|
|
72
|
+
if (media.matches === before) return;
|
|
73
|
+
media.removeEventListener("change", done);
|
|
74
|
+
resolve();
|
|
75
|
+
};
|
|
76
|
+
media.addEventListener("change", done);
|
|
77
|
+
// Страховка: если смена не дошла, работа всё равно завершается, а не висит
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
media.removeEventListener("change", done);
|
|
80
|
+
resolve();
|
|
81
|
+
}, 1000);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Работа с общим кроссфейдом: браузер снимает страницу до и после неё и переводит один кадр
|
|
87
|
+
* в другой. Нужен там, где мгновенное переносит не CSS-переход, а перерисовка, — смена темы:
|
|
88
|
+
* она приходит сменой `prefers-color-scheme`, и переходы на такое изменение браузер не
|
|
89
|
+
* запускает (проверено замером: цвет подложки меняется кадром, хотя класс с временем перехода
|
|
90
|
+
* на корне уже стоит).
|
|
91
|
+
*
|
|
92
|
+
* `waitFor` — имя запроса, значение которого должно смениться внутри работы до снимка
|
|
93
|
+
* «после»: без ожидания снимок может быть взят до перекраски, и кроссфейд пойдёт из старой
|
|
94
|
+
* схемы в старую. Возвращает то же, что вернула работа.
|
|
95
|
+
*/
|
|
96
|
+
async function crossFade(work, options = {}) {
|
|
97
|
+
if (reducedMotion() || typeof document === "undefined" || typeof document.startViewTransition !== "function") {
|
|
98
|
+
return work();
|
|
99
|
+
}
|
|
100
|
+
const media = options.waitFor === undefined ? undefined : window.matchMedia(options.waitFor);
|
|
101
|
+
const before = media === undefined ? undefined : media.matches;
|
|
102
|
+
let value;
|
|
103
|
+
const transition = document.startViewTransition(async () => {
|
|
104
|
+
value = await work();
|
|
105
|
+
// Ждём саму смену запроса: без этого снимок «после» может быть взят до перекраски,
|
|
106
|
+
// и кроссфейд пойдёт из старой схемы в старую. Кадров здесь нет намеренно:
|
|
107
|
+
// на время обработчика браузер останавливает отрисовку, и `requestAnimationFrame`
|
|
108
|
+
// не срабатывает вовсе (проверено — обработчик из-за этого зависал) —
|
|
109
|
+
// снимок новый браузер делает сам, когда обработчик завершится
|
|
110
|
+
if (media !== undefined) await waitForFlip(media, before);
|
|
111
|
+
});
|
|
112
|
+
await transition.updateCallbackDone;
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
exports.reducedMotion = reducedMotion;
|
|
117
|
+
exports.tokenTime = tokenTime;
|
|
118
|
+
exports.easeOut = easeOut;
|
|
119
|
+
exports.animate = animate;
|
|
120
|
+
exports.crossFade = crossFade;
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Виды движения. Имя кадра даёт JS (`Animation`), а длительность и кривая живут здесь:
|
|
3
|
+
* так у одного вида одно время на всё приложение, а не у каждого вызова своё.
|
|
4
|
+
* Вход базовый (0.25s) с торможением к покою, выход короче (0.15s) — уход не задерживает
|
|
5
|
+
* следующий экран, отклик чуть перелетает (пружина) — так читаются всплывающие меню и диалог.
|
|
6
|
+
* Значения — из темы (`--motion_*` в global_style.css).
|
|
7
|
+
*/
|
|
8
|
+
[data-motion="enter"] {
|
|
9
|
+
animation-duration: var(--motion_base);
|
|
10
|
+
animation-timing-function: var(--motion_ease);
|
|
11
|
+
animation-fill-mode: both;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
[data-motion="exit"] {
|
|
15
|
+
animation-duration: var(--motion_fast);
|
|
16
|
+
animation-timing-function: var(--motion_ease);
|
|
17
|
+
animation-fill-mode: both;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-motion="pop"] {
|
|
21
|
+
animation-duration: var(--motion_base);
|
|
22
|
+
animation-timing-function: var(--motion_spring);
|
|
23
|
+
animation-fill-mode: both;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Кадром владеет слой, а не переход: после выходного вида заполненный `transform` остаётся
|
|
27
|
+
на месте, а следующий кадр его не объявляет — переход вернул бы слой на место отдельным
|
|
28
|
+
движением, поверх проигрываемого. Поэтому у слоя выключен только `transform`, остальные
|
|
29
|
+
свойства живут по общему правилу `* { transition }` */
|
|
30
|
+
[data-motion] {
|
|
31
|
+
transition-property: background-color, color, border-color, box-shadow, filter, opacity;
|
|
32
|
+
}
|
|
33
|
+
|
|
1
34
|
/* FADE */
|
|
2
35
|
@keyframes fade-in {
|
|
3
36
|
from {
|
|
@@ -16,11 +49,11 @@
|
|
|
16
49
|
opacity: 0;
|
|
17
50
|
}
|
|
18
51
|
}
|
|
19
|
-
/* SCALE */
|
|
52
|
+
/* SCALE. Масштаб появления небольшой (0.92 вместо прежних 0.63): крупный «наезд» камеры выделял сам эффект, а не появление слоя */
|
|
20
53
|
@keyframes scale-in {
|
|
21
54
|
from {
|
|
22
55
|
opacity: 0;
|
|
23
|
-
transform: scale(0.
|
|
56
|
+
transform: scale(0.92);
|
|
24
57
|
}
|
|
25
58
|
to {
|
|
26
59
|
opacity: 1;
|
|
@@ -34,7 +67,7 @@
|
|
|
34
67
|
}
|
|
35
68
|
to {
|
|
36
69
|
opacity: 0;
|
|
37
|
-
transform: scale(0.
|
|
70
|
+
transform: scale(0.96);
|
|
38
71
|
}
|
|
39
72
|
}
|
|
40
73
|
/* SLIDE */
|
|
@@ -5,33 +5,36 @@ function sleep(ms) {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
/** Кэш прочитанных таблиц стилей: один файл отдаётся разным `id` без повторного чтения с диска */
|
|
8
|
-
const
|
|
8
|
+
const stylesCache = new Map();
|
|
9
9
|
|
|
10
10
|
function setStyles(pathToCSS = String(), component = String()) {
|
|
11
|
-
if (document.getElementById(component)
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
style
|
|
11
|
+
if (document.getElementById(component) !== null) return;
|
|
12
|
+
if (stylesCache.has(pathToCSS) === false) stylesCache.set(pathToCSS, fs.readFileSync(pathToCSS, "utf8"));
|
|
13
|
+
const style = document.createElement("style");
|
|
14
|
+
// Содержимое `<style>` — CSS, а не разметка: `textContent` не разбирает его как HTML,
|
|
15
|
+
// поэтому символы `<` в CSS не могут порвать таблицу (в jsdom `innerHTML` тоже работает,
|
|
16
|
+
// но `textContent` — это ровно то, чем является тело `<style>`)
|
|
17
|
+
style.textContent = stylesCache.get(pathToCSS);
|
|
15
18
|
style.type = "text/css";
|
|
16
19
|
style.setAttribute("id", component);
|
|
17
20
|
document.head.appendChild(style);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
function
|
|
23
|
+
function parseStyle(json, component) {
|
|
21
24
|
// Сначала проверяем, что стиль уже есть: сборка строки стоит дорого (для шрифтов — мегабайты)
|
|
22
|
-
if (document.getElementById(component)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
for (let i = 0; i <
|
|
26
|
-
|
|
27
|
-
JSON.parse(JSON.stringify(
|
|
28
|
-
if (typeof value !== "object")
|
|
25
|
+
if (document.getElementById(component) !== null) return;
|
|
26
|
+
const styleGroups = JSON.parse(JSON.stringify(json));
|
|
27
|
+
const styleParts = [];
|
|
28
|
+
for (let i = 0; i < styleGroups.length; i++) {
|
|
29
|
+
styleParts.push(`${styleGroups[i].name} {`);
|
|
30
|
+
JSON.parse(JSON.stringify(styleGroups[i].style), function (key, value) {
|
|
31
|
+
if (typeof value !== "object") styleParts.push(`${key}:${value};`);
|
|
29
32
|
return value;
|
|
30
33
|
});
|
|
31
|
-
|
|
34
|
+
styleParts.push(`}\n`);
|
|
32
35
|
}
|
|
33
|
-
|
|
34
|
-
style.
|
|
36
|
+
const style = document.createElement("style");
|
|
37
|
+
style.textContent = styleParts.join("").slice(0, -1);
|
|
35
38
|
style.type = "text/css";
|
|
36
39
|
style.setAttribute("id", component);
|
|
37
40
|
document.head.appendChild(style);
|
|
@@ -114,12 +117,12 @@ function shouldOpenDropdownUp(dropdown = new HTMLElement(), anchor = new HTMLEle
|
|
|
114
117
|
* явным `[текст](адрес)`. Разборщик создаётся при первом вызове: markdown нужен не каждой
|
|
115
118
|
* странице, а `require` тянет библиотеку целиком.
|
|
116
119
|
*/
|
|
117
|
-
let
|
|
120
|
+
let markdownParser = undefined;
|
|
118
121
|
function markdownToHtml(text = String()) {
|
|
119
122
|
if (text === undefined || text === null || String(text) === "") return "";
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
return
|
|
123
|
+
if (markdownParser === undefined)
|
|
124
|
+
markdownParser = new (require("markdown-it"))({ html: false, linkify: false, breaks: false });
|
|
125
|
+
return markdownParser.render(String(text)).trim();
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
/** Обратный разбор: HTML → markdown (`turndown`) */
|
|
@@ -130,12 +133,12 @@ function htmlToMarkdown(text = String()) {
|
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
function getDate() {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
const date = new Date();
|
|
137
|
+
const day = ("0" + Number(date.getDate()).toString()).slice(-2);
|
|
138
|
+
const month = ("0" + Number(date.getMonth() + 1).toString()).slice(-2);
|
|
139
|
+
const year = date.getFullYear();
|
|
140
|
+
const hours = ("0" + Number(date.getHours()).toString()).slice(-2);
|
|
141
|
+
const minutes = ("0" + Number(date.getMinutes()).toString()).slice(-2);
|
|
139
142
|
return `${day}.${month}.${year} ${hours}:${minutes}`;
|
|
140
143
|
}
|
|
141
144
|
|
|
@@ -210,7 +213,8 @@ exports.getDate = getDate;
|
|
|
210
213
|
exports.getTime = getTime;
|
|
211
214
|
exports.getDayKey = getDayKey;
|
|
212
215
|
exports.sleep = sleep;
|
|
213
|
-
|
|
216
|
+
// Ключ остаётся прежним: имя читает `chui_fonts.js` и чужой код, а функция внутри — своя
|
|
217
|
+
exports.style_parse = parseStyle;
|
|
214
218
|
exports.render = render;
|
|
215
219
|
exports.markdownToHtml = markdownToHtml;
|
|
216
220
|
exports.htmlToMarkdown = htmlToMarkdown;
|
|
@@ -1,37 +1,40 @@
|
|
|
1
|
-
/* Токены подсветки кода (модуль chui_highlight)
|
|
2
|
-
|
|
1
|
+
/* Токены подсветки кода (модуль chui_highlight). Цвета живут в общей палитре
|
|
2
|
+
(`--code_token_*` в `main_theme_style.css`) и у каждой схемы свои: акценты палитры,
|
|
3
|
+
приглушённые к заливке кода до нормы 4.5 — прежде здесь стояли цвета только тёмной
|
|
4
|
+
колонки, и в светлой схеме примеры читались тускло. Подключается самим модулем,
|
|
5
|
+
поэтому подсветка работает у любого потребителя. */
|
|
3
6
|
.code_token_comment {
|
|
4
|
-
color: var(--
|
|
7
|
+
color: var(--code_token_comment);
|
|
5
8
|
font-style: italic;
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
.code_token_string {
|
|
9
|
-
color:
|
|
12
|
+
color: var(--code_token_string);
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
.code_token_number {
|
|
13
|
-
color:
|
|
16
|
+
color: var(--code_token_number);
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
.code_token_keyword {
|
|
17
|
-
color:
|
|
20
|
+
color: var(--code_token_keyword);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
.code_token_function {
|
|
21
|
-
color:
|
|
24
|
+
color: var(--code_token_function);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
.code_token_property,
|
|
25
28
|
.code_token_key {
|
|
26
|
-
color:
|
|
29
|
+
color: var(--code_token_property);
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
.code_token_selector {
|
|
30
|
-
color:
|
|
33
|
+
color: var(--code_token_selector);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
.code_token_color {
|
|
34
|
-
color:
|
|
37
|
+
color: var(--code_token_color);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
.code_token_operator {
|
|
@@ -90,7 +90,7 @@ class I18n {
|
|
|
90
90
|
/** Сменить направление вручную: `"ltr"` или `"rtl"` */
|
|
91
91
|
static setDirection(direction = String(), silent = false) {
|
|
92
92
|
const next = direction === "rtl" ? "rtl" : "ltr";
|
|
93
|
-
const changed = next !== this.#direction || this.#locale !== this.#
|
|
93
|
+
const changed = next !== this.#direction || this.#locale !== this.#appliedLocale;
|
|
94
94
|
this.#direction = next;
|
|
95
95
|
this.#apply();
|
|
96
96
|
if (!silent && changed) this.#emit();
|
|
@@ -161,7 +161,7 @@ class I18n {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// Язык, для которого уже проставлены атрибуты документа (чтобы не дёргать DOM зря)
|
|
164
|
-
static #
|
|
164
|
+
static #appliedLocale = undefined;
|
|
165
165
|
|
|
166
166
|
static #lookup(key) {
|
|
167
167
|
const current = this.#dictionaries[this.#locale];
|
|
@@ -176,7 +176,7 @@ class I18n {
|
|
|
176
176
|
if (typeof document === "undefined" || document.documentElement === null) return;
|
|
177
177
|
document.documentElement.setAttribute("dir", this.#direction);
|
|
178
178
|
document.documentElement.setAttribute("lang", this.#locale);
|
|
179
|
-
this.#
|
|
179
|
+
this.#appliedLocale = this.#locale;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
static #emit() {
|
|
@@ -27,6 +27,37 @@ module.exports = {
|
|
|
27
27
|
"feedback.chatRead": "Прочитано",
|
|
28
28
|
"feedback.chatFailed": "Не отправлено",
|
|
29
29
|
|
|
30
|
+
"feedback.writeBar": "Панель ввода",
|
|
31
|
+
"feedback.writeBarField": "Текст сообщения",
|
|
32
|
+
"feedback.writeBarPlaceholder": "Напишите сообщение…",
|
|
33
|
+
"feedback.writeBarSend": "Отправить",
|
|
34
|
+
"feedback.writeBarButton": "Действие панели ввода",
|
|
35
|
+
"feedback.writeBarBadge": "выбрано: {value}",
|
|
36
|
+
"feedback.writeBarHint": "Enter — отправить, Shift+Enter — новая строка",
|
|
37
|
+
"feedback.messageBar": "Сообщения",
|
|
38
|
+
"feedback.messageBarEmpty": "Сообщений пока нет",
|
|
39
|
+
"feedback.messageBarNew": "Новые сообщения",
|
|
40
|
+
"feedback.messageBarNewCount": "Новые сообщения: {value}",
|
|
41
|
+
"feedback.messageBarDown": "К низу",
|
|
42
|
+
"feedback.userBar": "Пользователь",
|
|
43
|
+
"feedback.userBarBack": "Назад",
|
|
44
|
+
"feedback.userBarMore": "Ещё",
|
|
45
|
+
"feedback.userBarAction": "Действие",
|
|
46
|
+
"feedback.userBarBadge": "{value}",
|
|
47
|
+
"feedback.userBarOpen": "Открыть профиль: {name}",
|
|
48
|
+
"feedback.userListBar": "Разговоры",
|
|
49
|
+
"feedback.userListBarSearch": "Поиск разговоров",
|
|
50
|
+
"feedback.userListBarEmpty": "Разговоров нет",
|
|
51
|
+
"feedback.userListBarNoMatch": "Ничего не найдено",
|
|
52
|
+
"feedback.userListBarUnread": "непрочитанных: {value}",
|
|
53
|
+
"feedback.actionsBar": "Действия",
|
|
54
|
+
"feedback.actionsBarButton": "Действие",
|
|
55
|
+
"feedback.actionsBarBadge": "{value}",
|
|
56
|
+
"feedback.banner": "Объявление",
|
|
57
|
+
"feedback.cardScroll": "Карточки",
|
|
58
|
+
"feedback.cardScrollPrev": "Назад",
|
|
59
|
+
"feedback.cardScrollNext": "Вперёд",
|
|
60
|
+
|
|
30
61
|
"feedback.centerTitle": "Уведомления",
|
|
31
62
|
"feedback.centerEmpty": "Уведомлений нет",
|
|
32
63
|
"feedback.centerNew": "Новое уведомление: {title}",
|
|
@@ -89,6 +120,37 @@ module.exports = {
|
|
|
89
120
|
"feedback.chatRead": "Read",
|
|
90
121
|
"feedback.chatFailed": "Not sent",
|
|
91
122
|
|
|
123
|
+
"feedback.writeBar": "Write bar",
|
|
124
|
+
"feedback.writeBarField": "Message text",
|
|
125
|
+
"feedback.writeBarPlaceholder": "Write a message…",
|
|
126
|
+
"feedback.writeBarSend": "Send",
|
|
127
|
+
"feedback.writeBarButton": "Write bar action",
|
|
128
|
+
"feedback.writeBarBadge": "selected: {value}",
|
|
129
|
+
"feedback.writeBarHint": "Enter to send, Shift+Enter for a new line",
|
|
130
|
+
"feedback.messageBar": "Messages",
|
|
131
|
+
"feedback.messageBarEmpty": "No messages yet",
|
|
132
|
+
"feedback.messageBarNew": "New messages",
|
|
133
|
+
"feedback.messageBarNewCount": "New messages: {value}",
|
|
134
|
+
"feedback.messageBarDown": "Go to bottom",
|
|
135
|
+
"feedback.userBar": "User",
|
|
136
|
+
"feedback.userBarBack": "Back",
|
|
137
|
+
"feedback.userBarMore": "More",
|
|
138
|
+
"feedback.userBarAction": "Action",
|
|
139
|
+
"feedback.userBarBadge": "{value}",
|
|
140
|
+
"feedback.userBarOpen": "Open profile: {name}",
|
|
141
|
+
"feedback.userListBar": "Conversations",
|
|
142
|
+
"feedback.userListBarSearch": "Search conversations",
|
|
143
|
+
"feedback.userListBarEmpty": "No conversations",
|
|
144
|
+
"feedback.userListBarNoMatch": "Nothing found",
|
|
145
|
+
"feedback.userListBarUnread": "unread: {value}",
|
|
146
|
+
"feedback.actionsBar": "Actions",
|
|
147
|
+
"feedback.actionsBarButton": "Action",
|
|
148
|
+
"feedback.actionsBarBadge": "{value}",
|
|
149
|
+
"feedback.banner": "Announcement",
|
|
150
|
+
"feedback.cardScroll": "Cards",
|
|
151
|
+
"feedback.cardScrollPrev": "Previous",
|
|
152
|
+
"feedback.cardScrollNext": "Next",
|
|
153
|
+
|
|
92
154
|
"feedback.centerTitle": "Notifications",
|
|
93
155
|
"feedback.centerEmpty": "No notifications",
|
|
94
156
|
"feedback.centerNew": "New notification: {title}",
|
|
@@ -25,6 +25,8 @@ module.exports = {
|
|
|
25
25
|
"navigation.stepper": "Шаги",
|
|
26
26
|
"navigation.stepOf": "Шаг {index} из {total}",
|
|
27
27
|
"navigation.stepDone": "выполнено",
|
|
28
|
+
"navigation.stepError": "ошибка",
|
|
29
|
+
"navigation.stepPending": "ожидает выполнения",
|
|
28
30
|
"navigation.stepCurrent": "текущий шаг",
|
|
29
31
|
"navigation.stepTodo": "не начат",
|
|
30
32
|
|
|
@@ -64,6 +66,8 @@ module.exports = {
|
|
|
64
66
|
"navigation.stepper": "Steps",
|
|
65
67
|
"navigation.stepOf": "Step {index} of {total}",
|
|
66
68
|
"navigation.stepDone": "done",
|
|
69
|
+
"navigation.stepError": "error",
|
|
70
|
+
"navigation.stepPending": "in progress",
|
|
67
71
|
"navigation.stepCurrent": "current step",
|
|
68
72
|
"navigation.stepTodo": "not started",
|
|
69
73
|
|
|
@@ -18,8 +18,8 @@ function placeOf(text = String(), offset = Number()) {
|
|
|
18
18
|
const limit = Math.max(0, Math.min(Number(offset) || 0, value.length));
|
|
19
19
|
const before = value.slice(0, limit);
|
|
20
20
|
const line = before.split("\n").length;
|
|
21
|
-
const
|
|
22
|
-
const column = limit -
|
|
21
|
+
const lastBreak = before.lastIndexOf("\n");
|
|
22
|
+
const column = limit - lastBreak;
|
|
23
23
|
return { line: line, column: column, offset: limit };
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -5,7 +5,7 @@ const fs = require("fs");
|
|
|
5
5
|
class Logger {
|
|
6
6
|
constructor() {}
|
|
7
7
|
#generatedMessage(message, type = String()) {
|
|
8
|
-
|
|
8
|
+
const date = new Date().toLocaleString();
|
|
9
9
|
if (process && process.type === "renderer") {
|
|
10
10
|
return `[${date}] [${type}] [render] > ${message}`;
|
|
11
11
|
} else {
|
|
@@ -31,16 +31,16 @@ class Logger {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
#writeLog(text) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const today = new Date().toLocaleDateString().replace("/", ".");
|
|
35
|
+
const logDirPath = path.join(app.getPath("userData"), "logs");
|
|
36
|
+
const logFilePath = path.join(logDirPath, `app_${today}.log`);
|
|
37
37
|
|
|
38
|
-
if (!fs.existsSync(
|
|
39
|
-
fs.mkdirSync(
|
|
40
|
-
fs.writeFileSync(
|
|
38
|
+
if (!fs.existsSync(logDirPath)) {
|
|
39
|
+
fs.mkdirSync(logDirPath, { recursive: true });
|
|
40
|
+
fs.writeFileSync(logFilePath, "", "utf-8");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
fs.writeFile(
|
|
43
|
+
fs.writeFile(logFilePath, `${text}\n`, { flag: "a+" }, (err) => {
|
|
44
44
|
if (err) console.error(err);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -55,4 +55,19 @@ class Log {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Ошибка сервиса не должна исчезать в `catch {}`: её отдают колбэку `onError` компонента, а без
|
|
60
|
+
* колбэка пишут в лог. Молчание обходилось дорого: панель истории буфера при сбое оставалась
|
|
61
|
+
* пустой без единого слова о причине, а переключатель темы показывал режим, который не
|
|
62
|
+
* применился.
|
|
63
|
+
*/
|
|
64
|
+
function reportError(error, onError = undefined) {
|
|
65
|
+
if (typeof onError === "function") {
|
|
66
|
+
onError(error);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
Log.error(error);
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
exports.Log = Log;
|
|
73
|
+
exports.reportError = reportError;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const { Animation } = require("../chui_animations/animations");
|
|
2
2
|
const { Route } = require("../../appLayout/default/chui_app_layout");
|
|
3
3
|
const { Button } = require("../../components/chui_button/button");
|
|
4
4
|
const { Icon } = require("../../components/chui_icons/icons");
|
|
@@ -8,12 +8,12 @@ class Page {
|
|
|
8
8
|
#title = undefined;
|
|
9
9
|
#icon = undefined;
|
|
10
10
|
#main = false;
|
|
11
|
-
#
|
|
11
|
+
#menuBar = undefined;
|
|
12
12
|
constructor() {
|
|
13
13
|
require("../chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_Page");
|
|
14
14
|
}
|
|
15
15
|
setBackButton(options = { title: "", icon: undefined, reverse: false, page: undefined }) {
|
|
16
|
-
|
|
16
|
+
const back = new Button({
|
|
17
17
|
title: options.title,
|
|
18
18
|
icon: options.icon,
|
|
19
19
|
reverse: options.reverse,
|
|
@@ -22,24 +22,24 @@ class Page {
|
|
|
22
22
|
this.#page.appendChild(back.set());
|
|
23
23
|
}
|
|
24
24
|
setMenuBar(menubar) {
|
|
25
|
-
this.#
|
|
25
|
+
this.#menuBar = menubar.set();
|
|
26
26
|
}
|
|
27
27
|
getMenuBar() {
|
|
28
|
-
return this.#
|
|
28
|
+
return this.#menuBar;
|
|
29
29
|
}
|
|
30
30
|
addRouteEvent(self, event = () => {}) {
|
|
31
|
-
|
|
32
|
-
document.addEventListener(
|
|
33
|
-
setTimeout(() => document.removeEventListener(
|
|
31
|
+
const eventName = "route_event_" + self.constructor.name;
|
|
32
|
+
document.addEventListener(eventName, event);
|
|
33
|
+
setTimeout(() => document.removeEventListener(eventName, event), 100);
|
|
34
34
|
}
|
|
35
35
|
add(...components) {
|
|
36
|
-
for (
|
|
36
|
+
for (const component of components) {
|
|
37
37
|
new Animation(component.set()).fadeIn();
|
|
38
38
|
this.#page.appendChild(component.set());
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
remove(...components) {
|
|
42
|
-
for (
|
|
42
|
+
for (const component of components) {
|
|
43
43
|
new Animation(component.set()).fadeOutAndRemove();
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Палитра приложения для кода: канва и готовые наборы цветов не могут взять CSS-переменную,
|
|
3
|
+
* поэтому им нужны сами значения. Цвет заводится один раз — в теме
|
|
4
|
+
* (`main_theme_style.css`, ключи `--<имя>_prime_background`), а здесь та же таблица
|
|
5
|
+
* повторена для JS: сверка палитры (`npm run palette:check`) и сторож ключей
|
|
6
|
+
* (`test/chui_tokens.test.js`) требуют, чтобы таблица совпадала с темой, — иначе цвет
|
|
7
|
+
* разошёлся бы между холстом и вёрсткой.
|
|
8
|
+
*
|
|
9
|
+
* Схему модуль выбирает сам (`prefers-color-scheme`): цвет графика обязан совпадать с тем,
|
|
10
|
+
* что нарисовано вокруг, поэтому в светлой схеме берётся светлая колонка таблицы.
|
|
11
|
+
*
|
|
12
|
+
* ```javascript
|
|
13
|
+
* paletteColor("blue"); // «#007aff» в светлой схеме, «#0a84ff» в тёмной
|
|
14
|
+
* paletteColor("gray", { alpha: 0.25 }); // сетка графика: тот же серый акцент, но тише
|
|
15
|
+
* paletteColor("green", { scheme: "light" });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const ACCENTS = {
|
|
19
|
+
red: { light: "#ff3b30", dark: "#ff453a" },
|
|
20
|
+
orange: { light: "#ff9500", dark: "#ff9f0a" },
|
|
21
|
+
yellow: { light: "#ffcc00", dark: "#ffd60a" },
|
|
22
|
+
green: { light: "#34c759", dark: "#30d158" },
|
|
23
|
+
mint: { light: "#00c7be", dark: "#63e6e2" },
|
|
24
|
+
teal: { light: "#30b0c7", dark: "#40c8e0" },
|
|
25
|
+
cyan: { light: "#32ade6", dark: "#64d2ff" },
|
|
26
|
+
blue: { light: "#007aff", dark: "#0a84ff" },
|
|
27
|
+
indigo: { light: "#5856d6", dark: "#5e5ce6" },
|
|
28
|
+
purple: { light: "#af52de", dark: "#bf5af2" },
|
|
29
|
+
pink: { light: "#ff2d55", dark: "#ff375f" },
|
|
30
|
+
brown: { light: "#a2845e", dark: "#ac8e68" },
|
|
31
|
+
gray: { light: "#8e8e93", dark: "#98989d" },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Имена таблицы акцентов — в том же порядке, что в теме */
|
|
35
|
+
function paletteNames() {
|
|
36
|
+
return Object.keys(ACCENTS);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Схема окна: у канвы нет своей, она обязана совпадать с вёрсткой вокруг */
|
|
40
|
+
function paletteScheme() {
|
|
41
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
|
|
42
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Цвет акцента палитры: `alpha` (0…1) даёт полупрозрачную запись того же цвета —
|
|
47
|
+
* так рисуется сетка графика и дорожка шкалы.
|
|
48
|
+
*/
|
|
49
|
+
function paletteColor(name = "blue", options = {}) {
|
|
50
|
+
const { alpha = 1, scheme = paletteScheme() } = options;
|
|
51
|
+
const accent = ACCENTS[name];
|
|
52
|
+
if (accent === undefined) throw new Error("нет такого цвета в палитре: " + name);
|
|
53
|
+
const hex = accent[scheme] ?? accent.light;
|
|
54
|
+
if (alpha >= 1) return hex;
|
|
55
|
+
const [r, g, b] = [1, 3, 5].map((offset) => parseInt(hex.slice(offset, offset + 2), 16));
|
|
56
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exports.paletteNames = paletteNames;
|
|
60
|
+
exports.paletteScheme = paletteScheme;
|
|
61
|
+
exports.paletteColor = paletteColor;
|