chuijs 4.0.2 → 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 (292) hide show
  1. package/CHANGELOG.md +597 -0
  2. package/README.md +43 -644
  3. package/framework/appLayout/default/chui_app_layout.js +602 -496
  4. package/framework/appLayout/default/global_style.css +288 -97
  5. package/framework/appLayout/default/main_theme_style.css +188 -101
  6. package/framework/components/chui_accordion/accordion.js +38 -35
  7. package/framework/components/chui_accordion/styles.css +13 -12
  8. package/framework/components/chui_action_sheet/action_sheet.js +93 -64
  9. package/framework/components/chui_action_sheet/styles.css +12 -5
  10. package/framework/components/chui_actions_bar/actions_bar.js +338 -0
  11. package/framework/components/chui_actions_bar/styles.css +139 -0
  12. package/framework/components/chui_affix/affix.js +16 -11
  13. package/framework/components/chui_affix/styles.css +5 -0
  14. package/framework/components/chui_alert/alert.js +34 -19
  15. package/framework/components/chui_alert/styles.css +7 -0
  16. package/framework/components/chui_avatar/avatar.js +36 -35
  17. package/framework/components/chui_avatar/styles.css +1 -1
  18. package/framework/components/chui_avatar_group/avatar_group.js +25 -23
  19. package/framework/components/chui_avatar_group/styles.css +1 -1
  20. package/framework/components/chui_backtop/backtop.js +60 -21
  21. package/framework/components/chui_backtop/styles.css +21 -0
  22. package/framework/components/chui_badge/badge.js +30 -28
  23. package/framework/components/chui_badge/styles.css +6 -6
  24. package/framework/components/chui_banner/banner.js +222 -0
  25. package/framework/components/chui_banner/styles.css +170 -0
  26. package/framework/components/chui_breadcrumb/breadcrumb.js +15 -13
  27. package/framework/components/chui_breadcrumb/styles.css +1 -1
  28. package/framework/components/chui_button/button.js +52 -36
  29. package/framework/components/chui_button/styles.css +7 -7
  30. package/framework/components/chui_calendar/calendar.js +18 -16
  31. package/framework/components/chui_card/card.js +52 -32
  32. package/framework/components/chui_card_scroll/card_scroll.js +169 -0
  33. package/framework/components/chui_card_scroll/styles.css +86 -0
  34. package/framework/components/chui_chat/chat.js +140 -79
  35. package/framework/components/chui_chat/styles.css +18 -7
  36. package/framework/components/chui_clipboard_history/clipboard_history.js +53 -39
  37. package/framework/components/chui_clipboard_history/styles.css +13 -9
  38. package/framework/components/chui_code/code.js +10 -10
  39. package/framework/components/chui_code/styles.css +4 -4
  40. package/framework/components/chui_code_editor/code_editor.js +73 -57
  41. package/framework/components/chui_code_editor/styles.css +6 -5
  42. package/framework/components/chui_command_palette/command_palette.js +87 -64
  43. package/framework/components/chui_command_palette/styles.css +5 -5
  44. package/framework/components/chui_console/console.js +13 -13
  45. package/framework/components/chui_console/styles.css +6 -3
  46. package/framework/components/chui_content_block/content_block.js +35 -21
  47. package/framework/components/chui_content_block/styles.css +2 -2
  48. package/framework/components/chui_context_menu/context_menu.js +171 -58
  49. package/framework/components/chui_context_menu/styles.css +82 -8
  50. package/framework/components/chui_custom_element/custom_element.js +25 -18
  51. package/framework/components/chui_data_grid/data_grid.js +633 -471
  52. package/framework/components/chui_data_grid/styles.css +24 -22
  53. package/framework/components/chui_descriptions/descriptions.js +34 -17
  54. package/framework/components/chui_descriptions/styles.css +1 -1
  55. package/framework/components/chui_details/details.js +42 -36
  56. package/framework/components/chui_details/styles.css +12 -11
  57. package/framework/components/chui_diff_viewer/diff_viewer.js +172 -147
  58. package/framework/components/chui_diff_viewer/styles.css +10 -10
  59. package/framework/components/chui_divider/divider.js +19 -17
  60. package/framework/components/chui_divider/styles.css +2 -2
  61. package/framework/components/chui_drawer/drawer.js +63 -38
  62. package/framework/components/chui_drawer/styles.css +18 -4
  63. package/framework/components/chui_drop_zone/drop_zone.js +37 -27
  64. package/framework/components/chui_drop_zone/styles.css +10 -6
  65. package/framework/components/chui_empty_state/empty_state.js +20 -15
  66. package/framework/components/chui_fieldset/fieldset.js +40 -23
  67. package/framework/components/chui_fieldset/styles.css +6 -6
  68. package/framework/components/chui_file_explorer/file_explorer.js +260 -189
  69. package/framework/components/chui_file_explorer/styles.css +8 -7
  70. package/framework/components/chui_form/form.js +40 -28
  71. package/framework/components/chui_form/styles.css +1 -1
  72. package/framework/components/chui_gantt/gantt.js +102 -78
  73. package/framework/components/chui_gantt/styles.css +5 -5
  74. package/framework/components/chui_graphs/graphs.js +376 -239
  75. package/framework/components/chui_graphs/styles.css +27 -6
  76. package/framework/components/chui_hero/hero.js +98 -37
  77. package/framework/components/chui_hero/styles.css +176 -7
  78. package/framework/components/chui_html_block/html_block.js +8 -8
  79. package/framework/components/chui_html_block/styles.css +3 -3
  80. package/framework/components/chui_hx/hx.js +3 -3
  81. package/framework/components/chui_hx/styles.css +13 -6
  82. package/framework/components/chui_icons/icons.js +2115 -2115
  83. package/framework/components/chui_icons/styles.css +4 -3
  84. package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
  85. package/framework/components/chui_inputs/chui_check_box/styles.css +30 -21
  86. package/framework/components/chui_inputs/chui_chips/chips.js +141 -70
  87. package/framework/components/chui_inputs/chui_chips/styles.css +32 -7
  88. package/framework/components/chui_inputs/chui_color_picker/color_picker.js +55 -38
  89. package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
  90. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +127 -109
  91. package/framework/components/chui_inputs/chui_combo_box/styles.css +23 -21
  92. package/framework/components/chui_inputs/chui_date/date.js +161 -127
  93. package/framework/components/chui_inputs/chui_date/styles.css +20 -19
  94. package/framework/components/chui_inputs/chui_date_range/date_range.js +61 -50
  95. package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
  96. package/framework/components/chui_inputs/chui_datetime/datetime.js +47 -39
  97. package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
  98. package/framework/components/chui_inputs/chui_email/email.js +48 -36
  99. package/framework/components/chui_inputs/chui_email/styles.css +11 -11
  100. package/framework/components/chui_inputs/chui_file/file.js +71 -44
  101. package/framework/components/chui_inputs/chui_file/styles.css +23 -19
  102. package/framework/components/chui_inputs/chui_hotkey/hotkey.js +101 -76
  103. package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
  104. package/framework/components/chui_inputs/chui_masked/masked.js +83 -72
  105. package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
  106. package/framework/components/chui_inputs/chui_mention/mention.js +87 -40
  107. package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
  108. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +242 -212
  109. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +23 -15
  110. package/framework/components/chui_inputs/chui_number/number.js +84 -70
  111. package/framework/components/chui_inputs/chui_number/styles.css +13 -13
  112. package/framework/components/chui_inputs/chui_otp/otp.js +71 -62
  113. package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
  114. package/framework/components/chui_inputs/chui_password/password.js +72 -56
  115. package/framework/components/chui_inputs/chui_password/styles.css +15 -15
  116. package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
  117. package/framework/components/chui_inputs/chui_radio_button/styles.css +22 -22
  118. package/framework/components/chui_inputs/chui_rating/rating.js +54 -50
  119. package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
  120. package/framework/components/chui_inputs/chui_select_box/select_box.js +132 -108
  121. package/framework/components/chui_inputs/chui_select_box/styles.css +21 -21
  122. package/framework/components/chui_inputs/chui_slider/slider.js +41 -38
  123. package/framework/components/chui_inputs/chui_slider/styles.css +7 -5
  124. package/framework/components/chui_inputs/chui_text/styles.css +14 -14
  125. package/framework/components/chui_inputs/chui_text/text.js +69 -52
  126. package/framework/components/chui_inputs/chui_text_area/styles.css +14 -14
  127. package/framework/components/chui_inputs/chui_text_area/text_area.js +59 -36
  128. package/framework/components/chui_inputs/chui_time/styles.css +18 -20
  129. package/framework/components/chui_inputs/chui_time/time.js +71 -49
  130. package/framework/components/chui_inputs/chui_toggle/styles.css +13 -6
  131. package/framework/components/chui_inputs/chui_toggle/toggle.js +27 -27
  132. package/framework/components/chui_json_editor/json_editor.js +137 -127
  133. package/framework/components/chui_json_editor/styles.css +5 -5
  134. package/framework/components/chui_json_viewer/json_viewer.js +133 -114
  135. package/framework/components/chui_json_viewer/styles.css +9 -8
  136. package/framework/components/chui_kanban/kanban.js +78 -60
  137. package/framework/components/chui_kanban/styles.css +8 -4
  138. package/framework/components/chui_kbd/kbd.js +12 -10
  139. package/framework/components/chui_kbd/styles.css +1 -1
  140. package/framework/components/chui_label/label.js +31 -29
  141. package/framework/components/chui_label/styles.css +3 -3
  142. package/framework/components/chui_link/link.js +36 -29
  143. package/framework/components/chui_link/styles.css +11 -4
  144. package/framework/components/chui_list/list.js +42 -38
  145. package/framework/components/chui_list/styles.css +4 -3
  146. package/framework/components/chui_markdown_editor/markdown_editor.js +125 -109
  147. package/framework/components/chui_markdown_editor/styles.css +5 -5
  148. package/framework/components/chui_media/audio.js +457 -364
  149. package/framework/components/chui_media/audio_styles.css +74 -37
  150. package/framework/components/chui_media/image.js +99 -64
  151. package/framework/components/chui_media/image_compare.js +86 -55
  152. package/framework/components/chui_media/image_compare_styles.css +5 -5
  153. package/framework/components/chui_media/image_styles.css +4 -4
  154. package/framework/components/chui_media/video.js +264 -171
  155. package/framework/components/chui_media/video_styles.css +57 -24
  156. package/framework/components/chui_menu_bar/menu_bar.js +67 -65
  157. package/framework/components/chui_menu_bar/styles.css +14 -12
  158. package/framework/components/chui_message_bar/message_bar.js +551 -0
  159. package/framework/components/chui_message_bar/styles.css +248 -0
  160. package/framework/components/chui_modal/modal.js +81 -56
  161. package/framework/components/chui_modal/styles.css +3 -2
  162. package/framework/components/chui_notification/notification.js +107 -69
  163. package/framework/components/chui_notification/notification_download.js +28 -77
  164. package/framework/components/chui_notification/notification_download_card.js +115 -0
  165. package/framework/components/chui_notification/notification_download_progress.css +5 -4
  166. package/framework/components/chui_notification/notification_download_progress.js +73 -105
  167. package/framework/components/chui_notification/notification_update.js +44 -37
  168. package/framework/components/chui_notification/status_plate.css +3 -3
  169. package/framework/components/chui_notification/status_plate.js +2 -2
  170. package/framework/components/chui_notification/styles.css +6 -1
  171. package/framework/components/chui_notification/styles_download.css +2 -2
  172. package/framework/components/chui_notification/styles_update.css +3 -3
  173. package/framework/components/chui_org_chart/org_chart.js +101 -62
  174. package/framework/components/chui_org_chart/styles.css +2 -2
  175. package/framework/components/chui_pagination/pagination.js +65 -30
  176. package/framework/components/chui_pagination/styles.css +1 -1
  177. package/framework/components/chui_paragraph/paragraph.js +2 -2
  178. package/framework/components/chui_paragraph/styles.css +3 -3
  179. package/framework/components/chui_popover/popover.js +77 -50
  180. package/framework/components/chui_popover/styles.css +1 -1
  181. package/framework/components/chui_popups/popups.js +210 -181
  182. package/framework/components/chui_popups/styles.css +7 -7
  183. package/framework/components/chui_print_button/print_button.js +48 -33
  184. package/framework/components/chui_progress_bar/progress_bar.js +49 -19
  185. package/framework/components/chui_progress_bar/styles.css +6 -6
  186. package/framework/components/chui_progress_ring/progress_ring.js +32 -30
  187. package/framework/components/chui_progress_ring/styles.css +1 -1
  188. package/framework/components/chui_qrcode/qrcode.js +85 -41
  189. package/framework/components/chui_query_builder/query_builder.js +263 -170
  190. package/framework/components/chui_query_builder/styles.css +4 -4
  191. package/framework/components/chui_radio_group/radio_group.js +36 -26
  192. package/framework/components/chui_radio_group/styles.css +2 -2
  193. package/framework/components/chui_result/result.js +41 -31
  194. package/framework/components/chui_result/styles.css +3 -3
  195. package/framework/components/chui_scheduler/scheduler.js +126 -104
  196. package/framework/components/chui_scheduler/styles.css +11 -7
  197. package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
  198. package/framework/components/chui_screen_spinner/styles.css +49 -0
  199. package/framework/components/chui_segmented_control/segmented_control.js +90 -27
  200. package/framework/components/chui_segmented_control/styles.css +32 -6
  201. package/framework/components/chui_settings/settings.js +160 -87
  202. package/framework/components/chui_settings/styles.css +4 -4
  203. package/framework/components/chui_shortcut_help/shortcut_help.js +82 -61
  204. package/framework/components/chui_sidebar_nav/sidebar_nav.js +132 -53
  205. package/framework/components/chui_sidebar_nav/styles.css +9 -3
  206. package/framework/components/chui_skeleton/skeleton.js +29 -24
  207. package/framework/components/chui_slideshow/slideshow.js +122 -103
  208. package/framework/components/chui_slideshow/styles.css +22 -16
  209. package/framework/components/chui_spinner/spinner.js +17 -17
  210. package/framework/components/chui_spinner/styles.css +0 -1
  211. package/framework/components/chui_split_button/split_button.js +53 -30
  212. package/framework/components/chui_split_button/styles.css +22 -6
  213. package/framework/components/chui_splitter/splitter.js +46 -39
  214. package/framework/components/chui_splitter/styles.css +2 -2
  215. package/framework/components/chui_statistic/statistic.js +111 -84
  216. package/framework/components/chui_status_bar/status_bar.js +11 -9
  217. package/framework/components/chui_status_bar/styles.css +2 -2
  218. package/framework/components/chui_stepper/stepper.js +230 -54
  219. package/framework/components/chui_stepper/styles.css +79 -14
  220. package/framework/components/chui_sticky_bar/sticky_bar.js +53 -17
  221. package/framework/components/chui_sticky_bar/styles.css +11 -7
  222. package/framework/components/chui_tabs/chui_tabs.js +100 -46
  223. package/framework/components/chui_tabs/styles.css +43 -15
  224. package/framework/components/chui_text_editor/styles.css +24 -23
  225. package/framework/components/chui_text_editor/text_editor.js +103 -79
  226. package/framework/components/chui_text_editor/text_editor_panel.js +428 -367
  227. package/framework/components/chui_theme_toggle/theme_toggle.js +46 -25
  228. package/framework/components/chui_timeline/styles.css +3 -1
  229. package/framework/components/chui_timeline/timeline.js +14 -12
  230. package/framework/components/chui_toast_queue/styles.css +11 -11
  231. package/framework/components/chui_toast_queue/toast_queue.js +82 -73
  232. package/framework/components/chui_toolbar/styles.css +2 -2
  233. package/framework/components/chui_toolbar/toolbar.js +20 -13
  234. package/framework/components/chui_tooltip/styles.css +2 -2
  235. package/framework/components/chui_tooltip/tooltip.js +40 -27
  236. package/framework/components/chui_tour/styles.css +12 -7
  237. package/framework/components/chui_tour/tour.js +132 -98
  238. package/framework/components/chui_transfer/styles.css +8 -7
  239. package/framework/components/chui_transfer/transfer.js +143 -123
  240. package/framework/components/chui_tree_table/styles.css +4 -4
  241. package/framework/components/chui_tree_table/tree_table.js +88 -74
  242. package/framework/components/chui_tree_view/styles.css +14 -10
  243. package/framework/components/chui_tree_view/tree_view.js +298 -253
  244. package/framework/components/chui_user_bar/styles.css +145 -0
  245. package/framework/components/chui_user_bar/user_bar.js +402 -0
  246. package/framework/components/chui_user_list_bar/styles.css +173 -0
  247. package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
  248. package/framework/components/chui_watermark/watermark.js +41 -33
  249. package/framework/components/chui_webview/styles.css +2 -2
  250. package/framework/components/chui_webview/webview.js +64 -52
  251. package/framework/components/chui_write_bar/styles.css +172 -0
  252. package/framework/components/chui_write_bar/write_bar.js +320 -0
  253. package/framework/main_window.js +1234 -0
  254. package/framework/modules/chui_a11y/chui_a11y.js +153 -123
  255. package/framework/modules/chui_animations/animations.js +105 -43
  256. package/framework/modules/chui_animations/motion.js +120 -0
  257. package/framework/modules/chui_animations/styles.css +37 -4
  258. package/framework/modules/chui_bridge/preload.js +36 -25
  259. package/framework/modules/chui_functions.js +69 -63
  260. package/framework/modules/chui_highlight/chui_highlight.js +25 -22
  261. package/framework/modules/chui_highlight/styles.css +13 -10
  262. package/framework/modules/chui_i18n/chui_i18n.js +12 -10
  263. package/framework/modules/chui_i18n/locales/a11y.js +5 -5
  264. package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
  265. package/framework/modules/chui_i18n/locales/calendar.js +3 -3
  266. package/framework/modules/chui_i18n/locales/common.js +3 -3
  267. package/framework/modules/chui_i18n/locales/data.js +36 -26
  268. package/framework/modules/chui_i18n/locales/editors.js +40 -13
  269. package/framework/modules/chui_i18n/locales/feedback.js +65 -3
  270. package/framework/modules/chui_i18n/locales/forms.js +8 -8
  271. package/framework/modules/chui_i18n/locales/graphics.js +8 -8
  272. package/framework/modules/chui_i18n/locales/media.js +4 -4
  273. package/framework/modules/chui_i18n/locales/navigation.js +12 -8
  274. package/framework/modules/chui_i18n/locales/validate.js +3 -3
  275. package/framework/modules/chui_json/chui_json.js +11 -11
  276. package/framework/modules/chui_layout/chui_layout.js +12 -10
  277. package/framework/modules/chui_logger/chui_logger.js +47 -28
  278. package/framework/modules/chui_page/page.js +39 -33
  279. package/framework/modules/chui_palette/palette.js +61 -0
  280. package/framework/modules/chui_qr/chui_qr.js +179 -88
  281. package/framework/modules/chui_scrollbar/chui_scrollbar.js +79 -68
  282. package/framework/modules/chui_scrollbar/styles.css +4 -2
  283. package/framework/modules/chui_services/chui_channels.js +27 -13
  284. package/framework/modules/chui_services/chui_services.js +47 -39
  285. package/framework/modules/chui_services/desktop.js +108 -101
  286. package/framework/modules/chui_sortable/chui_sortable.js +33 -28
  287. package/framework/modules/chui_validate/chui_validate.js +10 -7
  288. package/framework/tray_bar/chui_menu_items.js +136 -46
  289. package/index.d.ts +5046 -0
  290. package/index.js +199 -1254
  291. package/package.json +81 -68
  292. package/framework/components/telegram_bot/chui_telegram_bot.js +0 -184
