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
@@ -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.63);
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.63);
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 styles_cache = new Map();
8
+ const stylesCache = new Map();
9
9
 
10
10
  function setStyles(pathToCSS = String(), component = String()) {
11
- if (document.getElementById(component) != null) return;
12
- if (styles_cache.has(pathToCSS) === false) styles_cache.set(pathToCSS, fs.readFileSync(pathToCSS, "utf8"));
13
- let style = document.createElement("style");
14
- style.innerHTML = styles_cache.get(pathToCSS);
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 style_parse(json, component) {
23
+ function parseStyle(json, component) {
21
24
  // Сначала проверяем, что стиль уже есть: сборка строки стоит дорого (для шрифтов — мегабайты)
22
- if (document.getElementById(component) != null) return;
23
- let stringz = JSON.parse(JSON.stringify(json));
24
- let style_string = [];
25
- for (let i = 0; i < stringz.length; i++) {
26
- style_string.push(`${stringz[i].name} {`);
27
- JSON.parse(JSON.stringify(stringz[i].style), function (key, value) {
28
- if (typeof value !== "object") style_string.push(`${key}:${value};`);
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
- style_string.push(`}\n`);
34
+ styleParts.push(`}\n`);
32
35
  }
33
- let style = document.createElement("style");
34
- style.innerText = style_string.join("").slice(0, -1);
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 markdown_parser = undefined;
120
+ let markdownParser = undefined;
118
121
  function markdownToHtml(text = String()) {
119
122
  if (text === undefined || text === null || String(text) === "") return "";
120
- if (markdown_parser === undefined)
121
- markdown_parser = new (require("markdown-it"))({ html: false, linkify: false, breaks: false });
122
- return markdown_parser.render(String(text)).trim();
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
- let date = new Date();
134
- let day = ("0" + Number(date.getDate()).toString()).slice(-2);
135
- let month = ("0" + Number(date.getMonth() + 1).toString()).slice(-2);
136
- let year = date.getFullYear();
137
- let hours = ("0" + Number(date.getHours()).toString()).slice(-2);
138
- let minutes = ("0" + Number(date.getMinutes()).toString()).slice(-2);
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
- exports.style_parse = style_parse;
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(--text_color_disabled);
7
+ color: var(--code_token_comment);
5
8
  font-style: italic;
6
9
  }
7
10
 
8
11
  .code_token_string {
9
- color: #30d158;
12
+ color: var(--code_token_string);
10
13
  }
11
14
 
12
15
  .code_token_number {
13
- color: #ff9f0a;
16
+ color: var(--code_token_number);
14
17
  }
15
18
 
16
19
  .code_token_keyword {
17
- color: #bf5af2;
20
+ color: var(--code_token_keyword);
18
21
  }
19
22
 
20
23
  .code_token_function {
21
- color: #0a84ff;
24
+ color: var(--code_token_function);
22
25
  }
23
26
 
24
27
  .code_token_property,
25
28
  .code_token_key {
26
- color: #64d2ff;
29
+ color: var(--code_token_property);
27
30
  }
28
31
 
29
32
  .code_token_selector {
30
- color: #ffd60a;
33
+ color: var(--code_token_selector);
31
34
  }
32
35
 
33
36
  .code_token_color {
34
- color: #ff9f0a;
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.#applied_locale;
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 #applied_locale = undefined;
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.#applied_locale = this.#locale;
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 last_break = before.lastIndexOf("\n");
22
- const column = limit - last_break;
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
- let date = new Date().toLocaleString();
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
- let today = new Date().toLocaleDateString().replace("/", ".");
35
- let log_dir_path = path.join(app.getPath("userData"), "logs");
36
- let log_file_path = path.join(log_dir_path, `app_${today}.log`);
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(log_dir_path)) {
39
- fs.mkdirSync(log_dir_path, { recursive: true });
40
- fs.writeFileSync(log_file_path, "", "utf-8");
38
+ if (!fs.existsSync(logDirPath)) {
39
+ fs.mkdirSync(logDirPath, { recursive: true });
40
+ fs.writeFileSync(logFilePath, "", "utf-8");
41
41
  }
42
42
 
43
- fs.writeFile(log_file_path, `${text}\n`, { flag: "a+" }, (err) => {
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
- let { Animation } = require("../chui_animations/animations");
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
- #menu_bar = undefined;
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
- let back = new Button({
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.#menu_bar = menubar.set();
25
+ this.#menuBar = menubar.set();
26
26
  }
27
27
  getMenuBar() {
28
- return this.#menu_bar;
28
+ return this.#menuBar;
29
29
  }
30
30
  addRouteEvent(self, event = () => {}) {
31
- let event_name = "route_event_" + self.constructor.name;
32
- document.addEventListener(event_name, event);
33
- setTimeout(() => document.removeEventListener(event_name, event), 100);
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 (let component of components) {
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 (let component of components) {
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;