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,4 +1,4 @@
1
- const {setStyles, optionOr, scrollParent} = require('../../modules/chui_functions');
1
+ const { setStyles, optionOr, scrollParent } = require("../../modules/chui_functions");
2
2
 
3
3
  /**
4
4
  * Прилипающая полоса: остаётся у верха области прокрутки, пока её содержимое не кончилось.
@@ -31,8 +31,11 @@ const {setStyles, optionOr, scrollParent} = require('../../modules/chui_function
31
31
  * пересекает границу ровно в момент закрепления и даёт неустойчивый сигнал на дробных пикселях.
32
32
  * Поэтому раз в кадр сравниваются верх полосы и верх области прокрутки; слушатель `scroll` —
33
33
  * пассивный, работа сведена к одному замеру на кадр через `requestAnimationFrame`.
34
- *
35
- * Доступность: компонент ничего не объявляет и не перехватывает — содержимое остаётся обычным
34
+ * * **Пока узел не в документе, контейнер не ищется.** Прокручиваемого предка у такого узла нет,
35
+ * и поиск вернул бы документ: порог встал бы на нуле, и полоса никогда не «закрепилась» бы —
36
+ * в разметке она осталась бы без фона, рамки и тени. Поэтому компонент ждёт появления узла
37
+ * в документе (наблюдателем, а не опросом по кадрам) и только потом подключается.
38
+ * * Доступность: компонент ничего не объявляет и не перехватывает — содержимое остаётся обычным
36
39
  * содержимым. `destroy()` снимает слушатели; повторный вызов безвреден.
37
40
  */
