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,24 +1,40 @@
1
- const {setStyles, optionOr} = require("../../modules/chui_functions");
2
- const {Icon, Icons} = require("../chui_icons/icons");
3
- const {I18n} = require("../../modules/chui_i18n/chui_i18n");
1
+ const { setStyles, optionOr } = require("../../modules/chui_functions");
2
+ const { Icon, Icons } = require("../chui_icons/icons");
3
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
4
+ const { Animation } = require("../../modules/chui_animations/animations");
5
+ const { Spinner } = require("../chui_spinner/spinner");
4
6
 
5
7
  /**
6
- * Шаги процесса: пройденные, текущий и предстоящие.
8
+ * Шаги процесса: пройденные, текущий и предстоящие — и их состояния.
7
9
  *
8
10
  * Разметка — настоящий `<ol>` со `<li>`, поэтому скринридер читает «список из N шагов», имя
9
11
  * списка задаёт опция `label` (по умолчанию «Шаги»). Линии между маркерами — по две полулинии
10
12
  * у каждого шага (до и после маркера): так они сходятся без дыр, растягиваются на любую ширину
11
13
  * и остаются внутри `<li>` — список не ломается посторонними узлами.
12
14
  *
15
+ * **Состояние шага.** Обычно оно идёт по месту: пройденный, текущий, предстоящий. Но шаг
16
+ * может нести и своё — `status` у шага или `setStatus(index, status)`: `Stepper.STATUS.DONE`
17
+ * (зелёная подсветка и галочка), `Stepper.STATUS.ERROR` (красная подсветка и крест)
18
+ * и `Stepper.STATUS.PENDING` (спиннер: шаг выполняется). `undefined` возвращает шаг
19
+ * к состоянию по месту. Состояния сообщаются слушателям — `addStatusListener` и опция
20
+ * `onStatus`: по ним приложение возвращает человека на шаг, где что-то пошло не так,
21
+ * а сам `setStatus` переход не делает.
22
+ *
23
+ * **Переходы идут движением.** Состояние применяется на месте, не пересборкой: узлы те же,
24
+ * поэтому смена цвета маркера и рост заливки линии читаются переходом, знак состояния
25
+ * появляется кадром, а фокус и прокрутка остаются на месте.
26
+ *
13
27
  * Доступность: текущий шаг помечен `aria-current="step"`, маркер имеет имя «Шаг 2 из 4.
14
28
  * Текущий шаг», а у кликабельного шага маркер — настоящий `<button>` (Tab и Enter/Space родные)
15
- * с тем же именем и названием шага. Смена шага объявляется скрытым `role="status"`.
29
+ * с тем же именем и названием шага. Смена шага и состояния объявляется скрытым `role="status"`.
16
30
  *
17
31
  * ```javascript
18
32
  * const steps = new Stepper({steps: [{title: 'Корзина'}, {title: 'Доставка'}, {title: 'Оплата'}],
19
33
  * active: 1, clickable: true, onChange: (index) => wizard.go(index)});
20
34
  * steps.setActive(2); // программный переход (уведомляет)
21
35
  * steps.setActive(2, true); // тихий: только вид, без уведомления
36
+ * steps.setStatus(2, Stepper.STATUS.PENDING);
37
+ * steps.setStatus(2, Stepper.STATUS.ERROR); // уведомляет слушателей состояния
22
38
  * ```
23
39
  *
24
40
  * Слушателей и таймеров вне своих узлов нет, поэтому `destroy()` не нужен.
@@ -26,11 +42,17 @@ const {I18n} = require("../../modules/chui_i18n/chui_i18n");
26
42
  class Stepper {
27
43
  static ORIENTATION = {
28
44
  HORIZONTAL: "horizontal",
29
- VERTICAL: "vertical"
45
+ VERTICAL: "vertical",
30
46
  };
31
47
  static SIZE = {
32
48
  SMALL: "small",
33
- DEFAULT: "default"
49
+ DEFAULT: "default",
50
+ };
51
+ /** Состояния шага: пройден, ошибка и ожидание выполнения */
52
+ static STATUS = {
53
+ DONE: "done",
54
+ ERROR: "error",
55
+ PENDING: "pending",
34
56
  };
