chuijs 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/CHANGELOG.md +597 -0
  2. package/README.md +43 -644
  3. package/framework/appLayout/default/chui_app_layout.js +602 -496
  4. package/framework/appLayout/default/global_style.css +288 -97
  5. package/framework/appLayout/default/main_theme_style.css +188 -101
  6. package/framework/components/chui_accordion/accordion.js +38 -35
  7. package/framework/components/chui_accordion/styles.css +13 -12
  8. package/framework/components/chui_action_sheet/action_sheet.js +93 -64
  9. package/framework/components/chui_action_sheet/styles.css +12 -5
  10. package/framework/components/chui_actions_bar/actions_bar.js +338 -0
  11. package/framework/components/chui_actions_bar/styles.css +139 -0
  12. package/framework/components/chui_affix/affix.js +16 -11
  13. package/framework/components/chui_affix/styles.css +5 -0
  14. package/framework/components/chui_alert/alert.js +34 -19
  15. package/framework/components/chui_alert/styles.css +7 -0
  16. package/framework/components/chui_avatar/avatar.js +36 -35
  17. package/framework/components/chui_avatar/styles.css +1 -1
  18. package/framework/components/chui_avatar_group/avatar_group.js +25 -23
  19. package/framework/components/chui_avatar_group/styles.css +1 -1
  20. package/framework/components/chui_backtop/backtop.js +60 -21
  21. package/framework/components/chui_backtop/styles.css +21 -0
  22. package/framework/components/chui_badge/badge.js +30 -28
  23. package/framework/components/chui_badge/styles.css +6 -6
  24. package/framework/components/chui_banner/banner.js +222 -0
  25. package/framework/components/chui_banner/styles.css +170 -0
  26. package/framework/components/chui_breadcrumb/breadcrumb.js +15 -13
  27. package/framework/components/chui_breadcrumb/styles.css +1 -1
  28. package/framework/components/chui_button/button.js +52 -36
  29. package/framework/components/chui_button/styles.css +7 -7
  30. package/framework/components/chui_calendar/calendar.js +18 -16
  31. package/framework/components/chui_card/card.js +52 -32
  32. package/framework/components/chui_card_scroll/card_scroll.js +169 -0
  33. package/framework/components/chui_card_scroll/styles.css +86 -0
  34. package/framework/components/chui_chat/chat.js +140 -79
  35. package/framework/components/chui_chat/styles.css +18 -7
  36. package/framework/components/chui_clipboard_history/clipboard_history.js +53 -39
  37. package/framework/components/chui_clipboard_history/styles.css +13 -9
  38. package/framework/components/chui_code/code.js +10 -10
  39. package/framework/components/chui_code/styles.css +4 -4
  40. package/framework/components/chui_code_editor/code_editor.js +73 -57
  41. package/framework/components/chui_code_editor/styles.css +6 -5
  42. package/framework/components/chui_command_palette/command_palette.js +87 -64
  43. package/framework/components/chui_command_palette/styles.css +5 -5
  44. package/framework/components/chui_console/console.js +13 -13
  45. package/framework/components/chui_console/styles.css +6 -3
  46. package/framework/components/chui_content_block/content_block.js +35 -21
  47. package/framework/components/chui_content_block/styles.css +2 -2
  48. package/framework/components/chui_context_menu/context_menu.js +171 -58
  49. package/framework/components/chui_context_menu/styles.css +82 -8
  50. package/framework/components/chui_custom_element/custom_element.js +25 -18
  51. package/framework/components/chui_data_grid/data_grid.js +633 -471
  52. package/framework/components/chui_data_grid/styles.css +24 -22
  53. package/framework/components/chui_descriptions/descriptions.js +34 -17
  54. package/framework/components/chui_descriptions/styles.css +1 -1
  55. package/framework/components/chui_details/details.js +42 -36
  56. package/framework/components/chui_details/styles.css +12 -11
  57. package/framework/components/chui_diff_viewer/diff_viewer.js +172 -147
  58. package/framework/components/chui_diff_viewer/styles.css +10 -10
  59. package/framework/components/chui_divider/divider.js +19 -17
  60. package/framework/components/chui_divider/styles.css +2 -2
  61. package/framework/components/chui_drawer/drawer.js +63 -38
  62. package/framework/components/chui_drawer/styles.css +18 -4
  63. package/framework/components/chui_drop_zone/drop_zone.js +37 -27
  64. package/framework/components/chui_drop_zone/styles.css +10 -6
  65. package/framework/components/chui_empty_state/empty_state.js +20 -15
  66. package/framework/components/chui_fieldset/fieldset.js +40 -23
  67. package/framework/components/chui_fieldset/styles.css +6 -6
  68. package/framework/components/chui_file_explorer/file_explorer.js +260 -189
  69. package/framework/components/chui_file_explorer/styles.css +8 -7
  70. package/framework/components/chui_form/form.js +40 -28
  71. package/framework/components/chui_form/styles.css +1 -1
  72. package/framework/components/chui_gantt/gantt.js +102 -78
  73. package/framework/components/chui_gantt/styles.css +5 -5
  74. package/framework/components/chui_graphs/graphs.js +376 -239
  75. package/framework/components/chui_graphs/styles.css +27 -6
  76. package/framework/components/chui_hero/hero.js +98 -37
  77. package/framework/components/chui_hero/styles.css +176 -7
  78. package/framework/components/chui_html_block/html_block.js +8 -8
  79. package/framework/components/chui_html_block/styles.css +3 -3
  80. package/framework/components/chui_hx/hx.js +3 -3
  81. package/framework/components/chui_hx/styles.css +13 -6
  82. package/framework/components/chui_icons/icons.js +2115 -2115
  83. package/framework/components/chui_icons/styles.css +4 -3
  84. package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
  85. package/framework/components/chui_inputs/chui_check_box/styles.css +30 -21
  86. package/framework/components/chui_inputs/chui_chips/chips.js +141 -70
  87. package/framework/components/chui_inputs/chui_chips/styles.css +32 -7
  88. package/framework/components/chui_inputs/chui_color_picker/color_picker.js +55 -38
  89. package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
  90. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +127 -109
  91. package/framework/components/chui_inputs/chui_combo_box/styles.css +23 -21
  92. package/framework/components/chui_inputs/chui_date/date.js +161 -127
  93. package/framework/components/chui_inputs/chui_date/styles.css +20 -19
  94. package/framework/components/chui_inputs/chui_date_range/date_range.js +61 -50
  95. package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
  96. package/framework/components/chui_inputs/chui_datetime/datetime.js +47 -39
  97. package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
  98. package/framework/components/chui_inputs/chui_email/email.js +48 -36
  99. package/framework/components/chui_inputs/chui_email/styles.css +11 -11
  100. package/framework/components/chui_inputs/chui_file/file.js +71 -44
  101. package/framework/components/chui_inputs/chui_file/styles.css +23 -19
  102. package/framework/components/chui_inputs/chui_hotkey/hotkey.js +101 -76
  103. package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
  104. package/framework/components/chui_inputs/chui_masked/masked.js +83 -72
  105. package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
  106. package/framework/components/chui_inputs/chui_mention/mention.js +87 -40
  107. package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
  108. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +242 -212
  109. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +23 -15
  110. package/framework/components/chui_inputs/chui_number/number.js +84 -70
  111. package/framework/components/chui_inputs/chui_number/styles.css +13 -13
  112. package/framework/components/chui_inputs/chui_otp/otp.js +71 -62
  113. package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
  114. package/framework/components/chui_inputs/chui_password/password.js +72 -56
  115. package/framework/components/chui_inputs/chui_password/styles.css +15 -15
  116. package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
  117. package/framework/components/chui_inputs/chui_radio_button/styles.css +22 -22
  118. package/framework/components/chui_inputs/chui_rating/rating.js +54 -50
  119. package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
  120. package/framework/components/chui_inputs/chui_select_box/select_box.js +132 -108
  121. package/framework/components/chui_inputs/chui_select_box/styles.css +21 -21
  122. package/framework/components/chui_inputs/chui_slider/slider.js +41 -38
  123. package/framework/components/chui_inputs/chui_slider/styles.css +7 -5
  124. package/framework/components/chui_inputs/chui_text/styles.css +14 -14
  125. package/framework/components/chui_inputs/chui_text/text.js +69 -52
  126. package/framework/components/chui_inputs/chui_text_area/styles.css +14 -14
  127. package/framework/components/chui_inputs/chui_text_area/text_area.js +59 -36
  128. package/framework/components/chui_inputs/chui_time/styles.css +18 -20
  129. package/framework/components/chui_inputs/chui_time/time.js +71 -49
  130. package/framework/components/chui_inputs/chui_toggle/styles.css +13 -6
  131. package/framework/components/chui_inputs/chui_toggle/toggle.js +27 -27
  132. package/framework/components/chui_json_editor/json_editor.js +137 -127
  133. package/framework/components/chui_json_editor/styles.css +5 -5
  134. package/framework/components/chui_json_viewer/json_viewer.js +133 -114
  135. package/framework/components/chui_json_viewer/styles.css +9 -8
  136. package/framework/components/chui_kanban/kanban.js +78 -60
  137. package/framework/components/chui_kanban/styles.css +8 -4
  138. package/framework/components/chui_kbd/kbd.js +12 -10
  139. package/framework/components/chui_kbd/styles.css +1 -1
  140. package/framework/components/chui_label/label.js +31 -29
  141. package/framework/components/chui_label/styles.css +3 -3
  142. package/framework/components/chui_link/link.js +36 -29
  143. package/framework/components/chui_link/styles.css +11 -4
  144. package/framework/components/chui_list/list.js +42 -38
  145. package/framework/components/chui_list/styles.css +4 -3
  146. package/framework/components/chui_markdown_editor/markdown_editor.js +125 -109
  147. package/framework/components/chui_markdown_editor/styles.css +5 -5
  148. package/framework/components/chui_media/audio.js +457 -364
  149. package/framework/components/chui_media/audio_styles.css +74 -37
  150. package/framework/components/chui_media/image.js +99 -64
  151. package/framework/components/chui_media/image_compare.js +86 -55
  152. package/framework/components/chui_media/image_compare_styles.css +5 -5
  153. package/framework/components/chui_media/image_styles.css +4 -4
  154. package/framework/components/chui_media/video.js +264 -171
  155. package/framework/components/chui_media/video_styles.css +57 -24
  156. package/framework/components/chui_menu_bar/menu_bar.js +67 -65
  157. package/framework/components/chui_menu_bar/styles.css +14 -12
  158. package/framework/components/chui_message_bar/message_bar.js +551 -0
  159. package/framework/components/chui_message_bar/styles.css +248 -0
  160. package/framework/components/chui_modal/modal.js +81 -56
  161. package/framework/components/chui_modal/styles.css +3 -2
  162. package/framework/components/chui_notification/notification.js +107 -69
  163. package/framework/components/chui_notification/notification_download.js +28 -77
  164. package/framework/components/chui_notification/notification_download_card.js +115 -0
  165. package/framework/components/chui_notification/notification_download_progress.css +5 -4
  166. package/framework/components/chui_notification/notification_download_progress.js +73 -105
  167. package/framework/components/chui_notification/notification_update.js +44 -37
  168. package/framework/components/chui_notification/status_plate.css +3 -3
  169. package/framework/components/chui_notification/status_plate.js +2 -2
  170. package/framework/components/chui_notification/styles.css +6 -1
  171. package/framework/components/chui_notification/styles_download.css +2 -2
  172. package/framework/components/chui_notification/styles_update.css +3 -3
  173. package/framework/components/chui_org_chart/org_chart.js +101 -62
  174. package/framework/components/chui_org_chart/styles.css +2 -2
  175. package/framework/components/chui_pagination/pagination.js +65 -30
  176. package/framework/components/chui_pagination/styles.css +1 -1
  177. package/framework/components/chui_paragraph/paragraph.js +2 -2
  178. package/framework/components/chui_paragraph/styles.css +3 -3
  179. package/framework/components/chui_popover/popover.js +77 -50
  180. package/framework/components/chui_popover/styles.css +1 -1
  181. package/framework/components/chui_popups/popups.js +210 -181
  182. package/framework/components/chui_popups/styles.css +7 -7
  183. package/framework/components/chui_print_button/print_button.js +48 -33
  184. package/framework/components/chui_progress_bar/progress_bar.js +49 -19
  185. package/framework/components/chui_progress_bar/styles.css +6 -6
  186. package/framework/components/chui_progress_ring/progress_ring.js +32 -30
  187. package/framework/components/chui_progress_ring/styles.css +1 -1
  188. package/framework/components/chui_qrcode/qrcode.js +85 -41
  189. package/framework/components/chui_query_builder/query_builder.js +263 -170
  190. package/framework/components/chui_query_builder/styles.css +4 -4
  191. package/framework/components/chui_radio_group/radio_group.js +36 -26
  192. package/framework/components/chui_radio_group/styles.css +2 -2
  193. package/framework/components/chui_result/result.js +41 -31
  194. package/framework/components/chui_result/styles.css +3 -3
  195. package/framework/components/chui_scheduler/scheduler.js +126 -104
  196. package/framework/components/chui_scheduler/styles.css +11 -7
  197. package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
  198. package/framework/components/chui_screen_spinner/styles.css +49 -0
  199. package/framework/components/chui_segmented_control/segmented_control.js +90 -27
  200. package/framework/components/chui_segmented_control/styles.css +32 -6
  201. package/framework/components/chui_settings/settings.js +160 -87
  202. package/framework/components/chui_settings/styles.css +4 -4
  203. package/framework/components/chui_shortcut_help/shortcut_help.js +82 -61
  204. package/framework/components/chui_sidebar_nav/sidebar_nav.js +132 -53
  205. package/framework/components/chui_sidebar_nav/styles.css +9 -3
  206. package/framework/components/chui_skeleton/skeleton.js +29 -24
  207. package/framework/components/chui_slideshow/slideshow.js +122 -103
  208. package/framework/components/chui_slideshow/styles.css +22 -16
  209. package/framework/components/chui_spinner/spinner.js +17 -17
  210. package/framework/components/chui_spinner/styles.css +0 -1
  211. package/framework/components/chui_split_button/split_button.js +53 -30
  212. package/framework/components/chui_split_button/styles.css +22 -6
  213. package/framework/components/chui_splitter/splitter.js +46 -39
  214. package/framework/components/chui_splitter/styles.css +2 -2
  215. package/framework/components/chui_statistic/statistic.js +111 -84
  216. package/framework/components/chui_status_bar/status_bar.js +11 -9
  217. package/framework/components/chui_status_bar/styles.css +2 -2
  218. package/framework/components/chui_stepper/stepper.js +230 -54
  219. package/framework/components/chui_stepper/styles.css +79 -14
  220. package/framework/components/chui_sticky_bar/sticky_bar.js +53 -17
  221. package/framework/components/chui_sticky_bar/styles.css +11 -7
  222. package/framework/components/chui_tabs/chui_tabs.js +100 -46
  223. package/framework/components/chui_tabs/styles.css +43 -15
  224. package/framework/components/chui_text_editor/styles.css +24 -23
  225. package/framework/components/chui_text_editor/text_editor.js +103 -79
  226. package/framework/components/chui_text_editor/text_editor_panel.js +428 -367
  227. package/framework/components/chui_theme_toggle/theme_toggle.js +46 -25
  228. package/framework/components/chui_timeline/styles.css +3 -1
  229. package/framework/components/chui_timeline/timeline.js +14 -12
  230. package/framework/components/chui_toast_queue/styles.css +11 -11
  231. package/framework/components/chui_toast_queue/toast_queue.js +82 -73
  232. package/framework/components/chui_toolbar/styles.css +2 -2
  233. package/framework/components/chui_toolbar/toolbar.js +20 -13
  234. package/framework/components/chui_tooltip/styles.css +2 -2
  235. package/framework/components/chui_tooltip/tooltip.js +40 -27
  236. package/framework/components/chui_tour/styles.css +12 -7
  237. package/framework/components/chui_tour/tour.js +132 -98
  238. package/framework/components/chui_transfer/styles.css +8 -7
  239. package/framework/components/chui_transfer/transfer.js +143 -123
  240. package/framework/components/chui_tree_table/styles.css +4 -4
  241. package/framework/components/chui_tree_table/tree_table.js +88 -74
  242. package/framework/components/chui_tree_view/styles.css +14 -10
  243. package/framework/components/chui_tree_view/tree_view.js +298 -253
  244. package/framework/components/chui_user_bar/styles.css +145 -0
  245. package/framework/components/chui_user_bar/user_bar.js +402 -0
  246. package/framework/components/chui_user_list_bar/styles.css +173 -0
  247. package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
  248. package/framework/components/chui_watermark/watermark.js +41 -33
  249. package/framework/components/chui_webview/styles.css +2 -2
  250. package/framework/components/chui_webview/webview.js +64 -52
  251. package/framework/components/chui_write_bar/styles.css +172 -0
  252. package/framework/components/chui_write_bar/write_bar.js +320 -0
  253. package/framework/main_window.js +1234 -0
  254. package/framework/modules/chui_a11y/chui_a11y.js +153 -123
  255. package/framework/modules/chui_animations/animations.js +105 -43
  256. package/framework/modules/chui_animations/motion.js +120 -0
  257. package/framework/modules/chui_animations/styles.css +37 -4
  258. package/framework/modules/chui_bridge/preload.js +36 -25
  259. package/framework/modules/chui_functions.js +69 -63
  260. package/framework/modules/chui_highlight/chui_highlight.js +25 -22
  261. package/framework/modules/chui_highlight/styles.css +13 -10
  262. package/framework/modules/chui_i18n/chui_i18n.js +12 -10
  263. package/framework/modules/chui_i18n/locales/a11y.js +5 -5
  264. package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
  265. package/framework/modules/chui_i18n/locales/calendar.js +3 -3
  266. package/framework/modules/chui_i18n/locales/common.js +3 -3
  267. package/framework/modules/chui_i18n/locales/data.js +36 -26
  268. package/framework/modules/chui_i18n/locales/editors.js +40 -13
  269. package/framework/modules/chui_i18n/locales/feedback.js +65 -3
  270. package/framework/modules/chui_i18n/locales/forms.js +8 -8
  271. package/framework/modules/chui_i18n/locales/graphics.js +8 -8
  272. package/framework/modules/chui_i18n/locales/media.js +4 -4
  273. package/framework/modules/chui_i18n/locales/navigation.js +12 -8
  274. package/framework/modules/chui_i18n/locales/validate.js +3 -3
  275. package/framework/modules/chui_json/chui_json.js +11 -11
  276. package/framework/modules/chui_layout/chui_layout.js +12 -10
  277. package/framework/modules/chui_logger/chui_logger.js +47 -28
  278. package/framework/modules/chui_page/page.js +39 -33
  279. package/framework/modules/chui_palette/palette.js +61 -0
  280. package/framework/modules/chui_qr/chui_qr.js +179 -88
  281. package/framework/modules/chui_scrollbar/chui_scrollbar.js +79 -68
  282. package/framework/modules/chui_scrollbar/styles.css +4 -2
  283. package/framework/modules/chui_services/chui_channels.js +27 -13
  284. package/framework/modules/chui_services/chui_services.js +47 -39
  285. package/framework/modules/chui_services/desktop.js +108 -101
  286. package/framework/modules/chui_sortable/chui_sortable.js +33 -28
  287. package/framework/modules/chui_validate/chui_validate.js +10 -7
  288. package/framework/tray_bar/chui_menu_items.js +136 -46
  289. package/index.d.ts +5046 -0
  290. package/index.js +199 -1254
  291. package/package.json +81 -68
  292. package/framework/components/telegram_bot/chui_telegram_bot.js +0 -184