@@ -1,114 +1,175 @@
1
- // transition: all var(--transition_time);
1
+ /** Виды движения: имя кадра даёт JS, а длительность и кривую — класс движения в styles.css */
2
+ const MOTION = {
3
+ ENTER: "enter",
4
+ EXIT: "exit",
5
+ POP: "pop",
6
+ };
7
+
8
+ /** Кадр появления всплывающей панели у триггера — общий кадр оболочки */
9
+ const PANEL_FRAME = "chui_panel_in";
2
10
 
3
11
  class Animation {
4
12
  #element = undefined;
5
13
 
6
14
  constructor(element) {
7
- require('../chui_functions').setStyles(__dirname + "/styles.css", "chUiJS_Animations");
15
+ require("../chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_Animations");
8
16
  if (element !== undefined) this.#element = element;
9
17
  }
10
18
 
19
+ /**
20
+ * Запустить кадры `name` как движение вида `kind`. Длительность и кривая — в `styles.css`
21
+ * по классу `data-motion`: вход базовый, выход короче, отклик с лёгким перелётом. Прежде
22
+ * время приходило из общего `animation-duration` на `*`, и все движения шли одним временем.
23
+ */
24
+ #play(name = String(), kind = String()) {
25
+ this.#element.dataset.motion = kind;
26
+ this.#element.style.animationName = name;
27
+ }
28
+
11
29
  /**
12
30
  * Отменить незавершённое скрытие: его `animationend` (removeEvents) снимает у слоя
13
31
  * `animation-name` и `display`. Если слой открыли заново раньше, чем анимация закрытия
14
32
  * доиграла, этот обработчик прятал уже открытый слой — вид расходился с состоянием.
15
33
  */
