chuijs 4.0.3 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CHANGELOG.md +536 -0
  2. package/framework/appLayout/default/chui_app_layout.js +361 -303
  3. package/framework/appLayout/default/global_style.css +243 -89
  4. package/framework/appLayout/default/main_theme_style.css +188 -101
  5. package/framework/components/chui_accordion/accordion.js +17 -17
  6. package/framework/components/chui_accordion/styles.css +7 -7
  7. package/framework/components/chui_action_sheet/action_sheet.js +35 -32
  8. package/framework/components/chui_action_sheet/styles.css +10 -4
  9. package/framework/components/chui_actions_bar/actions_bar.js +338 -0
  10. package/framework/components/chui_actions_bar/styles.css +139 -0
  11. package/framework/components/chui_affix/affix.js +4 -4
  12. package/framework/components/chui_affix/styles.css +5 -0
  13. package/framework/components/chui_alert/alert.js +11 -5
  14. package/framework/components/chui_alert/styles.css +7 -0
  15. package/framework/components/chui_avatar/avatar.js +13 -13
  16. package/framework/components/chui_avatar/styles.css +1 -1
  17. package/framework/components/chui_avatar_group/avatar_group.js +4 -4
  18. package/framework/components/chui_avatar_group/styles.css +1 -1
  19. package/framework/components/chui_backtop/backtop.js +25 -2
  20. package/framework/components/chui_backtop/styles.css +21 -0
  21. package/framework/components/chui_badge/badge.js +15 -15
  22. package/framework/components/chui_badge/styles.css +6 -6
  23. package/framework/components/chui_banner/banner.js +222 -0
  24. package/framework/components/chui_banner/styles.css +170 -0
  25. package/framework/components/chui_breadcrumb/styles.css +1 -1
  26. package/framework/components/chui_button/button.js +24 -28
  27. package/framework/components/chui_button/styles.css +5 -5
  28. package/framework/components/chui_calendar/calendar.js +6 -6
  29. package/framework/components/chui_card/card.js +14 -14
  30. package/framework/components/chui_card_scroll/card_scroll.js +169 -0
  31. package/framework/components/chui_card_scroll/styles.css +86 -0
  32. package/framework/components/chui_chat/chat.js +73 -51
  33. package/framework/components/chui_chat/styles.css +18 -7
  34. package/framework/components/chui_clipboard_history/clipboard_history.js +25 -20
  35. package/framework/components/chui_clipboard_history/styles.css +10 -8
  36. package/framework/components/chui_code/code.js +8 -8
  37. package/framework/components/chui_code/styles.css +2 -2
  38. package/framework/components/chui_code_editor/code_editor.js +26 -26
  39. package/framework/components/chui_code_editor/styles.css +3 -3
  40. package/framework/components/chui_command_palette/command_palette.js +29 -29
  41. package/framework/components/chui_command_palette/styles.css +5 -5
  42. package/framework/components/chui_console/console.js +9 -9
  43. package/framework/components/chui_console/styles.css +5 -2
  44. package/framework/components/chui_content_block/content_block.js +2 -2
  45. package/framework/components/chui_context_menu/context_menu.js +162 -54
  46. package/framework/components/chui_context_menu/styles.css +82 -8
  47. package/framework/components/chui_custom_element/custom_element.js +11 -11
  48. package/framework/components/chui_data_grid/data_grid.js +268 -268
  49. package/framework/components/chui_data_grid/styles.css +14 -14
  50. package/framework/components/chui_descriptions/styles.css +1 -1
  51. package/framework/components/chui_details/details.js +12 -12
  52. package/framework/components/chui_details/styles.css +7 -7
  53. package/framework/components/chui_diff_viewer/diff_viewer.js +88 -88
  54. package/framework/components/chui_diff_viewer/styles.css +9 -9
  55. package/framework/components/chui_divider/divider.js +6 -6
  56. package/framework/components/chui_divider/styles.css +2 -2
  57. package/framework/components/chui_drawer/drawer.js +37 -14
  58. package/framework/components/chui_drawer/styles.css +18 -4
  59. package/framework/components/chui_drop_zone/drop_zone.js +6 -6
  60. package/framework/components/chui_drop_zone/styles.css +10 -6
  61. package/framework/components/chui_fieldset/fieldset.js +6 -6
  62. package/framework/components/chui_fieldset/styles.css +5 -5
  63. package/framework/components/chui_file_explorer/file_explorer.js +115 -115
  64. package/framework/components/chui_file_explorer/styles.css +6 -6
  65. package/framework/components/chui_form/form.js +11 -11
  66. package/framework/components/chui_gantt/gantt.js +41 -41
  67. package/framework/components/chui_gantt/styles.css +5 -5
  68. package/framework/components/chui_graphs/graphs.js +82 -46
  69. package/framework/components/chui_graphs/styles.css +26 -5
  70. package/framework/components/chui_hero/hero.js +63 -20
  71. package/framework/components/chui_hero/styles.css +176 -7
  72. package/framework/components/chui_html_block/html_block.js +5 -5
  73. package/framework/components/chui_html_block/styles.css +1 -1
  74. package/framework/components/chui_hx/styles.css +2 -2
  75. package/framework/components/chui_icons/icons.js +7 -7
  76. package/framework/components/chui_icons/styles.css +2 -1
  77. package/framework/components/chui_inputs/chui_check_box/styles.css +12 -3
  78. package/framework/components/chui_inputs/chui_chips/chips.js +92 -31
  79. package/framework/components/chui_inputs/chui_chips/styles.css +28 -5
  80. package/framework/components/chui_inputs/chui_color_picker/color_picker.js +20 -15
  81. package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
  82. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +58 -58
  83. package/framework/components/chui_inputs/chui_combo_box/styles.css +9 -9
  84. package/framework/components/chui_inputs/chui_date/date.js +93 -95
  85. package/framework/components/chui_inputs/chui_date/styles.css +8 -8
  86. package/framework/components/chui_inputs/chui_date_range/date_range.js +22 -22
  87. package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
  88. package/framework/components/chui_inputs/chui_datetime/datetime.js +12 -12
  89. package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
  90. package/framework/components/chui_inputs/chui_email/email.js +10 -10
  91. package/framework/components/chui_inputs/chui_email/styles.css +6 -6
  92. package/framework/components/chui_inputs/chui_file/file.js +8 -8
  93. package/framework/components/chui_inputs/chui_file/styles.css +12 -9
  94. package/framework/components/chui_inputs/chui_hotkey/hotkey.js +39 -39
  95. package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
  96. package/framework/components/chui_inputs/chui_masked/masked.js +44 -44
  97. package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
  98. package/framework/components/chui_inputs/chui_mention/mention.js +13 -13
  99. package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
  100. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +147 -137
  101. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +14 -8
  102. package/framework/components/chui_inputs/chui_number/number.js +42 -45
  103. package/framework/components/chui_inputs/chui_number/styles.css +6 -6
  104. package/framework/components/chui_inputs/chui_otp/otp.js +32 -32
  105. package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
  106. package/framework/components/chui_inputs/chui_password/password.js +30 -36
  107. package/framework/components/chui_inputs/chui_password/styles.css +7 -7
  108. package/framework/components/chui_inputs/chui_radio_button/styles.css +2 -2
  109. package/framework/components/chui_inputs/chui_rating/rating.js +28 -28
  110. package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
  111. package/framework/components/chui_inputs/chui_select_box/select_box.js +54 -54
  112. package/framework/components/chui_inputs/chui_select_box/styles.css +10 -10
  113. package/framework/components/chui_inputs/chui_slider/slider.js +14 -14
  114. package/framework/components/chui_inputs/chui_slider/styles.css +5 -4
  115. package/framework/components/chui_inputs/chui_text/styles.css +7 -7
  116. package/framework/components/chui_inputs/chui_text/text.js +13 -17
  117. package/framework/components/chui_inputs/chui_text_area/styles.css +8 -8
  118. package/framework/components/chui_inputs/chui_text_area/text_area.js +23 -23
  119. package/framework/components/chui_inputs/chui_time/styles.css +13 -17
  120. package/framework/components/chui_inputs/chui_time/time.js +35 -24
  121. package/framework/components/chui_inputs/chui_toggle/styles.css +10 -3
  122. package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
  123. package/framework/components/chui_json_editor/json_editor.js +87 -87
  124. package/framework/components/chui_json_editor/styles.css +5 -5
  125. package/framework/components/chui_json_viewer/json_viewer.js +80 -80
  126. package/framework/components/chui_json_viewer/styles.css +6 -6
  127. package/framework/components/chui_kanban/kanban.js +27 -27
  128. package/framework/components/chui_kanban/styles.css +8 -4
  129. package/framework/components/chui_kbd/styles.css +1 -1
  130. package/framework/components/chui_label/label.js +17 -17
  131. package/framework/components/chui_label/styles.css +1 -1
  132. package/framework/components/chui_link/link.js +9 -9
  133. package/framework/components/chui_link/styles.css +11 -4
  134. package/framework/components/chui_list/list.js +11 -11
  135. package/framework/components/chui_list/styles.css +2 -2
  136. package/framework/components/chui_markdown_editor/markdown_editor.js +66 -66
  137. package/framework/components/chui_markdown_editor/styles.css +5 -5
  138. package/framework/components/chui_media/audio.js +240 -246
  139. package/framework/components/chui_media/audio_styles.css +41 -23
  140. package/framework/components/chui_media/image.js +49 -46
  141. package/framework/components/chui_media/image_compare.js +35 -35
  142. package/framework/components/chui_media/image_compare_styles.css +5 -5
  143. package/framework/components/chui_media/image_styles.css +1 -1
  144. package/framework/components/chui_media/video.js +148 -148
  145. package/framework/components/chui_media/video_styles.css +27 -11
  146. package/framework/components/chui_menu_bar/menu_bar.js +54 -54
  147. package/framework/components/chui_menu_bar/styles.css +10 -10
  148. package/framework/components/chui_message_bar/message_bar.js +551 -0
  149. package/framework/components/chui_message_bar/styles.css +248 -0
  150. package/framework/components/chui_modal/modal.js +37 -13
  151. package/framework/components/chui_notification/notification.js +76 -54
  152. package/framework/components/chui_notification/notification_download.js +26 -81
  153. package/framework/components/chui_notification/notification_download_card.js +115 -0
  154. package/framework/components/chui_notification/notification_download_progress.css +2 -2
  155. package/framework/components/chui_notification/notification_download_progress.js +70 -108
  156. package/framework/components/chui_notification/notification_update.js +31 -31
  157. package/framework/components/chui_notification/status_plate.css +3 -3
  158. package/framework/components/chui_notification/styles.css +6 -1
  159. package/framework/components/chui_notification/styles_download.css +1 -1
  160. package/framework/components/chui_notification/styles_update.css +2 -2
  161. package/framework/components/chui_org_chart/org_chart.js +29 -29
  162. package/framework/components/chui_org_chart/styles.css +2 -2
  163. package/framework/components/chui_pagination/pagination.js +3 -3
  164. package/framework/components/chui_pagination/styles.css +1 -1
  165. package/framework/components/chui_paragraph/styles.css +2 -2
  166. package/framework/components/chui_popover/popover.js +29 -23
  167. package/framework/components/chui_popover/styles.css +1 -1
  168. package/framework/components/chui_popups/popups.js +124 -124
  169. package/framework/components/chui_popups/styles.css +5 -5
  170. package/framework/components/chui_print_button/print_button.js +15 -10
  171. package/framework/components/chui_progress_bar/progress_bar.js +34 -12
  172. package/framework/components/chui_progress_bar/styles.css +4 -4
  173. package/framework/components/chui_progress_ring/progress_ring.js +15 -15
  174. package/framework/components/chui_progress_ring/styles.css +1 -1
  175. package/framework/components/chui_qrcode/qrcode.js +12 -12
  176. package/framework/components/chui_query_builder/query_builder.js +46 -46
  177. package/framework/components/chui_query_builder/styles.css +4 -4
  178. package/framework/components/chui_radio_group/styles.css +1 -1
  179. package/framework/components/chui_result/result.js +15 -15
  180. package/framework/components/chui_result/styles.css +3 -3
  181. package/framework/components/chui_scheduler/scheduler.js +74 -74
  182. package/framework/components/chui_scheduler/styles.css +5 -5
  183. package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
  184. package/framework/components/chui_screen_spinner/styles.css +49 -0
  185. package/framework/components/chui_segmented_control/segmented_control.js +63 -6
  186. package/framework/components/chui_segmented_control/styles.css +32 -6
  187. package/framework/components/chui_settings/settings.js +45 -46
  188. package/framework/components/chui_settings/styles.css +4 -4
  189. package/framework/components/chui_shortcut_help/shortcut_help.js +32 -34
  190. package/framework/components/chui_sidebar_nav/sidebar_nav.js +98 -29
  191. package/framework/components/chui_sidebar_nav/styles.css +9 -3
  192. package/framework/components/chui_slideshow/slideshow.js +85 -85
  193. package/framework/components/chui_slideshow/styles.css +6 -6
  194. package/framework/components/chui_spinner/spinner.js +10 -10
  195. package/framework/components/chui_split_button/split_button.js +30 -12
  196. package/framework/components/chui_split_button/styles.css +21 -5
  197. package/framework/components/chui_splitter/splitter.js +11 -11
  198. package/framework/components/chui_splitter/styles.css +2 -2
  199. package/framework/components/chui_statistic/statistic.js +47 -47
  200. package/framework/components/chui_status_bar/styles.css +2 -2
  201. package/framework/components/chui_stepper/stepper.js +202 -31
  202. package/framework/components/chui_stepper/styles.css +79 -14
  203. package/framework/components/chui_sticky_bar/sticky_bar.js +44 -13
  204. package/framework/components/chui_sticky_bar/styles.css +10 -8
  205. package/framework/components/chui_tabs/chui_tabs.js +74 -26
  206. package/framework/components/chui_tabs/styles.css +40 -12
  207. package/framework/components/chui_text_editor/styles.css +7 -7
  208. package/framework/components/chui_text_editor/text_editor.js +56 -56
  209. package/framework/components/chui_text_editor/text_editor_panel.js +173 -174
  210. package/framework/components/chui_theme_toggle/theme_toggle.js +21 -5
  211. package/framework/components/chui_timeline/styles.css +3 -1
  212. package/framework/components/chui_toast_queue/styles.css +11 -11
  213. package/framework/components/chui_toast_queue/toast_queue.js +30 -27
  214. package/framework/components/chui_toolbar/styles.css +2 -2
  215. package/framework/components/chui_tooltip/styles.css +2 -2
  216. package/framework/components/chui_tooltip/tooltip.js +3 -3
  217. package/framework/components/chui_tour/styles.css +8 -8
  218. package/framework/components/chui_tour/tour.js +72 -72
  219. package/framework/components/chui_transfer/styles.css +6 -6
  220. package/framework/components/chui_transfer/transfer.js +93 -106
  221. package/framework/components/chui_tree_table/styles.css +4 -4
  222. package/framework/components/chui_tree_table/tree_table.js +45 -45
  223. package/framework/components/chui_tree_view/styles.css +10 -8
  224. package/framework/components/chui_tree_view/tree_view.js +185 -183
  225. package/framework/components/chui_user_bar/styles.css +145 -0
  226. package/framework/components/chui_user_bar/user_bar.js +402 -0
  227. package/framework/components/chui_user_list_bar/styles.css +173 -0
  228. package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
  229. package/framework/components/chui_watermark/watermark.js +17 -17
  230. package/framework/components/chui_webview/webview.js +9 -9
  231. package/framework/components/chui_write_bar/styles.css +172 -0
  232. package/framework/components/chui_write_bar/write_bar.js +320 -0
  233. package/framework/main_window.js +1234 -0
  234. package/framework/modules/chui_a11y/chui_a11y.js +78 -79
  235. package/framework/modules/chui_animations/animations.js +77 -15
  236. package/framework/modules/chui_animations/motion.js +120 -0
  237. package/framework/modules/chui_animations/styles.css +36 -3
  238. package/framework/modules/chui_functions.js +31 -27
  239. package/framework/modules/chui_highlight/styles.css +13 -10
  240. package/framework/modules/chui_i18n/chui_i18n.js +3 -3
  241. package/framework/modules/chui_i18n/locales/feedback.js +62 -0
  242. package/framework/modules/chui_i18n/locales/navigation.js +4 -0
  243. package/framework/modules/chui_json/chui_json.js +2 -2
  244. package/framework/modules/chui_logger/chui_logger.js +23 -8
  245. package/framework/modules/chui_page/page.js +10 -10
  246. package/framework/modules/chui_palette/palette.js +61 -0
  247. package/framework/modules/chui_qr/chui_qr.js +23 -23
  248. package/framework/modules/chui_scrollbar/chui_scrollbar.js +45 -45
  249. package/framework/modules/chui_scrollbar/styles.css +3 -3
  250. package/framework/modules/chui_services/chui_services.js +8 -8
  251. package/framework/modules/chui_services/desktop.js +77 -71
  252. package/framework/modules/chui_sortable/chui_sortable.js +17 -17
  253. package/framework/modules/chui_validate/chui_validate.js +3 -3
  254. package/framework/tray_bar/chui_menu_items.js +2 -2
  255. package/index.d.ts +266 -34
  256. package/index.js +20 -1168
  257. package/package.json +6 -4
  258. package/framework/components/telegram_bot/chui_telegram_bot.js +0 -191