35
57
  static #counter = 0;
36
58
 
@@ -39,23 +61,30 @@ class Stepper {
39
61
  #status = document.createElement("stepper_status");
40
62
  #id = undefined;
41
63
  #items = [];
64
+ #nodes = [];
42
65
  #markers = [];
43
66
  #active = 0;
44
67
  #clickable = false;
45
68
  #listeners = [];
46
- #on_change = undefined;
47
-
48
- constructor(options = {
49
- id: String(),
50
- steps: [],
51
- active: Number(),
52
- orientation: String(),
53
- size: String(),
54
- clickable: Boolean(),
55
- label: String(),
56
- onChange: undefined,
57
- changeEvent: undefined
58
- }) {
69
+ #statusListeners = [];
70
+ #onChange = undefined;
71
+ #onStatus = undefined;
72
+
73
+ constructor(
74
+ options = {
75
+ id: String(),
76
+ steps: [],
77
+ active: Number(),
78
+ orientation: String(),
79
+ size: String(),
80
+ clickable: Boolean(),
81
+ label: String(),
82
+ onChange: undefined,
83
+ changeEvent: undefined,
84
+ onStatus: undefined,
85
+ statusEvent: undefined,
86
+ }
87
+ ) {
59
88
  setStyles(__dirname + "/styles.css", "chUiJS_Stepper");
60
89
  Stepper.#counter += 1;
61
90
  this.#id = options.id === undefined ? `chui_stepper_${Stepper.#counter}` : options.id;
@@ -65,8 +94,10 @@ class Stepper {
65
94
  this.#status.classList.add("chui_visually_hidden");
66
95
  this.#status.setAttribute("role", "status");
67
96
  this.#status.setAttribute("aria-live", "polite");
68
- this.#on_change = options.onChange;
97
+ this.#onChange = options.onChange;
69
98
  if (options.changeEvent !== undefined) this.addChangeListener(options.changeEvent);
99
+ this.#onStatus = options.onStatus;
100
+ if (options.statusEvent !== undefined) this.addStatusListener(options.statusEvent);
70
101
  this.setOrientation(options.orientation === undefined ? Stepper.ORIENTATION.HORIZONTAL : options.orientation);
71
102
  this.setSize(options.size === undefined ? Stepper.SIZE.DEFAULT : options.size);
72
103
  this.setClickable(options.clickable === true);
@@ -102,6 +133,20 @@ class Stepper {
102
133
  return item === undefined ? undefined : item.title;
103
134
  }
104
135
 
136
+ /**
137
+ * Состояние шага: `done`, `error`, `pending`, `current` или `todo`; `undefined`,
138
+ * если шага нет. Состояние по месту (`current`, `todo`) и заданное шагом (`done`,
139
+ * `error`, `pending`) приходят сюда одинаково
140
+ */
141
+ getStatus(index = Number()) {
142
+ return this.#items[index] === undefined ? undefined : this.#state(index);
143
+ }
144
+
145
+ /** Состояния всех шагов: годится для сводки «3 из 4 готовы» */
146
+ getStatuses() {
147
+ return this.#items.map((item, index) => this.#state(index));
148
+ }
149
+
105
150
  getOrientation() {
106
151
  return this.#root.dataset.orientation;
107
152
  }
@@ -117,7 +162,7 @@ class Stepper {
117
162
  return this;
118
163
  }
119
164
 
120
- /** Шаги: `[{title, description, icon, disabled}]` */
165
+ /** Шаги: `[{title, description, icon, disabled, status}]` */
121
166
  setSteps(steps = Array()) {
122
167
  this.#items = Array.isArray(steps) ? steps.map((item) => Object.assign({}, item)) : [];
123
168
  this.#active = Stepper.#clamp(this.#active, this.#items.length);
@@ -140,18 +185,43 @@ class Stepper {
140
185
  const next = Stepper.#clamp(index, this.#items.length);
141
186
  const changed = next !== this.#active;
142
187
  this.#active = next;
143
- this.#render();
188
+ // Состояние применяется на месте: узлы те же, поэтому переход читается движением,
189
+ // а фокус и прокрутка остаются на месте
190
+ this.#refresh();
144
191
  if (silent || !changed) return this;
145
192
  this.#announce();
146
193
  this.#notify();
147
194
  return this;
148
195
  }
149
196
 
197
+ /**
198
+ * Состояние шага: `Stepper.STATUS.{DONE, ERROR, PENDING}`; `undefined` возвращает шаг
199
+ * к состоянию по месту — пройден, текущий или предстоящий.
200
+ *
201
+ * Слушатели состояния (`addStatusListener`, `onStatus`) получают номер шага и его
202
+ * состояние. Сам `setStatus` на шаг не переходит: переход делает приложение — по событию
203
+ * оно возвращает человека туда, где что-то пошло не так.
204
+ */
205
+ setStatus(index = Number(), status = undefined) {
206
+ const item = this.#items[index];
207
+ if (item === undefined) return this;
208
+ const before = this.#state(index);
209
+ if (status === undefined || status === null) delete item.status;
210
+ else item.status = status;
211
+ const state = this.#state(index);
212
+ this.#refresh();
213
+ if (state === before) return this;
214
+ this.#announceStatus(index);
215
+ this.#notifyStatus(index, state);
216
+ return this;
217
+ }
218
+
150
219
  /** Направление шагов: `Stepper.ORIENTATION.{HORIZONTAL, VERTICAL}` */
151
220
  setOrientation(orientation = String()) {
152
- this.#root.dataset.orientation = orientation === Stepper.ORIENTATION.VERTICAL
153
- ? Stepper.ORIENTATION.VERTICAL
154
- : Stepper.ORIENTATION.HORIZONTAL;
221
+ this.#root.dataset.orientation =
222
+ orientation === Stepper.ORIENTATION.VERTICAL
223
+ ? Stepper.ORIENTATION.VERTICAL
224
+ : Stepper.ORIENTATION.HORIZONTAL;
155
225
  return this;
156
226
  }
157
227
 
@@ -181,13 +251,28 @@ class Stepper {
181
251
  return this;
182
252
  }
183
253
 
254
+ /** Слушатель состояния шага: `(index, status, stepper) => {}` */
255
+ addStatusListener(listener = () => {}) {
256
+ this.#statusListeners.push(listener);
257
+ return this;
258
+ }
259
+
184
260
  // RENDER
185
261
  set() {
186
262
  return this.#root;
187
263
  }
188
264
 
189
265
  // === ВНУТРЕННЕЕ ===
266
+ /**
267
+ * Состояние шага. Своё состояние шага сильнее состояния по месту: шаг с ошибкой остаётся
268
+ * с ошибкой, даже когда человек ушёл дальше, — иначе знак ошибки пропадал бы со сменой шага
269
+ */
190
270
  #state(index = Number()) {
271
+ const item = this.#items[index];
272
+ const status = item === undefined ? undefined : item.status;
273
+ if (status === Stepper.STATUS.DONE || status === Stepper.STATUS.ERROR || status === Stepper.STATUS.PENDING) {
274
+ return status;
275
+ }
191
276
  if (index < this.#active) return "done";
192
277
  if (index === this.#active) return "current";
193
278
  return "todo";
@@ -195,7 +280,9 @@ class Stepper {
195
280
 
196
281
  #stateWord(index = Number()) {
197
282
  const state = this.#state(index);
198
- if (state === "done") return I18n.t("navigation.stepDone");
283
+ if (state === Stepper.STATUS.DONE) return I18n.t("navigation.stepDone");
284
+ if (state === Stepper.STATUS.ERROR) return I18n.t("navigation.stepError");
285
+ if (state === Stepper.STATUS.PENDING) return I18n.t("navigation.stepPending");
199
286
  if (state === "current") return I18n.t("navigation.stepCurrent");
200
287
  return I18n.t("navigation.stepTodo");
201
288
  }
@@ -203,7 +290,9 @@ class Stepper {
203
290
  // Имя маркера: номер шага, название и состояние — иначе глиф читался бы как мусор
204
291
  #markerLabel(index = Number()) {
205
292
  const title = this.#items[index].title;
206
- const head = I18n.t("navigation.stepOf", {index: index + 1, total: this.#items.length}) + (title === undefined ? "" : `: ${title}`);
293
+ const head =
294
+ I18n.t("navigation.stepOf", { index: index + 1, total: this.#items.length }) +
295
+ (title === undefined ? "" : `: ${title}`);
207
296
  return `${head} — ${this.#stateWord(index)}`;
208
297
  }
209
298
 
@@ -212,34 +301,70 @@ class Stepper {
212
301
  const focused = this.#markers.indexOf(document.activeElement);
213
302
  this.#list.innerHTML = "";
214
303
  this.#markers = [];
215
- this.#items.forEach((item, index) => this.#list.appendChild(this.#item(item, index)));
304
+ this.#nodes = [];
305
+ this.#items.forEach((item, index) => {
306
+ const node = this.#item(item, index);
307
+ this.#nodes.push(node);
308
+ this.#list.appendChild(node);
309
+ });
216
310
  if (focused !== -1 && this.#markers[focused] !== undefined) this.#markers[focused].focus();
217
311
  }
218
312
 
313
+ /**
314
+ * Применить состояния заново — на тех же узлах (смена шага и состояния шага). Пересборка
315
+ * здесь была бы видна как вспышка: новый узел не помнит прошлого состояния, поэтому и цвет
316
+ * маркера, и заливка линии менялись бы рывком, а фокус терялся бы вовсе
317
+ */
318
+ #refresh() {
319
+ this.#items.forEach((item, index) => {
320
+ const node = this.#nodes[index];
321
+ if (node !== undefined) this.#apply(node, item, index);
322
+ });
323
+ }
324
+
325
+ /** Состояние одного шага: вид, роли и подписи */
326
+ #apply(node = undefined, item = Object(), index = Number()) {
327
+ const state = this.#state(index);
328
+ for (const name of ["done", "error", "pending", "current", "todo"]) {
329
+ node.classList.toggle(`stepper_item_${name}`, state === name);
330
+ }
331
+ node.dataset.state = state;
332
+ // Текущий шаг — тот, где мы стоим, и он остаётся им даже со своим состоянием:
333
+ // по нему идёт `aria-current`, а вид может быть занят ошибкой или спиннером
334
+ node.dataset.active = index === this.#active ? "true" : "false";
335
+ node.classList.toggle("stepper_item_clickable", this.#clickable && item.disabled !== true);
336
+ if (index === this.#active) node.setAttribute("aria-current", "step");
337
+ else node.removeAttribute("aria-current");
338
+ // Линия заполняется по шагу, от которого идёт: до маркера — предыдущим, после — им самим
339
+ node.querySelector(".stepper_line_before").classList.toggle(
340
+ "stepper_line_done",
341
+ index > 0 && this.#state(index - 1) === Stepper.STATUS.DONE
342
+ );
343
+ node.querySelector(".stepper_line_after").classList.toggle("stepper_line_done", state === Stepper.STATUS.DONE);
344
+ const marker = node.querySelector(".stepper_marker");
345
+ marker.setAttribute("aria-label", this.#markerLabel(index));
346
+ if (item.disabled === true) marker.setAttribute("aria-disabled", "true");
347
+ else marker.removeAttribute("aria-disabled");
348
+ this.#glyph(marker, item, index, state);
349
+ }
350
+
219
351
  #item(item = Object(), index = Number()) {
220
352
  const node = document.createElement("li");
221
353
  node.className = "stepper_item";
222
354
  node.dataset.index = String(index);
223
- if (this.#state(index) === "current") {
224
- node.setAttribute("aria-current", "step");
225
- node.classList.add("stepper_item_current");
226
- }
227
- if (this.#state(index) === "done") node.classList.add("stepper_item_done");
228
- if (this.#state(index) === "todo") node.classList.add("stepper_item_todo");
229
- if (this.#clickable) node.classList.add("stepper_item_clickable");
230
- node.appendChild(this.#line("before", index - 1));
355
+ node.appendChild(this.#line("before"));
231
356
  node.appendChild(this.#marker(item, index));
232
- node.appendChild(this.#line("after", index));
357
+ node.appendChild(this.#line("after"));
233
358
  node.appendChild(this.#text(item));
359
+ this.#apply(node, item, index);
234
360
  return node;
235
361
  }
236
362
 
237
363
  // Полулиния соединителя: слева от маркера идёт шаг до него, справа — после
238
- #line(position = String(), stateIndex = Number()) {
364
+ #line(position = String()) {
239
365
  const line = document.createElement("stepper_line");
240
366
  line.className = `stepper_line stepper_line_${position}`;
241
367
  line.setAttribute("aria-hidden", "true");
242
- if (stateIndex >= 0 && stateIndex < this.#active) line.classList.add("stepper_line_done");
243
368
  return line;
244
369
  }
245
370
 
@@ -250,26 +375,62 @@ class Stepper {
250
375
  marker.dataset.index = String(index);
251
376
  if (clickable) {
252
377
  marker.type = "button";
253
- // Кнопка означает «шаг можно выбрать», поэтому у неё есть и название шага
254
- marker.setAttribute("aria-label", this.#markerLabel(index));
255
378
  marker.addEventListener("click", () => this.setActive(index));
256
379
  } else {
257
380
  // Маркер без действия — картинка со своим именем: номер и состояние не дублируются
258
381
  marker.setAttribute("role", "img");
259
- marker.setAttribute("aria-label", this.#markerLabel(index));
260
- if (item.disabled === true) marker.setAttribute("aria-disabled", "true");
261
- }
262
- const glyph = this.#state(index) === "done" && item.icon === undefined ? Icons.NAVIGATION.CHECK : item.icon;
263
- if (glyph === undefined) marker.innerText = String(index + 1);
264
- else {
265
- const icon = new Icon(glyph, this.#iconSize()).set();
266
- icon.setAttribute("aria-hidden", "true");
267
- marker.appendChild(icon);
268
382
  }
269
383
  this.#markers.push(marker);
270
384
  return marker;
271
385
  }
272
386
 
387
+ /**
388
+ * Знак маркера по состоянию: галочка у пройденного, крест у ошибки, спиннер у шага,
389
+ * который выполняется, а в остальных случаях — свой значок шага или его номер.
390
+ *
391
+ * Знак пересобирается только когда он и впрямь сменился (`data-glyph` помнит прошлый):
392
+ * иначе кадр появления перезапускался бы при каждом переходе и все знаки мигали бы
393
+ */
394
+ #glyph(marker = undefined, item = Object(), index = Number(), state = String()) {
395
+ const key = this.#glyphKey(item, state);
396
+ if (marker.dataset.glyph === key) return;
397
+ marker.dataset.glyph = key;
398
+ marker.innerHTML = "";
399
+ if (state === Stepper.STATUS.PENDING) {
400
+ marker.classList.add("stepper_marker_spinner");
401
+ const spinner = new Spinner(this.#spinnerSize(), "0", "").set();
402
+ // Маркер назван сам («… — ожидает выполнения»): здесь спиннер — картинка,
403
+ // а не отдельный живой регион, иначе загрузка объявлялась бы на каждом переходе
404
+ spinner.setAttribute("aria-hidden", "true");
405
+ marker.appendChild(spinner);
406
+ return;
407
+ }
408
+ marker.classList.remove("stepper_marker_spinner");
409
+ const glyph = this.#glyphName(item, state);
410
+ if (glyph === undefined) {
411
+ marker.innerText = String(index + 1);
412
+ return;
413
+ }
414
+ const icon = new Icon(glyph, this.#iconSize()).set();
415
+ icon.setAttribute("aria-hidden", "true");
416
+ marker.appendChild(icon);
417
+ // Знак состояния приходит кадром: смена номера на галочку иначе читается вспышкой
418
+ new Animation(icon).scaleIn();
419
+ }
420
+
421
+ #glyphKey(item = Object(), state = String()) {
422
+ if (state === Stepper.STATUS.DONE) return "done";
423
+ if (state === Stepper.STATUS.ERROR) return "error";
424
+ if (state === Stepper.STATUS.PENDING) return "pending";
425
+ return item.icon === undefined ? "number" : `icon:${item.icon}`;
426
+ }
427
+
428
+ #glyphName(item = Object(), state = String()) {
429
+ if (state === Stepper.STATUS.DONE) return Icons.NAVIGATION.CHECK;
430
+ if (state === Stepper.STATUS.ERROR) return Icons.NAVIGATION.CLOSE;
431
+ return item.icon;
432
+ }
433
+
273
434
  #text(item = Object()) {
274
435
  const text = document.createElement("stepper_text");
275
436
  text.className = "stepper_text";
@@ -290,15 +451,30 @@ class Stepper {
290
451
  return this.#root.dataset.size === Stepper.SIZE.SMALL ? Stepper.SMALL_ICON_SIZE : Stepper.ICON_SIZE;
291
452
  }
292
453
 
454
+ // Спиннер шага — по размеру знака: размеры те же, что у значков маркера
455
+ #spinnerSize() {
456
+ return this.#root.dataset.size === Stepper.SIZE.SMALL ? { SIZE: 14, BORDER: 2 } : { SIZE: 18, BORDER: 2 };
457
+ }
458
+
293
459
  #announce() {
294
460
  const title = this.#items[this.#active] === undefined ? undefined : this.#items[this.#active].title;
295
- const head = I18n.t("navigation.stepOf", {index: this.#active + 1, total: this.#items.length});
461
+ const head = I18n.t("navigation.stepOf", { index: this.#active + 1, total: this.#items.length });
296
462
  this.#status.innerText = title === undefined ? head : `${head}: ${title}`;
297
463
  }
298
464
 
465
+ // О смене состояния сообщается тем же именем, что у маркера: шаг, название и состояние
466
+ #announceStatus(index = Number()) {
467
+ this.#status.innerText = this.#markerLabel(index);
468
+ }
469
+
299
470
  #notify() {
300
471
  for (const listener of [...this.#listeners]) listener(this.#active, this);
301
- if (this.#on_change !== undefined) this.#on_change(this.#active, this);
472
+ if (this.#onChange !== undefined) this.#onChange(this.#active, this);
473
+ }
474
+
475
+ #notifyStatus(index = Number(), status = String()) {
476
+ for (const listener of [...this.#statusListeners]) listener(index, status, this);
477
+ if (this.#onStatus !== undefined) this.#onStatus(index, status, this);
302
478
  }
303
479
 
304
480
  static #clamp(value = Number(), count = Number()) {
@@ -311,4 +487,4 @@ class Stepper {
311
487
  static SMALL_ICON_SIZE = "14px";
312
488
  }
313
489
 
314
- exports.Stepper = Stepper
490
+ exports.Stepper = Stepper;
@@ -2,7 +2,7 @@
2
2
  chui_stepper {
3
3
  display: block;
4
4
  width: -webkit-fill-available;
5
- margin: var(--margin);
5
+ margin: var(--block_margin);
6
6
  color: var(--text_color);
7
7
  font-size: var(--font_default_size);
8
8
  }
@@ -33,7 +33,12 @@ chui_stepper[data-size="small"] {
33
33
  min-width: 0;
34
34
  }
35
35
 
36
+ /* Полулиния соединителя: приглушённая дорожка и слой заливки поверх неё. Заливка растёт
37
+ переходом по токену движения — путь шагов читается пройденным, а не загорается сразу.
38
+ Полулинии у краёв списка не рисуются, но место остаётся, иначе крайние маркеры уехали бы
39
+ к краям карточки */
36
40
  .stepper_line {
41
+ position: relative;
37
42
  height: 2px;
38
43
  min-width: 12px;
39
44
  align-self: center;
@@ -42,6 +47,18 @@ chui_stepper[data-size="small"] {
42
47
  border-radius: var(--radius_pill);
43
48
  }
44
49
 
50
+ /* Заливка идёт от того края, где стоит маркер-предшественник: так видно, что шаг пройден */
51
+ .stepper_line::after {
52
+ content: "";
53
+ position: absolute;
54
+ inset: 0;
55
+ background: var(--green_prime_background);
56
+ border-radius: inherit;
57
+ transform: scaleX(0);
58
+ transform-origin: left center;
59
+ transition: transform var(--motion_base) var(--motion_ease);
60
+ }
61
+
45
62
  .stepper_line_before {
46
63
  grid-column: 1;
47
64
  grid-row: 1;
@@ -52,6 +69,10 @@ chui_stepper[data-size="small"] {
52
69
  grid-row: 1;
53
70
  }
54
71
 
72
+ .stepper_line_done::after {
73
+ transform: scaleX(1);
74
+ }
75
+
55
76
  /* Края списка никуда не ведут: полулинии у них не рисуются, но место остаётся,
56
77
  иначе крайние маркеры уехали бы к краям карточки */
57
78
  .stepper_item:first-child .stepper_line_before,
@@ -59,10 +80,6 @@ chui_stepper[data-size="small"] {
59
80
  visibility: hidden;
60
81
  }
61
82
 
62
- .stepper_line_done {
63
- background: var(--blue_prime_background);
64
- }
65
-
66
83
  .stepper_marker {
67
84
  grid-column: 2;
68
85
  grid-row: 1;
@@ -81,6 +98,11 @@ chui_stepper[data-size="small"] {
81
98
  font-size: var(--font_default_size);
82
99
  font-weight: 500;
83
100
  line-height: 1;
101
+ /* Цвета маркера ведёт переход: смена шага и состояния читается движением, а не вспышкой */
102
+ transition:
103
+ background-color var(--motion_base) var(--motion_ease),
104
+ border-color var(--motion_base) var(--motion_ease),
105
+ color var(--motion_base) var(--motion_ease);
84
106
  }
85
107
 
86
108
  .stepper_marker chui_icon {
@@ -93,10 +115,34 @@ chui_stepper[data-size="small"] .stepper_marker {
93
115
  font-size: var(--font_labels_size);
94
116
  }
95
117
 
96
- /* Пройденный шаг: тонировка акцента — видно, что галочка «своя», а не текущая */
118
+ /* Пройденный шаг: зелёная подсветка и галочка — готовность, а не текущее место */
97
119
  .stepper_item_done .stepper_marker {
98
- border-color: var(--blue_prime_background);
99
- background: var(--blue_prime_background_tint);
120
+ border-color: var(--green_prime_background);
121
+ background: color-mix(in srgb, var(--green_prime_background) 16%, transparent);
122
+ color: var(--green_prime_background);
123
+ }
124
+
125
+ /* Ошибка: красная подсветка и крест — тот же цвет у знака и у строки шага */
126
+ .stepper_item_error .stepper_marker {
127
+ border-color: var(--red_prime_background);
128
+ background: color-mix(in srgb, var(--red_prime_background) 16%, transparent);
129
+ color: var(--red_prime_background);
130
+ }
131
+
132
+ /* Шаг выполняется — в маркере спиннер: кольцо берёт цвет маркера (`currentColor`) */
133
+ .stepper_item_pending .stepper_marker {
134
+ border-color: var(--border_color);
135
+ background: var(--element_background_2);
136
+ color: var(--blue_prime_background);
137
+ }
138
+
139
+ .stepper_marker spinner {
140
+ margin: 0;
141
+ }
142
+
143
+ /* Ошибка видна и строкой шага: цвет знака и подписи — один на всю строку */
144
+ .stepper_item_error .stepper_title {
145
+ color: var(--red_prime_background);
100
146
  }
101
147
 
102
148
  /* Текущий шаг — единственный залитый акцентом: он и есть место, где мы находимся */
@@ -106,6 +152,12 @@ chui_stepper[data-size="small"] .stepper_marker {
106
152
  color: var(--text_color_hover);
107
153
  }
108
154
 
155
+ /* Шаг, на котором мы стоим, виден и тогда, когда маркер занят своим состоянием
156
+ (ошибкой или спиннером): его выдаёт акцентное кольцо вокруг маркера */
157
+ .stepper_item[data-active="true"]:not(.stepper_item_current) .stepper_marker {
158
+ box-shadow: 0 0 0 2px var(--blue_prime_background_tint);
159
+ }
160
+
109
161
  .stepper_item_todo .stepper_marker {
110
162
  color: var(--text_color_disabled);
111
163
  }
@@ -124,7 +176,7 @@ chui_stepper[data-size="small"] .stepper_marker {
124
176
  outline-offset: var(--focus_outline_offset);
125
177
  }
126
178
 
127
- .stepper_marker:disabled {
179
+ .stepper_marker[aria-disabled="true"] {
128
180
  cursor: not-allowed;
129
181
  color: var(--text_color_disabled);
130
182
  background: var(--element_background_disabled);
@@ -143,11 +195,8 @@ chui_stepper[data-size="small"] .stepper_marker {
143
195
  font-weight: 500;
144
196
  overflow: hidden;
145
197
  text-overflow: ellipsis;
146
- }
147
-
148
- .stepper_item_todo .stepper_title {
149
- color: var(--text_color_disabled);
150
- font-weight: 400;
198
+ /* Цвет подписи и пояснения тоже ведёт переход: ошибка и выбор шага не мигают */
199
+ transition: color var(--motion_base) var(--motion_ease);
151
200
  }
152
201
 
153
202
  .stepper_description {
@@ -155,6 +204,12 @@ chui_stepper[data-size="small"] .stepper_marker {
155
204
  margin-top: 2px;
156
205
  color: var(--text_color_disabled);
157
206
  font-size: var(--font_labels_size);
207
+ transition: color var(--motion_base) var(--motion_ease);
208
+ }
209
+
210
+ .stepper_item_todo .stepper_title {
211
+ color: var(--text_color_disabled);
212
+ font-weight: 400;
158
213
  }
159
214
 
160
215
  .stepper_description[hidden] {
@@ -167,6 +222,16 @@ chui_stepper[data-orientation="vertical"] .stepper_list {
167
222
  grid-auto-columns: auto;
168
223
  }
169
224
 
225
+ /* Заливка в столбце растёт сверху вниз — от маркера-предшественника к следующему */
226
+ chui_stepper[data-orientation="vertical"] .stepper_line::after {
227
+ transform: scaleY(0);
228
+ transform-origin: center top;
229
+ }
230
+
231
+ chui_stepper[data-orientation="vertical"] .stepper_line_done::after {
232
+ transform: scaleY(1);
233
+ }
234
+
170
235
  chui_stepper[data-orientation="vertical"] .stepper_item {
171
236
  grid-template-columns: auto 1fr;
172
237
  grid-template-rows: 1fr auto 1fr;