@@ -0,0 +1,167 @@
1
+ const { Spinner } = require("../chui_spinner/spinner");
2
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
3
+ const { setStyles, optionOr } = require("../../modules/chui_functions");
4
+ const { Animation } = require("../../modules/chui_animations/animations");
5
+ const { OverlayA11y } = require("../../modules/chui_a11y/chui_a11y");
6
+ const { tokenTime } = require("../../modules/chui_animations/motion");
7
+
8
+ /**
9
+ * Экран занят: слой поверх всего окна с индикатором загрузки. Пока он открыт, до содержимого
10
+ * под ним не добраться — ни мышью, ни клавиатурой, ни скринридером.
11
+ *
12
+ * От `Spinner` отличается тем, что тот просто рисует индикатор в потоке, а этот занимает экран:
13
+ * слой встаёт в `body` (в карточке с `backdrop-filter` Chromium собирает такой узел отдельной
14
+ * поверхностью и рисует её под слоями из `body` — так уже теряли диалог), фокус удерживается
15
+ * внутри, а фон становится инертным. Роль слоя — `dialog` с именем загрузки, внутри объявляется
16
+ * `progressbar`; Escape слой не закрывает: это не сообщение, а признак занятости.
17
+ *
18
+ * Опции: `id`, `label` (подпись и имя для скринридера, по умолчанию «{loading}» из словаря),
19
+ * `delay` (мс до появления — против мигания на коротких операциях).
20
+ *
21
+ * Методы: `show()`, `hide()`, `isShown()`, `setLabel(text)`, `destroy()`, `set()`.
22
+ * Статические: `ScreenSpinner.show(options)` (создать и показать), `ScreenSpinner.hide()`,
23
+ * `ScreenSpinner.current`.
24
+ *
25
+ * ```javascript
26
+ * const busy = new ScreenSpinner({ delay: 200 });
27
+ * busy.show();
28
+ * await load();
29
+ * busy.hide();
30
+ * ```
31
+ */
32
+ class ScreenSpinner {
33
+ // На экране один такой слой: два спиннера наложились бы друг на друга
34
+ static #current = undefined;
35
+
36
+ #root = document.createElement("chui_screen_spinner");
37
+ #card = document.createElement("screen_spinner_card");
38
+ #labelNode = document.createElement("screen_spinner_label");
39
+ #spinner = undefined;
40
+ #a11y = undefined;
41
+ #delay = 0;
42
+ #timer = undefined;
43
+ #shown = false;
44
+ #removed = false;
45
+
46
+ constructor(options = {}) {
47
+ setStyles(__dirname + "/styles.css", "chUiJS_ScreenSpinner");
48
+ if (options.id !== undefined) this.#root.id = options.id;
49
+ const delay = Number(options.delay);
50
+ this.#delay = Number.isFinite(delay) && delay > 0 ? delay : 0;
51
+ this.#spinner = new Spinner();
52
+ this.#card.append(this.#spinner.set(), this.#labelNode);
53
+ this.#root.appendChild(this.#card);
54
+ this.#root.setAttribute("aria-busy", "true");
55
+ this.setLabel(optionOr(options.label, I18n.t("feedback.loading")));
56
+ this.#a11y = new OverlayA11y(this.#root, {
57
+ role: "dialog",
58
+ label: this.#labelNode.innerText,
59
+ // Escape здесь ничего не закрывает: занятость — не сообщение, которое можно отклонить
60
+ onEscape: () => {},
61
+ });
62
+ }
63
+
64
+ isShown() {
65
+ return this.#shown;
66
+ }
67
+ getLabel() {
68
+ return this.#labelNode.innerText;
69
+ }
70
+
71
+ setLabel(text = String()) {
72
+ const value = String(text);
73
+ this.#labelNode.innerText = value;
74
+ // Подпись пуста — под ней и места не надо: индикатор говорит сам
75
+ this.#labelNode.hidden = value.trim() === "";
76
+ if (this.#a11y !== undefined) this.#a11y.setLabel(value.trim() === "" ? I18n.t("feedback.loading") : value);
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * Показ: слой встаёт в `body` и объявляет себя слоем. Задержка нужна против мигания —
82
+ * короткая операция успевает закончиться раньше, чем слой появится, и экран не дрожит.
83
+ */
84
+ show() {
85
+ if (this.#shown === true) return this;
86
+ this.#shown = true;
87
+ // Слой показывают заново: след уборки прошлого показа снимаем, иначе новый слой
88
+ // не уберётся по концу кадра (`finish` посчитал бы работу сделанной)
89
+ this.#removed = false;
90
+ if (ScreenSpinner.#current !== undefined && ScreenSpinner.#current !== this) ScreenSpinner.#current.hide();
91
+ ScreenSpinner.#current = this;
92
+ const reveal = () => {
93
+ this.#timer = undefined;
94
+ if (this.#shown === false) return;
95
+ if (document.body.contains(this.#root) === false) document.body.appendChild(this.#root);
96
+ new Animation(this.#root).fadeIn();
97
+ new Animation(this.#card).panelIn();
98
+ this.#a11y.open();
99
+ this.#focusIn();
100
+ };
101
+ if (this.#delay > 0) this.#timer = setTimeout(reveal, this.#delay);
102
+ else reveal();
103
+ return this;
104
+ }
105
+
106
+ /**
107
+ * Уход: слой гаснет, узел убирается по концу кадра. Слушатель одноразовый (проверка цели) —
108
+ * иначе он поймал бы любой конец кадра на слое, а срок из токена страхует случай, когда
109
+ * кадр не проиграется вовсе (слой скрыт вместе с родителем).
110
+ */
111
+ hide() {
112
+ if (this.#shown === false) return this;
113
+ this.#shown = false;
114
+ if (this.#timer !== undefined) {
115
+ clearTimeout(this.#timer);
116
+ this.#timer = undefined;
117
+ }
118
+ if (ScreenSpinner.#current === this) ScreenSpinner.#current = undefined;
119
+ // Слой ещё не успел появиться (сработала задержка) — убирать нечего
120
+ if (this.#root.parentElement === null) return this;
121
+ this.#a11y.close();
122
+ new Animation(this.#root).fadeOut();
123
+ new Animation(this.#card).fadeOut();
124
+ const finish = () => {
125
+ if (this.#removed === true) return;
126
+ this.#removed = true;
127
+ this.#root.removeEventListener("animationend", finish);
128
+ this.#root.remove();
129
+ };
130
+ this.#root.addEventListener("animationend", finish);
131
+ setTimeout(finish, tokenTime("--motion_fast"));
132
+ return this;
133
+ }
134
+
135
+ set() {
136
+ return this.#root;
137
+ }
138
+ /** Полное удаление: снять слой (вместе с инертным фоном и слушателями) и убрать узел */
139
+ destroy() {
140
+ this.hide();
141
+ this.#removed = true;
142
+ this.#root.remove();
143
+ }
144
+
145
+ // === СТАТИЧЕСКОЕ ===
146
+ /** Показать спиннер, не заводя переменную: `ScreenSpinner.show({ label: "Сохраняем" })` */
147
+ static show(options = {}) {
148
+ const spinner = new ScreenSpinner(options);
149
+ spinner.show();
150
+ return spinner;
151
+ }
152
+ static hide() {
153
+ if (ScreenSpinner.#current !== undefined) ScreenSpinner.#current.hide();
154
+ }
155
+ static get current() {
156
+ return ScreenSpinner.#current;
157
+ }
158
+
159
+ // === ВНУТРЕННЕЕ ===
160
+ /** Фокус — в слой: иначе он остался бы на кнопке под ним, и Tab уходил бы в фон */
161
+ #focusIn() {
162
+ this.#root.setAttribute("tabindex", "-1");
163
+ this.#root.focus({ preventScroll: true });
164
+ }
165
+ }
166
+
167
+ exports.ScreenSpinner = ScreenSpinner;
@@ -0,0 +1,49 @@
1
+ /* Экран занят: слой поверх окна с индикатором загрузки.
2
+ Занавес держит сам слой (он и есть затемнение), а материал карточки — размытие: у слоя
3
+ `backdrop-filter` нет, иначе карточка внутри не увидела бы страницу (элемент с фильтром
4
+ становится собственным «backdrop root»). */
5
+
6
+ chui_screen_spinner {
7
+ position: fixed;
8
+ inset: 0;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ box-sizing: border-box;
13
+ /* Выше модальных слоёв (у диалога 1000, у занавеса 999): занятость перекрывает всё */
14
+ z-index: 1100;
15
+ background: var(--modal_scrim);
16
+ /* Слой принимает на себя мышь и клавиатуру: под ним ничего не нажать */
17
+ cursor: progress;
18
+ }
19
+
20
+ chui_screen_spinner:focus-visible {
21
+ outline: none;
22
+ }
23
+
24
+ screen_spinner_card {
25
+ display: flex;
26
+ flex-direction: column;
27
+ align-items: center;
28
+ gap: 10px;
29
+ box-sizing: border-box;
30
+ padding: 18px 22px;
31
+ border: 1px solid var(--border_color);
32
+ border-radius: var(--radius_md);
33
+ background: var(--modal_background);
34
+ backdrop-filter: var(--global_blur);
35
+ -webkit-backdrop-filter: var(--global_blur);
36
+ box-shadow: var(--box_shadow_modal);
37
+ color: var(--text_color);
38
+ }
39
+
40
+ screen_spinner_label {
41
+ font-size: var(--font_labels_size);
42
+ color: var(--text_color_disabled);
43
+ text-align: center;
44
+ }
45
+
46
+ /* Подписи нет — под неё не остаётся и зазора */
47
+ screen_spinner_label[hidden] {
48
+ display: none;
49
+ }
@@ -1,5 +1,5 @@
1
- const {setStyles, optionOr} = require("../../modules/chui_functions");
2
- const {makeKeyboardButton} = require("../../modules/chui_a11y/chui_a11y");
1
+ const { setStyles, optionOr } = require("../../modules/chui_functions");
2
+ const { makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
3
3
 
4
4
  /**
5
5
  * Сегментированный переключатель: один выбор из небольшого набора равнозначных вариантов
@@ -35,7 +35,7 @@ const {makeKeyboardButton} = require("../../modules/chui_a11y/chui_a11y");
35
35
  class SegmentedControl {
36
36
  static SIZE = {
37
37
  SMALL: "small",
38
- DEFAULT: "default"
38
+ DEFAULT: "default",
39
39
  };
40
40
  static #counter = 0;
41
41
 
@@ -48,24 +48,29 @@ class SegmentedControl {
48
48
  // Вариант с tabindex="0": с ним же работает обход Tab; за фокусом и выбором следит отдельно
49
49
  #active = undefined;
50
50
  #disabled = new Set();
51
- #on_change = undefined;
52
- #activate_on_focus = false;
51
+ #onChange = undefined;
52
+ #activateOnFocus = false;
53
+ // Подложка выбора измеряется по вёрстке: наблюдатель и отложенный кадр держат её на месте
54
+ #observer = undefined;
55
+ #frame = undefined;
53
56
 
54
- constructor(options = {
55
- id: String(),
56
- options: [],
57
- value: String(),
58
- label: String(),
59
- size: String(),
60
- stretch: Boolean(),
61
- activateOnFocus: Boolean(),
62
- onChange: undefined
63
- }) {
57
+ constructor(
58
+ options = {
59
+ id: String(),
60
+ options: [],
61
+ value: String(),
62
+ label: String(),
63
+ size: String(),
64
+ stretch: Boolean(),
65
+ activateOnFocus: Boolean(),
66
+ onChange: undefined,
67
+ }
68
+ ) {
64
69
  setStyles(__dirname + "/styles.css", "chUiJS_SegmentedControl");
65
70
  SegmentedControl.#counter += 1;
66
71
  this.#id = options.id === undefined ? `chui_segmented_control_${SegmentedControl.#counter}` : options.id;
67
- this.#on_change = options.onChange;
68
- this.#activate_on_focus = options.activateOnFocus === true;
72
+ this.#onChange = options.onChange;
73
+ this.#activateOnFocus = options.activateOnFocus === true;
69
74
  this.#root.id = this.#id;
70
75
  this.#root.setAttribute("role", "radiogroup");
71
76
  this.setSize(options.size === undefined ? SegmentedControl.SIZE.DEFAULT : options.size);
@@ -78,6 +83,28 @@ class SegmentedControl {
78
83
  // Пустая заготовка опции — это «выбор не сделан», а не значение «»
79
84
  const value = optionOr(options.value, undefined);
80
85
  if (value !== undefined) this.setValue(value, true);
86
+ // Ширина меняется от раскладки (растянутый контрол, поворот окна): без пересчёта
87
+ // подложка осталась бы на прежнем варианте
88
+ if (typeof ResizeObserver === "function") {
89
+ this.#observer = new ResizeObserver(() => this.#syncIndicator());
90
+ this.#observer.observe(this.#root);
91
+ }
92
+ }
93
+
94
+ /** Полное удаление: снять наблюдатель, отложенный кадр и слушатели */
95
+ destroy() {
96
+ if (this.#observer !== undefined) {
97
+ this.#observer.disconnect();
98
+ this.#observer = undefined;
99
+ }
100
+ if (this.#frame !== undefined) {
101
+ cancelAnimationFrame(this.#frame);
102
+ this.#frame = undefined;
103
+ }
104
+ this.#root.removeEventListener("keydown", this.#onKeyDown);
105
+ this.#root.removeEventListener("click", this.#onClick);
106
+ this.#root.remove();
107
+ return this;
81
108
  }
82
109
 
83
110
  // GET
@@ -94,7 +121,7 @@ class SegmentedControl {
94
121
  getOptions() {
95
122
  return this.#order.map((value) => {
96
123
  const record = this.#options.get(value);
97
- return {value: record.value, title: record.title};
124
+ return { value: record.value, title: record.title };
98
125
  });
99
126
  }
100
127
 
@@ -125,7 +152,8 @@ class SegmentedControl {
125
152
 
126
153
  /** Размер вариантов: `SegmentedControl.SIZE.{SMALL, DEFAULT}` */
127
154
  setSize(size = String()) {
128
- this.#root.dataset.size = size === SegmentedControl.SIZE.SMALL ? SegmentedControl.SIZE.SMALL : SegmentedControl.SIZE.DEFAULT;
155
+ this.#root.dataset.size =
156
+ size === SegmentedControl.SIZE.SMALL ? SegmentedControl.SIZE.SMALL : SegmentedControl.SIZE.DEFAULT;
129
157
  return this;
130
158
  }
131
159
 
@@ -149,12 +177,12 @@ class SegmentedControl {
149
177
  this.#value = next;
150
178
  this.#active = next;
151
179
  this.#sync();
152
- if (!silent && changed && this.#on_change !== undefined) this.#on_change(next);
180
+ if (!silent && changed && this.#onChange !== undefined) this.#onChange(next);
153
181
  return this;
154
182
  }
155
183
 
156
184
  /** Добавить вариант: `{value, title, icon, disabled}` */
157
- addOption(option = {value: String(), title: String(), icon: undefined, disabled: Boolean()}) {
185
+ addOption(option = { value: String(), title: String(), icon: undefined, disabled: Boolean() }) {
158
186
  this.#root.appendChild(this.#createOption(option));
159
187
  this.#sync();
160
188
  return this;
@@ -174,7 +202,8 @@ class SegmentedControl {
174
202
  this.#value = undefined;
175
203
  this.#active = undefined;
176
204
  for (const option of list) this.#root.appendChild(this.#createOption(option));
177
- if (previous !== undefined && this.#options.has(previous) && !this.#disabled.has(previous)) this.#value = previous;
205
+ if (previous !== undefined && this.#options.has(previous) && !this.#disabled.has(previous))
206
+ this.#value = previous;
178
207
  this.#sync();
179
208
  return this;
180
209
  }
@@ -229,11 +258,12 @@ class SegmentedControl {
229
258
  makeKeyboardButton(node, "radio");
230
259
  node.setAttribute("aria-checked", "false");
231
260
  node.dataset.value = value;
232
- if (option.icon !== undefined && option.icon !== null) node.insertAdjacentHTML("afterbegin", option.icon.getHTML());
261
+ if (option.icon !== undefined && option.icon !== null)
262
+ node.insertAdjacentHTML("afterbegin", option.icon.getHTML());
233
263
  const title = document.createElement("segmented_title");
234
264
  title.innerText = option.title === undefined ? value : String(option.title);
235
265
  node.appendChild(title);
236
- this.#options.set(value, {value: value, title: title.innerText, node: node});
266
+ this.#options.set(value, { value: value, title: title.innerText, node: node });
237
267
  this.#order.push(value);
238
268
  // Запрет мог быть выставлен программно (setDisabled) и остаться после пересборки набора
239
269
  if (option.disabled === true || this.#disabled.has(value)) this.setDisabled(value, true);
@@ -261,13 +291,45 @@ class SegmentedControl {
261
291
  record.node.setAttribute("aria-checked", value === this.#value ? "true" : "false");
262
292
  record.node.setAttribute("tabindex", value === this.#active ? "0" : "-1");
263
293
  }
294
+ this.#scheduleIndicator();
295
+ }
296
+
297
+ /** Подложку двигают после раскладки: место варианта читается только из вёрстки */
298
+ #scheduleIndicator() {
299
+ if (typeof requestAnimationFrame !== "function") {
300
+ this.#syncIndicator();
301
+ return;
302
+ }
303
+ if (this.#frame !== undefined) return;
304
+ this.#frame = requestAnimationFrame(() => {
305
+ this.#frame = undefined;
306
+ this.#syncIndicator();
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Место и ширина подложки выбора. Ноль — выбора нет или вёрстки ещё нет: подложки
312
+ * нулевой ширины не видно, поэтому до первой раскладки контрол выглядит как прежде.
313
+ */
314
+ #syncIndicator() {
315
+ const record = this.#value === undefined ? undefined : this.#options.get(this.#value);
316
+ if (record === undefined || record.node.offsetWidth === 0) {
317
+ this.#root.style.setProperty("--segmented_width", "0px");
318
+ return;
319
+ }
320
+ // В RTL началом ряда служит правый край: подложка стоит у него и едет влево
321
+ const rtl = window.getComputedStyle(this.#root).direction === "rtl";
322
+ const node = record.node;
323
+ const x = rtl ? this.#root.clientWidth - (node.offsetLeft + node.offsetWidth) : node.offsetLeft;
324
+ this.#root.style.setProperty("--segmented_offset", `${rtl ? -x : x}px`);
325
+ this.#root.style.setProperty("--segmented_width", `${node.offsetWidth}px`);
264
326
  }
265
327
 
266
328
  #focus(value = String()) {
267
329
  this.#active = value;
268
330
  this.#sync();
269
331
  this.#options.get(value).node.focus();
270
- if (this.#activate_on_focus) this.setValue(value);
332
+ if (this.#activateOnFocus) this.setValue(value);
271
333
  }
272
334
 
273
335
  // Варианты — кастомные теги, поэтому клик приходит и от мыши, и от Enter/Space
@@ -294,10 +356,11 @@ class SegmentedControl {
294
356
  let next;
295
357
  if (event.key === "Home") next = 0;
296
358
  else if (event.key === "End") next = available.length - 1;
297
- else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (current - 1 + available.length) % available.length;
359
+ else if (event.key === "ArrowLeft" || event.key === "ArrowUp")
360
+ next = (current - 1 + available.length) % available.length;
298
361
  else next = (current + 1) % available.length;
299
362
  this.#focus(available[next]);
300
363
  };
301
364
  }
302
365
 
303
- exports.SegmentedControl = SegmentedControl
366
+ exports.SegmentedControl = SegmentedControl;
@@ -1,5 +1,5 @@
1
1
  /* Сегментированный переключатель: рамка — общая заливка контрола,
2
- выбранный вариант — акцент (--blue_prime_background) */
2
+ выбранный вариант — подвижная подложка акцента */
3
3
  chui_segmented_control {
4
4
  display: inline-flex;
5
5
  align-items: center;
@@ -7,8 +7,30 @@ chui_segmented_control {
7
7
  padding: 2px;
8
8
  width: max-content;
9
9
  box-sizing: border-box;
10
- border-radius: var(--border_radius);
10
+ border-radius: var(--radius_sm);
11
11
  background: var(--element_background);
12
+ /* Место и ширина подложки: считает JS по вёрстке, нули — выбор не сделан */
13
+ --segmented_offset: 0px;
14
+ --segmented_width: 0px;
15
+ }
16
+
17
+ /* Подвижная подложка выбранного варианта: одна и та же деталь переезжает между
18
+ вариантами, вместо мгновенной смены заливки — как у системного переключателя.
19
+ Внутренний отступ равен отступу контейнера, поэтому подложка встаёт в ряд вариантов */
20
+ chui_segmented_control::before {
21
+ content: "";
22
+ position: absolute;
23
+ z-index: 0;
24
+ inset-block: 2px;
25
+ inset-inline-start: 0;
26
+ width: var(--segmented_width);
27
+ border-radius: var(--radius_sm);
28
+ background: var(--blue_prime_background);
29
+ transform: translateX(var(--segmented_offset));
30
+ transition:
31
+ transform var(--motion_base) var(--motion_ease),
32
+ width var(--motion_base) var(--motion_ease);
33
+ pointer-events: none;
12
34
  }
13
35
 
14
36
  segmented_option {
@@ -17,7 +39,7 @@ segmented_option {
17
39
  gap: 6px;
18
40
  padding: 4px 10px;
19
41
  border: none;
20
- border-radius: var(--border_radius);
42
+ border-radius: var(--radius_sm);
21
43
  background: transparent;
22
44
  color: var(--text_color);
23
45
  font-family: inherit;
@@ -26,14 +48,18 @@ segmented_option {
26
48
  cursor: pointer;
27
49
  outline: none;
28
50
  user-select: none;
51
+ /* Вариант выше подложки: иначе акцент остался бы за текстом */
52
+ z-index: 1;
29
53
  }
30
54
 
31
- segmented_option:hover {
55
+ /* Наведение — только у невыбранных: у выбранного заливку несёт подложка, и вторая
56
+ заливка читалась бы как другой цвет */
57
+ segmented_option:not([aria-checked="true"]):hover {
32
58
  background: var(--element_background_hover);
33
59
  }
34
60
 
35
61
  segmented_option[aria-checked="true"] {
36
- background: var(--blue_prime_background);
62
+ background: transparent;
37
63
  color: var(--text_color_hover);
38
64
  }
39
65
 
@@ -56,7 +82,7 @@ segmented_option[aria-disabled="true"]:hover {
56
82
 
57
83
  segmented_option[aria-disabled="true"][aria-checked="true"] {
58
84
  background: var(--blue_prime_background);
59
- color: var(--text_color_hover_disable);
85
+ color: var(--text_color_hover_disabled);
60
86
  }
61
87
 
62
88
  /* chui_icon задаёт собственный цвет, поэтому без этого правила глиф остаётся белым */