@@ -0,0 +1,145 @@
1
+ /* ── UserBar — полоса пользователя ─────────────────────────────
2
+ Шапка разговора: кто перед вами, чем занят и что с ним можно сделать.
3
+ Полоса не полагает, где она стоит: в карточке переписки она читается кромкой
4
+ той же карточки, а отдельной строкой списка — самостоятельной поверхностью. */
5
+
6
+ chui_user_bar {
7
+ display: flex;
8
+ flex-direction: row;
9
+ align-items: center;
10
+ gap: var(--control_padding);
11
+ box-sizing: border-box;
12
+ width: -webkit-fill-available;
13
+ padding: var(--control_padding) var(--block_padding);
14
+ background: var(--element_background_2);
15
+ border-bottom: 1px solid var(--border_color);
16
+ font-size: var(--font_default_size);
17
+ color: var(--text_color);
18
+ }
19
+
20
+ /* Кликабельная полоса — кнопка: отклик на курсор показывает, что по человеку можно нажать */
21
+ chui_user_bar[role="button"] {
22
+ cursor: pointer;
23
+ }
24
+
25
+ chui_user_bar[role="button"]:hover {
26
+ background: var(--element_background_hover);
27
+ }
28
+
29
+ user_bar_texts {
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 2px;
33
+ flex: 1 1 auto;
34
+ min-width: 0;
35
+ }
36
+
37
+ user_bar_name {
38
+ display: block;
39
+ overflow: hidden;
40
+ font-weight: 600;
41
+ color: var(--text_color);
42
+ white-space: nowrap;
43
+ text-overflow: ellipsis;
44
+ }
45
+
46
+ /* Подпись под именем: своя строка или состояние человека */
47
+ user_bar_subtitle {
48
+ display: block;
49
+ overflow: hidden;
50
+ color: var(--text_color_disabled);
51
+ font-size: var(--font_labels_size);
52
+ white-space: nowrap;
53
+ text-overflow: ellipsis;
54
+ }
55
+
56
+ user_bar_subtitle[hidden] {
57
+ display: none;
58
+ }
59
+
60
+ user_bar_actions {
61
+ display: flex;
62
+ flex-direction: row;
63
+ align-items: center;
64
+ gap: 2px;
65
+ flex: none;
66
+ }
67
+
68
+ /* Меню действий: панель внутри поповера своей поверхности и кромки не носит —
69
+ меню и панель одна плоскость, а второй слой заливки читался бы как вложенная карточка */
70
+ user_bar_menu chui_actions_bar {
71
+ padding: 0;
72
+ background: transparent;
73
+ border: none;
74
+ }
75
+
76
+ /* Кнопка полосы: круг со значком, подпись живёт в `aria-label` и подсказке.
77
+ Та же мера, что у кнопок панели ввода: рядом стоят аватар и кнопки действий,
78
+ и разная высота читалась бы небрежностью */
79
+ .user_bar_button {
80
+ position: relative;
81
+ flex: none;
82
+ cursor: pointer;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ box-sizing: border-box;
87
+ width: var(--control_height);
88
+ height: var(--control_height);
89
+ padding: 0;
90
+ border: none;
91
+ border-radius: var(--radius_circle);
92
+ background: transparent;
93
+ color: var(--text_color);
94
+ }
95
+
96
+ .user_bar_button:hover {
97
+ background: var(--element_background_hover);
98
+ }
99
+
100
+ /* Нажатие — акцент, как у Button: контрол отвечает на клик ещё до действия */
101
+ .user_bar_button:active:not(:disabled) {
102
+ background: var(--blue_prime_background);
103
+ color: var(--text_color_hover);
104
+ }
105
+
106
+ .user_bar_button:disabled {
107
+ cursor: not-allowed;
108
+ background: transparent;
109
+ color: var(--text_color_disabled);
110
+ }
111
+
112
+ .user_bar_button[data-accent="true"] {
113
+ background: var(--blue_prime_background);
114
+ color: var(--text_color_hover);
115
+ }
116
+
117
+ .user_bar_button[data-accent="true"]:hover {
118
+ background: var(--blue_prime_background_tint_strong);
119
+ }
120
+
121
+ .user_bar_button chui_icon {
122
+ color: inherit;
123
+ }
124
+
125
+ /* Счётчик кнопки: число важнее подписи, поэтому плашка в углу круга */
126
+ user_bar_badge {
127
+ position: absolute;
128
+ top: -2px;
129
+ inset-inline-end: -2px;
130
+ box-sizing: border-box;
131
+ min-width: 16px;
132
+ height: 16px;
133
+ padding-inline: 4px;
134
+ border-radius: var(--radius_pill);
135
+ background: var(--red_prime_background);
136
+ color: var(--text_color_hover);
137
+ font-size: 10px;
138
+ font-weight: 600;
139
+ line-height: 16px;
140
+ text-align: center;
141
+ }
142
+
143
+ user_bar_badge[hidden] {
144
+ display: none;
145
+ }
@@ -0,0 +1,402 @@
1
+ const { Icon, Icons } = require("../chui_icons/icons");
2
+ const { Avatar } = require("../chui_avatar/avatar");
3
+ const { Popover } = require("../chui_popover/popover");
4
+ const { ActionsBar } = require("../chui_actions_bar/actions_bar");
5
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
6
+ const { makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
7
+ const { optionOr, setStyles } = require("../../modules/chui_functions");
8
+
9
+ /**
10
+ * Кнопка полосы пользователя: значок с подписью для скринридера и счётчиком.
11
+ *
12
+ * Настоящий `<button>`, а не кастомный тег: клавиатура, фокус и роль у него уже есть.
13
+ * Подпись обязательна — лигатура значка («call», «more_vert») ничего не говорит скринридеру,
14
+ * а сам значок скрыт от дерева доступности.
15
+ */
16
+ class UserBarButton {
17
+ #root = document.createElement("button");
18
+ #label = String();
19
+ #onClick = undefined;
20
+ #disabled = false;
21
+ #badge = document.createElement("user_bar_badge");
22
+
23
+ constructor(options = {}) {
24
+ const icon = optionOr(options.icon, String());
25
+ const label = optionOr(options.label, String());
26
+ this.#label = label === "" ? I18n.t("feedback.userBarAction") : label;
27
+ this.#root.type = "button";
28
+ this.#root.className = "user_bar_button";
29
+ if (icon !== "") this.#root.innerHTML = new Icon(icon, "20px").getHTML();
30
+ if (options.accent === true) this.#root.dataset.accent = "true";
31
+ this.#root.setAttribute("aria-label", this.#label);
32
+ this.#root.title = this.#label;
33
+ this.#onClick = typeof options.onClick === "function" ? options.onClick : undefined;
34
+ this.#root.addEventListener("click", () => {
35
+ if (this.#disabled === true) return;
36
+ if (this.#onClick !== undefined) this.#onClick(this);
37
+ });
38
+ if (options.badge !== undefined) this.setBadge(options.badge);
39
+ if (options.disabled === true) this.setDisabled(true);
40
+ }
41
+
42
+ /** Счётчик на кнопке: число показывает плашка, скринридеру оно попадает в подпись */
43
+ setBadge(count = Number()) {
44
+ const value = Number(count);
45
+ const shown = Number.isFinite(value) && value > 0 ? String(value) : "";
46
+ this.#badge.innerText = shown;
47
+ this.#badge.hidden = shown === "";
48
+ this.#root.setAttribute(
49
+ "aria-label",
50
+ shown === "" ? this.#label : `${this.#label}, ${I18n.t("feedback.userBarBadge", { value: shown })}`
51
+ );
52
+ if (shown !== "" && this.#badge.parentNode === null) this.#root.appendChild(this.#badge);
53
+ return this;
54
+ }
55
+
56
+ setDisabled(boolean = Boolean()) {
57
+ this.#disabled = boolean === true;
58
+ this.#root.disabled = this.#disabled;
59
+ return this;
60
+ }
61
+
62
+ setLabel(text = String()) {
63
+ this.#label = String(text);
64
+ this.#root.setAttribute("aria-label", this.#label);
65
+ this.#root.title = this.#label;
66
+ return this;
67
+ }
68
+
69
+ set() {
70
+ return this.#root;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Полоса пользователя — шапка разговора: кто перед вами, чем занят и что с ним можно сделать.
76
+ *
77
+ * Третья полоса к `MessageBar` и `WriteBar`: полоса пользователя сверху, лента сообщений
78
+ * в середине, панель ввода снизу — из этих трёх частей собирается экран переписки. Полоса
79
+ * самостоятельная: её ставят и шапкой разговора, и строкой человека в списке (тогда её делают
80
+ * кликабельной через `onClick`).
81
+ *
82
+ * Состояние человека показывается дважды: точкой на аватаре и подписью под именем. Если своей
83
+ * подписи нет, она берётся из состояния («в сети», «отошёл», «занят», «не в сети»), поэтому
84
+ * приложению не нужно держать эти слова у себя. `setSubtitle("Иван печатает…")` показывает
85
+ * вместо неё что угодно — например, набор текста; `setSubtitle()` без аргумента возвращает
86
+ * подпись состояния.
87
+ *
88
+ * Доступность: у полосы есть имя (имя человека и его состояние), аватар декоративен — имя уже
89
+ * написано рядом. Кнопки «назад» и действий — настоящие `<button>` с подписью для скринридера;
90
+ * кликабельная полоса получает роль кнопки, обход клавиатурой и активацию Enter/Space, а её имя
91
+ * говорит, что откроется профиль.
92
+ *
93
+ * ```javascript
94
+ * const bar = new UserBar({
95
+ * name: 'Иван Петров',
96
+ * status: UserBar.STATUS.ONLINE,
97
+ * photo: 'avatar.png',
98
+ * back: true,
99
+ * onBack: () => Route.back(),
100
+ * actions: [{ icon: Icons.COMMUNICATION.CALL, label: 'Позвонить' }],
101
+ * });
102
+ * mount(bar); // или body.appendChild(bar.set())
103
+ * ```
104
+ *
105
+ * Опции: `id`, `name`, `subtitle`, `photo`, `status`, `back`, `backLabel`, `actions`, `onBack`,
106
+ * `onClick`.
107
+ * Кнопки полосы собираются фабрикой `UserBar.Button({ icon, label, onClick, badge, accent,
108
+ * disabled })`.
109
+ * API: `getName()`, `setName(text)`, `getSubtitle()`, `setSubtitle(text)`, `getStatus()`,
110
+ * `setStatus(status)`, `setPhoto(path)`, `getAvatar()`, `addAction(options)`, `getActions()`,
111
+ * `set()`, `destroy()`.
112
+ */
113
+ class UserBar {
114
+ static STATUS = Avatar.STATUS;
115
+ /** Фабрика кнопок полосы: `UserBar.Button({ icon, label, onClick, badge, accent })` */
116
+ static Button = (options = {}) => new UserBarButton(options);
117
+
118
+ #root = document.createElement("chui_user_bar");
119
+ #back = document.createElement("button");
120
+ #avatar = undefined;
121
+ #texts = document.createElement("user_bar_texts");
122
+ #nameNode = document.createElement("user_bar_name");
123
+ #subtitle = document.createElement("user_bar_subtitle");
124
+ #actions = document.createElement("user_bar_actions");
125
+ #name = undefined;
126
+ #subtitleText = undefined;
127
+ #status = undefined;
128
+ #photo = undefined;
129
+ #icons = [];
130
+ #menu = undefined;
131
+ #menuLabel = undefined;
132
+ #menuButton = undefined;
133
+ #popover = undefined;
134
+ #onBack = undefined;
135
+ #onClick = undefined;
136
+
137
+ constructor(
138
+ options = {
139
+ id: String(),
140
+ name: String(),
141
+ subtitle: String(),
142
+ photo: String(),
143
+ status: String(),
144
+ back: Boolean(),
145
+ backLabel: String(),
146
+ actions: [],
147
+ menu: undefined,
148
+ menuLabel: String(),
149
+ onBack: undefined,
150
+ onClick: undefined,
151
+ }
152
+ ) {
153
+ setStyles(__dirname + "/styles.css", "chUiJS_UserBar");
154
+ if (options.id !== undefined) this.#root.id = options.id;
155
+ this.#name = options.name;
156
+ this.#photo = options.photo;
157
+ this.#status = options.status;
158
+ this.#subtitleText = options.subtitle;
159
+ this.#menuLabel = options.menuLabel;
160
+ this.#onBack = typeof options.onBack === "function" ? options.onBack : undefined;
161
+ this.#onClick = typeof options.onClick === "function" ? options.onClick : undefined;
162
+
163
+ // Аватар декоративен: имя человека стоит рядом, и второй раз оно не нужно
164
+ this.#avatar = new Avatar({
165
+ name: this.#name,
166
+ image: this.#photo === undefined ? undefined : { path: this.#photo },
167
+ status: this.#status,
168
+ size: Avatar.SIZE.DEFAULT,
169
+ decorative: true,
170
+ });
171
+ this.#nameNode.innerText = this.#name === undefined ? "" : String(this.#name);
172
+ this.#subtitle.hidden = true;
173
+ this.#texts.append(this.#nameNode, this.#subtitle);
174
+
175
+ if (options.back === true) {
176
+ this.#back.type = "button";
177
+ this.#back.className = "user_bar_button";
178
+ this.#back.dataset.side = "back";
179
+ const label =
180
+ optionOr(options.backLabel, undefined) === undefined
181
+ ? I18n.t("feedback.userBarBack")
182
+ : String(options.backLabel);
183
+ this.#back.setAttribute("aria-label", label);
184
+ this.#back.title = label;
185
+ this.#back.innerHTML = new Icon(Icons.NAVIGATION.ARROW_BACK, "20px").getHTML();
186
+ this.#back.addEventListener("click", () => {
187
+ if (this.#onBack !== undefined) this.#onBack(this);
188
+ });
189
+ this.#root.appendChild(this.#back);
190
+ }
191
+ this.#root.append(this.#avatar.set(), this.#texts, this.#actions);
192
+
193
+ if (Array.isArray(options.actions)) for (const action of options.actions) this.addAction(action);
194
+ // Меню идёт последним: кнопка «⋯» встаёт за кнопками действий
195
+ if (options.menu !== undefined) this.setMenu(options.menu);
196
+ if (this.#onClick !== undefined) this.#makeClickable();
197
+ else this.#root.setAttribute("role", "group");
198
+ this.#syncSubtitle();
199
+ this.#syncName();
200
+ }
201
+
202
+ // GET
203
+ getName() {
204
+ return this.#nameNode.innerText;
205
+ }
206
+ /** Видимая подпись под именем: своя или из состояния человека */
207
+ getSubtitle() {
208
+ return this.#subtitle.hidden === true ? "" : this.#subtitle.innerText;
209
+ }
210
+ getStatus() {
211
+ return this.#status;
212
+ }
213
+ /** Аватар — для того, кто хочет повесить на него своё: полоса только показывает человека */
214
+ getAvatar() {
215
+ return this.#avatar;
216
+ }
217
+ getActions() {
218
+ return this.#icons.slice();
219
+ }
220
+
221
+ // SET
222
+ setName(text = String()) {
223
+ this.#name = String(text);
224
+ this.#nameNode.innerText = this.#name;
225
+ this.#avatar.setName(this.#name);
226
+ this.#syncName();
227
+ return this;
228
+ }
229
+ /**
230
+ * Подпись под именем: своя строка (например, «печатает…») или состояние человека,
231
+ * если аргумента нет. Своя подпись состояние не теряет — `getStatus()` его помнит
232
+ */
233
+ setSubtitle(text = undefined) {
234
+ this.#subtitleText = text;
235
+ this.#syncSubtitle();
236
+ return this;
237
+ }
238
+ setStatus(status = undefined) {
239
+ this.#status = status;
240
+ this.#avatar.setStatus(status);
241
+ this.#syncSubtitle();
242
+ this.#syncName();
243
+ return this;
244
+ }
245
+ setPhoto(path = undefined) {
246
+ this.#photo = path;
247
+ this.#avatar.setImage(path === undefined ? undefined : { path: path });
248
+ return this;
249
+ }
250
+
251
+ /** Кнопка действий справа: звонок, поиск, меню — что угодно со значком и подписью */
252
+ addAction(options = {}) {
253
+ const button = options instanceof UserBarButton ? options : new UserBarButton(options);
254
+ this.#icons.push(button);
255
+ this.#actions.appendChild(button.set());
256
+ return button;
257
+ }
258
+
259
+ // МЕНЮ ДЕЙСТВИЙ
260
+ /**
261
+ * Меню действий за кнопкой «⋯»: сюда переезжает панель `ActionsBar`, чтобы в шапке
262
+ * разговора не заводить вторую полосу. Действия идут столбцом, как пункты меню.
263
+ *
264
+ * Принимается и список действий (`{ icon, label, toggle, danger, onClick }`), и готовая
265
+ * панель `ActionsBar` — у неё ориентация переводится в столбец сама. Выбор обычного действия
266
+ * закрывает меню, переключатели остаются открытыми: состояние видно на месте.
267
+ */
268
+ setMenu(menu = undefined) {
269
+ this.#popoverDestroy();
270
+ if (this.#menuButton !== undefined) {
271
+ this.#menuButton.set().remove();
272
+ this.#icons = this.#icons.filter((button) => button !== this.#menuButton);
273
+ this.#menuButton = undefined;
274
+ }
275
+ this.#menu = undefined;
276
+ if (menu === undefined || menu === null) return this;
277
+ this.#menu =
278
+ menu instanceof ActionsBar
279
+ ? menu.setOrientation(ActionsBar.ORIENTATION.VERTICAL)
280
+ : new ActionsBar({
281
+ orientation: ActionsBar.ORIENTATION.VERTICAL,
282
+ actions: this.#closeOnPick(Array.isArray(menu) ? menu : []),
283
+ });
284
+ this.#menuButton = this.addAction({ icon: Icons.NAVIGATION.MORE_VERT, label: this.#menuLabelText() });
285
+ const host = document.createElement("user_bar_menu");
286
+ host.appendChild(this.#menu.set());
287
+ // Подсказку и состояние кнопке даёт `Popover`: `aria-haspopup="dialog"` и `aria-expanded`
288
+ this.#popover = new Popover({
289
+ trigger: this.#menuButton.set(),
290
+ content: host,
291
+ placement: Popover.PLACEMENT.BOTTOM,
292
+ label: I18n.t("feedback.actionsBar"),
293
+ });
294
+ return this;
295
+ }
296
+ /** Имя кнопки меню: по умолчанию — «Ещё» из словаря */
297
+ setMenuLabel(text = String()) {
298
+ this.#menuLabel = text;
299
+ if (this.#menuButton !== undefined) this.#menuButton.setLabel(text);
300
+ return this;
301
+ }
302
+ /** Панель действий внутри меню — её правят тем же API, что и любую `ActionsBar` */
303
+ getMenu() {
304
+ return this.#menu;
305
+ }
306
+ openMenu() {
307
+ if (this.#popover !== undefined) this.#popover.open();
308
+ return this;
309
+ }
310
+ closeMenu() {
311
+ this.#closeMenu();
312
+ return this;
313
+ }
314
+ toggleMenu() {
315
+ if (this.#popover !== undefined) this.#popover.toggle();
316
+ return this;
317
+ }
318
+ isMenuOpen() {
319
+ return this.#popover === undefined ? false : this.#popover.isOpen === true;
320
+ }
321
+
322
+ set() {
323
+ return this.#root;
324
+ }
325
+ destroy() {
326
+ this.#popoverDestroy();
327
+ this.#icons = [];
328
+ this.#onBack = undefined;
329
+ this.#onClick = undefined;
330
+ this.#root.remove();
331
+ }
332
+
333
+ // === ВНУТРЕННЕЕ ===
334
+ /** Имя кнопки меню: по умолчанию — «Ещё» из словаря */
335
+ #menuLabelText() {
336
+ return optionOr(this.#menuLabel, undefined) === undefined
337
+ ? I18n.t("feedback.userBarMore")
338
+ : String(this.#menuLabel);
339
+ }
340
+ /** Выбор обычного действия закрывает меню: переключатели остаются — состояние видно на месте */
341
+ #closeOnPick(actions = []) {
342
+ return actions.map((action) => {
343
+ if (action === undefined || action === null || action.toggle === true) return action;
344
+ return Object.assign({}, action, {
345
+ onClick: (button, active) => {
346
+ if (typeof action.onClick === "function") action.onClick(button, active);
347
+ this.#closeMenu();
348
+ },
349
+ });
350
+ });
351
+ }
352
+ #closeMenu() {
353
+ if (this.#popover !== undefined) this.#popover.close();
354
+ return this;
355
+ }
356
+ /** Слой меню снимается вместе с полосой: без этого его слушатели остались бы на окне */
357
+ #popoverDestroy() {
358
+ if (this.#popover !== undefined) this.#popover.destroy();
359
+ this.#popover = undefined;
360
+ }
361
+ /** Подпись под именем: своя или из состояния человека — второе приложение держать не должно */
362
+ #syncSubtitle() {
363
+ const own = optionOr(this.#subtitleText, undefined);
364
+ const text = own === undefined ? this.#statusLabel() : String(own);
365
+ this.#subtitle.innerText = text;
366
+ this.#subtitle.hidden = text === "";
367
+ }
368
+ #statusLabel() {
369
+ if (this.#status === undefined) return "";
370
+ const label = Avatar.STATUS_LABEL[this.#status];
371
+ return label === undefined ? "" : label;
372
+ }
373
+ /**
374
+ * Имя полосы для скринридера: человек и его состояние («Иван Петров, в сети»). У кликабельной
375
+ * полосы имя говорит, что откроется профиль, — иначе роль кнопки читалась бы просто «кнопка».
376
+ */
377
+ #syncName() {
378
+ const name = this.#nameNode.innerText.trim();
379
+ const person = name === "" ? I18n.t("feedback.userBar") : name;
380
+ const status = this.#statusLabel();
381
+ if (this.#onClick === undefined) {
382
+ this.#root.setAttribute("aria-label", status === "" ? person : `${person}, ${status}`);
383
+ return;
384
+ }
385
+ this.#root.setAttribute(
386
+ "aria-label",
387
+ I18n.t("feedback.userBarOpen", { name: status === "" ? person : `${person}, ${status}` })
388
+ );
389
+ }
390
+ /** Клик по всей полосе: роль кнопки, обход клавиатурой и активация Enter/Space */
391
+ #makeClickable() {
392
+ makeKeyboardButton(this.#root);
393
+ this.#root.addEventListener("click", (event) => {
394
+ // Кнопки полосы (назад, действия) — свои действия: их щелчок профиль не открывает
395
+ if (event.target instanceof Element && event.target.closest("button") !== null) return;
396
+ if (this.#onClick !== undefined) this.#onClick(this);
397
+ });
398
+ }
399
+ }
400
+
401
+ exports.UserBar = UserBar;
402
+ exports.UserBarButton = UserBarButton;