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
@@ -31,8 +31,11 @@ const { setStyles, optionOr, scrollParent } = require("../../modules/chui_functi
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,11 +47,13 @@ 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
58
  constructor(
54
59
  options = {
@@ -66,7 +71,7 @@ class StickyBar {
66
71
  this.#root.dataset.stuck = "false";
67
72
  this.#root.appendChild(this.#content);
68
73
  this.#target = options.target;
69
- this.#on_change = options.onChange;
74
+ this.#onChange = options.onChange;
70
75
  this.setOffset(options.offset === undefined ? 0 : options.offset);
71
76
  this.#observe();
72
77
  }
@@ -112,13 +117,13 @@ class StickyBar {
112
117
  /** Отступ закрепления: число пикселей или строка CSS (`"12px"`, `"var(--header_height)"`) */
113
118
  setOffset(offset = Number()) {
114
119
  this.#offset = typeof offset === "string" ? offset : `${Number(offset) || 0}px`;
115
- this.#root.style.setProperty("--sticky_offset", this.#offset);
120
+ this.#root.style.setProperty("--sticky_bar_offset", this.#offset);
116
121
  this.update();
117
122
  return this;
118
123
  }
119
124
 
120
125
  setOnChange(listener = undefined) {
121
- this.#on_change = listener;
126
+ this.#onChange = listener;
122
127
  return this;
123
128
  }
124
129
 
@@ -160,8 +165,9 @@ class StickyBar {
160
165
  // === ВНУТРЕННЕЕ ===
161
166
  /**
162
167
  * Первое подключение — на следующем кадре: узел обычно вставляют в документ сразу после
163
- * `set()`, и только тогда виден настоящий контейнер прокрутки. Повторные вызовы ничего не
164
- * делают, пока подключение не снято.
168
+ * `set()`, и только тогда виден настоящий контейнер прокрутки. Если к этому кадру узел ещё
169
+ * не в документе (страница собирается позже), ждём вставки — иначе контейнером стал бы документ.
170
+ * Повторные вызовы ничего не делают, пока подключение не снято.
165
171
  */
166
172
  #observe() {
167
173
  if (this.#destroyed === true) return;
@@ -174,6 +180,13 @@ class StickyBar {
174
180
 
175
181
  #attach() {
176
182
  if (this.#destroyed === true) return;
183
+ // Узел ещё не в документе: прокручиваемого предка у него нет, и поиск контейнера вернул бы
184
+ // документ — порог встал бы на нуле, и полоса никогда не «закрепилась» бы (без фона, рамки
185
+ // и тени). Ждём вставки наблюдателем, а не опросом по кадрам
186
+ if (this.#root.isConnected === false) {
187
+ this.#awaitInsert();
188
+ return;
189
+ }
177
190
  this.#container = this.getTarget();
178
191
  if (this.#container instanceof Element) {
179
192
  this.#container.addEventListener("scroll", this.#onScroll, { passive: true });
@@ -182,7 +195,25 @@ class StickyBar {
182
195
  this.#schedule();
183
196
  }
184
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
+
185
215
  #detach() {
216
+ this.#dropInsert();
186
217
  if (this.#container instanceof Element) {
187
218
  this.#container.removeEventListener("scroll", this.#onScroll);
188
219
  }
@@ -218,17 +249,17 @@ class StickyBar {
218
249
  if (!(container instanceof Element) || this.#root.isConnected === false) return;
219
250
  // Порог — от края содержимого контейнера: собственный отступ прокрутки браузер
220
251
  // учитывает сам, а рамку — нет, поэтому она прибавляется отдельно
221
- const container_style = window.getComputedStyle(container);
222
- const padding = Number.parseFloat(container_style.paddingTop) || 0;
223
- 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;
224
255
  const inset = Number.parseFloat(window.getComputedStyle(this.#root).top);
225
256
  const offset = Number.isFinite(inset) ? inset : 0;
226
- const stuck = this.#root.getBoundingClientRect().top <= reference_top + offset + 1;
257
+ const stuck = this.#root.getBoundingClientRect().top <= referenceTop + offset + 1;
227
258
  if (stuck === this.#stuck) return;
228
259
  this.#stuck = stuck;
229
260
  this.#root.dataset.stuck = String(stuck);
230
261
  this.#root.classList.toggle("sticky_bar_stuck", stuck);
231
- if (typeof this.#on_change === "function") this.#on_change(stuck);
262
+ if (typeof this.#onChange === "function") this.#onChange(stuck);
232
263
  }
233
264
  }
234
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;
@@ -25,15 +25,17 @@ sticky_bar_content {
25
25
  border: 1px solid transparent;
26
26
  border-radius: var(--radius_md);
27
27
  transition:
28
- background-color var(--transition_time) ease,
29
- box-shadow var(--transition_time) ease;
28
+ background-color var(--motion_fast) var(--motion_ease),
29
+ box-shadow var(--motion_fast) var(--motion_ease);
30
30
  }
31
31
 
32
- /* Закреплённая полоса читается отдельным слоем: материал как у шапки, волосяной край и тень.
33
- В покое она — обычная строка содержимого, ни фона, ни тени */
32
+ /* Закреплённая полоса читается отдельным слоем: волосяной край и тень, а заливка — цвет
33
+ подложки (`--bar_background`), как у шапки, меню маршрутов и ящика уведомлений: полоса
34
+ выходит из подложки, а не лежит на ней светлой плашкой. В покое она — обычная строка
35
+ содержимого, ни фона, ни тени */
34
36
  chui_sticky_bar[data-stuck="true"] sticky_bar_content {
35
37
  border-color: var(--border_color);
36
- background: var(--header_background);
38
+ background: var(--bar_background);
37
39
  backdrop-filter: var(--global_blur);
38
40
  box-shadow: var(--box_shadow_main);
39
41
  }
@@ -1,14 +1,14 @@
1
- let { Animation } = require("../../modules/chui_animations/animations");
1
+ const { Animation } = require("../../modules/chui_animations/animations");
2
2
  const { Icon, Icons } = require("../chui_icons/icons");
3
3
  const { makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
4
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
10
  #tab = document.createElement("tab");
11
- #tab_content = [];
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` закрывает её
@@ -72,14 +72,16 @@ class Tabs {
72
72
  #active = -1;
73
73
  // Вкладка, содержимое которой сейчас в панели: индекс меняется при закрытии соседей
74
74
  #rendered = undefined;
75
- #id_contents = require("randomstring").generate();
75
+ #idContents = require("randomstring").generate();
76
76
  #tabs = document.createElement("tabs");
77
- #id_list = require("randomstring").generate();
77
+ #idList = require("randomstring").generate();
78
78
  #list = document.createElement("list");
79
79
  #content = document.createElement("content");
80
80
  #status = document.createElement("tabs_status");
81
- #on_tab_close = undefined;
82
- #animationend_event = () => {
81
+ // Отложенный кадр пересчёта подложки выбранной вкладки
82
+ #indicatorFrame = undefined;
83
+ #onTabClose = undefined;
84
+ #animationendEvent = () => {
83
85
  // Следы анимации снимаются точечно: `removeAttribute("style")` снёс бы и чужие
84
86
  // инлайновые значения, если они появятся
85
87
  this.#content.style.removeProperty("animation-name");
@@ -90,9 +92,9 @@ class Tabs {
90
92
  options = { width: String(), default: Number(), tabs: [], closable: Boolean(), onTabClose: undefined }
91
93
  ) {
92
94
  require("../../modules/chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_Tabs");
93
- this.#on_tab_close = options.onTabClose;
94
- this.#list.id = this.#id_list;
95
- this.#content.id = this.#id_contents;
95
+ this.#onTabClose = options.onTabClose;
96
+ this.#list.id = this.#idList;
97
+ this.#content.id = this.#idContents;
96
98
  // Доступность: tablist/tab/tabpanel с roving tabindex
97
99
  this.#list.setAttribute("role", "tablist");
98
100
  this.#content.setAttribute("role", "tabpanel");
@@ -166,8 +168,10 @@ class Tabs {
166
168
  if (!Number.isInteger(at) || at < 0 || at >= this.#items.length) return this;
167
169
  const tab = this.#items[at];
168
170
  // Смотрим на вкладку, а не на её место: индекс активной мог сдвинуться при закрытии
169
- const closing_rendered = tab === this.#rendered;
170
- tab.getTab().remove();
171
+ const closingRendered = tab === this.#rendered;
172
+ // Уход идёт движением: узел убирается по концу кадра, а состав и индексы меняются
173
+ // сразу — иначе активная вкладка считалась бы по несуществующему узлу
174
+ this.#leaveNode(tab.getTab());
171
175
  this.#items.splice(at, 1);
172
176
  // Активная вкладка осталась собой, но её место сдвинулось
173
177
  if (at < this.#active) this.#active -= 1;
@@ -179,10 +183,10 @@ class Tabs {
179
183
  this.#announce(I18n.t("navigation.tabClosedNothing", { title: tab.getTitle() }));
180
184
  return this;
181
185
  }
182
- if (closing_rendered) this.#active = Math.min(at, this.#items.length - 1);
183
- this.#activate(this.#active, closing_rendered);
186
+ if (closingRendered) this.#active = Math.min(at, this.#items.length - 1);
187
+ this.#activate(this.#active, closingRendered);
184
188
  this.#announce(
185
- closing_rendered
189
+ closingRendered
186
190
  ? I18n.t("navigation.tabClosedOpened", {
187
191
  title: tab.getTitle(),
188
192
  active: this.#items[this.#active].getTitle(),
@@ -196,11 +200,11 @@ class Tabs {
196
200
  // так `Tab.setClosable()` работает и после того, как вкладка встала в панель
197
201
  #bindTab(tab = new Tab(), at = Number()) {
198
202
  const node = tab.getTab();
199
- 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);
200
204
  node.setAttribute("role", "tab");
201
205
  node.setAttribute("tabindex", "-1");
202
206
  node.setAttribute("aria-selected", "false");
203
- node.setAttribute("aria-controls", this.#id_contents);
207
+ node.setAttribute("aria-controls", this.#idContents);
204
208
  makeKeyboardButton(node, "tab");
205
209
  node.addEventListener("click", () => this.#activate(this.#items.indexOf(tab), false));
206
210
  const before = this.#list.children[at];
@@ -222,7 +226,7 @@ class Tabs {
222
226
  #requestClose(tab = new Tab()) {
223
227
  const at = this.#items.indexOf(tab);
224
228
  if (at === -1 || !tab.isClosable()) return false;
225
- 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;
226
230
  this.closeTab(at);
227
231
  return true;
228
232
  }
@@ -246,17 +250,61 @@ class Tabs {
246
250
  this.#renderContent(tab);
247
251
  this.#rendered = tab;
248
252
  }
253
+ // Подложка выбранной вкладки: место и ширину снимаем по вёрстке
254
+ this.#scheduleIndicator();
249
255
  if (focus) tab.getTab().focus();
250
256
  return true;
251
257
  }
252
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
+
253
301
  #renderContent(tab = new Tab()) {
254
302
  this.#content.innerHTML = "";
255
303
  for (const con of tab.getContent()) this.#content.appendChild(con.set());
256
304
  new Animation(this.#content).fadeIn();
257
305
  // Анимация ставит инлайн-стиль: снимаем его по завершении, иначе он копится
258
- this.#content.removeEventListener("animationend", this.#animationend_event);
259
- this.#content.addEventListener("animationend", this.#animationend_event);
306
+ this.#content.removeEventListener("animationend", this.#animationendEvent);
307
+ this.#content.addEventListener("animationend", this.#animationendEvent);
260
308
  }
261
309
 
262
310
  #onListKeyDown = (event) => {
@@ -280,15 +328,15 @@ class Tabs {
280
328
  };
281
329
 
282
330
  // Активная вкладка: aria-selected, tabindex и связь с панелью
283
- #applyAria(active_tab) {
331
+ #applyAria(activeTab) {
284
332
  for (const item of this.#items) {
285
333
  const tab = item.getTab();
286
- const selected = tab === active_tab;
334
+ const selected = tab === activeTab;
287
335
  tab.setAttribute("aria-selected", selected ? "true" : "false");
288
336
  tab.setAttribute("tabindex", selected ? "0" : "-1");
289
337
  }
290
- if (active_tab.id !== undefined && active_tab.id !== "") {
291
- this.#content.setAttribute("aria-labelledby", active_tab.id);
338
+ if (activeTab.id !== undefined && activeTab.id !== "") {
339
+ this.#content.setAttribute("aria-labelledby", activeTab.id);
292
340
  }
293
341
  }
294
342
 
@@ -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,7 +66,7 @@ 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;
@@ -55,23 +78,28 @@ content {
55
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
 
@@ -6,7 +6,7 @@ chui_text_editor_test {
6
6
  flex-direction: column;
7
7
  width: -webkit-fill-available;
8
8
  height: max-content;
9
- margin: var(--margin);
9
+ margin: var(--block_margin);
10
10
  }
11
11
 
12
12
  chui_text_editor {
@@ -26,7 +26,7 @@ chui_editor_controls {
26
26
  padding: 4px;
27
27
  border: 1px solid var(--border_color);
28
28
  border-bottom: none;
29
- border-radius: var(--border_radius) var(--border_radius) 0 0;
29
+ border-radius: var(--radius_sm) var(--radius_sm) 0 0;
30
30
  background: var(--element_background_2);
31
31
  }
32
32
 
@@ -36,7 +36,7 @@ chui_editor_text_input {
36
36
  width: -webkit-fill-available;
37
37
  padding: 8px 10px;
38
38
  border: 1px solid var(--border_color);
39
- border-radius: 0 0 var(--border_radius) var(--border_radius);
39
+ border-radius: 0 0 var(--radius_sm) var(--radius_sm);
40
40
  background: var(--element_background);
41
41
  color: var(--text_color);
42
42
  font-size: var(--font_default_size);
@@ -60,7 +60,7 @@ chui_editor_text_input div {
60
60
  .text_editor_label {
61
61
  height: max-content;
62
62
  width: max-content;
63
- margin: var(--margin);
63
+ margin: var(--block_margin);
64
64
  font-size: var(--font_labels_size);
65
65
  font-weight: 500;
66
66
  color: var(--text_color);
@@ -76,7 +76,7 @@ p img {
76
76
  width: auto;
77
77
  height: auto;
78
78
  border: 2px solid transparent;
79
- border-radius: var(--border_radius);
79
+ border-radius: var(--radius_sm);
80
80
  }
81
81
 
82
82
  /* Кнопки панели — как действия панели разметки у MarkdownEditor: сторона из токена
@@ -184,7 +184,7 @@ text_editor_selectbox_dropdown {
184
184
  display: none;
185
185
  height: max-content;
186
186
  width: max-content;
187
- border-radius: var(--border_radius);
187
+ border-radius: var(--radius_sm);
188
188
  margin-top: 0;
189
189
  padding: 6px;
190
190
  font-size: var(--font_default_size);
@@ -209,7 +209,7 @@ text_editor_select_button_open {
209
209
  text_editor_selectbox_option {
210
210
  text-align: start;
211
211
  padding: 6px 12px;
212
- border-radius: var(--border_radius);
212
+ border-radius: var(--radius_sm);
213
213
  }
214
214
 
215
215
  /* Заголовок пункта — абзац или заголовок (тег задаёт вызывающий): отступы снимает CSS.