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
@@ -55,18 +55,18 @@ class ActionSheet {
55
55
  #head = document.createElement("action_sheet_head");
56
56
  #title = document.createElement("action_sheet_title");
57
57
  #message = document.createElement("action_sheet_message");
58
- #actions_node = document.createElement("action_sheet_actions");
58
+ #actionsNode = document.createElement("action_sheet_actions");
59
59
  #cancel = document.createElement("button");
60
60
  #actions = [];
61
61
  #rows = [];
62
- #cancel_text = undefined;
62
+ #cancelText = undefined;
63
63
  #closable = true;
64
- #close_outside = true;
65
- #labelled_by_title = false;
64
+ #closeOutside = true;
65
+ #labelledByTitle = false;
66
66
  #chosen = undefined;
67
67
  #a11y = undefined;
68
- #on_select = undefined;
69
- #on_close = undefined;
68
+ #onSelect = undefined;
69
+ #onClose = undefined;
70
70
  isOpen = false;
71
71
 
72
72
  constructor(
@@ -88,10 +88,10 @@ class ActionSheet {
88
88
  this.#id = options.id === undefined ? `chui_action_sheet_${ActionSheet.#counter}` : options.id;
89
89
  this.#sheet.id = this.#id;
90
90
  this.#closable = options.closable !== false;
91
- if (options.closeOnOutsideClick !== undefined) this.#close_outside = options.closeOnOutsideClick !== false;
92
- this.#cancel_text = options.cancelText;
93
- this.#on_select = options.onSelect;
94
- this.#on_close = options.onClose;
91
+ if (options.closeOnOutsideClick !== undefined) this.#closeOutside = options.closeOnOutsideClick !== false;
92
+ this.#cancelText = options.cancelText;
93
+ this.#onSelect = options.onSelect;
94
+ this.#onClose = options.onClose;
95
95
 
96
96
  this.#title.id = `${this.#id}_title`;
97
97
  this.#message.id = `${this.#id}_message`;
@@ -100,20 +100,20 @@ class ActionSheet {
100
100
  this.#cancel.addEventListener("click", this.#onCancelClick);
101
101
 
102
102
  this.#group.appendChild(this.#head);
103
- this.#group.appendChild(this.#actions_node);
103
+ this.#group.appendChild(this.#actionsNode);
104
104
  this.#panel.appendChild(this.#group);
105
105
  this.#panel.appendChild(this.#cancel);
106
106
  this.#sheet.appendChild(this.#panel);
107
107
  document.body.appendChild(this.#sheet);
108
108
  // Заголовок в разметке — имя слоя; иначе имя берётся из label или словаря
109
109
  const title = optionOr(options.title, undefined);
110
- this.#labelled_by_title = title !== undefined;
110
+ this.#labelledByTitle = title !== undefined;
111
111
  if (title !== undefined) this.setTitle(title);
112
112
  if (optionOr(options.message, undefined) !== undefined) this.setMessage(options.message);
113
113
  this.#a11y = new OverlayA11y(this.#sheet, {
114
114
  role: "dialog",
115
- labelNode: this.#labelled_by_title ? this.#title : undefined,
116
- label: this.#labelled_by_title ? undefined : optionOr(options.label, I18n.t("feedback.actionSheet")),
115
+ labelNode: this.#labelledByTitle ? this.#title : undefined,
116
+ label: this.#labelledByTitle ? undefined : optionOr(options.label, I18n.t("feedback.actionSheet")),
117
117
  onEscape: () => this.close(),
118
118
  });
119
119
  this.#syncCancelText();
@@ -146,7 +146,7 @@ class ActionSheet {
146
146
  setTitle(text = String()) {
147
147
  this.#title.innerText = text;
148
148
  this.#syncHead();
149
- if (!this.#labelled_by_title) this.#a11y.setLabel(text);
149
+ if (!this.#labelledByTitle) this.#a11y.setLabel(text);
150
150
  return this;
151
151
  }
152
152
  setMessage(text = String()) {
@@ -155,7 +155,7 @@ class ActionSheet {
155
155
  return this;
156
156
  }
157
157
  setCancelText(text = String()) {
158
- this.#cancel_text = text;
158
+ this.#cancelText = text;
159
159
  this.#syncCancelText();
160
160
  return this;
161
161
  }
@@ -163,7 +163,7 @@ class ActionSheet {
163
163
  setActions(actions = []) {
164
164
  this.#actions = [];
165
165
  this.#rows = [];
166
- this.#actions_node.innerHTML = "";
166
+ this.#actionsNode.innerHTML = "";
167
167
  for (const action of actions) this.#appendAction(action);
168
168
  return this;
169
169
  }
@@ -177,6 +177,7 @@ class ActionSheet {
177
177
  this.isOpen = true;
178
178
  this.#chosen = undefined;
179
179
  this.#panel.style.animationName = "action_sheet_in";
180
+ this.#panel.dataset.motion = "enter";
180
181
  new Animation(this.#sheet).fadeIn();
181
182
  this.#a11y.open();
182
183
  return this;
@@ -190,8 +191,9 @@ class ActionSheet {
190
191
  this.isOpen = false;
191
192
  this.#a11y.close();
192
193
  this.#panel.style.animationName = "action_sheet_out";
194
+ this.#panel.dataset.motion = "exit";
193
195
  new Animation(this.#sheet).fadeOut();
194
- if (this.#chosen === undefined && this.#on_close !== undefined) this.#on_close();
196
+ if (this.#chosen === undefined && this.#onClose !== undefined) this.#onClose();
195
197
  return this;
196
198
  }
197
199
  /** Полное удаление: закрыть слой, снять слушатели и убрать занавес из документа */
@@ -211,12 +213,13 @@ class ActionSheet {
211
213
  * Временный слой после ответа убирается: иначе каждый вызов оставлял бы узел в документе */
212
214
  static show(options = {}) {
213
215
  return new Promise((resolve) => {
214
- let sheet;
215
216
  const answer = (value) => {
216
217
  resolve(value);
217
218
  sheet.destroy();
218
219
  };
219
- sheet = new ActionSheet(
220
+ // Слой собирается один раз, и `answer` зовут только после сборки:
221
+ // к моменту вызова имя уже определено, поэтому здесь можно `const`
222
+ const sheet = new ActionSheet(
220
223
  Object.assign({}, options, {
221
224
  onSelect: (action, index) => {
222
225
  if (options.onSelect !== undefined) options.onSelect(action, index);
@@ -256,19 +259,19 @@ class ActionSheet {
256
259
  row.addEventListener("click", () => this.#choose(action, this.#actions.indexOf(action)));
257
260
  this.#actions.push(action);
258
261
  this.#rows.push(row);
259
- this.#actions_node.appendChild(row);
262
+ this.#actionsNode.appendChild(row);
260
263
  return row;
261
264
  }
262
265
  #choose(action = {}, index = -1) {
263
266
  if (action.disabled === true) return;
264
267
  this.#chosen = action;
265
268
  if (action.onClick !== undefined) action.onClick(action, index);
266
- if (this.#on_select !== undefined) this.#on_select(action, index);
269
+ if (this.#onSelect !== undefined) this.#onSelect(action, index);
267
270
  this.close();
268
271
  }
269
272
  #onCancelClick = () => this.close();
270
273
  #onSheetClick = (event) => {
271
- if (!this.#close_outside || event.target !== this.#sheet) return;
274
+ if (!this.#closeOutside || event.target !== this.#sheet) return;
272
275
  this.close();
273
276
  };
274
277
  // После анимации имя снимается: иначе следующий показ не проиграется заново
@@ -278,18 +281,18 @@ class ActionSheet {
278
281
  };
279
282
  /** Заголовок и пояснение показываются, только когда заданы; пояснение — описание слоя */
280
283
  #syncHead() {
281
- const has_title = this.#title.innerText.trim() !== "";
282
- const has_message = this.#message.innerText.trim() !== "";
283
- if (has_title && this.#title.parentElement === null) this.#head.appendChild(this.#title);
284
- if (!has_title && this.#title.parentElement !== null) this.#title.remove();
285
- if (has_message && this.#message.parentElement === null) this.#head.appendChild(this.#message);
286
- if (!has_message && this.#message.parentElement !== null) this.#message.remove();
287
- this.#head.style.display = has_title || has_message ? "" : "none";
288
- if (has_message) this.#sheet.setAttribute("aria-describedby", this.#message.id);
284
+ const hasTitle = this.#title.innerText.trim() !== "";
285
+ const hasMessage = this.#message.innerText.trim() !== "";
286
+ if (hasTitle && this.#title.parentElement === null) this.#head.appendChild(this.#title);
287
+ if (!hasTitle && this.#title.parentElement !== null) this.#title.remove();
288
+ if (hasMessage && this.#message.parentElement === null) this.#head.appendChild(this.#message);
289
+ if (!hasMessage && this.#message.parentElement !== null) this.#message.remove();
290
+ this.#head.style.display = hasTitle || hasMessage ? "" : "none";
291
+ if (hasMessage) this.#sheet.setAttribute("aria-describedby", this.#message.id);
289
292
  else this.#sheet.removeAttribute("aria-describedby");
290
293
  }
291
294
  #syncCancelText() {
292
- this.#cancel.innerText = this.#cancel_text === undefined ? I18n.t("common.cancel") : this.#cancel_text;
295
+ this.#cancel.innerText = this.#cancelText === undefined ? I18n.t("common.cancel") : this.#cancelText;
293
296
  this.#cancel.style.display = this.#closable ? "" : "none";
294
297
  }
295
298
  }
@@ -26,7 +26,13 @@ action_sheet_panel {
26
26
  width: min(480px, 100%);
27
27
  max-height: 80vh;
28
28
  overflow-y: auto;
29
- animation: action_sheet_in var(--transition_time) ease;
29
+ animation: action_sheet_in var(--motion_base) var(--motion_ease);
30
+ }
31
+
32
+ /* Вид движения ставит action_sheet.js: выезд идёт базовым временем из правила выше, а уход
33
+ берёт короткое — закрытие не задерживает следующий экран */
34
+ action_sheet_panel[data-motion="exit"] {
35
+ animation-duration: var(--motion_fast);
30
36
  }
31
37
 
32
38
  @keyframes action_sheet_in {
@@ -68,7 +74,7 @@ action_sheet_head {
68
74
  display: flex;
69
75
  flex-direction: column;
70
76
  gap: 2px;
71
- padding: var(--control_padding) var(--main_padding);
77
+ padding: var(--control_padding) var(--block_padding);
72
78
  text-align: center;
73
79
  border-bottom: 1px solid var(--border_color);
74
80
  }
@@ -102,7 +108,7 @@ action_sheet_actions {
102
108
  width: 100%;
103
109
  box-sizing: border-box;
104
110
  min-height: 44px;
105
- padding: var(--main_padding);
111
+ padding: var(--block_padding);
106
112
  background: transparent;
107
113
  border: none;
108
114
  border-top: 1px solid var(--border_color);
@@ -172,7 +178,7 @@ action_sheet_action_description {
172
178
  width: 100%;
173
179
  box-sizing: border-box;
174
180
  min-height: 44px;
175
- padding: var(--main_padding);
181
+ padding: var(--block_padding);
176
182
  border-radius: var(--radius_md);
177
183
  background: var(--modal_background);
178
184
  border: 1px solid var(--border_color);
@@ -0,0 +1,338 @@
1
+ const { Icon } = require("../chui_icons/icons");
2
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
3
+ const { Toolbar } = require("../chui_toolbar/toolbar");
4
+ const { optionOr, setStyles } = require("../../modules/chui_functions");
5
+
6
+ /**
7
+ * Действие панели: значок с подписью, состоянием переключателя и счётчиком.
8
+ *
9
+ * Это настоящий `<button>`: клавиатура, фокус и роль у него уже есть. Переключатель
10
+ * (`toggle`) объявляет себя `aria-pressed` — «закреплено», «без звука» это состояние,
11
+ * а не действие, и скринридер обязан его читать. Опасное действие (удалить, очистить)
12
+ * помечается `danger` — подписью красного цвета, без отдельной иконки-предупреждения.
13
+ */
14
+ class ActionsBarButton {
15
+ static #counter = 0;
16
+
17
+ #root = document.createElement("button");
18
+ #iconNode = document.createElement("actions_bar_button_icon");
19
+ #text = document.createElement("actions_bar_button_text");
20
+ #badge = document.createElement("actions_bar_badge");
21
+ #id = undefined;
22
+ #label = undefined;
23
+ #active = false;
24
+ #toggle = false;
25
+ #disabled = false;
26
+ #danger = false;
27
+ #onClick = undefined;
28
+ #onToggle = undefined;
29
+
30
+ constructor(options = {}) {
31
+ const id = optionOr(options.id, undefined);
32
+ this.#id = id === undefined ? String((ActionsBarButton.#counter += 1)) : String(id);
33
+ this.#label = options.label === undefined ? "" : String(options.label);
34
+ this.#toggle = options.toggle === true;
35
+ this.#danger = options.danger === true;
36
+ this.#onClick = typeof options.onClick === "function" ? options.onClick : undefined;
37
+ // Свой слушатель состояния — им о переключении узнаёт панель (публичного `onToggle` нет)
38
+ this.#onToggle = typeof options.onToggle === "function" ? options.onToggle : undefined;
39
+ this.#root.type = "button";
40
+ this.#root.className = "actions_bar_button";
41
+ this.#root.dataset.id = this.#id;
42
+ if (optionOr(options.icon, undefined) !== undefined)
43
+ this.#iconNode.innerHTML = new Icon(options.icon, "18px").getHTML();
44
+ this.#iconNode.setAttribute("aria-hidden", "true");
45
+ this.#text.innerText = this.#label;
46
+ this.#root.append(this.#iconNode, this.#text);
47
+ // Значка нет — кнопка подписью не обходится: имя ей даёт подсказка или словарь
48
+ if (this.#label === "") this.setLabel(optionOr(options.hint, I18n.t("feedback.actionsBarButton")));
49
+ else this.#syncName();
50
+ if (this.#danger === true) this.#root.dataset.danger = "true";
51
+ if (this.#toggle === true) this.setActive(options.active === true);
52
+ if (options.badge !== undefined) this.setBadge(options.badge);
53
+ if (options.disabled === true) this.setDisabled(true);
54
+ this.#root.addEventListener("click", () => {
55
+ if (this.#disabled === true) return;
56
+ if (this.#toggle === true) {
57
+ this.setActive(this.#active === false);
58
+ if (this.#onToggle !== undefined) this.#onToggle(this);
59
+ }
60
+ if (this.#onClick !== undefined) this.#onClick(this, this.#active);
61
+ });
62
+ }
63
+
64
+ getId() {
65
+ return this.#id;
66
+ }
67
+ getLabel() {
68
+ return this.#text.innerText;
69
+ }
70
+ /** Переключатель включён: «закреплено», «без звука» */
71
+ isActive() {
72
+ return this.#active;
73
+ }
74
+ isDisabled() {
75
+ return this.#disabled;
76
+ }
77
+
78
+ /**
79
+ * Состояние переключателя: видно подложкой, скринридеру — через `aria-pressed`.
80
+ * У обычного действия состояния нет: `setActive` на нём ничего не делает — подсветка
81
+ * без программного состояния читалась бы как выбранное действие
82
+ */
83
+ setActive(boolean = Boolean()) {
84
+ if (this.#toggle !== true) return this;
85
+ this.#active = boolean === true;
86
+ this.#root.dataset.active = this.#active ? "true" : "false";
87
+ this.#root.setAttribute("aria-pressed", this.#active ? "true" : "false");
88
+ return this;
89
+ }
90
+ setDisabled(boolean = Boolean()) {
91
+ this.#disabled = boolean === true;
92
+ this.#root.disabled = this.#disabled;
93
+ return this;
94
+ }
95
+ /** Счётчик действия: число показывает плашка, скринридеру оно попадает в имя */
96
+ setBadge(count = Number()) {
97
+ const value = Number(count);
98
+ const shown = Number.isFinite(value) && value > 0 ? String(value) : "";
99
+ this.#badge.innerText = shown;
100
+ this.#badge.hidden = shown === "";
101
+ if (shown !== "" && this.#badge.parentNode === null) this.#root.appendChild(this.#badge);
102
+ this.#syncName(shown);
103
+ return this;
104
+ }
105
+ setLabel(text = String()) {
106
+ const label = String(text);
107
+ if (this.#label === "") {
108
+ // Кнопка без подписи: имя живёт только в атрибутах и подсказке
109
+ this.#root.setAttribute("aria-label", label);
110
+ this.#root.title = label;
111
+ return this;
112
+ }
113
+ this.#root.setAttribute("aria-label", label);
114
+ this.#text.innerText = label;
115
+ return this;
116
+ }
117
+ /** Имя кнопки: своя подпись или значок с подсказкой, плюс счётчик, если он есть */
118
+ #syncName(count = undefined) {
119
+ if (this.#label === "") return;
120
+ const shown = count === undefined ? (this.#badge.hidden ? "" : this.#badge.innerText) : count;
121
+ this.#root.setAttribute(
122
+ "aria-label",
123
+ shown === "" ? this.#label : `${this.#label}, ${I18n.t("feedback.actionsBarBadge", { value: shown })}`
124
+ );
125
+ }
126
+
127
+ set() {
128
+ return this.#root;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Панель действий — что можно сделать с тем, о чём сейчас речь.
134
+ *
135
+ * Пятая часть мессенджера: список разговоров слева, шапка разговора, **панель действий**,
136
+ * лента сообщений и панель ввода — так собирается экран переписки. Панель ставят и под
137
+ * шапкой разговора, и в карточке файла или профиля: она не полагает, о чём именно действия.
138
+ *
139
+ * Действия — подписанные кнопки со значком: «Закрепить», «Без звука», «Найти в переписке»,
140
+ * «Очистить историю». Часть из них — переключатели (`toggle: true`): у них есть состояние,
141
+ * и оно объявляется `aria-pressed`, а видно по подложке. Опасное действие помечается
142
+ * `danger: true` — подписью красного цвета.
143
+ *
144
+ * Панель держит `Toolbar` внутри: стрелки ←/→ ходят по действиям, Home и End ведут к краям,
145
+ * а Tab проходит по кнопкам по очереди — каждая из них настоящий `<button>`, как у полосы
146
+ * пользователя и панели ввода. Строка клавиатуры внутри панели своей кромки не рисует:
147
+ * панель и полоса — одна поверхность, и вложенная ступень здесь была бы лишней.
148
+ *
149
+ * ```javascript
150
+ * const actions = new ActionsBar({
151
+ * title: 'Разговор',
152
+ * actions: [
153
+ * { id: 'pin', icon: Icons.CONTENT.PUSH_PIN, label: 'Закрепить', toggle: true },
154
+ * { id: 'mute', icon: Icons.SOCIAL.NOTIFICATIONS_OFF, label: 'Без звука', toggle: true, active: true },
155
+ * { id: 'find', icon: Icons.ACTIONS.SEARCH, label: 'Найти в переписке', onClick: () => find() },
156
+ * { id: 'clear', icon: Icons.ACTIONS.DELETE, label: 'Очистить историю', danger: true, onClick: () => clear() },
157
+ * ],
158
+ * onChange: (button) => Log.info(button.getId(), button.isActive()),
159
+ * });
160
+ * ```
161
+ *
162
+ * Опции: `id`, `title`, `label`, `orientation`, `actions`, `onChange`.
163
+ * Действие: `{ id, icon, label, hint, badge, toggle, active, danger, disabled, onClick }`;
164
+ * фабрика — `ActionsBar.Button(...)`.
165
+ * API: `setTitle(text)`, `getTitle()`, `setLabel(text)`, `setOrientation(o)`, `getOrientation()`,
166
+ * `addAction(options)`, `removeAction(id)`, `setActions(list)`, `clear()`, `getActions()`,
167
+ * `getAction(id)`, `setActive(id, bool)`, `toggle(id)`, `getActive()`, `setDisabled(id, bool)`,
168
+ * `setBadge(id, count)`, `getToolbar()`, `set()`, `destroy()`.
169
+ * Константа: `ActionsBar.ORIENTATION.{HORIZONTAL, VERTICAL}`.
170
+ *
171
+ * **Строка или столбец.** По умолчанию действия идут строкой с переносом — так панель стоит
172
+ * под шапкой разговора. `ORIENTATION.VERTICAL` ставит их столбцом во всю ширину, как пункты
173
+ * меню: в таком виде панель кладут в выпадающее меню — например, в кнопку «⋯» полосы
174
+ * пользователя (`UserBar({ menu: [...] })`), и отдельная полоса в шапке не нужна.
175
+ */
176
+ class ActionsBar {
177
+ /** Фабрика действия: `ActionsBar.Button({ icon, label, toggle, danger, onClick })` */
178
+ static Button = (options = {}) => new ActionsBarButton(options);
179
+ static ORIENTATION = Toolbar.ORIENTATION;
180
+ static #counter = 0;
181
+
182
+ #id = undefined;
183
+ #root = document.createElement("chui_actions_bar");
184
+ #title = document.createElement("actions_bar_title");
185
+ #toolbar = undefined;
186
+ #actions = [];
187
+ #label = undefined;
188
+ #orientation = undefined;
189
+ #onChange = undefined;
190
+
191
+ constructor(
192
+ options = {
193
+ id: String(),
194
+ title: String(),
195
+ label: String(),
196
+ orientation: String(),
197
+ actions: [],
198
+ onChange: undefined,
199
+ }
200
+ ) {
201
+ setStyles(__dirname + "/styles.css", "chUiJS_ActionsBar");
202
+ ActionsBar.#counter += 1;
203
+ this.#id = options.id === undefined ? `chui_actions_bar_${ActionsBar.#counter}` : options.id;
204
+ this.#root.id = this.#id;
205
+ this.#label = options.label;
206
+ this.#onChange = typeof options.onChange === "function" ? options.onChange : undefined;
207
+ this.#toolbar = new Toolbar({ label: this.#labelText() });
208
+ this.setOrientation(
209
+ options.orientation === undefined ? ActionsBar.ORIENTATION.HORIZONTAL : options.orientation
210
+ );
211
+ this.#title.hidden = true;
212
+ this.#root.append(this.#title, this.#toolbar.set());
213
+ if (options.title !== undefined) this.setTitle(options.title);
214
+ if (Array.isArray(options.actions)) for (const action of options.actions) this.addAction(action);
215
+ }
216
+
217
+ // GET
218
+ getId() {
219
+ return this.#id;
220
+ }
221
+ getTitle() {
222
+ return this.#title.hidden ? "" : this.#title.innerText;
223
+ }
224
+ getOrientation() {
225
+ return this.#orientation;
226
+ }
227
+ getActions() {
228
+ return this.#actions.slice();
229
+ }
230
+ getAction(id = String()) {
231
+ return this.#actions.find((button) => button.getId() === String(id));
232
+ }
233
+ /** Идентификаторы включённых переключателей — состояние панели одним списком */
234
+ getActive() {
235
+ return this.#actions.filter((button) => button.isActive() === true).map((button) => button.getId());
236
+ }
237
+ /** Полоса действий: нужна тому, кто ставит в панель своё рядом с кнопками */
238
+ getToolbar() {
239
+ return this.#toolbar;
240
+ }
241
+
242
+ // SET
243
+ /** Подпись панели: над действиями, объясняет, к чему они относятся */
244
+ setTitle(text = String()) {
245
+ this.#title.innerText = String(text);
246
+ this.#title.hidden = this.#title.innerText.trim() === "";
247
+ return this;
248
+ }
249
+ /** Имя панели для скринридера: по умолчанию — общая подпись из словаря */
250
+ setLabel(text = String()) {
251
+ this.#label = text;
252
+ this.#toolbar.setLabel(this.#labelText());
253
+ return this;
254
+ }
255
+ /**
256
+ * Строка или столбец: столбец — вид меню, в нём действия идут во всю ширину.
257
+ * Ориентацию несёт и полоса внутри (`Toolbar`), и сама панель — по ней считаются отступы
258
+ */
259
+ setOrientation(orientation = String()) {
260
+ this.#orientation =
261
+ orientation === ActionsBar.ORIENTATION.VERTICAL
262
+ ? ActionsBar.ORIENTATION.VERTICAL
263
+ : ActionsBar.ORIENTATION.HORIZONTAL;
264
+ this.#root.dataset.orientation = this.#orientation;
265
+ this.#toolbar.setOrientation(this.#orientation);
266
+ return this;
267
+ }
268
+ setActions(actions = []) {
269
+ this.clear();
270
+ for (const action of actions) this.addAction(action);
271
+ return this;
272
+ }
273
+ addAction(options = {}) {
274
+ const button =
275
+ options instanceof ActionsBarButton
276
+ ? options
277
+ : new ActionsBarButton(Object.assign({}, options, { onToggle: (instance) => this.#notify(instance) }));
278
+ this.#actions.push(button);
279
+ this.#toolbar.add(button);
280
+ return button;
281
+ }
282
+ removeAction(id = String()) {
283
+ const index = this.#actions.findIndex((button) => button.getId() === String(id));
284
+ if (index === -1) return this;
285
+ const [button] = this.#actions.splice(index, 1);
286
+ button.set().remove();
287
+ return this;
288
+ }
289
+ clear() {
290
+ for (const button of this.#actions) button.set().remove();
291
+ this.#actions = [];
292
+ return this;
293
+ }
294
+ /** Включить или выключить переключатель из кода: состояние то же, что после щелчка */
295
+ setActive(id = String(), boolean = Boolean()) {
296
+ const button = this.getAction(id);
297
+ if (button === undefined || button.isActive() === boolean) return this;
298
+ button.setActive(boolean);
299
+ this.#notify(button);
300
+ return this;
301
+ }
302
+ toggle(id = String()) {
303
+ const button = this.getAction(id);
304
+ if (button === undefined) return this;
305
+ return this.setActive(id, button.isActive() === false);
306
+ }
307
+ setDisabled(id = String(), boolean = Boolean()) {
308
+ const button = this.getAction(id);
309
+ if (button !== undefined) button.setDisabled(boolean);
310
+ return this;
311
+ }
312
+ setBadge(id = String(), count = Number()) {
313
+ const button = this.getAction(id);
314
+ if (button !== undefined) button.setBadge(count);
315
+ return this;
316
+ }
317
+
318
+ set() {
319
+ return this.#root;
320
+ }
321
+ destroy() {
322
+ this.#actions = [];
323
+ this.#onChange = undefined;
324
+ this.#root.remove();
325
+ }
326
+
327
+ // === ВНУТРЕННЕЕ ===
328
+ /** О смене состояния сообщается один раз: и после щелчка, и после правки из кода */
329
+ #notify(button) {
330
+ if (this.#onChange !== undefined) this.#onChange(button, this);
331
+ }
332
+ #labelText() {
333
+ return optionOr(this.#label, undefined) === undefined ? I18n.t("feedback.actionsBar") : String(this.#label);
334
+ }
335
+ }
336
+
337
+ exports.ActionsBar = ActionsBar;
338
+ exports.ActionsBarButton = ActionsBarButton;