38
41
  class StickyBar {
@@ -44,15 +47,22 @@ class StickyBar {
44
47
  #offset = "0px";
45
48
  #target = undefined;
46
49
  #stuck = false;
47
- #on_change = undefined;
50
+ #onChange = undefined;
48
51
  #container = undefined;
49
52
  #destroyed = false;
50
53
  #frame = 0;
51
54
  #pending = 0;
55
+ // Наблюдатель за вставкой узла в документ: до неё области прокрутки ещё нет
56
+ #insert = undefined;
52
57
 
53
- constructor(options = {
54
- id: String(), offset: Number(), target: undefined, onChange: undefined
55
- }) {
58
+ constructor(
59
+ options = {
60
+ id: String(),
61
+ offset: Number(),
62
+ target: undefined,
63
+ onChange: undefined,
64
+ }
65
+ ) {
56
66
  setStyles(__dirname + "/styles.css", "chUiJS_StickyBar");
57
67
  StickyBar.#counter += 1;
58
68
  this.#id = optionOr(options.id, `chui_sticky_bar_${StickyBar.#counter}`);
@@ -61,7 +71,7 @@ class StickyBar {
61
71
  this.#root.dataset.stuck = "false";
62
72
  this.#root.appendChild(this.#content);
63
73
  this.#target = options.target;
64
- this.#on_change = options.onChange;
74
+ this.#onChange = options.onChange;
65
75
  this.setOffset(options.offset === undefined ? 0 : options.offset);
66
76
  this.#observe();
67
77
  }
@@ -107,13 +117,13 @@ class StickyBar {
107
117
  /** Отступ закрепления: число пикселей или строка CSS (`"12px"`, `"var(--header_height)"`) */
108
118
  setOffset(offset = Number()) {
109
119
  this.#offset = typeof offset === "string" ? offset : `${Number(offset) || 0}px`;
110
- this.#root.style.setProperty("--sticky_offset", this.#offset);
120
+ this.#root.style.setProperty("--sticky_bar_offset", this.#offset);
111
121
  this.update();
112
122
  return this;
113
123
  }
114
124
 
115
125
  setOnChange(listener = undefined) {
116
- this.#on_change = listener;
126
+ this.#onChange = listener;
117
127
  return this;
118
128
  }
119
129
 
@@ -155,8 +165,9 @@ class StickyBar {
155
165
  // === ВНУТРЕННЕЕ ===
156
166
  /**
157
167
  * Первое подключение — на следующем кадре: узел обычно вставляют в документ сразу после
158
- * `set()`, и только тогда виден настоящий контейнер прокрутки. Повторные вызовы ничего не
159
- * делают, пока подключение не снято.
168
+ * `set()`, и только тогда виден настоящий контейнер прокрутки. Если к этому кадру узел ещё
169
+ * не в документе (страница собирается позже), ждём вставки — иначе контейнером стал бы документ.
170
+ * Повторные вызовы ничего не делают, пока подключение не снято.
160
171
  */
161
172
  #observe() {
162
173
  if (this.#destroyed === true) return;
@@ -169,6 +180,13 @@ class StickyBar {
169
180
 
170
181
  #attach() {
171
182
  if (this.#destroyed === true) return;
183
+ // Узел ещё не в документе: прокручиваемого предка у него нет, и поиск контейнера вернул бы
184
+ // документ — порог встал бы на нуле, и полоса никогда не «закрепилась» бы (без фона, рамки
185
+ // и тени). Ждём вставки наблюдателем, а не опросом по кадрам
186
+ if (this.#root.isConnected === false) {
187
+ this.#awaitInsert();
188
+ return;
189
+ }
172
190
  this.#container = this.getTarget();
173
191
  if (this.#container instanceof Element) {
174
192
  this.#container.addEventListener("scroll", this.#onScroll, { passive: true });
@@ -177,7 +195,25 @@ class StickyBar {
177
195
  this.#schedule();
178
196
  }
179
197
 
198
+ /** Ждём появления узла в документе; наблюдатель снимается сразу после вставки или в `detach()` */
199
+ #awaitInsert() {
200
+ if (this.#insert !== undefined || this.#destroyed === true) return;
201
+ this.#insert = new MutationObserver(() => {
202
+ if (this.#root.isConnected === false) return;
203
+ this.#dropInsert();
204
+ this.#attach();
205
+ });
206
+ this.#insert.observe(document.body, { childList: true, subtree: true });
207
+ }
208
+
209
+ #dropInsert() {
210
+ if (this.#insert === undefined) return;
211
+ this.#insert.disconnect();
212
+ this.#insert = undefined;
213
+ }
214
+
180
215
  #detach() {
216
+ this.#dropInsert();
181
217
  if (this.#container instanceof Element) {
182
218
  this.#container.removeEventListener("scroll", this.#onScroll);
183
219
  }
@@ -213,17 +249,17 @@ class StickyBar {
213
249
  if (!(container instanceof Element) || this.#root.isConnected === false) return;
214
250
  // Порог — от края содержимого контейнера: собственный отступ прокрутки браузер
215
251
  // учитывает сам, а рамку — нет, поэтому она прибавляется отдельно
216
- const container_style = window.getComputedStyle(container);
217
- const padding = Number.parseFloat(container_style.paddingTop) || 0;
218
- const reference_top = container.getBoundingClientRect().top + container.clientTop + padding;
252
+ const containerStyle = window.getComputedStyle(container);
253
+ const padding = Number.parseFloat(containerStyle.paddingTop) || 0;
254
+ const referenceTop = container.getBoundingClientRect().top + container.clientTop + padding;
219
255
  const inset = Number.parseFloat(window.getComputedStyle(this.#root).top);
220
256
  const offset = Number.isFinite(inset) ? inset : 0;
221
- const stuck = this.#root.getBoundingClientRect().top <= reference_top + offset + 1;
257
+ const stuck = this.#root.getBoundingClientRect().top <= referenceTop + offset + 1;
222
258
  if (stuck === this.#stuck) return;
223
259
  this.#stuck = stuck;
224
260
  this.#root.dataset.stuck = String(stuck);
225
261
  this.#root.classList.toggle("sticky_bar_stuck", stuck);
226
- if (typeof this.#on_change === "function") this.#on_change(stuck);
262
+ if (typeof this.#onChange === "function") this.#onChange(stuck);
227
263
  }
228
264
  }
229
265
 
@@ -1,11 +1,11 @@
1
1
  /* Прилипающая полоса: корень сам липнет к верху области прокрутки, поэтому место в потоке
2
- сохраняется и подпорка-заглушка не нужна. Отступ задаёт компонент переменной `--sticky_offset`
2
+ сохраняется и подпорка-заглушка не нужна. Отступ задаёт компонент переменной `--sticky_bar_offset`
3
3
  (число или CSS-значение); отсчитывается он от края содержимого области прокрутки. */
4
4
  chui_sticky_bar {
5
- --sticky_offset: 0px;
5
+ --sticky_bar_offset: 0px;
6
6
  display: block;
7
7
  position: sticky;
8
- inset-block-start: var(--sticky_offset);
8
+ inset-block-start: var(--sticky_bar_offset);
9
9
  width: -webkit-fill-available;
10
10
  /* Ниже шапки (999) и выше содержимого страницы: полоса — слой над текстом, но под оболочкой */
11
11
  z-index: 6;
@@ -24,14 +24,18 @@ sticky_bar_content {
24
24
  padding: 8px 10px;
25
25
  border: 1px solid transparent;
26
26
  border-radius: var(--radius_md);
27
- transition: background-color var(--transition_time) ease, box-shadow var(--transition_time) ease;
27
+ transition:
28
+ background-color var(--motion_fast) var(--motion_ease),
29
+ box-shadow var(--motion_fast) var(--motion_ease);
28
30
  }
29
31
 
30
- /* Закреплённая полоса читается отдельным слоем: материал как у шапки, волосяной край и тень.
31
- В покое она — обычная строка содержимого, ни фона, ни тени */
32
+ /* Закреплённая полоса читается отдельным слоем: волосяной край и тень, а заливка — цвет
33
+ подложки (`--bar_background`), как у шапки, меню маршрутов и ящика уведомлений: полоса
34
+ выходит из подложки, а не лежит на ней светлой плашкой. В покое она — обычная строка
35
+ содержимого, ни фона, ни тени */
32
36
  chui_sticky_bar[data-stuck="true"] sticky_bar_content {
33
37
  border-color: var(--border_color);
34
- background: var(--header_background);
38
+ background: var(--bar_background);
35
39
  backdrop-filter: var(--global_blur);
36
40
  box-shadow: var(--box_shadow_main);
37
41
  }
@@ -1,14 +1,14 @@
1
- let {Animation} = require('../../modules/chui_animations/animations');
2
- const {Icon, Icons} = require('../chui_icons/icons');
3
- const {makeKeyboardButton} = require('../../modules/chui_a11y/chui_a11y');
4
- const {I18n} = require('../../modules/chui_i18n/chui_i18n');
1
+ const { Animation } = require("../../modules/chui_animations/animations");
2
+ const { Icon, Icons } = require("../chui_icons/icons");
3
+ const { makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
4
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
5
5
 
6
6
  /** Идентификаторы вкладок для aria-controls/aria-labelledby */
7
- let a11y_tab_counter = 0;
7
+ let a11yTabCounter = 0;
8
8
 
9
9
  class Tab {
10
- #tab = document.createElement('tab');
11
- #tab_content = [];
10
+ #tab = document.createElement("tab");
11
+ #tabContent = [];
12
12
  #title = String();
13
13
  #closable = false;
14
14
  #close = undefined;
@@ -19,7 +19,7 @@ class Tab {
19
19
  if (options.closable === true) this.setClosable(true);
20
20
  }
21
21
  addContent(...contents) {
22
- for (let content of contents) this.#tab_content.push(content);
22
+ for (const content of contents) this.#tabContent.push(content);
23
23
  }
24
24
  getTab() {
25
25
  return this.#tab;
@@ -28,7 +28,7 @@ class Tab {
28
28
  return this.#title;
29
29
  }
30
30
  getContent() {
31
- return this.#tab_content;
31
+ return this.#tabContent;
32
32
  }
33
33
  /**
34
34
  * Закрываемая вкладка: у неё появляется кнопка закрытия, а `Tabs` закрывает её
@@ -42,7 +42,7 @@ class Tab {
42
42
  this.#close = document.createElement("button");
43
43
  this.#close.type = "button";
44
44
  this.#close.className = "tab_close";
45
- this.#close.setAttribute("aria-label", I18n.t("navigation.closeTab", {title: this.#title}));
45
+ this.#close.setAttribute("aria-label", I18n.t("navigation.closeTab", { title: this.#title }));
46
46
  this.#close.setAttribute("title", I18n.t("common.close"));
47
47
  this.#close.appendChild(new Icon(Icons.NAVIGATION.CLOSE, "14px").set());
48
48
  this.#tab.appendChild(this.#close);
@@ -72,25 +72,29 @@ class Tabs {
72
72
  #active = -1;
73
73
  // Вкладка, содержимое которой сейчас в панели: индекс меняется при закрытии соседей
74
74
  #rendered = undefined;
75
- #id_contents = require("randomstring").generate();
76
- #tabs = document.createElement('tabs');
77
- #id_list = require("randomstring").generate();
78
- #list = document.createElement('list');
79
- #content = document.createElement('content');
80
- #status = document.createElement('tabs_status');
81
- #on_tab_close = undefined;
82
- #animationend_event = () => {
75
+ #idContents = require("randomstring").generate();
76
+ #tabs = document.createElement("tabs");
77
+ #idList = require("randomstring").generate();
78
+ #list = document.createElement("list");
79
+ #content = document.createElement("content");
80
+ #status = document.createElement("tabs_status");
81
+ // Отложенный кадр пересчёта подложки выбранной вкладки
82
+ #indicatorFrame = undefined;
83
+ #onTabClose = undefined;
84
+ #animationendEvent = () => {
83
85
  // Следы анимации снимаются точечно: `removeAttribute("style")` снёс бы и чужие
84
86
  // инлайновые значения, если они появятся
85
87
  this.#content.style.removeProperty("animation-name");
86
88
  this.#content.style.removeProperty("transition");
87
89
  this.#content.style.removeProperty("display");
88
90
  };
89
- constructor(options = { width: String(), default: Number(), tabs: [], closable: Boolean(), onTabClose: undefined }) {
90
- require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_Tabs');
91
- this.#on_tab_close = options.onTabClose;
92
- this.#list.id = this.#id_list;
93
- this.#content.id = this.#id_contents;
91
+ constructor(
92
+ options = { width: String(), default: Number(), tabs: [], closable: Boolean(), onTabClose: undefined }
93
+ ) {
94
+ require("../../modules/chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_Tabs");
95
+ this.#onTabClose = options.onTabClose;
96
+ this.#list.id = this.#idList;
97
+ this.#content.id = this.#idContents;
94
98
  // Доступность: tablist/tab/tabpanel с roving tabindex
95
99
  this.#list.setAttribute("role", "tablist");
96
100
  this.#content.setAttribute("role", "tabpanel");
@@ -143,10 +147,9 @@ class Tabs {
143
147
  * сразу становится активной.
144
148
  */
145
149
  addTab(tab = undefined, index = undefined) {
146
- if (tab === undefined || typeof tab.getTab !== "function") throw new Error("Tabs.addTab(): нужен экземпляр Tab");
147
- const at = index === undefined
148
- ? this.#items.length
149
- : Math.max(0, Math.min(Number(index), this.#items.length));
150
+ if (tab === undefined || typeof tab.getTab !== "function")
151
+ throw new Error("Tabs.addTab(): нужен экземпляр Tab");
152
+ const at = index === undefined ? this.#items.length : Math.max(0, Math.min(Number(index), this.#items.length));
150
153
  this.#items.splice(at, 0, tab);
151
154
  this.#bindTab(tab, at);
152
155
  // Новая вкладка встала не позже активной — активная сдвинулась на место вправо
@@ -165,8 +168,10 @@ class Tabs {
165
168
  if (!Number.isInteger(at) || at < 0 || at >= this.#items.length) return this;
166
169
  const tab = this.#items[at];
167
170
  // Смотрим на вкладку, а не на её место: индекс активной мог сдвинуться при закрытии
168
- const closing_rendered = tab === this.#rendered;
169
- tab.getTab().remove();
171
+ const closingRendered = tab === this.#rendered;
172
+ // Уход идёт движением: узел убирается по концу кадра, а состав и индексы меняются
173
+ // сразу — иначе активная вкладка считалась бы по несуществующему узлу
174
+ this.#leaveNode(tab.getTab());
170
175
  this.#items.splice(at, 1);
171
176
  // Активная вкладка осталась собой, но её место сдвинулось
172
177
  if (at < this.#active) this.#active -= 1;
@@ -175,14 +180,19 @@ class Tabs {
175
180
  this.#rendered = undefined;
176
181
  this.#content.innerHTML = "";
177
182
  this.#content.removeAttribute("aria-labelledby");
178
- this.#announce(I18n.t("navigation.tabClosedNothing", {title: tab.getTitle()}));
183
+ this.#announce(I18n.t("navigation.tabClosedNothing", { title: tab.getTitle() }));
179
184
  return this;
180
185
  }
181
- if (closing_rendered) this.#active = Math.min(at, this.#items.length - 1);
182
- this.#activate(this.#active, closing_rendered);
183
- this.#announce(closing_rendered
184
- ? I18n.t("navigation.tabClosedOpened", {title: tab.getTitle(), active: this.#items[this.#active].getTitle()})
185
- : I18n.t("navigation.tabClosed", {title: tab.getTitle()}));
186
+ if (closingRendered) this.#active = Math.min(at, this.#items.length - 1);
187
+ this.#activate(this.#active, closingRendered);
188
+ this.#announce(
189
+ closingRendered
190
+ ? I18n.t("navigation.tabClosedOpened", {
191
+ title: tab.getTitle(),
192
+ active: this.#items[this.#active].getTitle(),
193
+ })
194
+ : I18n.t("navigation.tabClosed", { title: tab.getTitle() })
195
+ );
186
196
  return this;
187
197
  }
188
198
 
@@ -190,11 +200,11 @@ class Tabs {
190
200
  // так `Tab.setClosable()` работает и после того, как вкладка встала в панель
191
201
  #bindTab(tab = new Tab(), at = Number()) {
192
202
  const node = tab.getTab();
193
- if (node.id === undefined || node.id === "") node.id = "chui_a11y_tab_" + (a11y_tab_counter += 1);
203
+ if (node.id === undefined || node.id === "") node.id = "chui_a11y_tab_" + (a11yTabCounter += 1);
194
204
  node.setAttribute("role", "tab");
195
205
  node.setAttribute("tabindex", "-1");
196
206
  node.setAttribute("aria-selected", "false");
197
- node.setAttribute("aria-controls", this.#id_contents);
207
+ node.setAttribute("aria-controls", this.#idContents);
198
208
  makeKeyboardButton(node, "tab");
199
209
  node.addEventListener("click", () => this.#activate(this.#items.indexOf(tab), false));
200
210
  const before = this.#list.children[at];
@@ -216,7 +226,7 @@ class Tabs {
216
226
  #requestClose(tab = new Tab()) {
217
227
  const at = this.#items.indexOf(tab);
218
228
  if (at === -1 || !tab.isClosable()) return false;
219
- if (this.#on_tab_close !== undefined && this.#on_tab_close(tab, at) === false) return false;
229
+ if (this.#onTabClose !== undefined && this.#onTabClose(tab, at) === false) return false;
220
230
  this.closeTab(at);
221
231
  return true;
222
232
  }
@@ -240,17 +250,61 @@ class Tabs {
240
250
  this.#renderContent(tab);
241
251
  this.#rendered = tab;
242
252
  }
253
+ // Подложка выбранной вкладки: место и ширину снимаем по вёрстке
254
+ this.#scheduleIndicator();
243
255
  if (focus) tab.getTab().focus();
244
256
  return true;
245
257
  }
246
258
 
259
+ /** Подложку двигают после раскладки: место вкладки читается только из вёрстки */
260
+ #scheduleIndicator() {
261
+ if (typeof requestAnimationFrame !== "function") {
262
+ this.#syncIndicator();
263
+ return;
264
+ }
265
+ if (this.#indicatorFrame !== undefined) return;
266
+ this.#indicatorFrame = requestAnimationFrame(() => {
267
+ this.#indicatorFrame = undefined;
268
+ this.#syncIndicator();
269
+ });
270
+ }
271
+
272
+ /** Уход узла: он гаснет и сходит вверх, а из документа убирается по концу кадра */
273
+ #leaveNode(node) {
274
+ node.classList.add("tab_leaving");
275
+ const done = (event) => {
276
+ if (event.target !== node) return;
277
+ node.removeEventListener("animationend", done);
278
+ node.remove();
279
+ };
280
+ node.addEventListener("animationend", done);
281
+ }
282
+
283
+ /**
284
+ * Место и ширина подложки выбранной вкладки. Ноль — выбора нет или вёрстки ещё нет:
285
+ * подложки нулевой ширины не видно, поэтому до первой раскладки вид прежний.
286
+ */
287
+ #syncIndicator() {
288
+ const item = this.#items[this.#active];
289
+ const tab = item === undefined ? undefined : item.getTab();
290
+ if (tab === undefined || tab.offsetWidth === 0) {
291
+ this.#list.style.setProperty("--tabs_width", "0px");
292
+ return;
293
+ }
294
+ // В RTL началом ряда служит правый край: подложка стоит у него и едет влево
295
+ const rtl = window.getComputedStyle(this.#list).direction === "rtl";
296
+ const x = rtl ? this.#list.clientWidth - (tab.offsetLeft + tab.offsetWidth) : tab.offsetLeft;
297
+ this.#list.style.setProperty("--tabs_offset", `${rtl ? -x : x}px`);
298
+ this.#list.style.setProperty("--tabs_width", `${tab.offsetWidth}px`);
299
+ }
300
+
247
301
  #renderContent(tab = new Tab()) {
248
302
  this.#content.innerHTML = "";
249
303
  for (const con of tab.getContent()) this.#content.appendChild(con.set());
250
304
  new Animation(this.#content).fadeIn();
251
305
  // Анимация ставит инлайн-стиль: снимаем его по завершении, иначе он копится
252
- this.#content.removeEventListener("animationend", this.#animationend_event);
253
- this.#content.addEventListener("animationend", this.#animationend_event);
306
+ this.#content.removeEventListener("animationend", this.#animationendEvent);
307
+ this.#content.addEventListener("animationend", this.#animationendEvent);
254
308
  }
255
309
 
256
310
  #onListKeyDown = (event) => {
@@ -274,15 +328,15 @@ class Tabs {
274
328
  };
275
329
 
276
330
  // Активная вкладка: aria-selected, tabindex и связь с панелью
277
- #applyAria(active_tab) {
331
+ #applyAria(activeTab) {
278
332
  for (const item of this.#items) {
279
333
  const tab = item.getTab();
280
- const selected = tab === active_tab;
334
+ const selected = tab === activeTab;
281
335
  tab.setAttribute("aria-selected", selected ? "true" : "false");
282
336
  tab.setAttribute("tabindex", selected ? "0" : "-1");
283
337
  }
284
- if (active_tab.id !== undefined && active_tab.id !== "") {
285
- this.#content.setAttribute("aria-labelledby", active_tab.id);
338
+ if (activeTab.id !== undefined && activeTab.id !== "") {
339
+ this.#content.setAttribute("aria-labelledby", activeTab.id);
286
340
  }
287
341
  }
288
342
 
@@ -295,5 +349,5 @@ class Tabs {
295
349
  }
296
350
  }
297
351
 
298
- exports.Tab = Tab
299
- exports.Tabs = Tabs
352
+ exports.Tab = Tab;
353
+ exports.Tabs = Tabs;
@@ -9,16 +9,38 @@ list {
9
9
  вокруг которого вкладки плавали без границ */
10
10
  padding: 3px;
11
11
  border: 1px solid var(--border_color);
12
- border-radius: var(--border_radius);
12
+ border-radius: var(--radius_sm);
13
13
  background: var(--element_background);
14
- margin: var(--margin) 0;
14
+ margin: var(--block_margin) 0;
15
+ /* Место и ширина подложки выбранной вкладки: считает JS по вёрстке, нули — выбора нет */
16
+ --tabs_offset: 0px;
17
+ --tabs_width: 0px;
18
+ }
19
+
20
+ /* Подложка выбранной вкладки: одна и та же деталь переезжает между вкладками, вместо
21
+ мгновенной смены заливки — как у SegmentedControl. Отступ по вертикали равен отступу
22
+ дорожки, поэтому подложка встаёт в ряд вкладок */
23
+ list::before {
24
+ content: "";
25
+ position: absolute;
26
+ z-index: 0;
27
+ inset-block: 3px;
28
+ inset-inline-start: 0;
29
+ width: var(--tabs_width);
30
+ border-radius: var(--radius_sm);
31
+ background: var(--blue_prime_background);
32
+ transform: translateX(var(--tabs_offset));
33
+ transition:
34
+ transform var(--motion_base) var(--motion_ease),
35
+ width var(--motion_base) var(--motion_ease);
36
+ pointer-events: none;
15
37
  }
16
38
 
17
39
  tab {
18
40
  cursor: pointer;
19
41
  /* Отступ даёт только `gap` дорожки: свой `margin` добавлял к нему ещё по 1 px
20
42
  и дорожка вырастала на 2 px без причины */
21
- padding: var(--main_padding_sm);
43
+ padding: var(--block_padding_sm);
22
44
  min-height: var(--control_height);
23
45
  box-sizing: border-box;
24
46
  font-size: var(--font_default_size);
@@ -28,12 +50,13 @@ tab {
28
50
  gap: 4px;
29
51
  font-weight: 500;
30
52
  color: var(--text_color);
31
- border-radius: var(--border_radius);
53
+ border-radius: var(--radius_sm);
32
54
  }
33
55
 
34
56
  /* Наведение — нейтральная тонировка, а не заливка во весь акцент: полная заливка
35
- срабатывала на каждое наведение и перетягивала внимание (то же решение, что у Button) */
36
- tab:hover {
57
+ срабатывала на каждое наведение и перетягивала внимание (то же решение, что у Button).
58
+ У выбранной вкладки заливку несёт подложка, поэтому она наведение не ловит */
59
+ tab:not(.tab_active):hover {
37
60
  background: var(--element_background_hover);
38
61
  }
39
62
 
@@ -43,35 +66,40 @@ tab:active {
43
66
  }
44
67
 
45
68
  tabs {
46
- margin: var(--margin);
69
+ margin: var(--block_margin);
47
70
  display: flex;
48
71
  flex-direction: column;
49
72
  justify-content: flex-start;
50
- align-items: flex-start
73
+ align-items: flex-start;
51
74
  }
52
75
 
53
76
  content {
54
77
  display: flex;
55
- flex-direction:column;
78
+ flex-direction: column;
56
79
  width: -webkit-fill-available;
57
80
  padding: var(--control_padding);
58
- border-radius: var(--border_radius);
81
+ border-radius: var(--radius_sm);
59
82
  background: transparent;
60
83
  border: none;
61
84
  }
62
85
 
63
86
  .tab_active {
64
- /* Выбранная вкладка — акцентная заливка и белый текст, как у выбранного сегмента
87
+ /* Выбранная вкладка — белый текст на подложке-акценте, как у выбранного сегмента
65
88
  SegmentedControl и у нажатой Button. Прежняя «тонировка + акцентный текст» давала
66
89
  синий текст на синей тонировке (контраст ≈2.5:1) и читалась грязным пятном */
67
- background: var(--blue_prime_background);
90
+ background: transparent;
68
91
  color: var(--text_color_hover);
69
92
  font-weight: 600;
70
93
  }
71
94
 
72
- /* Наведение на выбранной вкладке ничего не меняет: отклик уже в самой заливке */
95
+ /* Уход закрываемой вкладки: она гаснет и сходит вверх, а из дорожки убирается по концу кадра */
96
+ .tab_leaving {
97
+ animation: chui_leave_up var(--motion_fast) var(--motion_ease) forwards;
98
+ }
99
+
100
+ /* Наведение на выбранной вкладке ничего не меняет: отклик уже в самой подложке */
73
101
  .tab_active:hover {
74
- background: var(--blue_prime_background);
102
+ background: transparent;
75
103
  color: var(--text_color_hover);
76
104
  }
77
105
 
@@ -96,7 +124,7 @@ tab:has(.tab_close) {
96
124
  /* Цвет наследуется от вкладки: крестик не отваливается от подписи, а наглухо
97
125
  серый читался в ней случайной точкой */
98
126
  color: inherit;
99
- opacity: .6;
127
+ opacity: 0.6;
100
128
  cursor: pointer;
101
129
  }
102
130