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.
- package/CHANGELOG.md +536 -0
- package/framework/appLayout/default/chui_app_layout.js +361 -303
- package/framework/appLayout/default/global_style.css +243 -89
- package/framework/appLayout/default/main_theme_style.css +188 -101
- package/framework/components/chui_accordion/accordion.js +17 -17
- package/framework/components/chui_accordion/styles.css +7 -7
- package/framework/components/chui_action_sheet/action_sheet.js +35 -32
- package/framework/components/chui_action_sheet/styles.css +10 -4
- package/framework/components/chui_actions_bar/actions_bar.js +338 -0
- package/framework/components/chui_actions_bar/styles.css +139 -0
- package/framework/components/chui_affix/affix.js +4 -4
- package/framework/components/chui_affix/styles.css +5 -0
- package/framework/components/chui_alert/alert.js +11 -5
- package/framework/components/chui_alert/styles.css +7 -0
- package/framework/components/chui_avatar/avatar.js +13 -13
- package/framework/components/chui_avatar/styles.css +1 -1
- package/framework/components/chui_avatar_group/avatar_group.js +4 -4
- package/framework/components/chui_avatar_group/styles.css +1 -1
- package/framework/components/chui_backtop/backtop.js +25 -2
- package/framework/components/chui_backtop/styles.css +21 -0
- package/framework/components/chui_badge/badge.js +15 -15
- package/framework/components/chui_badge/styles.css +6 -6
- package/framework/components/chui_banner/banner.js +222 -0
- package/framework/components/chui_banner/styles.css +170 -0
- package/framework/components/chui_breadcrumb/styles.css +1 -1
- package/framework/components/chui_button/button.js +24 -28
- package/framework/components/chui_button/styles.css +5 -5
- package/framework/components/chui_calendar/calendar.js +6 -6
- package/framework/components/chui_card/card.js +14 -14
- package/framework/components/chui_card_scroll/card_scroll.js +169 -0
- package/framework/components/chui_card_scroll/styles.css +86 -0
- package/framework/components/chui_chat/chat.js +73 -51
- package/framework/components/chui_chat/styles.css +18 -7
- package/framework/components/chui_clipboard_history/clipboard_history.js +25 -20
- package/framework/components/chui_clipboard_history/styles.css +10 -8
- package/framework/components/chui_code/code.js +8 -8
- package/framework/components/chui_code/styles.css +2 -2
- package/framework/components/chui_code_editor/code_editor.js +26 -26
- package/framework/components/chui_code_editor/styles.css +3 -3
- package/framework/components/chui_command_palette/command_palette.js +29 -29
- package/framework/components/chui_command_palette/styles.css +5 -5
- package/framework/components/chui_console/console.js +9 -9
- package/framework/components/chui_console/styles.css +5 -2
- package/framework/components/chui_content_block/content_block.js +2 -2
- package/framework/components/chui_context_menu/context_menu.js +162 -54
- package/framework/components/chui_context_menu/styles.css +82 -8
- package/framework/components/chui_custom_element/custom_element.js +11 -11
- package/framework/components/chui_data_grid/data_grid.js +268 -268
- package/framework/components/chui_data_grid/styles.css +14 -14
- package/framework/components/chui_descriptions/styles.css +1 -1
- package/framework/components/chui_details/details.js +12 -12
- package/framework/components/chui_details/styles.css +7 -7
- package/framework/components/chui_diff_viewer/diff_viewer.js +88 -88
- package/framework/components/chui_diff_viewer/styles.css +9 -9
- package/framework/components/chui_divider/divider.js +6 -6
- package/framework/components/chui_divider/styles.css +2 -2
- package/framework/components/chui_drawer/drawer.js +37 -14
- package/framework/components/chui_drawer/styles.css +18 -4
- package/framework/components/chui_drop_zone/drop_zone.js +6 -6
- package/framework/components/chui_drop_zone/styles.css +10 -6
- package/framework/components/chui_fieldset/fieldset.js +6 -6
- package/framework/components/chui_fieldset/styles.css +5 -5
- package/framework/components/chui_file_explorer/file_explorer.js +115 -115
- package/framework/components/chui_file_explorer/styles.css +6 -6
- package/framework/components/chui_form/form.js +11 -11
- package/framework/components/chui_gantt/gantt.js +41 -41
- package/framework/components/chui_gantt/styles.css +5 -5
- package/framework/components/chui_graphs/graphs.js +82 -46
- package/framework/components/chui_graphs/styles.css +26 -5
- package/framework/components/chui_hero/hero.js +63 -20
- package/framework/components/chui_hero/styles.css +176 -7
- package/framework/components/chui_html_block/html_block.js +5 -5
- package/framework/components/chui_html_block/styles.css +1 -1
- package/framework/components/chui_hx/styles.css +2 -2
- package/framework/components/chui_icons/icons.js +7 -7
- package/framework/components/chui_icons/styles.css +2 -1
- package/framework/components/chui_inputs/chui_check_box/styles.css +12 -3
- package/framework/components/chui_inputs/chui_chips/chips.js +92 -31
- package/framework/components/chui_inputs/chui_chips/styles.css +28 -5
- package/framework/components/chui_inputs/chui_color_picker/color_picker.js +20 -15
- package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
- package/framework/components/chui_inputs/chui_combo_box/combo_box.js +58 -58
- package/framework/components/chui_inputs/chui_combo_box/styles.css +9 -9
- package/framework/components/chui_inputs/chui_date/date.js +93 -95
- package/framework/components/chui_inputs/chui_date/styles.css +8 -8
- package/framework/components/chui_inputs/chui_date_range/date_range.js +22 -22
- package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
- package/framework/components/chui_inputs/chui_datetime/datetime.js +12 -12
- package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
- package/framework/components/chui_inputs/chui_email/email.js +10 -10
- package/framework/components/chui_inputs/chui_email/styles.css +6 -6
- package/framework/components/chui_inputs/chui_file/file.js +8 -8
- package/framework/components/chui_inputs/chui_file/styles.css +12 -9
- package/framework/components/chui_inputs/chui_hotkey/hotkey.js +39 -39
- package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
- package/framework/components/chui_inputs/chui_masked/masked.js +44 -44
- package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
- package/framework/components/chui_inputs/chui_mention/mention.js +13 -13
- package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
- package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +147 -137
- package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +14 -8
- package/framework/components/chui_inputs/chui_number/number.js +42 -45
- package/framework/components/chui_inputs/chui_number/styles.css +6 -6
- package/framework/components/chui_inputs/chui_otp/otp.js +32 -32
- package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
- package/framework/components/chui_inputs/chui_password/password.js +30 -36
- package/framework/components/chui_inputs/chui_password/styles.css +7 -7
- package/framework/components/chui_inputs/chui_radio_button/styles.css +2 -2
- package/framework/components/chui_inputs/chui_rating/rating.js +28 -28
- package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
- package/framework/components/chui_inputs/chui_select_box/select_box.js +54 -54
- package/framework/components/chui_inputs/chui_select_box/styles.css +10 -10
- package/framework/components/chui_inputs/chui_slider/slider.js +14 -14
- package/framework/components/chui_inputs/chui_slider/styles.css +5 -4
- package/framework/components/chui_inputs/chui_text/styles.css +7 -7
- package/framework/components/chui_inputs/chui_text/text.js +13 -17
- package/framework/components/chui_inputs/chui_text_area/styles.css +8 -8
- package/framework/components/chui_inputs/chui_text_area/text_area.js +23 -23
- package/framework/components/chui_inputs/chui_time/styles.css +13 -17
- package/framework/components/chui_inputs/chui_time/time.js +35 -24
- package/framework/components/chui_inputs/chui_toggle/styles.css +10 -3
- package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
- package/framework/components/chui_json_editor/json_editor.js +87 -87
- package/framework/components/chui_json_editor/styles.css +5 -5
- package/framework/components/chui_json_viewer/json_viewer.js +80 -80
- package/framework/components/chui_json_viewer/styles.css +6 -6
- package/framework/components/chui_kanban/kanban.js +27 -27
- package/framework/components/chui_kanban/styles.css +8 -4
- package/framework/components/chui_kbd/styles.css +1 -1
- package/framework/components/chui_label/label.js +17 -17
- package/framework/components/chui_label/styles.css +1 -1
- package/framework/components/chui_link/link.js +9 -9
- package/framework/components/chui_link/styles.css +11 -4
- package/framework/components/chui_list/list.js +11 -11
- package/framework/components/chui_list/styles.css +2 -2
- package/framework/components/chui_markdown_editor/markdown_editor.js +66 -66
- package/framework/components/chui_markdown_editor/styles.css +5 -5
- package/framework/components/chui_media/audio.js +240 -246
- package/framework/components/chui_media/audio_styles.css +41 -23
- package/framework/components/chui_media/image.js +49 -46
- package/framework/components/chui_media/image_compare.js +35 -35
- package/framework/components/chui_media/image_compare_styles.css +5 -5
- package/framework/components/chui_media/image_styles.css +1 -1
- package/framework/components/chui_media/video.js +148 -148
- package/framework/components/chui_media/video_styles.css +27 -11
- package/framework/components/chui_menu_bar/menu_bar.js +54 -54
- package/framework/components/chui_menu_bar/styles.css +10 -10
- package/framework/components/chui_message_bar/message_bar.js +551 -0
- package/framework/components/chui_message_bar/styles.css +248 -0
- package/framework/components/chui_modal/modal.js +37 -13
- package/framework/components/chui_notification/notification.js +76 -54
- package/framework/components/chui_notification/notification_download.js +26 -81
- package/framework/components/chui_notification/notification_download_card.js +115 -0
- package/framework/components/chui_notification/notification_download_progress.css +2 -2
- package/framework/components/chui_notification/notification_download_progress.js +70 -108
- package/framework/components/chui_notification/notification_update.js +31 -31
- package/framework/components/chui_notification/status_plate.css +3 -3
- package/framework/components/chui_notification/styles.css +6 -1
- package/framework/components/chui_notification/styles_download.css +1 -1
- package/framework/components/chui_notification/styles_update.css +2 -2
- package/framework/components/chui_org_chart/org_chart.js +29 -29
- package/framework/components/chui_org_chart/styles.css +2 -2
- package/framework/components/chui_pagination/pagination.js +3 -3
- package/framework/components/chui_pagination/styles.css +1 -1
- package/framework/components/chui_paragraph/styles.css +2 -2
- package/framework/components/chui_popover/popover.js +29 -23
- package/framework/components/chui_popover/styles.css +1 -1
- package/framework/components/chui_popups/popups.js +124 -124
- package/framework/components/chui_popups/styles.css +5 -5
- package/framework/components/chui_print_button/print_button.js +15 -10
- package/framework/components/chui_progress_bar/progress_bar.js +34 -12
- package/framework/components/chui_progress_bar/styles.css +4 -4
- package/framework/components/chui_progress_ring/progress_ring.js +15 -15
- package/framework/components/chui_progress_ring/styles.css +1 -1
- package/framework/components/chui_qrcode/qrcode.js +12 -12
- package/framework/components/chui_query_builder/query_builder.js +46 -46
- package/framework/components/chui_query_builder/styles.css +4 -4
- package/framework/components/chui_radio_group/styles.css +1 -1
- package/framework/components/chui_result/result.js +15 -15
- package/framework/components/chui_result/styles.css +3 -3
- package/framework/components/chui_scheduler/scheduler.js +74 -74
- package/framework/components/chui_scheduler/styles.css +5 -5
- package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
- package/framework/components/chui_screen_spinner/styles.css +49 -0
- package/framework/components/chui_segmented_control/segmented_control.js +63 -6
- package/framework/components/chui_segmented_control/styles.css +32 -6
- package/framework/components/chui_settings/settings.js +45 -46
- package/framework/components/chui_settings/styles.css +4 -4
- package/framework/components/chui_shortcut_help/shortcut_help.js +32 -34
- package/framework/components/chui_sidebar_nav/sidebar_nav.js +98 -29
- package/framework/components/chui_sidebar_nav/styles.css +9 -3
- package/framework/components/chui_slideshow/slideshow.js +85 -85
- package/framework/components/chui_slideshow/styles.css +6 -6
- package/framework/components/chui_spinner/spinner.js +10 -10
- package/framework/components/chui_split_button/split_button.js +30 -12
- package/framework/components/chui_split_button/styles.css +21 -5
- package/framework/components/chui_splitter/splitter.js +11 -11
- package/framework/components/chui_splitter/styles.css +2 -2
- package/framework/components/chui_statistic/statistic.js +47 -47
- package/framework/components/chui_status_bar/styles.css +2 -2
- package/framework/components/chui_stepper/stepper.js +202 -31
- package/framework/components/chui_stepper/styles.css +79 -14
- package/framework/components/chui_sticky_bar/sticky_bar.js +44 -13
- package/framework/components/chui_sticky_bar/styles.css +10 -8
- package/framework/components/chui_tabs/chui_tabs.js +74 -26
- package/framework/components/chui_tabs/styles.css +40 -12
- package/framework/components/chui_text_editor/styles.css +7 -7
- package/framework/components/chui_text_editor/text_editor.js +56 -56
- package/framework/components/chui_text_editor/text_editor_panel.js +173 -174
- package/framework/components/chui_theme_toggle/theme_toggle.js +21 -5
- package/framework/components/chui_timeline/styles.css +3 -1
- package/framework/components/chui_toast_queue/styles.css +11 -11
- package/framework/components/chui_toast_queue/toast_queue.js +30 -27
- package/framework/components/chui_toolbar/styles.css +2 -2
- package/framework/components/chui_tooltip/styles.css +2 -2
- package/framework/components/chui_tooltip/tooltip.js +3 -3
- package/framework/components/chui_tour/styles.css +8 -8
- package/framework/components/chui_tour/tour.js +72 -72
- package/framework/components/chui_transfer/styles.css +6 -6
- package/framework/components/chui_transfer/transfer.js +93 -106
- package/framework/components/chui_tree_table/styles.css +4 -4
- package/framework/components/chui_tree_table/tree_table.js +45 -45
- package/framework/components/chui_tree_view/styles.css +10 -8
- package/framework/components/chui_tree_view/tree_view.js +185 -183
- package/framework/components/chui_user_bar/styles.css +145 -0
- package/framework/components/chui_user_bar/user_bar.js +402 -0
- package/framework/components/chui_user_list_bar/styles.css +173 -0
- package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
- package/framework/components/chui_watermark/watermark.js +17 -17
- package/framework/components/chui_webview/webview.js +9 -9
- package/framework/components/chui_write_bar/styles.css +172 -0
- package/framework/components/chui_write_bar/write_bar.js +320 -0
- package/framework/main_window.js +1234 -0
- package/framework/modules/chui_a11y/chui_a11y.js +78 -79
- package/framework/modules/chui_animations/animations.js +77 -15
- package/framework/modules/chui_animations/motion.js +120 -0
- package/framework/modules/chui_animations/styles.css +36 -3
- package/framework/modules/chui_functions.js +31 -27
- package/framework/modules/chui_highlight/styles.css +13 -10
- package/framework/modules/chui_i18n/chui_i18n.js +3 -3
- package/framework/modules/chui_i18n/locales/feedback.js +62 -0
- package/framework/modules/chui_i18n/locales/navigation.js +4 -0
- package/framework/modules/chui_json/chui_json.js +2 -2
- package/framework/modules/chui_logger/chui_logger.js +23 -8
- package/framework/modules/chui_page/page.js +10 -10
- package/framework/modules/chui_palette/palette.js +61 -0
- package/framework/modules/chui_qr/chui_qr.js +23 -23
- package/framework/modules/chui_scrollbar/chui_scrollbar.js +45 -45
- package/framework/modules/chui_scrollbar/styles.css +3 -3
- package/framework/modules/chui_services/chui_services.js +8 -8
- package/framework/modules/chui_services/desktop.js +77 -71
- package/framework/modules/chui_sortable/chui_sortable.js +17 -17
- package/framework/modules/chui_validate/chui_validate.js +3 -3
- package/framework/tray_bar/chui_menu_items.js +2 -2
- package/index.d.ts +266 -34
- package/index.js +20 -1168
- package/package.json +6 -4
- 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
|
-
#
|
|
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
|
-
#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
206
|
-
this.#root.style.setProperty("--watermark_z_index", String(this.#
|
|
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.#
|
|
284
|
-
const
|
|
285
|
-
const
|
|
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 =
|
|
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
|
|
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 *
|
|
298
|
-
canvas.height = Math.round(tile.y * 2 *
|
|
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(
|
|
300
|
+
context.scale(pixelRatio, pixelRatio);
|
|
301
301
|
context.globalAlpha = this.#opacity;
|
|
302
|
-
if (
|
|
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 (
|
|
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
|
-
#
|
|
39
|
+
#mainBlock = document.createElement("chui_webview");
|
|
40
40
|
#WebView = document.createElement("webview");
|
|
41
|
-
#
|
|
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
|
-
|
|
70
|
-
this.#
|
|
71
|
-
this.#
|
|
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.#
|
|
73
|
+
this.#mainBlock.appendChild(this.#chuiLoad);
|
|
74
74
|
};
|
|
75
75
|
const loadStop = () => {
|
|
76
76
|
new Animation(this.#WebView).fadeIn();
|
|
77
|
-
this.#
|
|
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
|
-
|
|
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.#
|
|
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;
|