16
34
  #cancelHide() {
17
- this.#element.removeEventListener('animationend', removeEvents);
18
- this.#element.style.removeProperty('animation-name');
35
+ this.#element.removeEventListener("animationend", removeEvents);
36
+ this.#element.style.removeProperty("animation-name");
37
+ }
38
+
39
+ /**
40
+ * Кадр появления доигран — вид движения снимается, а имя кадра остаётся. По имени видно,
41
+ * что слой уже открыт (повторный показ не перезапускает кадр), а само оно без вида ничего
42
+ * не держит. Вид держит конечное состояние (`animation-fill-mode: both` в styles.css),
43
+ * и на слое оставался бы вычисленный `transform` — пусть и единичный: элемент с `transform`
44
+ * становится собственным «backdrop root», и размытие подложки перестаёт видеть содержимое
45
+ * под слоем (замер: меню контекста с блюром и без — картинка не менялась, пока вид стоял).
46
+ *
47
+ * Стрелочное поле, а не метод: у метода в слушателе нет `this`, и он падал бы на первом
48
+ * же конце кадра.
49
+ */
50
+ #entranceEnd = (event) => {
51
+ if (event.target !== this.#element) return;
52
+ this.#element.removeEventListener("animationend", this.#entranceEnd);
53
+ this.#element.removeAttribute("data-motion");
54
+ };
55
+
56
+ /** Показать слой кадром появления и снять вид по концу кадра */
57
+ #enter(name = String(), kind = String()) {
58
+ this.#play(name, kind);
59
+ this.#element.addEventListener("animationend", this.#entranceEnd);
19
60
  }
