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
@@ -44,12 +44,12 @@ class Watermark {
44
44
  #id = undefined;
45
45
  #text = undefined;
46
46
  #image = undefined;
47
- #image_node = undefined;
47
+ #imageNode = undefined;
48
48
  #font = { size: Watermark.#DEFAULT_FONT_SIZE, weight: 400, family: undefined, color: undefined };
49
49
  #rotate = Watermark.#DEFAULT_ROTATE;
50
50
  #gap = { ...Watermark.#DEFAULT_GAP };
51
51
  #opacity = Watermark.#DEFAULT_OPACITY;
52
- #z_index = Watermark.#DEFAULT_Z_INDEX;
52
+ #zIndex = Watermark.#DEFAULT_Z_INDEX;
53
53
  #scheme = undefined;
54
54
  #destroyed = false;
55
55
 
@@ -123,7 +123,7 @@ class Watermark {
123
123
  }
124
124
 
125
125
  getZIndex() {
126
- return this.#z_index;
126
+ return this.#zIndex;
127
127
  }
128
128
 
129
129
  /** Слой с метками (для своих стилей) */
@@ -156,12 +156,12 @@ class Watermark {
156
156
  */
157
157
  setImage(image = undefined) {
158
158
  this.#image = image;
159
- this.#image_node = undefined;
159
+ this.#imageNode = undefined;
160
160
  if (image !== undefined && image !== null && image !== "") {
161
161
  const node = new Image();
162
162
  node.onload = () => this.#draw();
163
163
  node.src = Watermark.#resolveSource(image);
164
- this.#image_node = node;
164
+ this.#imageNode = node;
165
165
  }
166
166
  this.#draw();
167
167
  return this;
@@ -202,8 +202,8 @@ class Watermark {
202
202
 
203
203
  setZIndex(zIndex = Number()) {
204
204
  const number = Number(zIndex);
205
- this.#z_index = Number.isFinite(number) ? number : Watermark.#DEFAULT_Z_INDEX;
206
- this.#root.style.setProperty("--watermark_z_index", String(this.#z_index));
205
+ this.#zIndex = Number.isFinite(number) ? number : Watermark.#DEFAULT_Z_INDEX;
206
+ this.#root.style.setProperty("--watermark_z_index", String(this.#zIndex));
207
207
  return this;
208
208
  }
209
209
 
@@ -280,26 +280,26 @@ class Watermark {
280
280
  #draw() {
281
281
  if (this.#destroyed) return this;
282
282
  const text = this.#text === undefined ? "" : String(this.#text);
283
- const image = this.#image_node;
284
- const has_image = image !== undefined && image.complete === true && image.naturalWidth > 0;
285
- const has_text = text !== "";
283
+ const image = this.#imageNode;
284
+ const hasImage = image !== undefined && image.complete === true && image.naturalWidth > 0;
285
+ const hasText = text !== "";
286
286
  // Метки нечем рисовать — слой не показываем, чтобы не занимать место
287
- this.#layer.hidden = has_text === false && has_image === false;
287
+ this.#layer.hidden = hasText === false && hasImage === false;
288
288
  if (this.#layer.hidden) {
289
289
  // Снимаем значение, а не ставим `none`: базовое состояние даёт CSS
290
290
  this.#layer.style.removeProperty("background-image");
291
291
  return this;
292
292
  }
293
- const pixel_ratio = typeof window !== "undefined" && window.devicePixelRatio > 0 ? window.devicePixelRatio : 1;
293
+ const pixelRatio = typeof window !== "undefined" && window.devicePixelRatio > 0 ? window.devicePixelRatio : 1;
294
294
  const tile = { x: this.#gap.x, y: this.#gap.y };
295
295
  // Плитка = две метки по каждой оси: на стыках соседних плиток рисунок сходится
296
296
  const canvas = document.createElement("canvas");
297
- canvas.width = Math.round(tile.x * 2 * pixel_ratio);
298
- canvas.height = Math.round(tile.y * 2 * pixel_ratio);
297
+ canvas.width = Math.round(tile.x * 2 * pixelRatio);
298
+ canvas.height = Math.round(tile.y * 2 * pixelRatio);
299
299
  const context = canvas.getContext("2d");
300
- context.scale(pixel_ratio, pixel_ratio);
300
+ context.scale(pixelRatio, pixelRatio);
301
301
  context.globalAlpha = this.#opacity;
302
- if (has_text) {
302
+ if (hasText) {
303
303
  const font = this.#resolveFont();
304
304
  context.font = `${font.weight} ${font.size}px ${font.family}`;
305
305
  context.fillStyle = font.color;
@@ -315,7 +315,7 @@ class Watermark {
315
315
  }
316
316
  }
317
317
  }
318
- if (has_image) {
318
+ if (hasImage) {
319
319
  const max = Math.min(tile.x, tile.y) * 0.6;
320
320
  const ratio = Math.min(max / image.naturalWidth, max / image.naturalHeight, 1);
321
321
  const width = image.naturalWidth * ratio;
@@ -36,9 +36,9 @@ class WebView {
36
36
  webPreferences: "javascript=yes",
37
37
  };
38
38
  #id = require("randomstring").generate();
39
- #main_block = document.createElement("chui_webview");
39
+ #mainBlock = document.createElement("chui_webview");
40
40
  #WebView = document.createElement("webview");
41
- #chui_load = document.createElement("chui_load");
41
+ #chuiLoad = document.createElement("chui_load");
42
42
  constructor(url = String(), dev = false, options = {}) {
43
43
  require("../../modules/chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_WebView");
44
44
  const settings = { ...WebView.DEFAULTS };
@@ -66,15 +66,15 @@ class WebView {
66
66
  );
67
67
  if (settings.partition !== undefined) this.#WebView.setAttribute("partition", String(settings.partition));
68
68
  if (settings.preload !== undefined) this.#WebView.setAttribute("preload", String(settings.preload));
69
- let spinner = new Spinner(Spinner.SIZE.BIG);
70
- this.#chui_load.appendChild(spinner.set());
71
- this.#main_block.appendChild(this.#WebView);
69
+ const spinner = new Spinner(Spinner.SIZE.BIG);
70
+ this.#chuiLoad.appendChild(spinner.set());
71
+ this.#mainBlock.appendChild(this.#WebView);
72
72
  const loadStart = () => {
73
- this.#main_block.appendChild(this.#chui_load);
73
+ this.#mainBlock.appendChild(this.#chuiLoad);
74
74
  };
75
75
  const loadStop = () => {
76
76
  new Animation(this.#WebView).fadeIn();
77
- this.#main_block.removeChild(this.#chui_load);
77
+ this.#mainBlock.removeChild(this.#chuiLoad);
78
78
  if (dev) this.#WebView.openDevTools();
79
79
  };
80
80
  this.#WebView.addEventListener("did-start-loading", loadStart);
@@ -106,7 +106,7 @@ class WebView {
106
106
  this.#WebView.addEventListener("did-frame-finish-load", async () => {
107
107
  const fs = require("fs");
108
108
  if (options.cssPath !== undefined) {
109
- let data = fs.readFileSync(options.cssPath, "utf8");
109
+ const data = fs.readFileSync(options.cssPath, "utf8");
110
110
  this.#WebView.insertCSS(String(data)).catch((r) => Log.error(r));
111
111
  }
112
112
  if (options.jsPath !== undefined) {
@@ -131,7 +131,7 @@ class WebView {
131
131
  return this.#WebView.getWebContentsId();
132
132
  }
133
133
  set() {
134
- return this.#main_block;
134
+ return this.#mainBlock;
135
135
  }
136
136
  }
137
137
 
@@ -0,0 +1,172 @@
1
+ /* Панель ввода: строка «вложения — текст — отправка».
2
+ Панель не полагает, где она стоит: в карточке она читается кромкой той же карточки,
3
+ а поверх содержимого её поднимают тенью (`data-shadow="true"`), которую включает
4
+ приложение — заодно с `StickyBar`, если панель должна прилипать к низу окна. */
5
+
6
+ chui_write_bar {
7
+ display: block;
8
+ box-sizing: border-box;
9
+ width: -webkit-fill-available;
10
+ }
11
+
12
+ write_bar_row {
13
+ display: flex;
14
+ flex-direction: row;
15
+ /* Кнопки встают по низу: пока поле в одну линию, разницы нет, а на выросшем поле
16
+ строка кнопок остаётся там же, где была, — она не едет за текстом */
17
+ align-items: flex-end;
18
+ gap: 6px;
19
+ box-sizing: border-box;
20
+ width: -webkit-fill-available;
21
+ padding: var(--control_padding);
22
+ background: var(--element_background_2);
23
+ border-top: 1px solid var(--border_color);
24
+ }
25
+
26
+ /* Тень вместо верхней линии: панель поверх содержимого отделяется материалом, а не чертой */
27
+ chui_write_bar[data-shadow="true"] write_bar_row {
28
+ border-top-color: transparent;
29
+ box-shadow: var(--box_shadow_main);
30
+ }
31
+
32
+ /* Пустой слот места не занимает: у панели без вложений слева нет зазора */
33
+ write_bar_slot {
34
+ display: flex;
35
+ flex-direction: row;
36
+ align-items: center;
37
+ gap: 2px;
38
+ flex: none;
39
+ }
40
+
41
+ write_bar_slot:empty {
42
+ display: none;
43
+ }
44
+
45
+ .write_bar_field {
46
+ flex: 1 1 auto;
47
+ min-width: 0;
48
+ box-sizing: border-box;
49
+ resize: none;
50
+ /* Коробка поля — как у остальных полей фреймворка: заливка контрола, волосяная линия
51
+ и радиус. Отступы и строка подобраны под `--control_height`, поэтому в одну линию
52
+ поле ровно той же высоты, что кнопки панели, и строка читается одной линией:
53
+ с прежними отступами 8px поле было на 8px выше кнопок и панель выглядела кривой. */
54
+ min-height: var(--control_height);
55
+ padding: calc(var(--control_padding_sm) - 1px) 10px;
56
+ line-height: 1.25;
57
+ /* Прокрутка появляется только на выросшем поле: в одну линию у поля нет полосы,
58
+ которая отнимала бы ширину у текста (предел роста задаёт JS — maxHeight) */
59
+ overflow-y: hidden;
60
+ border-radius: var(--radius_sm);
61
+ border: 1px solid var(--border_color);
62
+ background: var(--element_background);
63
+ color: var(--text_color);
64
+ font-family: inherit;
65
+ font-size: var(--font_default_size);
66
+ }
67
+
68
+ .write_bar_field[data-scrollable="true"] {
69
+ overflow-y: auto;
70
+ }
71
+
72
+ .write_bar_field::placeholder {
73
+ color: var(--text_color_disabled);
74
+ }
75
+
76
+ .write_bar_field:hover:not(:disabled) {
77
+ border-color: var(--border_color_hover);
78
+ }
79
+
80
+ /* Активное поле выдаёт кромка, а не кольцо: в панели ввода каретка и так показывает,
81
+ где набирают, а контур вокруг строки рябит на каждом щелчке по полю */
82
+ .write_bar_field:focus {
83
+ outline: none;
84
+ border-color: var(--blue_prime_background);
85
+ }
86
+
87
+ .write_bar_field:disabled {
88
+ cursor: not-allowed;
89
+ background: var(--element_background_disabled);
90
+ color: var(--text_color_disabled);
91
+ }
92
+
93
+ /* Кнопка панели: круг со значком, подпись живёт в `aria-label` и подсказке */
94
+ .write_bar_button {
95
+ position: relative;
96
+ flex: none;
97
+ cursor: pointer;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ box-sizing: border-box;
102
+ width: var(--control_height);
103
+ height: var(--control_height);
104
+ padding: 0;
105
+ border: none;
106
+ border-radius: var(--radius_circle);
107
+ background: transparent;
108
+ color: var(--text_color);
109
+ }
110
+
111
+ .write_bar_button:hover {
112
+ background: var(--element_background_hover);
113
+ }
114
+
115
+ /* Нажатие — акцент, как у Button: контрол отвечает на клик ещё до действия */
116
+ .write_bar_button:active:not(:disabled) {
117
+ background: var(--blue_prime_background);
118
+ color: var(--text_color_hover);
119
+ }
120
+
121
+ .write_bar_button:disabled {
122
+ cursor: not-allowed;
123
+ background: transparent;
124
+ color: var(--text_color_disabled);
125
+ }
126
+
127
+ /* Отправка — акцентная кнопка панели: она одна здесь означает «сделать» */
128
+ .write_bar_button[data-accent="true"] {
129
+ background: var(--blue_prime_background);
130
+ color: var(--text_color_hover);
131
+ }
132
+
133
+ .write_bar_button[data-accent="true"]:hover {
134
+ background: var(--blue_prime_background_tint_strong);
135
+ }
136
+
137
+ .write_bar_button[data-accent="true"]:disabled {
138
+ cursor: not-allowed;
139
+ background: var(--button_background_disabled);
140
+ color: var(--text_color_disabled);
141
+ }
142
+
143
+ .write_bar_button chui_icon {
144
+ color: inherit;
145
+ }
146
+
147
+ /* Счётчик кнопки: число выбранного важнее подписи, поэтому плашка в углу круга */
148
+ write_bar_badge {
149
+ position: absolute;
150
+ top: -2px;
151
+ inset-inline-end: -2px;
152
+ box-sizing: border-box;
153
+ min-width: 16px;
154
+ height: 16px;
155
+ padding-inline: 4px;
156
+ border-radius: var(--radius_pill);
157
+ background: var(--red_prime_background);
158
+ color: var(--text_color_hover);
159
+ font-size: 10px;
160
+ font-weight: 600;
161
+ line-height: 16px;
162
+ text-align: center;
163
+ }
164
+
165
+ write_bar_badge[hidden] {
166
+ display: none;
167
+ }
168
+
169
+ /* DISABLED STYLES */
170
+ chui_write_bar[data-disabled="true"] write_bar_row {
171
+ background: var(--element_background_disabled);
172
+ }
@@ -0,0 +1,320 @@
1
+ const { Icon, Icons } = require("../chui_icons/icons");
2
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
3
+ const { optionOr, setStyles } = require("../../modules/chui_functions");
4
+
5
+ /** Предел роста поля: дальше текст уходит в прокрутку самого поля, а не растёт панель */
6
+ const DEFAULT_MAX_HEIGHT = 120;
7
+
8
+ /**
9
+ * Кнопка панели ввода: значок с подписью для скринридера и счётчиком.
10
+ *
11
+ * Это настоящий `<button>`, а не кастомный тег: клавиатура, фокус и роль у него уже есть,
12
+ * и скринридер читает его как кнопку без дополнительных договоров.
13
+ *
14
+ * Подпись обязательна — лигатура значка («attach_file») ничего не говорит скринридеру,
15
+ * а сам значок скрыт от дерева доступности (`Icon` ставит `aria-hidden`). Счётчик нужен
16
+ * кнопкам, за которыми стоит число: выбранные вложения, эмодзи, черновик.
17
+ */
18
+ class WriteBarIcon {
19
+ #root = document.createElement("button");
20
+ #label = String();
21
+ #onClick = undefined;
22
+ #disabled = false;
23
+ #badge = document.createElement("write_bar_badge");
24
+
25
+ constructor(options = {}) {
26
+ const icon = optionOr(options.icon, String());
27
+ const label = optionOr(options.label, String());
28
+ this.#label = label === "" ? I18n.t("feedback.writeBarButton") : label;
29
+ this.#root.type = "button";
30
+ this.#root.className = "write_bar_button";
31
+ if (icon !== "") this.#root.innerHTML = new Icon(icon, "20px").getHTML();
32
+ if (options.accent === true) this.#root.dataset.accent = "true";
33
+ this.#root.setAttribute("aria-label", this.#label);
34
+ // Подсказка мышью повторяет имя кнопки: подписи в панели нет, а догадываться — не дело
35
+ this.#root.title = this.#label;
36
+ this.#onClick = typeof options.onClick === "function" ? options.onClick : undefined;
37
+ this.#root.addEventListener("click", () => {
38
+ if (this.#disabled === true) return;
39
+ if (this.#onClick !== undefined) this.#onClick(this);
40
+ });
41
+ if (options.badge !== undefined) this.setBadge(options.badge);
42
+ if (options.disabled === true) this.setDisabled(true);
43
+ }
44
+
45
+ /** Счётчик на кнопке: число показывает плашка, скринридеру оно попадает в подпись */
46
+ setBadge(count = Number()) {
47
+ const value = Number(count);
48
+ const shown = Number.isFinite(value) && value > 0 ? String(value) : "";
49
+ this.#badge.innerText = shown;
50
+ this.#badge.hidden = shown === "";
51
+ this.#root.setAttribute(
52
+ "aria-label",
53
+ shown === "" ? this.#label : `${this.#label}, ${I18n.t("feedback.writeBarBadge", { value: shown })}`
54
+ );
55
+ if (shown !== "" && this.#badge.parentNode === null) this.#root.appendChild(this.#badge);
56
+ return this;
57
+ }
58
+
59
+ setDisabled(boolean = Boolean()) {
60
+ this.#disabled = boolean === true;
61
+ this.#root.disabled = this.#disabled;
62
+ return this;
63
+ }
64
+
65
+ setLabel(text = String()) {
66
+ this.#label = String(text);
67
+ this.#root.setAttribute("aria-label", this.#label);
68
+ this.#root.title = this.#label;
69
+ return this;
70
+ }
71
+
72
+ set() {
73
+ return this.#root;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Панель ввода — нижняя строка приложения: слева вложения и эмодзи, по центру поле текста,
79
+ * справа отправка.
80
+ *
81
+ * Поле растёт под содержимое до `maxHeight` (по умолчанию 120 px), дальше текст уходит
82
+ * в прокрутку поля — панель места не отнимает. Enter отправляет, Shift+Enter переносит
83
+ * строку (`setEnterSends(false)` возвращает обычное поле с переносом), во время набора
84
+ * иероглифов Enter выбирает вариант и не отправляет.
85
+ *
86
+ * Опции: `id`, `placeholder`, `value`, `maxHeight`, `disabled`, `shadow` (тень вместо
87
+ * верхней волосяной линии — для панели поверх содержимого), `enterSends`, `sendText`
88
+ * (подпись кнопки отправки), `buttons` (кнопки панели), `onSend`.
89
+ *
90
+ * Методы: `getValue()`, `setValue(text)`, `clear()`, `send()`, `addButton(options)`,
91
+ * `addSendListener(fn)`, `removeSendListener(fn)`, `setPlaceholder(text)`, `setDisabled(bool)`,
92
+ * `isDisabled()`, `setEnterSends(bool)`, `setSendLabel(text)`, `focus()`, `blur()`, `getField()`,
93
+ * `getSendButton()`, `set()`, `destroy()`.
94
+ *
95
+ * Кнопки панели собираются фабрикой `WriteBar.Icon({ icon, label, onClick, badge, accent })`:
96
+ * своей кнопке нужен значок из `Icons` и подпись для скринридера.
97
+ *
98
+ * Прилипание к низу окна компонент не делает сам — для этого в фреймворке есть `StickyBar`;
99
+ * панель не полагает, где она стоит, и потому одинаково работает и в карточке, и в окне.
100
+ */
101
+ class WriteBar {
102
+ /** Фабрика кнопок панели: `WriteBar.Icon({ icon, label, onClick, badge, accent })` */
103
+ static Icon = (options = {}) => new WriteBarIcon(options);
104
+
105
+ #root = document.createElement("chui_write_bar");
106
+ #row = document.createElement("write_bar_row");
107
+ #field = document.createElement("textarea");
108
+ #before = document.createElement("write_bar_slot");
109
+ #after = document.createElement("write_bar_slot");
110
+ #send = undefined;
111
+ #icons = [];
112
+ #maxHeight = DEFAULT_MAX_HEIGHT;
113
+ #disabled = false;
114
+ #enterSends = true;
115
+ #onSend = undefined;
116
+ #sendListeners = [];
117
+
118
+ constructor(options = {}) {
119
+ setStyles(__dirname + "/styles.css", "chUiJS_WriteBar");
120
+ if (options.id !== undefined) this.#root.id = options.id;
121
+ const maxHeight = Number(options.maxHeight);
122
+ this.#maxHeight = Number.isFinite(maxHeight) && maxHeight > 0 ? maxHeight : DEFAULT_MAX_HEIGHT;
123
+ this.#onSend = typeof options.onSend === "function" ? options.onSend : undefined;
124
+ this.#enterSends = options.enterSends !== false;
125
+
126
+ this.#field.className = "write_bar_field";
127
+ // Одна строка: `rows` держит высоту в покое, растущую высоту ведёт `#autosize`
128
+ this.#field.rows = 1;
129
+ this.#field.setAttribute("aria-multiline", "true");
130
+ this.#field.setAttribute("aria-keyshortcuts", "Enter");
131
+ this.#field.setAttribute("aria-label", I18n.t("feedback.writeBarField"));
132
+ this.#syncHint();
133
+ this.#root.setAttribute("role", "group");
134
+ this.#root.setAttribute("aria-label", I18n.t("feedback.writeBar"));
135
+
136
+ this.#send = new WriteBarIcon({
137
+ icon: Icons.CONTENT.SEND,
138
+ label: I18n.t("feedback.writeBarSend"),
139
+ accent: true,
140
+ onClick: () => this.send(),
141
+ });
142
+ this.#after.appendChild(this.#send.set());
143
+ // Слоты: слева — вложения и эмодзи, справа — свои действия перед отправкой
144
+ this.#row.append(this.#before, this.#field, this.#after);
145
+ this.#root.appendChild(this.#row);
146
+
147
+ this.#field.addEventListener("input", () => {
148
+ this.#autosize();
149
+ this.#syncSendState();
150
+ });
151
+ this.#field.addEventListener("keydown", (event) => this.#onKeydown(event));
152
+
153
+ this.setPlaceholder(
154
+ options.placeholder === undefined ? I18n.t("feedback.writeBarPlaceholder") : options.placeholder
155
+ );
156
+ if (options.sendText !== undefined) this.setSendLabel(options.sendText);
157
+ if (options.value !== undefined) this.setValue(options.value);
158
+ if (Array.isArray(options.buttons)) for (const button of options.buttons) this.#addBefore(button);
159
+ if (options.shadow === true) this.#root.dataset.shadow = "true";
160
+ if (options.disabled === true) this.setDisabled(true);
161
+ this.#autosize();
162
+ this.#syncSendState();
163
+ }
164
+
165
+ // GET
166
+ getValue() {
167
+ return this.#field.value;
168
+ }
169
+ isDisabled() {
170
+ return this.#disabled;
171
+ }
172
+ getField() {
173
+ return this.#field;
174
+ }
175
+ getSendButton() {
176
+ return this.#send;
177
+ }
178
+ /** Кнопки, добавленные слотом (отправка сюда не входит — у неё своё место) */
179
+ getIcons() {
180
+ return this.#icons.slice();
181
+ }
182
+
183
+ // SET
184
+ setValue(text = String()) {
185
+ this.#field.value = String(text);
186
+ this.#autosize();
187
+ this.#syncSendState();
188
+ return this;
189
+ }
190
+ clear() {
191
+ return this.setValue("");
192
+ }
193
+ setPlaceholder(text = String()) {
194
+ this.#field.placeholder = String(text);
195
+ return this;
196
+ }
197
+ setSendLabel(text = String()) {
198
+ this.#send.setLabel(String(text));
199
+ return this;
200
+ }
201
+ setDisabled(boolean = Boolean()) {
202
+ this.#disabled = boolean === true;
203
+ this.#root.dataset.disabled = this.#disabled ? "true" : "false";
204
+ this.#field.disabled = this.#disabled;
205
+ this.#syncSendState();
206
+ return this;
207
+ }
208
+ /**
209
+ * Enter отправляет (по умолчанию) или переносит строку. Выключают там, где панель —
210
+ * обычное поле в форме: случайная отправка посреди набора там дороже лишнего щелчка.
211
+ */
212
+ setEnterSends(boolean = Boolean()) {
213
+ this.#enterSends = boolean === true;
214
+ this.#syncHint();
215
+ return this;
216
+ }
217
+ focus() {
218
+ this.#field.focus();
219
+ return this;
220
+ }
221
+ blur() {
222
+ this.#field.blur();
223
+ return this;
224
+ }
225
+
226
+ /** Кнопка в левом слоте — вложения, эмодзи, что угодно со значком и подписью */
227
+ addButton(options = {}) {
228
+ const button = options instanceof WriteBarIcon ? options : new WriteBarIcon(options);
229
+ this.#icons.push(button);
230
+ return button;
231
+ }
232
+ #addBefore(options = {}) {
233
+ const button = this.addButton(options);
234
+ this.#before.appendChild(button.set());
235
+ }
236
+
237
+ /** Отправка: пустое поле и выключенная панель ничего не делают — как нажатие кнопки */
238
+ send() {
239
+ if (this.#disabled === true) return this;
240
+ const text = this.#field.value.trim();
241
+ if (text === "") return this;
242
+ this.#field.value = "";
243
+ this.#autosize();
244
+ this.#syncSendState();
245
+ if (this.#onSend !== undefined) this.#onSend(text, this);
246
+ for (const listener of this.#sendListeners) listener(text, this);
247
+ this.focus();
248
+ return this;
249
+ }
250
+ addSendListener(listener = () => {}) {
251
+ if (typeof listener === "function") this.#sendListeners.push(listener);
252
+ return this;
253
+ }
254
+ /** Снять слушателя отправки: нужен тому, кто связывает панель с лентой и разрывает связь */
255
+ removeSendListener(listener = () => {}) {
256
+ const index = this.#sendListeners.indexOf(listener);
257
+ if (index !== -1) this.#sendListeners.splice(index, 1);
258
+ return this;
259
+ }
260
+
261
+ set() {
262
+ return this.#root;
263
+ }
264
+ destroy() {
265
+ this.#sendListeners = [];
266
+ this.#icons = [];
267
+ this.#onSend = undefined;
268
+ this.#root.remove();
269
+ }
270
+
271
+ // === ВНУТРЕННЕЕ ===
272
+ /**
273
+ * Подсказка про Enter висит на поле только тогда, когда Enter действительно отправляет:
274
+ * иначе она обещала бы то, чего панель не делает.
275
+ */
276
+ #syncHint() {
277
+ if (this.#enterSends === true) this.#field.title = I18n.t("feedback.writeBarHint");
278
+ else this.#field.removeAttribute("title");
279
+ }
280
+ /** Enter отправляет, Shift+Enter переносит; во время набора иероглифов Enter не наш */
281
+ #onKeydown(event = {}) {
282
+ if (this.#enterSends === false) return;
283
+ if (event.key !== "Enter" || event.shiftKey === true) return;
284
+ if (event.isComposing === true) return;
285
+ event.preventDefault();
286
+ this.send();
287
+ }
288
+ /**
289
+ * Высота поля по содержимому, до предела. Сначала высоту снимаем (`auto`): иначе поле
290
+ * никогда не уменьшается — `scrollHeight` не бывает меньше текущей высоты. На пределе
291
+ * содержимое уходит в прокрутку поля, а панель перестаёт расти.
292
+ *
293
+ * Пустое поле высоты не получает вовсе: её держит `rows="1"` и нижний предел стилей
294
+ * (`min-height: --control_height` — по нему же выровнены кнопки панели), поэтому
295
+ * панель не прыгает на первом же символе: в одну линию поле всегда высотой с кнопку.
296
+ */
297
+ #autosize() {
298
+ const field = this.#field;
299
+ const limit = this.#maxHeight;
300
+ if (field.value === "") {
301
+ field.style.height = "";
302
+ field.dataset.scrollable = "false";
303
+ return;
304
+ }
305
+ field.style.height = "auto";
306
+ // `scrollHeight` — содержимое с отступами, но без рамки: в `border-box` её надо добавить,
307
+ // иначе поле «съёживается» на толщину рамки при первом же символе
308
+ const border = field.offsetHeight - field.clientHeight;
309
+ const height = field.scrollHeight + border;
310
+ field.style.height = Math.min(height, limit) + "px";
311
+ field.dataset.scrollable = height > limit ? "true" : "false";
312
+ }
313
+ /** Отправка гаснет на пустом поле: так панель говорит, что отправлять нечего */
314
+ #syncSendState() {
315
+ this.#send.setDisabled(this.#disabled === true || this.#field.value.trim() === "");
316
+ }
317
+ }
318
+
319
+ exports.WriteBar = WriteBar;
320
+ exports.WriteBarIcon = WriteBarIcon;