chuijs 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +597 -0
- package/README.md +43 -644
- package/framework/appLayout/default/chui_app_layout.js +602 -496
- package/framework/appLayout/default/global_style.css +288 -97
- package/framework/appLayout/default/main_theme_style.css +188 -101
- package/framework/components/chui_accordion/accordion.js +38 -35
- package/framework/components/chui_accordion/styles.css +13 -12
- package/framework/components/chui_action_sheet/action_sheet.js +93 -64
- package/framework/components/chui_action_sheet/styles.css +12 -5
- 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 +16 -11
- package/framework/components/chui_affix/styles.css +5 -0
- package/framework/components/chui_alert/alert.js +34 -19
- package/framework/components/chui_alert/styles.css +7 -0
- package/framework/components/chui_avatar/avatar.js +36 -35
- package/framework/components/chui_avatar/styles.css +1 -1
- package/framework/components/chui_avatar_group/avatar_group.js +25 -23
- package/framework/components/chui_avatar_group/styles.css +1 -1
- package/framework/components/chui_backtop/backtop.js +60 -21
- package/framework/components/chui_backtop/styles.css +21 -0
- package/framework/components/chui_badge/badge.js +30 -28
- 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/breadcrumb.js +15 -13
- package/framework/components/chui_breadcrumb/styles.css +1 -1
- package/framework/components/chui_button/button.js +52 -36
- package/framework/components/chui_button/styles.css +7 -7
- package/framework/components/chui_calendar/calendar.js +18 -16
- package/framework/components/chui_card/card.js +52 -32
- 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 +140 -79
- package/framework/components/chui_chat/styles.css +18 -7
- package/framework/components/chui_clipboard_history/clipboard_history.js +53 -39
- package/framework/components/chui_clipboard_history/styles.css +13 -9
- package/framework/components/chui_code/code.js +10 -10
- package/framework/components/chui_code/styles.css +4 -4
- package/framework/components/chui_code_editor/code_editor.js +73 -57
- package/framework/components/chui_code_editor/styles.css +6 -5
- package/framework/components/chui_command_palette/command_palette.js +87 -64
- package/framework/components/chui_command_palette/styles.css +5 -5
- package/framework/components/chui_console/console.js +13 -13
- package/framework/components/chui_console/styles.css +6 -3
- package/framework/components/chui_content_block/content_block.js +35 -21
- package/framework/components/chui_content_block/styles.css +2 -2
- package/framework/components/chui_context_menu/context_menu.js +171 -58
- package/framework/components/chui_context_menu/styles.css +82 -8
- package/framework/components/chui_custom_element/custom_element.js +25 -18
- package/framework/components/chui_data_grid/data_grid.js +633 -471
- package/framework/components/chui_data_grid/styles.css +24 -22
- package/framework/components/chui_descriptions/descriptions.js +34 -17
- package/framework/components/chui_descriptions/styles.css +1 -1
- package/framework/components/chui_details/details.js +42 -36
- package/framework/components/chui_details/styles.css +12 -11
- package/framework/components/chui_diff_viewer/diff_viewer.js +172 -147
- package/framework/components/chui_diff_viewer/styles.css +10 -10
- package/framework/components/chui_divider/divider.js +19 -17
- package/framework/components/chui_divider/styles.css +2 -2
- package/framework/components/chui_drawer/drawer.js +63 -38
- package/framework/components/chui_drawer/styles.css +18 -4
- package/framework/components/chui_drop_zone/drop_zone.js +37 -27
- package/framework/components/chui_drop_zone/styles.css +10 -6
- package/framework/components/chui_empty_state/empty_state.js +20 -15
- package/framework/components/chui_fieldset/fieldset.js +40 -23
- package/framework/components/chui_fieldset/styles.css +6 -6
- package/framework/components/chui_file_explorer/file_explorer.js +260 -189
- package/framework/components/chui_file_explorer/styles.css +8 -7
- package/framework/components/chui_form/form.js +40 -28
- package/framework/components/chui_form/styles.css +1 -1
- package/framework/components/chui_gantt/gantt.js +102 -78
- package/framework/components/chui_gantt/styles.css +5 -5
- package/framework/components/chui_graphs/graphs.js +376 -239
- package/framework/components/chui_graphs/styles.css +27 -6
- package/framework/components/chui_hero/hero.js +98 -37
- package/framework/components/chui_hero/styles.css +176 -7
- package/framework/components/chui_html_block/html_block.js +8 -8
- package/framework/components/chui_html_block/styles.css +3 -3
- package/framework/components/chui_hx/hx.js +3 -3
- package/framework/components/chui_hx/styles.css +13 -6
- package/framework/components/chui_icons/icons.js +2115 -2115
- package/framework/components/chui_icons/styles.css +4 -3
- package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
- package/framework/components/chui_inputs/chui_check_box/styles.css +30 -21
- package/framework/components/chui_inputs/chui_chips/chips.js +141 -70
- package/framework/components/chui_inputs/chui_chips/styles.css +32 -7
- package/framework/components/chui_inputs/chui_color_picker/color_picker.js +55 -38
- package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
- package/framework/components/chui_inputs/chui_combo_box/combo_box.js +127 -109
- package/framework/components/chui_inputs/chui_combo_box/styles.css +23 -21
- package/framework/components/chui_inputs/chui_date/date.js +161 -127
- package/framework/components/chui_inputs/chui_date/styles.css +20 -19
- package/framework/components/chui_inputs/chui_date_range/date_range.js +61 -50
- package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
- package/framework/components/chui_inputs/chui_datetime/datetime.js +47 -39
- package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
- package/framework/components/chui_inputs/chui_email/email.js +48 -36
- package/framework/components/chui_inputs/chui_email/styles.css +11 -11
- package/framework/components/chui_inputs/chui_file/file.js +71 -44
- package/framework/components/chui_inputs/chui_file/styles.css +23 -19
- package/framework/components/chui_inputs/chui_hotkey/hotkey.js +101 -76
- package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
- package/framework/components/chui_inputs/chui_masked/masked.js +83 -72
- package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
- package/framework/components/chui_inputs/chui_mention/mention.js +87 -40
- package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
- package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +242 -212
- package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +23 -15
- package/framework/components/chui_inputs/chui_number/number.js +84 -70
- package/framework/components/chui_inputs/chui_number/styles.css +13 -13
- package/framework/components/chui_inputs/chui_otp/otp.js +71 -62
- package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
- package/framework/components/chui_inputs/chui_password/password.js +72 -56
- package/framework/components/chui_inputs/chui_password/styles.css +15 -15
- package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
- package/framework/components/chui_inputs/chui_radio_button/styles.css +22 -22
- package/framework/components/chui_inputs/chui_rating/rating.js +54 -50
- package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
- package/framework/components/chui_inputs/chui_select_box/select_box.js +132 -108
- package/framework/components/chui_inputs/chui_select_box/styles.css +21 -21
- package/framework/components/chui_inputs/chui_slider/slider.js +41 -38
- package/framework/components/chui_inputs/chui_slider/styles.css +7 -5
- package/framework/components/chui_inputs/chui_text/styles.css +14 -14
- package/framework/components/chui_inputs/chui_text/text.js +69 -52
- package/framework/components/chui_inputs/chui_text_area/styles.css +14 -14
- package/framework/components/chui_inputs/chui_text_area/text_area.js +59 -36
- package/framework/components/chui_inputs/chui_time/styles.css +18 -20
- package/framework/components/chui_inputs/chui_time/time.js +71 -49
- package/framework/components/chui_inputs/chui_toggle/styles.css +13 -6
- package/framework/components/chui_inputs/chui_toggle/toggle.js +27 -27
- package/framework/components/chui_json_editor/json_editor.js +137 -127
- package/framework/components/chui_json_editor/styles.css +5 -5
- package/framework/components/chui_json_viewer/json_viewer.js +133 -114
- package/framework/components/chui_json_viewer/styles.css +9 -8
- package/framework/components/chui_kanban/kanban.js +78 -60
- package/framework/components/chui_kanban/styles.css +8 -4
- package/framework/components/chui_kbd/kbd.js +12 -10
- package/framework/components/chui_kbd/styles.css +1 -1
- package/framework/components/chui_label/label.js +31 -29
- package/framework/components/chui_label/styles.css +3 -3
- package/framework/components/chui_link/link.js +36 -29
- package/framework/components/chui_link/styles.css +11 -4
- package/framework/components/chui_list/list.js +42 -38
- package/framework/components/chui_list/styles.css +4 -3
- package/framework/components/chui_markdown_editor/markdown_editor.js +125 -109
- package/framework/components/chui_markdown_editor/styles.css +5 -5
- package/framework/components/chui_media/audio.js +457 -364
- package/framework/components/chui_media/audio_styles.css +74 -37
- package/framework/components/chui_media/image.js +99 -64
- package/framework/components/chui_media/image_compare.js +86 -55
- package/framework/components/chui_media/image_compare_styles.css +5 -5
- package/framework/components/chui_media/image_styles.css +4 -4
- package/framework/components/chui_media/video.js +264 -171
- package/framework/components/chui_media/video_styles.css +57 -24
- package/framework/components/chui_menu_bar/menu_bar.js +67 -65
- package/framework/components/chui_menu_bar/styles.css +14 -12
- 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 +81 -56
- package/framework/components/chui_modal/styles.css +3 -2
- package/framework/components/chui_notification/notification.js +107 -69
- package/framework/components/chui_notification/notification_download.js +28 -77
- package/framework/components/chui_notification/notification_download_card.js +115 -0
- package/framework/components/chui_notification/notification_download_progress.css +5 -4
- package/framework/components/chui_notification/notification_download_progress.js +73 -105
- package/framework/components/chui_notification/notification_update.js +44 -37
- package/framework/components/chui_notification/status_plate.css +3 -3
- package/framework/components/chui_notification/status_plate.js +2 -2
- package/framework/components/chui_notification/styles.css +6 -1
- package/framework/components/chui_notification/styles_download.css +2 -2
- package/framework/components/chui_notification/styles_update.css +3 -3
- package/framework/components/chui_org_chart/org_chart.js +101 -62
- package/framework/components/chui_org_chart/styles.css +2 -2
- package/framework/components/chui_pagination/pagination.js +65 -30
- package/framework/components/chui_pagination/styles.css +1 -1
- package/framework/components/chui_paragraph/paragraph.js +2 -2
- package/framework/components/chui_paragraph/styles.css +3 -3
- package/framework/components/chui_popover/popover.js +77 -50
- package/framework/components/chui_popover/styles.css +1 -1
- package/framework/components/chui_popups/popups.js +210 -181
- package/framework/components/chui_popups/styles.css +7 -7
- package/framework/components/chui_print_button/print_button.js +48 -33
- package/framework/components/chui_progress_bar/progress_bar.js +49 -19
- package/framework/components/chui_progress_bar/styles.css +6 -6
- package/framework/components/chui_progress_ring/progress_ring.js +32 -30
- package/framework/components/chui_progress_ring/styles.css +1 -1
- package/framework/components/chui_qrcode/qrcode.js +85 -41
- package/framework/components/chui_query_builder/query_builder.js +263 -170
- package/framework/components/chui_query_builder/styles.css +4 -4
- package/framework/components/chui_radio_group/radio_group.js +36 -26
- package/framework/components/chui_radio_group/styles.css +2 -2
- package/framework/components/chui_result/result.js +41 -31
- package/framework/components/chui_result/styles.css +3 -3
- package/framework/components/chui_scheduler/scheduler.js +126 -104
- package/framework/components/chui_scheduler/styles.css +11 -7
- 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 +90 -27
- package/framework/components/chui_segmented_control/styles.css +32 -6
- package/framework/components/chui_settings/settings.js +160 -87
- package/framework/components/chui_settings/styles.css +4 -4
- package/framework/components/chui_shortcut_help/shortcut_help.js +82 -61
- package/framework/components/chui_sidebar_nav/sidebar_nav.js +132 -53
- package/framework/components/chui_sidebar_nav/styles.css +9 -3
- package/framework/components/chui_skeleton/skeleton.js +29 -24
- package/framework/components/chui_slideshow/slideshow.js +122 -103
- package/framework/components/chui_slideshow/styles.css +22 -16
- package/framework/components/chui_spinner/spinner.js +17 -17
- package/framework/components/chui_spinner/styles.css +0 -1
- package/framework/components/chui_split_button/split_button.js +53 -30
- package/framework/components/chui_split_button/styles.css +22 -6
- package/framework/components/chui_splitter/splitter.js +46 -39
- package/framework/components/chui_splitter/styles.css +2 -2
- package/framework/components/chui_statistic/statistic.js +111 -84
- package/framework/components/chui_status_bar/status_bar.js +11 -9
- package/framework/components/chui_status_bar/styles.css +2 -2
- package/framework/components/chui_stepper/stepper.js +230 -54
- package/framework/components/chui_stepper/styles.css +79 -14
- package/framework/components/chui_sticky_bar/sticky_bar.js +53 -17
- package/framework/components/chui_sticky_bar/styles.css +11 -7
- package/framework/components/chui_tabs/chui_tabs.js +100 -46
- package/framework/components/chui_tabs/styles.css +43 -15
- package/framework/components/chui_text_editor/styles.css +24 -23
- package/framework/components/chui_text_editor/text_editor.js +103 -79
- package/framework/components/chui_text_editor/text_editor_panel.js +428 -367
- package/framework/components/chui_theme_toggle/theme_toggle.js +46 -25
- package/framework/components/chui_timeline/styles.css +3 -1
- package/framework/components/chui_timeline/timeline.js +14 -12
- package/framework/components/chui_toast_queue/styles.css +11 -11
- package/framework/components/chui_toast_queue/toast_queue.js +82 -73
- package/framework/components/chui_toolbar/styles.css +2 -2
- package/framework/components/chui_toolbar/toolbar.js +20 -13
- package/framework/components/chui_tooltip/styles.css +2 -2
- package/framework/components/chui_tooltip/tooltip.js +40 -27
- package/framework/components/chui_tour/styles.css +12 -7
- package/framework/components/chui_tour/tour.js +132 -98
- package/framework/components/chui_transfer/styles.css +8 -7
- package/framework/components/chui_transfer/transfer.js +143 -123
- package/framework/components/chui_tree_table/styles.css +4 -4
- package/framework/components/chui_tree_table/tree_table.js +88 -74
- package/framework/components/chui_tree_view/styles.css +14 -10
- package/framework/components/chui_tree_view/tree_view.js +298 -253
- 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 +41 -33
- package/framework/components/chui_webview/styles.css +2 -2
- package/framework/components/chui_webview/webview.js +64 -52
- 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 +153 -123
- package/framework/modules/chui_animations/animations.js +105 -43
- package/framework/modules/chui_animations/motion.js +120 -0
- package/framework/modules/chui_animations/styles.css +37 -4
- package/framework/modules/chui_bridge/preload.js +36 -25
- package/framework/modules/chui_functions.js +69 -63
- package/framework/modules/chui_highlight/chui_highlight.js +25 -22
- package/framework/modules/chui_highlight/styles.css +13 -10
- package/framework/modules/chui_i18n/chui_i18n.js +12 -10
- package/framework/modules/chui_i18n/locales/a11y.js +5 -5
- package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
- package/framework/modules/chui_i18n/locales/calendar.js +3 -3
- package/framework/modules/chui_i18n/locales/common.js +3 -3
- package/framework/modules/chui_i18n/locales/data.js +36 -26
- package/framework/modules/chui_i18n/locales/editors.js +40 -13
- package/framework/modules/chui_i18n/locales/feedback.js +65 -3
- package/framework/modules/chui_i18n/locales/forms.js +8 -8
- package/framework/modules/chui_i18n/locales/graphics.js +8 -8
- package/framework/modules/chui_i18n/locales/media.js +4 -4
- package/framework/modules/chui_i18n/locales/navigation.js +12 -8
- package/framework/modules/chui_i18n/locales/validate.js +3 -3
- package/framework/modules/chui_json/chui_json.js +11 -11
- package/framework/modules/chui_layout/chui_layout.js +12 -10
- package/framework/modules/chui_logger/chui_logger.js +47 -28
- package/framework/modules/chui_page/page.js +39 -33
- package/framework/modules/chui_palette/palette.js +61 -0
- package/framework/modules/chui_qr/chui_qr.js +179 -88
- package/framework/modules/chui_scrollbar/chui_scrollbar.js +79 -68
- package/framework/modules/chui_scrollbar/styles.css +4 -2
- package/framework/modules/chui_services/chui_channels.js +27 -13
- package/framework/modules/chui_services/chui_services.js +47 -39
- package/framework/modules/chui_services/desktop.js +108 -101
- package/framework/modules/chui_sortable/chui_sortable.js +33 -28
- package/framework/modules/chui_validate/chui_validate.js +10 -7
- package/framework/tray_bar/chui_menu_items.js +136 -46
- package/index.d.ts +5046 -0
- package/index.js +199 -1254
- package/package.json +81 -68
- package/framework/components/telegram_bot/chui_telegram_bot.js +0 -184
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const { Icon, Icons } = require("../chui_icons/icons");
|
|
2
|
+
const { I18n } = require("../../modules/chui_i18n/chui_i18n");
|
|
3
|
+
const { setStyles, optionOr } = require("../../modules/chui_functions");
|
|
4
|
+
const { reducedMotion } = require("../../modules/chui_animations/motion");
|
|
5
|
+
|
|
6
|
+
/** Зазор между карточками по умолчанию: тот же шаг, что у отступов компонентов */
|
|
7
|
+
const DEFAULT_GAP = 12;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Лента карточек — горизонтальный список, который прокручивается колесом, стрелками на краях
|
|
11
|
+
* и клавиатурой.
|
|
12
|
+
*
|
|
13
|
+
* От `SlideShow` отличается тем, что не показывает слайды по одному: карточки идут подряд,
|
|
14
|
+
* лента листается свободно, а прилипание к началу карточки лишь помогает остановиться ровно.
|
|
15
|
+
* От `List` — направлением: строки идут вбок и не переносятся.
|
|
16
|
+
*
|
|
17
|
+
* Опции: `id`, `gap`, `snap` (прилипание к карточке, по умолчанию включено), `arrows`
|
|
18
|
+
* (стрелки на краях, по умолчанию включены), `label` (имя списка для скринридера),
|
|
19
|
+
* `items` (карточки).
|
|
20
|
+
*
|
|
21
|
+
* Методы: `addCard(component)`, `getCards()`, `scrollToCard(index)`, `scrollToEnd()`,
|
|
22
|
+
* `destroy()`, `set()`. В ленту принимаются компоненты фреймворка (у них есть `set()`)
|
|
23
|
+
* и готовые узлы.
|
|
24
|
+
*/
|
|
25
|
+
class CardScroll {
|
|
26
|
+
#root = document.createElement("chui_card_scroll");
|
|
27
|
+
#track = document.createElement("card_scroll_track");
|
|
28
|
+
#start = undefined;
|
|
29
|
+
#end = undefined;
|
|
30
|
+
#gap = DEFAULT_GAP;
|
|
31
|
+
#observer = undefined;
|
|
32
|
+
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
setStyles(__dirname + "/styles.css", "chUiJS_CardScroll");
|
|
35
|
+
if (options.id !== undefined) this.#root.id = options.id;
|
|
36
|
+
const gap = Number(options.gap);
|
|
37
|
+
this.#gap = Number.isFinite(gap) && gap >= 0 ? gap : DEFAULT_GAP;
|
|
38
|
+
this.#track.style.gap = `${this.#gap}px`;
|
|
39
|
+
// Список, а не просто лента: скринридер объявляет число карточек, а каждая —
|
|
40
|
+
// свой `listitem` (роль ставит `addCard`)
|
|
41
|
+
this.#track.setAttribute("role", "list");
|
|
42
|
+
this.#track.setAttribute("aria-label", optionOr(options.label, I18n.t("feedback.cardScroll")));
|
|
43
|
+
// Лента сама принимает фокус: стрелки клавиатуры прокручивают её, не уводя фокус в карточки
|
|
44
|
+
this.#track.setAttribute("tabindex", "0");
|
|
45
|
+
this.#track.addEventListener("keydown", (event) => this.#onKeydown(event));
|
|
46
|
+
// Вертикальное колесо прокручивает ленту вбок — так её листают мышью с колесом
|
|
47
|
+
this.#track.addEventListener("wheel", (event) => this.#onWheel(event), { passive: false });
|
|
48
|
+
this.#track.addEventListener("scroll", () => this.#syncArrows());
|
|
49
|
+
if (options.snap === false) this.#root.dataset.snap = "false";
|
|
50
|
+
if (options.arrows !== false) {
|
|
51
|
+
this.#start = this.#makeArrow(Icons.NAVIGATION.CHEVRON_LEFT, I18n.t("feedback.cardScrollPrev"), -1);
|
|
52
|
+
this.#end = this.#makeArrow(Icons.NAVIGATION.CHEVRON_RIGHT, I18n.t("feedback.cardScrollNext"), 1);
|
|
53
|
+
this.#root.append(this.#start, this.#track, this.#end);
|
|
54
|
+
} else {
|
|
55
|
+
this.#root.appendChild(this.#track);
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(options.items)) for (const item of options.items) this.addCard(item);
|
|
58
|
+
// Стрелки появляются только там, где лента действительно уходит за край: следим
|
|
59
|
+
// за размером (карточку могли добавить после сборки) и за прокруткой
|
|
60
|
+
if (typeof ResizeObserver === "function") {
|
|
61
|
+
this.#observer = new ResizeObserver(() => this.#syncArrows());
|
|
62
|
+
this.#observer.observe(this.#track);
|
|
63
|
+
}
|
|
64
|
+
this.#syncArrows();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Карточка ленты: компонент фреймворка (у него есть `set()`) или готовый узел */
|
|
68
|
+
addCard(component = undefined) {
|
|
69
|
+
if (component === undefined || component === null) return this;
|
|
70
|
+
const item = document.createElement("card_scroll_item");
|
|
71
|
+
item.setAttribute("role", "listitem");
|
|
72
|
+
item.appendChild(typeof component.set === "function" ? component.set() : component);
|
|
73
|
+
this.#track.appendChild(item);
|
|
74
|
+
this.#syncArrows();
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getCards() {
|
|
79
|
+
return [...this.#track.children];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Переход к карточке по номеру: место берут у самой карточки, а не считают по ширине */
|
|
83
|
+
scrollToCard(index = Number()) {
|
|
84
|
+
const cards = this.getCards();
|
|
85
|
+
const card = cards[Number(index)];
|
|
86
|
+
if (card === undefined) return this;
|
|
87
|
+
this.#scrollTo(card.offsetLeft - this.#track.offsetLeft);
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
scrollToEnd() {
|
|
92
|
+
this.#scrollTo(this.#track.scrollWidth);
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
set() {
|
|
97
|
+
return this.#root;
|
|
98
|
+
}
|
|
99
|
+
destroy() {
|
|
100
|
+
if (this.#observer !== undefined) this.#observer.disconnect();
|
|
101
|
+
this.#observer = undefined;
|
|
102
|
+
this.#root.remove();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// === ВНУТРЕННЕЕ ===
|
|
106
|
+
#makeArrow(icon = String(), label = String(), direction = 1) {
|
|
107
|
+
const button = document.createElement("button");
|
|
108
|
+
button.type = "button";
|
|
109
|
+
button.className = "card_scroll_arrow";
|
|
110
|
+
// Сторона видна и стилям, и замерам: край ленты — то, что здесь важно
|
|
111
|
+
button.dataset.side = direction < 0 ? "start" : "end";
|
|
112
|
+
button.innerHTML = new Icon(icon, "20px").getHTML();
|
|
113
|
+
button.setAttribute("aria-label", label);
|
|
114
|
+
button.title = label;
|
|
115
|
+
button.addEventListener("click", () => this.#step(direction));
|
|
116
|
+
return button;
|
|
117
|
+
}
|
|
118
|
+
#scrollTo(left = Number()) {
|
|
119
|
+
// При уменьшенном движении лента перескакивает без плавности
|
|
120
|
+
this.#track.scrollTo({ left: left, behavior: reducedMotion() ? "auto" : "smooth" });
|
|
121
|
+
}
|
|
122
|
+
/** Шаг — одна карточка с зазором: он берётся у самой карточки, а не из настроек */
|
|
123
|
+
#step(direction = 1) {
|
|
124
|
+
const card = this.#track.querySelector("card_scroll_item");
|
|
125
|
+
const step = card === null ? this.#track.clientWidth : card.getBoundingClientRect().width + this.#gap;
|
|
126
|
+
this.#scrollTo(this.#track.scrollLeft + step * direction);
|
|
127
|
+
}
|
|
128
|
+
#onKeydown(event = {}) {
|
|
129
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
this.#step(event.key === "ArrowRight" ? 1 : -1);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (event.key === "Home") {
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
this.#scrollTo(0);
|
|
137
|
+
}
|
|
138
|
+
if (event.key === "End") {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
this.scrollToEnd();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Вертикальное колесо двигает ленту вбок. Когда лента упирается, событие отдаётся странице:
|
|
145
|
+
* иначе список «съел» бы прокрутку, и до содержимого ниже было бы не добраться.
|
|
146
|
+
*/
|
|
147
|
+
#onWheel(event = {}) {
|
|
148
|
+
const track = this.#track;
|
|
149
|
+
if (event.deltaY === 0) return;
|
|
150
|
+
const max = track.scrollWidth - track.clientWidth;
|
|
151
|
+
if (max <= 0) return;
|
|
152
|
+
if ((track.scrollLeft <= 0 && event.deltaY < 0) || (track.scrollLeft >= max && event.deltaY > 0)) return;
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
track.scrollLeft += event.deltaY;
|
|
155
|
+
}
|
|
156
|
+
/** Стрелка видна только там, где за краем есть карточки */
|
|
157
|
+
#syncArrows() {
|
|
158
|
+
if (this.#start === undefined || this.#end === undefined) return;
|
|
159
|
+
const track = this.#track;
|
|
160
|
+
const max = track.scrollWidth - track.clientWidth;
|
|
161
|
+
// Отступ ленты (он держит тень карточек) сдвигает нулевую позицию: без него
|
|
162
|
+
// лента с прилипанием стоит на нём, и стрелка «назад» светилась бы на старте
|
|
163
|
+
const offset = parseFloat(getComputedStyle(track).paddingLeft) || 0;
|
|
164
|
+
this.#start.hidden = track.scrollLeft <= offset + 1;
|
|
165
|
+
this.#end.hidden = track.scrollLeft >= max - 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
exports.CardScroll = CardScroll;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Лента карточек: горизонтальный список со стрелками на краях.
|
|
2
|
+
Своей палитры у компонента нет: карточки приносят её сами, полоса прокрутки — общая
|
|
3
|
+
(`chui_scrollbar`). */
|
|
4
|
+
|
|
5
|
+
chui_card_scroll {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: row;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: 8px;
|
|
10
|
+
/* Компонент сам задаёт ширину: в flex-колонке с `align-items: flex-start` лента
|
|
11
|
+
без ширины получила бы `max-content` и вылезла за содержимое */
|
|
12
|
+
width: -webkit-fill-available;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
card_scroll_track {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: row;
|
|
19
|
+
flex: 1 1 auto;
|
|
20
|
+
min-width: 0;
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
/* Лента идёт вбок: по вертикали содержимое не переносится и не обрезается тенью карточек */
|
|
23
|
+
overflow-x: auto;
|
|
24
|
+
overflow-y: hidden;
|
|
25
|
+
/* Прилипание к началу карточки помогает остановиться ровно, но не заставляет ленту
|
|
26
|
+
прыгать через карточку: `proximity`, а не `mandatory` */
|
|
27
|
+
scroll-snap-type: x proximity;
|
|
28
|
+
/* Отступ под тень карточек: у обрезанного слоя тень срезалась бы по краю */
|
|
29
|
+
padding: 4px;
|
|
30
|
+
margin: -4px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
chui_card_scroll[data-snap="false"] card_scroll_track {
|
|
34
|
+
scroll-snap-type: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Лента сама принимает фокус: стрелки клавиатуры прокручивают её, а не уводят фокус в карточку */
|
|
38
|
+
card_scroll_track:focus-visible {
|
|
39
|
+
outline: var(--focus_outline);
|
|
40
|
+
outline-offset: var(--focus_outline_offset);
|
|
41
|
+
border-radius: var(--radius_sm);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
card_scroll_item {
|
|
45
|
+
flex: none;
|
|
46
|
+
display: flex;
|
|
47
|
+
scroll-snap-align: start;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Стрелка на краю ленты — круглая кнопка со значком: имя ей даёт словарь, значка скринридер
|
|
51
|
+
не читает (`Icon` помечает его `aria-hidden`) */
|
|
52
|
+
.card_scroll_arrow {
|
|
53
|
+
flex: none;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
width: var(--control_height);
|
|
60
|
+
height: var(--control_height);
|
|
61
|
+
padding: 0;
|
|
62
|
+
border: 1px solid var(--border_color);
|
|
63
|
+
border-radius: var(--radius_circle);
|
|
64
|
+
background: var(--element_background_2);
|
|
65
|
+
color: var(--text_color);
|
|
66
|
+
box-shadow: var(--box_shadow_main);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.card_scroll_arrow:hover {
|
|
70
|
+
background: var(--element_background_hover);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Нажатие — акцент, как у прочих кнопок тонкой настройки */
|
|
74
|
+
.card_scroll_arrow:active {
|
|
75
|
+
background: var(--blue_prime_background);
|
|
76
|
+
color: var(--text_color_hover);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.card_scroll_arrow chui_icon {
|
|
80
|
+
color: inherit;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* За краем карточек нет — стрелки нет: `hidden` убирает её и из обхода клавиатурой */
|
|
84
|
+
.card_scroll_arrow[hidden] {
|
|
85
|
+
display: none;
|
|
86
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
const { setStyles, markdownToHtml, optionOr } = require(
|
|
2
|
-
const { Avatar } = require(
|
|
3
|
-
const { Icon, Icons } = require(
|
|
4
|
-
const { I18n } = require(
|
|
1
|
+
const { setStyles, markdownToHtml, optionOr } = require("../../modules/chui_functions");
|
|
2
|
+
const { Avatar } = require("../chui_avatar/avatar");
|
|
3
|
+
const { Icon, Icons } = require("../chui_icons/icons");
|
|
4
|
+
const { I18n } = require("../../modules/chui_i18n/chui_i18n");
|
|
5
5
|
|
|
6
6
|
/** Значки состояния отправленного сообщения: в пути, доставлено, прочитано, ошибка */
|
|
7
7
|
const STATUS_ICONS = {
|
|
8
8
|
sending: Icons.ACTIONS.SCHEDULE,
|
|
9
9
|
sent: Icons.NAVIGATION.CHECK,
|
|
10
10
|
read: Icons.ACTIONS.DONE_ALL,
|
|
11
|
-
failed: Icons.ALERT.ERROR_OUTLINE
|
|
11
|
+
failed: Icons.ALERT.ERROR_OUTLINE,
|
|
12
12
|
};
|
|
13
13
|
/** Прокрутка считается «у нижнего края», если до него осталось меньше этого, px */
|
|
14
14
|
const NEAR_BOTTOM = 80;
|
|
@@ -63,7 +63,7 @@ class Chat {
|
|
|
63
63
|
SENDING: "sending",
|
|
64
64
|
SENT: "sent",
|
|
65
65
|
READ: "read",
|
|
66
|
-
FAILED: "failed"
|
|
66
|
+
FAILED: "failed",
|
|
67
67
|
};
|
|
68
68
|
static #counter = 0;
|
|
69
69
|
|
|
@@ -76,42 +76,54 @@ class Chat {
|
|
|
76
76
|
#typing = document.createElement("chat_typing");
|
|
77
77
|
#composer = document.createElement("chat_composer");
|
|
78
78
|
#input = document.createElement("textarea");
|
|
79
|
-
#
|
|
79
|
+
#sendButton = document.createElement("button");
|
|
80
80
|
#messages = [];
|
|
81
81
|
#own = undefined;
|
|
82
82
|
#placeholder = undefined;
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
83
|
+
#sendText = undefined;
|
|
84
|
+
#sendLabel = undefined;
|
|
85
|
+
#dateSeparators = true;
|
|
86
|
+
#composerVisible = true;
|
|
87
87
|
#disabled = false;
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
88
|
+
#typingName = undefined;
|
|
89
|
+
#sendListeners = [];
|
|
90
|
+
#onSend = undefined;
|
|
91
|
+
#onMessageClick = undefined;
|
|
92
92
|
// Текущий день и текущая группа — по ним решается, дописывать пузырь или начать новую;
|
|
93
93
|
// группа — это {node, bubbles, avatar, author, day}
|
|
94
|
-
#
|
|
95
|
-
#
|
|
94
|
+
#lastDate = undefined;
|
|
95
|
+
#lastGroup = undefined;
|
|
96
96
|
|
|
97
|
-
constructor(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
constructor(
|
|
98
|
+
options = {
|
|
99
|
+
id: String(),
|
|
100
|
+
title: String(),
|
|
101
|
+
subtitle: String(),
|
|
102
|
+
messages: [],
|
|
103
|
+
own: String(),
|
|
104
|
+
placeholder: String(),
|
|
105
|
+
sendText: String(),
|
|
106
|
+
sendLabel: String(),
|
|
107
|
+
composer: Boolean(),
|
|
108
|
+
height: String(),
|
|
109
|
+
width: String(),
|
|
110
|
+
dateSeparators: Boolean(),
|
|
111
|
+
onSend: undefined,
|
|
112
|
+
onMessageClick: undefined,
|
|
113
|
+
}
|
|
114
|
+
) {
|
|
103
115
|
setStyles(__dirname + "/styles.css", "chUiJS_Chat");
|
|
104
116
|
Chat.#counter += 1;
|
|
105
117
|
this.#id = options.id === undefined ? `chui_chat_${Chat.#counter}` : options.id;
|
|
106
118
|
this.#root.id = this.#id;
|
|
107
119
|
this.#own = options.own;
|
|
108
120
|
this.#placeholder = options.placeholder;
|
|
109
|
-
this.#
|
|
110
|
-
this.#
|
|
111
|
-
this.#
|
|
112
|
-
this.#
|
|
113
|
-
this.#
|
|
114
|
-
this.#
|
|
121
|
+
this.#sendText = options.sendText;
|
|
122
|
+
this.#sendLabel = options.sendLabel;
|
|
123
|
+
this.#onSend = options.onSend;
|
|
124
|
+
this.#onMessageClick = options.onMessageClick;
|
|
125
|
+
this.#dateSeparators = options.dateSeparators !== false;
|
|
126
|
+
this.#composerVisible = options.composer !== false;
|
|
115
127
|
if (options.width !== undefined) this.#root.style.width = options.width;
|
|
116
128
|
// Высота журнала: своя переменная, чтобы поле ввода и шапка оставались на месте
|
|
117
129
|
this.#log.style.setProperty("--chat_height", options.height === undefined ? "320px" : options.height);
|
|
@@ -130,19 +142,20 @@ class Chat {
|
|
|
130
142
|
this.#composer.setAttribute("aria-label", I18n.t("feedback.chatComposer"));
|
|
131
143
|
this.#input.className = "chat_input";
|
|
132
144
|
this.#input.rows = 1;
|
|
133
|
-
this.#input.placeholder =
|
|
145
|
+
this.#input.placeholder =
|
|
146
|
+
this.#placeholder === undefined ? I18n.t("feedback.chatPlaceholder") : this.#placeholder;
|
|
134
147
|
this.#input.setAttribute("aria-label", I18n.t("feedback.chatComposer"));
|
|
135
|
-
this.#
|
|
136
|
-
this.#
|
|
137
|
-
this.#
|
|
138
|
-
this.#
|
|
139
|
-
this.#
|
|
140
|
-
this.#
|
|
148
|
+
this.#sendButton.type = "button";
|
|
149
|
+
this.#sendButton.className = "chat_send";
|
|
150
|
+
this.#sendButton.disabled = true;
|
|
151
|
+
this.#sendButton.setAttribute("aria-label", this.#sendLabelText());
|
|
152
|
+
this.#sendButton.setAttribute("title", this.#sendTextText());
|
|
153
|
+
this.#sendButton.innerHTML = new Icon(Icons.CONTENT.SEND, "16px").getHTML();
|
|
141
154
|
|
|
142
155
|
this.#header.appendChild(this.#title);
|
|
143
156
|
this.#header.appendChild(this.#subtitle);
|
|
144
157
|
this.#composer.appendChild(this.#input);
|
|
145
|
-
this.#composer.appendChild(this.#
|
|
158
|
+
this.#composer.appendChild(this.#sendButton);
|
|
146
159
|
this.#root.appendChild(this.#header);
|
|
147
160
|
this.#root.appendChild(this.#log);
|
|
148
161
|
this.#root.appendChild(this.#typing);
|
|
@@ -151,27 +164,43 @@ class Chat {
|
|
|
151
164
|
this.#subtitle.hidden = true;
|
|
152
165
|
if (optionOr(options.title, undefined) !== undefined) this.setTitle(options.title);
|
|
153
166
|
if (optionOr(options.subtitle, undefined) !== undefined) this.setSubtitle(options.subtitle);
|
|
154
|
-
if (!this.#
|
|
167
|
+
if (!this.#composerVisible) this.#composer.hidden = true;
|
|
155
168
|
if (this.#disabled) this.setDisabled(true);
|
|
156
169
|
|
|
157
170
|
this.#input.addEventListener("input", () => this.#syncSendState());
|
|
158
171
|
this.#input.addEventListener("keydown", this.#onInputKeyDown);
|
|
159
|
-
this.#
|
|
172
|
+
this.#sendButton.addEventListener("click", () => this.send());
|
|
160
173
|
this.#log.addEventListener("click", this.#onLogClick);
|
|
161
174
|
this.setMessages(options.messages === undefined ? [] : options.messages);
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
// GET
|
|
165
|
-
getId() {
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
getId() {
|
|
179
|
+
return this.#id;
|
|
180
|
+
}
|
|
181
|
+
getTitle() {
|
|
182
|
+
return this.#title.innerText;
|
|
183
|
+
}
|
|
184
|
+
getSubtitle() {
|
|
185
|
+
return this.#subtitle.innerText;
|
|
186
|
+
}
|
|
168
187
|
/** Текст в поле ввода — то, что ещё не отправлено */
|
|
169
|
-
getValue() {
|
|
188
|
+
getValue() {
|
|
189
|
+
return this.#input.value;
|
|
190
|
+
}
|
|
170
191
|
/** Копии сообщений: правка копии не меняет журнал */
|
|
171
|
-
getMessages() {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
192
|
+
getMessages() {
|
|
193
|
+
return this.#messages.map((message) => Object.assign({}, message));
|
|
194
|
+
}
|
|
195
|
+
isTyping() {
|
|
196
|
+
return this.#typingName !== undefined;
|
|
197
|
+
}
|
|
198
|
+
isDisabled() {
|
|
199
|
+
return this.#disabled;
|
|
200
|
+
}
|
|
201
|
+
getLog() {
|
|
202
|
+
return this.#log;
|
|
203
|
+
}
|
|
175
204
|
|
|
176
205
|
// SET
|
|
177
206
|
setTitle(text = String()) {
|
|
@@ -209,17 +238,19 @@ class Chat {
|
|
|
209
238
|
return this;
|
|
210
239
|
}
|
|
211
240
|
addSendListener(listener = () => {}) {
|
|
212
|
-
this.#
|
|
241
|
+
this.#sendListeners.push(listener);
|
|
213
242
|
return this;
|
|
214
243
|
}
|
|
215
|
-
set() {
|
|
244
|
+
set() {
|
|
245
|
+
return this.#root;
|
|
246
|
+
}
|
|
216
247
|
|
|
217
248
|
// СООБЩЕНИЯ
|
|
218
249
|
/** Полная замена списка: журнал собирается заново, группы схлопываются по автору и дню */
|
|
219
250
|
setMessages(messages = []) {
|
|
220
251
|
this.#messages = [];
|
|
221
|
-
this.#
|
|
222
|
-
this.#
|
|
252
|
+
this.#lastDate = undefined;
|
|
253
|
+
this.#lastGroup = undefined;
|
|
223
254
|
this.#log.innerHTML = "";
|
|
224
255
|
for (const message of messages) this.#appendMessage(Object.assign({}, message), false);
|
|
225
256
|
this.scrollToBottom(false);
|
|
@@ -244,22 +275,45 @@ class Chat {
|
|
|
244
275
|
}
|
|
245
276
|
clear() {
|
|
246
277
|
this.#messages = [];
|
|
247
|
-
this.#
|
|
248
|
-
this.#
|
|
278
|
+
this.#lastDate = undefined;
|
|
279
|
+
this.#lastGroup = undefined;
|
|
249
280
|
this.#log.innerHTML = "";
|
|
250
281
|
return this;
|
|
251
282
|
}
|
|
252
283
|
/** «Иван печатает…»: имя автора; `undefined` убирает строку */
|
|
253
284
|
setTyping(name = undefined) {
|
|
254
|
-
this.#
|
|
255
|
-
this.#typing.
|
|
256
|
-
this.#
|
|
285
|
+
this.#typingName = name;
|
|
286
|
+
this.#typing.innerText = name === undefined ? "" : I18n.t("feedback.chatTyping", { name: name });
|
|
287
|
+
this.#showTyping(name !== undefined);
|
|
257
288
|
return this;
|
|
258
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Строка «печатает» появляется и уходит движением: `hidden` возвращается по концу кадра,
|
|
293
|
+
* поэтому строка не пропадает рывком. Открытие за время ухода отменяет снятие.
|
|
294
|
+
*/
|
|
295
|
+
#showTyping(visible) {
|
|
296
|
+
if (visible === true) {
|
|
297
|
+
this.#typing.classList.remove("chat_typing_leaving");
|
|
298
|
+
this.#typing.hidden = false;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (this.#typing.hidden === true) return;
|
|
302
|
+
this.#typing.classList.add("chat_typing_leaving");
|
|
303
|
+
const done = (event) => {
|
|
304
|
+
if (event.target !== this.#typing) return;
|
|
305
|
+
this.#typing.removeEventListener("animationend", done);
|
|
306
|
+
if (this.#typing.classList.contains("chat_typing_leaving") === false) return;
|
|
307
|
+
this.#typing.classList.remove("chat_typing_leaving");
|
|
308
|
+
this.#typing.hidden = true;
|
|
309
|
+
};
|
|
310
|
+
this.#typing.addEventListener("animationend", done);
|
|
311
|
+
}
|
|
259
312
|
scrollToBottom(smooth = true) {
|
|
260
313
|
// Плавная прокрутка — движение; при `prefers-reduced-motion` она идёт сразу
|
|
261
314
|
const behavior = smooth && this.#reducedMotion() === false ? "smooth" : "auto";
|
|
262
|
-
if (typeof this.#log.scrollTo === "function")
|
|
315
|
+
if (typeof this.#log.scrollTo === "function")
|
|
316
|
+
this.#log.scrollTo({ top: this.#log.scrollHeight, behavior: behavior });
|
|
263
317
|
else this.#log.scrollTop = this.#log.scrollHeight;
|
|
264
318
|
return this;
|
|
265
319
|
}
|
|
@@ -276,23 +330,23 @@ class Chat {
|
|
|
276
330
|
author: this.#own,
|
|
277
331
|
own: true,
|
|
278
332
|
time: Date.now(),
|
|
279
|
-
status: Chat.STATUS.SENDING
|
|
333
|
+
status: Chat.STATUS.SENDING,
|
|
280
334
|
});
|
|
281
335
|
this.#input.value = "";
|
|
282
336
|
this.#syncSendState();
|
|
283
337
|
const added = this.#appendMessage(message, true);
|
|
284
|
-
if (this.#
|
|
285
|
-
for (const listener of [...this.#
|
|
338
|
+
if (this.#onSend !== undefined) this.#onSend(text, added);
|
|
339
|
+
for (const listener of [...this.#sendListeners]) listener(text, added);
|
|
286
340
|
return this;
|
|
287
341
|
}
|
|
288
342
|
|
|
289
343
|
// === ВНУТРЕННЕЕ ===
|
|
290
344
|
#sendTextText() {
|
|
291
|
-
return this.#
|
|
345
|
+
return this.#sendText === undefined ? I18n.t("feedback.chatSend") : this.#sendText;
|
|
292
346
|
}
|
|
293
347
|
/** Имя кнопки отправки: своё (`sendLabel`), иначе `sendText`, иначе словарь */
|
|
294
348
|
#sendLabelText() {
|
|
295
|
-
if (this.#
|
|
349
|
+
if (this.#sendLabel !== undefined) return this.#sendLabel;
|
|
296
350
|
return this.#sendTextText();
|
|
297
351
|
}
|
|
298
352
|
#reducedMotion() {
|
|
@@ -301,7 +355,7 @@ class Chat {
|
|
|
301
355
|
return query.matches === true;
|
|
302
356
|
}
|
|
303
357
|
#syncSendState() {
|
|
304
|
-
this.#
|
|
358
|
+
this.#sendButton.disabled = this.#disabled || this.#input.value.trim() === "";
|
|
305
359
|
}
|
|
306
360
|
/**
|
|
307
361
|
* Добавить сообщение в журнал. Если автор и день совпадают с прошлой группой, пузырь
|
|
@@ -329,23 +383,27 @@ class Chat {
|
|
|
329
383
|
}
|
|
330
384
|
/** Разделитель дня: между сообщениями разных дней и в начале журнала */
|
|
331
385
|
#insertDateSeparator(date) {
|
|
332
|
-
if (date === undefined || this.#
|
|
386
|
+
if (date === undefined || this.#dateSeparators === false) return;
|
|
333
387
|
const key = this.#dayKey(date);
|
|
334
|
-
if (key === this.#
|
|
335
|
-
this.#
|
|
388
|
+
if (key === this.#lastDate) return;
|
|
389
|
+
this.#lastDate = key;
|
|
336
390
|
const separator = document.createElement("chat_date");
|
|
337
391
|
separator.dataset.day = key;
|
|
338
392
|
const label = document.createElement("chat_date_label");
|
|
339
393
|
label.innerText = this.#dayLabel(date);
|
|
340
394
|
separator.appendChild(label);
|
|
341
395
|
this.#log.appendChild(separator);
|
|
342
|
-
this.#
|
|
396
|
+
this.#lastGroup = undefined;
|
|
343
397
|
}
|
|
344
398
|
/** Группа для сообщения: та же, если автор и день совпадают, иначе новая */
|
|
345
399
|
#groupFor(message) {
|
|
346
400
|
const author = this.#authorOf(message);
|
|
347
|
-
if (
|
|
348
|
-
|
|
401
|
+
if (
|
|
402
|
+
this.#lastGroup !== undefined &&
|
|
403
|
+
this.#lastGroup.author === author &&
|
|
404
|
+
this.#lastGroup.day === this.#lastDate
|
|
405
|
+
) {
|
|
406
|
+
return this.#lastGroup;
|
|
349
407
|
}
|
|
350
408
|
const node = document.createElement("chat_group");
|
|
351
409
|
node.dataset.own = message.own === true ? "true" : "false";
|
|
@@ -356,15 +414,15 @@ class Chat {
|
|
|
356
414
|
if (message.own !== true) {
|
|
357
415
|
avatar = new Avatar({
|
|
358
416
|
name: author,
|
|
359
|
-
image: message.avatar === undefined ? undefined : {path: message.avatar},
|
|
417
|
+
image: message.avatar === undefined ? undefined : { path: message.avatar },
|
|
360
418
|
size: Avatar.SIZE.SMALL,
|
|
361
|
-
decorative: true
|
|
419
|
+
decorative: true,
|
|
362
420
|
});
|
|
363
421
|
node.prepend(avatar.set());
|
|
364
422
|
}
|
|
365
423
|
this.#log.appendChild(node);
|
|
366
|
-
this.#
|
|
367
|
-
return this.#
|
|
424
|
+
this.#lastGroup = { node: node, bubbles: bubbles, avatar: avatar, author: author, day: this.#lastDate };
|
|
425
|
+
return this.#lastGroup;
|
|
368
426
|
}
|
|
369
427
|
#bubble(message, first) {
|
|
370
428
|
const bubble = document.createElement("chat_bubble");
|
|
@@ -418,7 +476,10 @@ class Chat {
|
|
|
418
476
|
status.dataset.status = message.status;
|
|
419
477
|
status.setAttribute("aria-label", this.#statusLabel(message.status));
|
|
420
478
|
status.setAttribute("role", "img");
|
|
421
|
-
status.innerHTML = new Icon(
|
|
479
|
+
status.innerHTML = new Icon(
|
|
480
|
+
STATUS_ICONS[message.status] === undefined ? STATUS_ICONS.sent : STATUS_ICONS[message.status],
|
|
481
|
+
"13px"
|
|
482
|
+
).getHTML();
|
|
422
483
|
meta.appendChild(status);
|
|
423
484
|
}
|
|
424
485
|
return meta;
|
|
@@ -442,10 +503,10 @@ class Chat {
|
|
|
442
503
|
if (this.#dayKey(date) === this.#dayKey(now)) return I18n.t("common.today");
|
|
443
504
|
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
|
444
505
|
if (this.#dayKey(date) === this.#dayKey(yesterday)) return I18n.t("common.yesterday");
|
|
445
|
-
return new Intl.DateTimeFormat(I18n.getLocale(), {day: "numeric", month: "long"}).format(date);
|
|
506
|
+
return new Intl.DateTimeFormat(I18n.getLocale(), { day: "numeric", month: "long" }).format(date);
|
|
446
507
|
}
|
|
447
508
|
#timeLabel(date) {
|
|
448
|
-
return new Intl.DateTimeFormat(I18n.getLocale(), {hour: "2-digit", minute: "2-digit"}).format(date);
|
|
509
|
+
return new Intl.DateTimeFormat(I18n.getLocale(), { hour: "2-digit", minute: "2-digit" }).format(date);
|
|
449
510
|
}
|
|
450
511
|
|
|
451
512
|
// === ОБРАБОТЧИКИ ===
|
|
@@ -456,15 +517,15 @@ class Chat {
|
|
|
456
517
|
this.send();
|
|
457
518
|
};
|
|
458
519
|
#onLogClick = (event) => {
|
|
459
|
-
if (this.#
|
|
520
|
+
if (this.#onMessageClick === undefined) return;
|
|
460
521
|
const bubble = event.target instanceof Element ? event.target.closest("chat_bubble") : null;
|
|
461
522
|
if (bubble === null) return;
|
|
462
523
|
const message = this.#messages.find((entry) => entry.id === bubble.dataset.id);
|
|
463
|
-
if (message !== undefined) this.#
|
|
524
|
+
if (message !== undefined) this.#onMessageClick(message);
|
|
464
525
|
};
|
|
465
526
|
}
|
|
466
527
|
|
|
467
|
-
exports.Chat = Chat
|
|
528
|
+
exports.Chat = Chat;
|
|
468
529
|
|
|
469
530
|
// Описание сообщения собирает фабрика — как у ContextMenu и SplitButton
|
|
470
531
|
Chat.Message = (options = {}) => Object.assign({}, options);
|