20
61
 
21
62
  fadeIn() {
22
63
  this.#cancelHide();
23
- if (window.getComputedStyle(this.#element, null).display === 'none') {
24
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
64
+ if (window.getComputedStyle(this.#element, null).display === "none") {
65
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
25
66
  }
26
- this.#element.style.transition = "transform .0s"
27
- this.#element.style.animationName = 'fade-in';
67
+ this.#enter("fade-in", MOTION.ENTER);
28
68
  }
29
69
 
30
70
  fadeOut() {
31
- this.#element.style.transition = "transform .0s"
32
71
  if (this.#element.style.animationName) {
33
- this.#element.style.animationName = 'fade-out';
34
- this.#element.addEventListener('animationend', removeEvents);
72
+ this.#play("fade-out", MOTION.EXIT);
73
+ this.#element.addEventListener("animationend", removeEvents);
35
74
  }
36
75
  }
37
76
 
77
+ /**
78
+ * Появление всплывающей панели у триггера (список, меню, поповер): панель приходит
79
+ * сверху вниз и чуть вырастает. Вид `pop` — кривая с лёгким перелётом, поэтому
80
+ * открытие читается откликом, а не вспышкой: прежний `fadeIn` менял только прозрачность.
81
+ *
82
+ * Повторный показ уже открытой панели кадр не запускает: поле открывают и `focus`,
83
+ * и `click` — одно действие приходило сюда дважды, а `#cancelHide` перед кадром снимает
84
+ * имя кадра, поэтому второй вызов начинал его заново и панель мигала на открытии.
85
+ */
86
+ panelIn() {
87
+ const open =
88
+ this.#element.style.animationName === PANEL_FRAME &&
89
+ window.getComputedStyle(this.#element, null).display !== "none";
90
+ if (open) return;
91
+ this.#cancelHide();
92
+ if (window.getComputedStyle(this.#element, null).display === "none") {
93
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
94
+ }
95
+ this.#play(PANEL_FRAME, MOTION.POP);
96
+ this.#element.addEventListener("animationend", this.#entranceEnd);
97
+ }
98
+
38
99
  // SCALE
39
100
  scaleIn() {
40
101
  this.#cancelHide();
41
- if (window.getComputedStyle(this.#element, null).display === 'none') {
42
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
102
+ if (window.getComputedStyle(this.#element, null).display === "none") {
103
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
43
104
  }
44
- this.#element.style.animationName = 'scale-in';
105
+ this.#enter("scale-in", MOTION.POP);
45
106
  }
46
107
 
47
108
  scaleOut() {
48
109
  if (this.#element.style.animationName) {
49
- this.#element.style.animationName = 'scale-out';
50
- this.#element.addEventListener('animationend', removeEvents);
110
+ this.#play("scale-out", MOTION.EXIT);
111
+ this.#element.addEventListener("animationend", removeEvents);
51
112
  }
52
113
  }
53
114
 
54
115
  // SLIDE RIGHT
55
116
  slideRightIn() {
56
117
  this.#cancelHide();
57
- if (window.getComputedStyle(this.#element, null).display === 'none') {
58
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
118
+ if (window.getComputedStyle(this.#element, null).display === "none") {
119
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
59
120
  }
60
- this.#element.style.animationName = 'slide-right-in';
121
+ this.#enter("slide-right-in", MOTION.ENTER);
61
122
  }
62
123
 
63
124
  slideRightOut() {
64
125
  if (this.#element.style.animationName) {
65
- this.#element.style.animationName = 'slide-right-out';
66
- this.#element.addEventListener('animationend', removeEvents);
126
+ this.#play("slide-right-out", MOTION.EXIT);
127
+ this.#element.addEventListener("animationend", removeEvents);
67
128
  }
68
129
  }
69
130
 
70
131
  // SLIDE BOTTOM
71
132
  slideBottomIn() {
72
133
  this.#cancelHide();
73
- if (window.getComputedStyle(this.#element, null).display === 'none') {
74
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
134
+ if (window.getComputedStyle(this.#element, null).display === "none") {
135
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
75
136
  }
76
- this.#element.style.animationName = 'slide-bottom-in';
137
+ this.#enter("slide-bottom-in", MOTION.ENTER);
77
138
  }
78
139
 
79
140
  slideBottomOut() {
80
141
  if (this.#element.style.animationName) {
81
- this.#element.style.animationName = 'slide-bottom-out';
82
- this.#element.addEventListener('animationend', removeEvents);
142
+ this.#play("slide-bottom-out", MOTION.EXIT);
143
+ this.#element.addEventListener("animationend", removeEvents);
83
144
  }
84
145
  }
85
146
 
86
147
  // REMOVE
87
148
  slideBottomOutAndRemove() {
88
149
  if (this.#element.style.animationName) {
89
- this.#element.style.animationName = 'slide-bottom-out';
90
- this.#element.addEventListener('animationend', removeEvents);
150
+ this.#play("slide-bottom-out", MOTION.EXIT);
151
+ this.#element.addEventListener("animationend", removeEvents);
91
152
  }
92
153
  }
93
154
 
94
155
  slideRightOutAndRemove() {
95
156
  if (this.#element.style.animationName) {
96
- this.#element.style.animationName = 'slide-right-out';
97
- this.#element.addEventListener('animationend', removeEvents);
157
+ this.#play("slide-right-out", MOTION.EXIT);
158
+ this.#element.addEventListener("animationend", removeEvents);
98
159
  }
99
160
  }
100
161
 
101
162
  scaleOutAndRemove() {
102
163
  if (this.#element.style.animationName) {
103
- this.#element.style.animationName = 'scale-out';
104
- this.#element.addEventListener('animationend', removeEvents2);
164
+ this.#play("scale-out", MOTION.EXIT);
165
+ this.#element.addEventListener("animationend", removeEvents2);
105
166
  }
106
167
  }
107
168
 
108
169
  fadeOutAndRemove() {
109
170
  if (this.#element.style.animationName) {
110
- this.#element.style.animationName = 'fade-out';
111
- this.#element.addEventListener('animationend', removeEvents2);
171
+ this.#play("fade-out", MOTION.EXIT);
172
+ this.#element.addEventListener("animationend", removeEvents2);
112
173
  }
113
174
  }
114
175
 
@@ -118,19 +179,20 @@ class Animation {
118
179
  }
119
180
 
120
181
  removeStyles() {
121
- this.#element.removeAttribute("style")
182
+ this.#element.removeAttribute("data-motion");
183
+ this.#element.removeAttribute("style");
122
184
  }
123
185
  }
124
186
 
125
187
  const removeEvents2 = (event) => {
126
- event.target.remove()
127
- event.target.removeEventListener('animationend', removeEvents2);
128
- }
188
+ event.target.remove();
189
+ event.target.removeEventListener("animationend", removeEvents2);
190
+ };
129
191
 
130
192
  const removeEvents = (event) => {
131
- event.target.style.removeProperty('animation-name');
132
- event.target.style.removeProperty('display');
133
- event.target.removeEventListener('animationend', removeEvents);
134
- }
193
+ event.target.style.removeProperty("animation-name");
194
+ event.target.style.removeProperty("display");
195
+ event.target.removeEventListener("animationend", removeEvents);
196
+ };
135
197
 
136
- exports.Animation = Animation
198
+ exports.Animation = Animation;
@@ -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 */
@@ -77,4 +110,4 @@
77
110
  opacity: 0;
78
111
  transform: translateY(100%);
79
112
  }
80
- }
113
+ }
@@ -1,8 +1,14 @@
1
- const {contextBridge, ipcRenderer} = require("electron");
2
- const {SERVICES_CHANNELS, SERVICES_CALLS, SERVICES_SYNC_CALLS, BRIDGE_CHANNELS, BRIDGE_ARGS} = require("../chui_services/chui_channels");
1
+ const { contextBridge, ipcRenderer } = require("electron");
2
+ const {
3
+ SERVICES_CHANNELS,
4
+ SERVICES_CALLS,
5
+ SERVICES_SYNC_CALLS,
6
+ BRIDGE_CHANNELS,
7
+ BRIDGE_ARGS,
8
+ } = require("../chui_services/chui_channels");
3
9
  // Мост переиспользует ту же обёртку сервисов, что и страница: разница только в транспорте
4
10
  // (`Desktop` в preload'е сам вызовет ipcRenderer, потому что `window.chui` ему не виден)
5
- const {Desktop} = require("../chui_services/desktop");
11
+ const { Desktop } = require("../chui_services/desktop");
6
12
 
7
13
  /**
8
14
  * Preload безопасного режима (`Main({security: {contextIsolation: true}})`).
@@ -32,7 +38,10 @@ function argValue(prefix = String()) {
32
38
 
33
39
  const renderPath = argValue(BRIDGE_ARGS.RENDER);
34
40
  const csp = argValue(BRIDGE_ARGS.CSP);
35
- const channels = (argValue(BRIDGE_ARGS.CHANNELS) ?? "").split(",").map((channel) => channel.trim()).filter(Boolean);
41
+ const channels = (argValue(BRIDGE_ARGS.CHANNELS) ?? "")
42
+ .split(",")
43
+ .map((channel) => channel.trim())
44
+ .filter(Boolean);
36
45
 
37
46
  /**
38
47
  * Content-Security-Policy вставляется в документ до его скриптов: preload выполняется раньше
@@ -50,7 +59,7 @@ function applyCsp() {
50
59
  return true;
51
60
  }
52
61
 
53
- if (!applyCsp()) document.addEventListener("readystatechange", () => applyCsp(), {once: true});
62
+ if (!applyCsp()) document.addEventListener("readystatechange", () => applyCsp(), { once: true });
54
63
 
55
64
  const call = (method = String(), ...args) => {
56
65
  const channel = SERVICES_CALLS[method];
@@ -100,7 +109,7 @@ contextBridge.exposeInMainWorld("chui", {
100
109
  versions: {
101
110
  node: process.versions.node,
102
111
  chrome: process.versions.chrome,
103
- electron: process.versions.electron
112
+ electron: process.versions.electron,
104
113
  },
105
114
  platform: process.platform,
106
115
  /** Путь к странице окна (как он задан в опции `render`) */
@@ -120,20 +129,20 @@ contextBridge.exposeInMainWorld("chui", {
120
129
  copy: (text = String()) => Desktop.clipboard.history.copy(text),
121
130
  remove: (target = undefined) => Desktop.clipboard.history.remove(target),
122
131
  clear: () => Desktop.clipboard.history.clear(),
123
- onChange: (listener = () => {}) => services.onClipboardHistory(listener)
124
- }
132
+ onChange: (listener = () => {}) => services.onClipboardHistory(listener),
133
+ },
125
134
  },
126
135
  recent: {
127
136
  add: (path = String(), name = undefined) => Desktop.recent.add(path, name),
128
137
  clear: () => Desktop.recent.clear(),
129
- list: () => Desktop.recent.list()
138
+ list: () => Desktop.recent.list(),
130
139
  },
131
140
  progress: {
132
141
  set: (value = Number(), mode = "normal") => Desktop.progress.set(value, mode),
133
- clear: () => Desktop.progress.clear()
142
+ clear: () => Desktop.progress.clear(),
134
143
  },
135
144
  shell: {
136
- openExternal: (url = String()) => Desktop.shell.openExternal(url)
145
+ openExternal: (url = String()) => Desktop.shell.openExternal(url),
137
146
  },
138
147
  app: {
139
148
  // Путь приходит значением, без промиса (синхронный канал), а `Desktop` в preload'е
@@ -148,36 +157,36 @@ contextBridge.exposeInMainWorld("chui", {
148
157
  // Закрытие и перезапуск — как у `Desktop` из пакета: те же шаги, что у пункта меню.
149
158
  // Отвечают значением, а не промисом (синхронный канал), поэтому `await` не нужен
150
159
  quit: () => Desktop.app.quit(),
151
- restart: () => Desktop.app.restart()
160
+ restart: () => Desktop.app.restart(),
152
161
  },
153
162
  dialog: {
154
163
  open: (options = {}) => Desktop.dialog.open(options),
155
164
  save: (options = {}) => Desktop.dialog.save(options),
156
- message: (options = {}) => Desktop.dialog.message(options)
165
+ message: (options = {}) => Desktop.dialog.message(options),
157
166
  },
158
167
  session: {
159
168
  getCacheSize: () => Desktop.session.getCacheSize(),
160
169
  clearCache: () => Desktop.session.clearCache(),
161
- clearStorageData: (options = {}) => Desktop.session.clearStorageData(options)
170
+ clearStorageData: (options = {}) => Desktop.session.clearStorageData(options),
162
171
  },
163
172
  print: {
164
173
  page: (options = {}) => Desktop.print.page(options),
165
174
  html: (html = String(), options = {}) => Desktop.print.html(html, options),
166
- pdf: (html = String(), options = {}) => Desktop.print.pdf(html, options)
175
+ pdf: (html = String(), options = {}) => Desktop.print.pdf(html, options),
167
176
  },
168
177
  shortcuts: {
169
178
  on: (accelerator = String(), handler = () => {}) => Desktop.shortcuts.on(accelerator, handler),
170
179
  off: (accelerator = String(), handler = () => {}) => Desktop.shortcuts.off(accelerator, handler),
171
180
  offAll: () => Desktop.shortcuts.offAll(),
172
- get: () => Desktop.shortcuts.get()
173
- }
181
+ get: () => Desktop.shortcuts.get(),
182
+ },
174
183
  },
175
184
  window: {
176
185
  minimize: () => windowAction("minimize"),
177
186
  toggleMaximize: () => windowAction("toggle-maximize"),
178
187
  restore: () => windowAction("restore"),
179
188
  close: () => windowAction("close"),
180
- getState: () => ipcRenderer.invoke(BRIDGE_CHANNELS.WINDOW_STATE)
189
+ getState: () => ipcRenderer.invoke(BRIDGE_CHANNELS.WINDOW_STATE),
181
190
  },
182
191
  theme: {
183
192
  get: () => ipcRenderer.invoke(BRIDGE_CHANNELS.THEME_GET),
@@ -187,7 +196,7 @@ contextBridge.exposeInMainWorld("chui", {
187
196
  onChange: (listener = () => {}) => {
188
197
  themeListeners.add(listener);
189
198
  return () => themeListeners.delete(listener);
190
- }
199
+ },
191
200
  },
192
201
  locale: {
193
202
  get: () => ipcRenderer.invoke(BRIDGE_CHANNELS.LOCALE_GET),
@@ -196,17 +205,19 @@ contextBridge.exposeInMainWorld("chui", {
196
205
  onChange: (listener = () => {}) => {
197
206
  localeListeners.add(listener);
198
207
  return () => localeListeners.delete(listener);
199
- }
208
+ },
200
209
  },
201
210
  messaging: {
202
211
  /** Каналы задаёт приложение: `security: {channels: ['preview:update']}` */
203
212
  channels: [...channels],
204
213
  send: (channel = String(), ...args) => {
205
- if (!channels.includes(channel)) throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
214
+ if (!channels.includes(channel))
215
+ throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
206
216
  return ipcRenderer.invoke(BRIDGE_CHANNELS.MESSAGE, channel, ...args);
207
217
  },
208
218
  on: (channel = String(), listener = () => {}) => {
209
- if (!channels.includes(channel)) throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
219
+ if (!channels.includes(channel))
220
+ throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
210
221
  const listeners = messageListeners.get(channel) ?? new Set();
211
222
  listeners.add(listener);
212
223
  messageListeners.set(channel, listeners);
@@ -214,8 +225,8 @@ contextBridge.exposeInMainWorld("chui", {
214
225
  listeners.delete(listener);
215
226
  if (listeners.size === 0) messageListeners.delete(channel);
216
227
  };
217
- }
218
- }
228
+ },
229
+ },
219
230
  });
220
231
 
221
232
  // Скрипт страницы подключается здесь: в безопасном режиме `render` — это скрипт документа,
@@ -227,6 +238,6 @@ if (renderPath !== undefined) {
227
238
  script.defer = true;
228
239
  document.head.appendChild(script);
229
240
  };
230
- if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", addPageScript, {once: true});
241
+ if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", addPageScript, { once: true });
231
242
  else addPageScript();
232
243
  }