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
@@ -0,0 +1,169 @@
1
+ const { Icon, Icons } = require("../chui_icons/icons");
2
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
3
+ const { setStyles, optionOr } = require("../../modules/chui_functions");
4
+ const { reducedMotion } = require("../../modules/chui_animations/motion");
5
+
6
+ /** Зазор между карточками по умолчанию: тот же шаг, что у отступов компонентов */
7
+ const DEFAULT_GAP = 12;
8
+
9
+ /**
10
+ * Лента карточек — горизонтальный список, который прокручивается колесом, стрелками на краях
11
+ * и клавиатурой.
12
+ *
13
+ * От `SlideShow` отличается тем, что не показывает слайды по одному: карточки идут подряд,
14
+ * лента листается свободно, а прилипание к началу карточки лишь помогает остановиться ровно.
15
+ * От `List` — направлением: строки идут вбок и не переносятся.
16
+ *
17
+ * Опции: `id`, `gap`, `snap` (прилипание к карточке, по умолчанию включено), `arrows`
18
+ * (стрелки на краях, по умолчанию включены), `label` (имя списка для скринридера),
19
+ * `items` (карточки).
20
+ *
21
+ * Методы: `addCard(component)`, `getCards()`, `scrollToCard(index)`, `scrollToEnd()`,
22
+ * `destroy()`, `set()`. В ленту принимаются компоненты фреймворка (у них есть `set()`)
23
+ * и готовые узлы.
24
+ */
25
+ class CardScroll {
26
+ #root = document.createElement("chui_card_scroll");
27
+ #track = document.createElement("card_scroll_track");
28
+ #start = undefined;
29
+ #end = undefined;
30
+ #gap = DEFAULT_GAP;
31
+ #observer = undefined;
32
+
33
+ constructor(options = {}) {
34
+ setStyles(__dirname + "/styles.css", "chUiJS_CardScroll");
35
+ if (options.id !== undefined) this.#root.id = options.id;
36
+ const gap = Number(options.gap);
37
+ this.#gap = Number.isFinite(gap) && gap >= 0 ? gap : DEFAULT_GAP;
38
+ this.#track.style.gap = `${this.#gap}px`;
39
+ // Список, а не просто лента: скринридер объявляет число карточек, а каждая —
40
+ // свой `listitem` (роль ставит `addCard`)
41
+ this.#track.setAttribute("role", "list");
42
+ this.#track.setAttribute("aria-label", optionOr(options.label, I18n.t("feedback.cardScroll")));
43
+ // Лента сама принимает фокус: стрелки клавиатуры прокручивают её, не уводя фокус в карточки
44
+ this.#track.setAttribute("tabindex", "0");
45
+ this.#track.addEventListener("keydown", (event) => this.#onKeydown(event));
46
+ // Вертикальное колесо прокручивает ленту вбок — так её листают мышью с колесом
47
+ this.#track.addEventListener("wheel", (event) => this.#onWheel(event), { passive: false });
48
+ this.#track.addEventListener("scroll", () => this.#syncArrows());
49
+ if (options.snap === false) this.#root.dataset.snap = "false";
50
+ if (options.arrows !== false) {
51
+ this.#start = this.#makeArrow(Icons.NAVIGATION.CHEVRON_LEFT, I18n.t("feedback.cardScrollPrev"), -1);
52
+ this.#end = this.#makeArrow(Icons.NAVIGATION.CHEVRON_RIGHT, I18n.t("feedback.cardScrollNext"), 1);
53
+ this.#root.append(this.#start, this.#track, this.#end);
54
+ } else {
55
+ this.#root.appendChild(this.#track);
56
+ }
57
+ if (Array.isArray(options.items)) for (const item of options.items) this.addCard(item);
58
+ // Стрелки появляются только там, где лента действительно уходит за край: следим
59
+ // за размером (карточку могли добавить после сборки) и за прокруткой
60
+ if (typeof ResizeObserver === "function") {
61
+ this.#observer = new ResizeObserver(() => this.#syncArrows());
62
+ this.#observer.observe(this.#track);
63
+ }
64
+ this.#syncArrows();
65
+ }
66
+
67
+ /** Карточка ленты: компонент фреймворка (у него есть `set()`) или готовый узел */
68
+ addCard(component = undefined) {
69
+ if (component === undefined || component === null) return this;
70
+ const item = document.createElement("card_scroll_item");
71
+ item.setAttribute("role", "listitem");
72
+ item.appendChild(typeof component.set === "function" ? component.set() : component);
73
+ this.#track.appendChild(item);
74
+ this.#syncArrows();
75
+ return this;
76
+ }
77
+
78
+ getCards() {
79
+ return [...this.#track.children];
80
+ }
81
+
82
+ /** Переход к карточке по номеру: место берут у самой карточки, а не считают по ширине */
83
+ scrollToCard(index = Number()) {
84
+ const cards = this.getCards();
85
+ const card = cards[Number(index)];
86
+ if (card === undefined) return this;
87
+ this.#scrollTo(card.offsetLeft - this.#track.offsetLeft);
88
+ return this;
89
+ }
90
+
91
+ scrollToEnd() {
92
+ this.#scrollTo(this.#track.scrollWidth);
93
+ return this;
94
+ }
95
+
96
+ set() {
97
+ return this.#root;
98
+ }
99
+ destroy() {
100
+ if (this.#observer !== undefined) this.#observer.disconnect();
101
+ this.#observer = undefined;
102
+ this.#root.remove();
103
+ }
104
+
105
+ // === ВНУТРЕННЕЕ ===
106
+ #makeArrow(icon = String(), label = String(), direction = 1) {
107
+ const button = document.createElement("button");
108
+ button.type = "button";
109
+ button.className = "card_scroll_arrow";
110
+ // Сторона видна и стилям, и замерам: край ленты — то, что здесь важно
111
+ button.dataset.side = direction < 0 ? "start" : "end";
112
+ button.innerHTML = new Icon(icon, "20px").getHTML();
113
+ button.setAttribute("aria-label", label);
114
+ button.title = label;
115
+ button.addEventListener("click", () => this.#step(direction));
116
+ return button;
117
+ }
118
+ #scrollTo(left = Number()) {
119
+ // При уменьшенном движении лента перескакивает без плавности
120
+ this.#track.scrollTo({ left: left, behavior: reducedMotion() ? "auto" : "smooth" });
121
+ }
122
+ /** Шаг — одна карточка с зазором: он берётся у самой карточки, а не из настроек */
123
+ #step(direction = 1) {
124
+ const card = this.#track.querySelector("card_scroll_item");
125
+ const step = card === null ? this.#track.clientWidth : card.getBoundingClientRect().width + this.#gap;
126
+ this.#scrollTo(this.#track.scrollLeft + step * direction);
127
+ }
128
+ #onKeydown(event = {}) {
129
+ if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
130
+ event.preventDefault();
131
+ this.#step(event.key === "ArrowRight" ? 1 : -1);
132
+ return;
133
+ }
134
+ if (event.key === "Home") {
135
+ event.preventDefault();
136
+ this.#scrollTo(0);
137
+ }
138
+ if (event.key === "End") {
139
+ event.preventDefault();
140
+ this.scrollToEnd();
141
+ }
142
+ }
143
+ /**
144
+ * Вертикальное колесо двигает ленту вбок. Когда лента упирается, событие отдаётся странице:
145
+ * иначе список «съел» бы прокрутку, и до содержимого ниже было бы не добраться.
146
+ */
147
+ #onWheel(event = {}) {
148
+ const track = this.#track;
149
+ if (event.deltaY === 0) return;
150
+ const max = track.scrollWidth - track.clientWidth;
151
+ if (max <= 0) return;
152
+ if ((track.scrollLeft <= 0 && event.deltaY < 0) || (track.scrollLeft >= max && event.deltaY > 0)) return;
153
+ event.preventDefault();
154
+ track.scrollLeft += event.deltaY;
155
+ }
156
+ /** Стрелка видна только там, где за краем есть карточки */
157
+ #syncArrows() {
158
+ if (this.#start === undefined || this.#end === undefined) return;
159
+ const track = this.#track;
160
+ const max = track.scrollWidth - track.clientWidth;
161
+ // Отступ ленты (он держит тень карточек) сдвигает нулевую позицию: без него
162
+ // лента с прилипанием стоит на нём, и стрелка «назад» светилась бы на старте
163
+ const offset = parseFloat(getComputedStyle(track).paddingLeft) || 0;
164
+ this.#start.hidden = track.scrollLeft <= offset + 1;
165
+ this.#end.hidden = track.scrollLeft >= max - 1;
166
+ }
167
+ }
168
+
169
+ exports.CardScroll = CardScroll;
@@ -0,0 +1,86 @@
1
+ /* Лента карточек: горизонтальный список со стрелками на краях.
2
+ Своей палитры у компонента нет: карточки приносят её сами, полоса прокрутки — общая
3
+ (`chui_scrollbar`). */
4
+
5
+ chui_card_scroll {
6
+ display: flex;
7
+ flex-direction: row;
8
+ align-items: center;
9
+ gap: 8px;
10
+ /* Компонент сам задаёт ширину: в flex-колонке с `align-items: flex-start` лента
11
+ без ширины получила бы `max-content` и вылезла за содержимое */
12
+ width: -webkit-fill-available;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ card_scroll_track {
17
+ display: flex;
18
+ flex-direction: row;
19
+ flex: 1 1 auto;
20
+ min-width: 0;
21
+ box-sizing: border-box;
22
+ /* Лента идёт вбок: по вертикали содержимое не переносится и не обрезается тенью карточек */
23
+ overflow-x: auto;
24
+ overflow-y: hidden;
25
+ /* Прилипание к началу карточки помогает остановиться ровно, но не заставляет ленту
26
+ прыгать через карточку: `proximity`, а не `mandatory` */
27
+ scroll-snap-type: x proximity;
28
+ /* Отступ под тень карточек: у обрезанного слоя тень срезалась бы по краю */
29
+ padding: 4px;
30
+ margin: -4px;
31
+ }
32
+
33
+ chui_card_scroll[data-snap="false"] card_scroll_track {
34
+ scroll-snap-type: none;
35
+ }
36
+
37
+ /* Лента сама принимает фокус: стрелки клавиатуры прокручивают её, а не уводят фокус в карточку */
38
+ card_scroll_track:focus-visible {
39
+ outline: var(--focus_outline);
40
+ outline-offset: var(--focus_outline_offset);
41
+ border-radius: var(--radius_sm);
42
+ }
43
+
44
+ card_scroll_item {
45
+ flex: none;
46
+ display: flex;
47
+ scroll-snap-align: start;
48
+ }
49
+
50
+ /* Стрелка на краю ленты — круглая кнопка со значком: имя ей даёт словарь, значка скринридер
51
+ не читает (`Icon` помечает его `aria-hidden`) */
52
+ .card_scroll_arrow {
53
+ flex: none;
54
+ cursor: pointer;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ box-sizing: border-box;
59
+ width: var(--control_height);
60
+ height: var(--control_height);
61
+ padding: 0;
62
+ border: 1px solid var(--border_color);
63
+ border-radius: var(--radius_circle);
64
+ background: var(--element_background_2);
65
+ color: var(--text_color);
66
+ box-shadow: var(--box_shadow_main);
67
+ }
68
+
69
+ .card_scroll_arrow:hover {
70
+ background: var(--element_background_hover);
71
+ }
72
+
73
+ /* Нажатие — акцент, как у прочих кнопок тонкой настройки */
74
+ .card_scroll_arrow:active {
75
+ background: var(--blue_prime_background);
76
+ color: var(--text_color_hover);
77
+ }
78
+
79
+ .card_scroll_arrow chui_icon {
80
+ color: inherit;
81
+ }
82
+
83
+ /* За краем карточек нет — стрелки нет: `hidden` убирает её и из обхода клавиатурой */
84
+ .card_scroll_arrow[hidden] {
85
+ display: none;
86
+ }
@@ -1,14 +1,14 @@
1
- const { setStyles, markdownToHtml, optionOr } = require('../../modules/chui_functions');
2
- const { Avatar } = require('../chui_avatar/avatar');
3
- const { Icon, Icons } = require('../chui_icons/icons');
4
- const { I18n } = require('../../modules/chui_i18n/chui_i18n');
1
+ const { setStyles, markdownToHtml, optionOr } = require("../../modules/chui_functions");
2
+ const { Avatar } = require("../chui_avatar/avatar");
3
+ const { Icon, Icons } = require("../chui_icons/icons");
4
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
5
5
 
6
6
  /** Значки состояния отправленного сообщения: в пути, доставлено, прочитано, ошибка */
7
7
  const STATUS_ICONS = {
8
8
  sending: Icons.ACTIONS.SCHEDULE,
9
9
  sent: Icons.NAVIGATION.CHECK,
10
10
  read: Icons.ACTIONS.DONE_ALL,
11
- failed: Icons.ALERT.ERROR_OUTLINE
11
+ failed: Icons.ALERT.ERROR_OUTLINE,
12
12
  };
13
13
  /** Прокрутка считается «у нижнего края», если до него осталось меньше этого, px */
14
14
  const NEAR_BOTTOM = 80;
@@ -63,7 +63,7 @@ class Chat {
63
63
  SENDING: "sending",
64
64
  SENT: "sent",
65
65
  READ: "read",
66
- FAILED: "failed"
66
+ FAILED: "failed",
67
67
  };
68
68
  static #counter = 0;
69
69
 
@@ -76,42 +76,54 @@ class Chat {
76
76
  #typing = document.createElement("chat_typing");
77
77
  #composer = document.createElement("chat_composer");
78
78
  #input = document.createElement("textarea");
79
- #send_button = document.createElement("button");
79
+ #sendButton = document.createElement("button");
80
80
  #messages = [];
81
81
  #own = undefined;
82
82
  #placeholder = undefined;
83
- #send_text = undefined;
84
- #send_label = undefined;
85
- #date_separators = true;
86
- #composer_visible = true;
83
+ #sendText = undefined;
84
+ #sendLabel = undefined;
85
+ #dateSeparators = true;
86
+ #composerVisible = true;
87
87
  #disabled = false;
88
- #typing_name = undefined;
89
- #send_listeners = [];
90
- #on_send = undefined;
91
- #on_message_click = undefined;
88
+ #typingName = undefined;
89
+ #sendListeners = [];
90
+ #onSend = undefined;
91
+ #onMessageClick = undefined;
92
92
  // Текущий день и текущая группа — по ним решается, дописывать пузырь или начать новую;
93
93
  // группа — это {node, bubbles, avatar, author, day}
94
- #last_date = undefined;
95
- #last_group = undefined;
94
+ #lastDate = undefined;
95
+ #lastGroup = undefined;
96
96
 
97
- constructor(options = {
98
- id: String(), title: String(), subtitle: String(), messages: [], own: String(),
99
- placeholder: String(), sendText: String(), sendLabel: String(), composer: Boolean(),
100
- height: String(), width: String(), dateSeparators: Boolean(),
101
- onSend: undefined, onMessageClick: undefined
102
- }) {
97
+ constructor(
98
+ options = {
99
+ id: String(),
100
+ title: String(),
101
+ subtitle: String(),
102
+ messages: [],
103
+ own: String(),
104
+ placeholder: String(),
105
+ sendText: String(),
106
+ sendLabel: String(),
107
+ composer: Boolean(),
108
+ height: String(),
109
+ width: String(),
110
+ dateSeparators: Boolean(),
111
+ onSend: undefined,
112
+ onMessageClick: undefined,
113
+ }
114
+ ) {
103
115
  setStyles(__dirname + "/styles.css", "chUiJS_Chat");
104
116
  Chat.#counter += 1;
105
117
  this.#id = options.id === undefined ? `chui_chat_${Chat.#counter}` : options.id;
106
118
  this.#root.id = this.#id;
107
119
  this.#own = options.own;
108
120
  this.#placeholder = options.placeholder;
109
- this.#send_text = options.sendText;
110
- this.#send_label = options.sendLabel;
111
- this.#on_send = options.onSend;
112
- this.#on_message_click = options.onMessageClick;
113
- this.#date_separators = options.dateSeparators !== false;
114
- this.#composer_visible = options.composer !== false;
121
+ this.#sendText = options.sendText;
122
+ this.#sendLabel = options.sendLabel;
123
+ this.#onSend = options.onSend;
124
+ this.#onMessageClick = options.onMessageClick;
125
+ this.#dateSeparators = options.dateSeparators !== false;
126
+ this.#composerVisible = options.composer !== false;
115
127
  if (options.width !== undefined) this.#root.style.width = options.width;
116
128
  // Высота журнала: своя переменная, чтобы поле ввода и шапка оставались на месте
117
129
  this.#log.style.setProperty("--chat_height", options.height === undefined ? "320px" : options.height);
@@ -130,19 +142,20 @@ class Chat {
130
142
  this.#composer.setAttribute("aria-label", I18n.t("feedback.chatComposer"));
131
143
  this.#input.className = "chat_input";
132
144
  this.#input.rows = 1;
133
- this.#input.placeholder = this.#placeholder === undefined ? I18n.t("feedback.chatPlaceholder") : this.#placeholder;
145
+ this.#input.placeholder =
146
+ this.#placeholder === undefined ? I18n.t("feedback.chatPlaceholder") : this.#placeholder;
134
147
  this.#input.setAttribute("aria-label", I18n.t("feedback.chatComposer"));
135
- this.#send_button.type = "button";
136
- this.#send_button.className = "chat_send";
137
- this.#send_button.disabled = true;
138
- this.#send_button.setAttribute("aria-label", this.#sendLabelText());
139
- this.#send_button.setAttribute("title", this.#sendTextText());
140
- this.#send_button.innerHTML = new Icon(Icons.CONTENT.SEND, "16px").getHTML();
148
+ this.#sendButton.type = "button";
149
+ this.#sendButton.className = "chat_send";
150
+ this.#sendButton.disabled = true;
151
+ this.#sendButton.setAttribute("aria-label", this.#sendLabelText());
152
+ this.#sendButton.setAttribute("title", this.#sendTextText());
153
+ this.#sendButton.innerHTML = new Icon(Icons.CONTENT.SEND, "16px").getHTML();
141
154
 
142
155
  this.#header.appendChild(this.#title);
143
156
  this.#header.appendChild(this.#subtitle);
144
157
  this.#composer.appendChild(this.#input);
145
- this.#composer.appendChild(this.#send_button);
158
+ this.#composer.appendChild(this.#sendButton);
146
159
  this.#root.appendChild(this.#header);
147
160
  this.#root.appendChild(this.#log);
148
161
  this.#root.appendChild(this.#typing);
@@ -151,27 +164,43 @@ class Chat {
151
164
  this.#subtitle.hidden = true;
152
165
  if (optionOr(options.title, undefined) !== undefined) this.setTitle(options.title);
153
166
  if (optionOr(options.subtitle, undefined) !== undefined) this.setSubtitle(options.subtitle);
154
- if (!this.#composer_visible) this.#composer.hidden = true;
167
+ if (!this.#composerVisible) this.#composer.hidden = true;
155
168
  if (this.#disabled) this.setDisabled(true);
156
169
 
157
170
  this.#input.addEventListener("input", () => this.#syncSendState());
158
171
  this.#input.addEventListener("keydown", this.#onInputKeyDown);
159
- this.#send_button.addEventListener("click", () => this.send());
172
+ this.#sendButton.addEventListener("click", () => this.send());
160
173
  this.#log.addEventListener("click", this.#onLogClick);
161
174
  this.setMessages(options.messages === undefined ? [] : options.messages);
162
175
  }
163
176
 
164
177
  // GET
165
- getId() { return this.#id; }
166
- getTitle() { return this.#title.innerText; }
167
- getSubtitle() { return this.#subtitle.innerText; }
178
+ getId() {
179
+ return this.#id;
180
+ }
181
+ getTitle() {
182
+ return this.#title.innerText;
183
+ }
184
+ getSubtitle() {
185
+ return this.#subtitle.innerText;
186
+ }
168
187
  /** Текст в поле ввода — то, что ещё не отправлено */
169
- getValue() { return this.#input.value; }
188
+ getValue() {
189
+ return this.#input.value;
190
+ }
170
191
  /** Копии сообщений: правка копии не меняет журнал */
171
- getMessages() { return this.#messages.map((message) => Object.assign({}, message)); }
172
- isTyping() { return this.#typing_name !== undefined; }
173
- isDisabled() { return this.#disabled; }
174
- getLog() { return this.#log; }
192
+ getMessages() {
193
+ return this.#messages.map((message) => Object.assign({}, message));
194
+ }
195
+ isTyping() {
196
+ return this.#typingName !== undefined;
197
+ }
198
+ isDisabled() {
199
+ return this.#disabled;
200
+ }
201
+ getLog() {
202
+ return this.#log;
203
+ }
175
204
 
176
205
  // SET
177
206
  setTitle(text = String()) {
@@ -209,17 +238,19 @@ class Chat {
209
238
  return this;
210
239
  }
211
240
  addSendListener(listener = () => {}) {
212
- this.#send_listeners.push(listener);
241
+ this.#sendListeners.push(listener);
213
242
  return this;
214
243
  }
215
- set() { return this.#root; }
244
+ set() {
245
+ return this.#root;
246
+ }
216
247
 
217
248
  // СООБЩЕНИЯ
218
249
  /** Полная замена списка: журнал собирается заново, группы схлопываются по автору и дню */
219
250
  setMessages(messages = []) {
220
251
  this.#messages = [];
221
- this.#last_date = undefined;
222
- this.#last_group = undefined;
252
+ this.#lastDate = undefined;
253
+ this.#lastGroup = undefined;
223
254
  this.#log.innerHTML = "";
224
255
  for (const message of messages) this.#appendMessage(Object.assign({}, message), false);
225
256
  this.scrollToBottom(false);
@@ -244,22 +275,45 @@ class Chat {
244
275
  }
245
276
  clear() {
246
277
  this.#messages = [];
247
- this.#last_date = undefined;
248
- this.#last_group = undefined;
278
+ this.#lastDate = undefined;
279
+ this.#lastGroup = undefined;
249
280
  this.#log.innerHTML = "";
250
281
  return this;
251
282
  }
252
283
  /** «Иван печатает…»: имя автора; `undefined` убирает строку */
253
284
  setTyping(name = undefined) {
254
- this.#typing_name = name;
255
- this.#typing.hidden = name === undefined;
256
- this.#typing.innerText = name === undefined ? "" : I18n.t("feedback.chatTyping", {name: name});
285
+ this.#typingName = name;
286
+ this.#typing.innerText = name === undefined ? "" : I18n.t("feedback.chatTyping", { name: name });
287
+ this.#showTyping(name !== undefined);
257
288
  return this;
258
289
  }
290
+
291
+ /**
292
+ * Строка «печатает» появляется и уходит движением: `hidden` возвращается по концу кадра,
293
+ * поэтому строка не пропадает рывком. Открытие за время ухода отменяет снятие.
294
+ */
295
+ #showTyping(visible) {
296
+ if (visible === true) {
297
+ this.#typing.classList.remove("chat_typing_leaving");
298
+ this.#typing.hidden = false;
299
+ return;
300
+ }
301
+ if (this.#typing.hidden === true) return;
302
+ this.#typing.classList.add("chat_typing_leaving");
303
+ const done = (event) => {
304
+ if (event.target !== this.#typing) return;
305
+ this.#typing.removeEventListener("animationend", done);
306
+ if (this.#typing.classList.contains("chat_typing_leaving") === false) return;
307
+ this.#typing.classList.remove("chat_typing_leaving");
308
+ this.#typing.hidden = true;
309
+ };
310
+ this.#typing.addEventListener("animationend", done);
311
+ }
259
312
  scrollToBottom(smooth = true) {
260
313
  // Плавная прокрутка — движение; при `prefers-reduced-motion` она идёт сразу
261
314
  const behavior = smooth && this.#reducedMotion() === false ? "smooth" : "auto";
262
- if (typeof this.#log.scrollTo === "function") this.#log.scrollTo({top: this.#log.scrollHeight, behavior: behavior});
315
+ if (typeof this.#log.scrollTo === "function")
316
+ this.#log.scrollTo({ top: this.#log.scrollHeight, behavior: behavior });
263
317
  else this.#log.scrollTop = this.#log.scrollHeight;
264
318
  return this;
265
319
  }
@@ -276,23 +330,23 @@ class Chat {
276
330
  author: this.#own,
277
331
  own: true,
278
332
  time: Date.now(),
279
- status: Chat.STATUS.SENDING
333
+ status: Chat.STATUS.SENDING,
280
334
  });
281
335
  this.#input.value = "";
282
336
  this.#syncSendState();
283
337
  const added = this.#appendMessage(message, true);
284
- if (this.#on_send !== undefined) this.#on_send(text, added);
285
- for (const listener of [...this.#send_listeners]) listener(text, added);
338
+ if (this.#onSend !== undefined) this.#onSend(text, added);
339
+ for (const listener of [...this.#sendListeners]) listener(text, added);
286
340
  return this;
287
341
  }
288
342
 
289
343
  // === ВНУТРЕННЕЕ ===
290
344
  #sendTextText() {
291
- return this.#send_text === undefined ? I18n.t("feedback.chatSend") : this.#send_text;
345
+ return this.#sendText === undefined ? I18n.t("feedback.chatSend") : this.#sendText;
292
346
  }
293
347
  /** Имя кнопки отправки: своё (`sendLabel`), иначе `sendText`, иначе словарь */
294
348
  #sendLabelText() {
295
- if (this.#send_label !== undefined) return this.#send_label;
349
+ if (this.#sendLabel !== undefined) return this.#sendLabel;
296
350
  return this.#sendTextText();
297
351
  }
298
352
  #reducedMotion() {
@@ -301,7 +355,7 @@ class Chat {
301
355
  return query.matches === true;
302
356
  }
303
357
  #syncSendState() {
304
- this.#send_button.disabled = this.#disabled || this.#input.value.trim() === "";
358
+ this.#sendButton.disabled = this.#disabled || this.#input.value.trim() === "";
305
359
  }
306
360
  /**
307
361
  * Добавить сообщение в журнал. Если автор и день совпадают с прошлой группой, пузырь
@@ -329,23 +383,27 @@ class Chat {
329
383
  }
330
384
  /** Разделитель дня: между сообщениями разных дней и в начале журнала */
331
385
  #insertDateSeparator(date) {
332
- if (date === undefined || this.#date_separators === false) return;
386
+ if (date === undefined || this.#dateSeparators === false) return;
333
387
  const key = this.#dayKey(date);
334
- if (key === this.#last_date) return;
335
- this.#last_date = key;
388
+ if (key === this.#lastDate) return;
389
+ this.#lastDate = key;
336
390
  const separator = document.createElement("chat_date");
337
391
  separator.dataset.day = key;
338
392
  const label = document.createElement("chat_date_label");
339
393
  label.innerText = this.#dayLabel(date);
340
394
  separator.appendChild(label);
341
395
  this.#log.appendChild(separator);
342
- this.#last_group = undefined;
396
+ this.#lastGroup = undefined;
343
397
  }
344
398
  /** Группа для сообщения: та же, если автор и день совпадают, иначе новая */
345
399
  #groupFor(message) {
346
400
  const author = this.#authorOf(message);
347
- if (this.#last_group !== undefined && this.#last_group.author === author && this.#last_group.day === this.#last_date) {
348
- return this.#last_group;
401
+ if (
402
+ this.#lastGroup !== undefined &&
403
+ this.#lastGroup.author === author &&
404
+ this.#lastGroup.day === this.#lastDate
405
+ ) {
406
+ return this.#lastGroup;
349
407
  }
350
408
  const node = document.createElement("chat_group");
351
409
  node.dataset.own = message.own === true ? "true" : "false";
@@ -356,15 +414,15 @@ class Chat {
356
414
  if (message.own !== true) {
357
415
  avatar = new Avatar({
358
416
  name: author,
359
- image: message.avatar === undefined ? undefined : {path: message.avatar},
417
+ image: message.avatar === undefined ? undefined : { path: message.avatar },
360
418
  size: Avatar.SIZE.SMALL,
361
- decorative: true
419
+ decorative: true,
362
420
  });
363
421
  node.prepend(avatar.set());
364
422
  }
365
423
  this.#log.appendChild(node);
366
- this.#last_group = {node: node, bubbles: bubbles, avatar: avatar, author: author, day: this.#last_date};
367
- return this.#last_group;
424
+ this.#lastGroup = { node: node, bubbles: bubbles, avatar: avatar, author: author, day: this.#lastDate };
425
+ return this.#lastGroup;
368
426
  }
369
427
  #bubble(message, first) {
370
428
  const bubble = document.createElement("chat_bubble");
@@ -418,7 +476,10 @@ class Chat {
418
476
  status.dataset.status = message.status;
419
477
  status.setAttribute("aria-label", this.#statusLabel(message.status));
420
478
  status.setAttribute("role", "img");
421
- status.innerHTML = new Icon(STATUS_ICONS[message.status] === undefined ? STATUS_ICONS.sent : STATUS_ICONS[message.status], "13px").getHTML();
479
+ status.innerHTML = new Icon(
480
+ STATUS_ICONS[message.status] === undefined ? STATUS_ICONS.sent : STATUS_ICONS[message.status],
481
+ "13px"
482
+ ).getHTML();
422
483
  meta.appendChild(status);
423
484
  }
424
485
  return meta;
@@ -442,10 +503,10 @@ class Chat {
442
503
  if (this.#dayKey(date) === this.#dayKey(now)) return I18n.t("common.today");
443
504
  const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
444
505
  if (this.#dayKey(date) === this.#dayKey(yesterday)) return I18n.t("common.yesterday");
445
- return new Intl.DateTimeFormat(I18n.getLocale(), {day: "numeric", month: "long"}).format(date);
506
+ return new Intl.DateTimeFormat(I18n.getLocale(), { day: "numeric", month: "long" }).format(date);
446
507
  }
447
508
  #timeLabel(date) {
448
- return new Intl.DateTimeFormat(I18n.getLocale(), {hour: "2-digit", minute: "2-digit"}).format(date);
509
+ return new Intl.DateTimeFormat(I18n.getLocale(), { hour: "2-digit", minute: "2-digit" }).format(date);
449
510
  }
450
511
 
451
512
  // === ОБРАБОТЧИКИ ===
@@ -456,15 +517,15 @@ class Chat {
456
517
  this.send();
457
518
  };
458
519
  #onLogClick = (event) => {
459
- if (this.#on_message_click === undefined) return;
520
+ if (this.#onMessageClick === undefined) return;
460
521
  const bubble = event.target instanceof Element ? event.target.closest("chat_bubble") : null;
461
522
  if (bubble === null) return;
462
523
  const message = this.#messages.find((entry) => entry.id === bubble.dataset.id);
463
- if (message !== undefined) this.#on_message_click(message);
524
+ if (message !== undefined) this.#onMessageClick(message);
464
525
  };
465
526
  }
466
527
 
467
- exports.Chat = Chat
528
+ exports.Chat = Chat;
468
529
 
469
530
  // Описание сообщения собирает фабрика — как у ContextMenu и SplitButton
470
531
  Chat.Message = (options = {}) => Object.assign({}, options);