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
|
@@ -23,7 +23,7 @@ class ContentBlock {
|
|
|
23
23
|
if (this.#disableMarginChild) this.disableMarginChild();
|
|
24
24
|
}
|
|
25
25
|
remove(...components) {
|
|
26
|
-
for (
|
|
26
|
+
for (const component of components) new Animation(component.set()).fadeOutAndRemove();
|
|
27
27
|
}
|
|
28
28
|
// FUNCTIONS
|
|
29
29
|
disableMarginChild() {
|
|
@@ -32,7 +32,7 @@ class ContentBlock {
|
|
|
32
32
|
this.#body.classList.add("contentblock_no_margin");
|
|
33
33
|
}
|
|
34
34
|
add(...components) {
|
|
35
|
-
for (
|
|
35
|
+
for (const component of components) {
|
|
36
36
|
new Animation(component.set()).fadeIn();
|
|
37
37
|
this.#body.appendChild(component.set());
|
|
38
38
|
}
|
|
@@ -4,18 +4,27 @@ const { MenuKeyboard, A11Y_FOCUSABLE_SELECTOR } = require("../../modules/chui_a1
|
|
|
4
4
|
|
|
5
5
|
class ContextMenu {
|
|
6
6
|
#menu = document.createElement("chui_context_menu");
|
|
7
|
+
// Слой пунктов: кадр появления и ухода играет он, а поверхность меню с блюром стоит
|
|
8
|
+
// на месте. Элемент с `transform` или `opacity` становится «backdrop root», и его
|
|
9
|
+
// `backdrop-filter` перестаёт видеть страницу — прежний кадр на самом меню из-за этого
|
|
10
|
+
// шёл без блюра, и блюр появлялся только после конца кадра
|
|
11
|
+
#panel = document.createElement("context_menu_panel");
|
|
7
12
|
#targets = [];
|
|
8
13
|
#visible = false;
|
|
9
14
|
#hideHandler = undefined;
|
|
10
15
|
#keyboard = undefined;
|
|
16
|
+
#label = undefined;
|
|
11
17
|
// Цель `attach()`, к которой возвращается фокус после закрытия меню
|
|
12
|
-
#
|
|
18
|
+
#returnFocus = undefined;
|
|
13
19
|
// Где фокус был до открытия меню: цель может быть нефокусируемой обёрткой (chui_button)
|
|
14
|
-
#
|
|
20
|
+
#previousFocus = undefined;
|
|
15
21
|
|
|
16
22
|
constructor(options = { items: [] }) {
|
|
17
23
|
setStyles(__dirname + "/styles.css", "chUiJS_ContextMenu");
|
|
18
|
-
|
|
24
|
+
// Роль меню лежит на самом слое: `MenuKeyboard` и отбирает пункты по ближайшему меню,
|
|
25
|
+
// и ставит её сам, а слой пунктов нужен только для кадра
|
|
26
|
+
this.#menu.appendChild(this.#panel);
|
|
27
|
+
this.#renderItems(this.#panel, options.items);
|
|
19
28
|
// Меню — фокусируемый слой: открытие мышью фокуса в нём не оставляет, но клавиши
|
|
20
29
|
// (стрелки, набор букв) должны попадать в список, а не элементу под курсором
|
|
21
30
|
this.#menu.setAttribute("tabindex", "-1");
|
|
@@ -32,7 +41,14 @@ class ContextMenu {
|
|
|
32
41
|
|
|
33
42
|
// === СТАТИЧЕСКИЕ ОПИСАНИЯ ПУНКТОВ ===
|
|
34
43
|
static Item(
|
|
35
|
-
options = {
|
|
44
|
+
options = {
|
|
45
|
+
title: String(),
|
|
46
|
+
icon: String(),
|
|
47
|
+
shortcut: String(),
|
|
48
|
+
disabled: Boolean(),
|
|
49
|
+
danger: Boolean(),
|
|
50
|
+
clickEvent: () => {},
|
|
51
|
+
}
|
|
36
52
|
) {
|
|
37
53
|
return { type: "item", ...options };
|
|
38
54
|
}
|
|
@@ -46,45 +62,50 @@ class ContextMenu {
|
|
|
46
62
|
// === ПОСТРОЕНИЕ ===
|
|
47
63
|
#renderItems(container, items) {
|
|
48
64
|
if (items === undefined) return;
|
|
49
|
-
for (
|
|
65
|
+
for (const item of items) {
|
|
50
66
|
if (item.type === "separator") {
|
|
51
67
|
// Разделитель — не пункт меню: роль `separator` говорит об этом и скринридеру
|
|
52
|
-
|
|
68
|
+
const separator = document.createElement("context_menu_separator");
|
|
53
69
|
separator.setAttribute("role", "separator");
|
|
54
70
|
container.appendChild(separator);
|
|
55
71
|
continue;
|
|
56
72
|
}
|
|
57
|
-
|
|
73
|
+
const row = document.createElement("context_menu_item");
|
|
74
|
+
row.setAttribute("role", "menuitem");
|
|
58
75
|
if (item.icon) {
|
|
59
|
-
|
|
76
|
+
const iconWrap = document.createElement("context_menu_item_icon");
|
|
60
77
|
iconWrap.innerHTML = new Icon(item.icon).getHTML();
|
|
61
78
|
// Значок — украшение: его лигатура не должна попадать ни в имя пункта, ни в набор
|
|
62
79
|
iconWrap.setAttribute("aria-hidden", "true");
|
|
63
80
|
row.appendChild(iconWrap);
|
|
64
81
|
}
|
|
65
|
-
|
|
82
|
+
const title = document.createElement("context_menu_item_title");
|
|
66
83
|
if (item.title !== undefined) title.innerText = item.title;
|
|
67
84
|
row.appendChild(title);
|
|
68
85
|
|
|
69
86
|
if (item.type === "submenu") {
|
|
87
|
+
row.setAttribute("aria-haspopup", "menu");
|
|
70
88
|
if (item.shortcut) {
|
|
71
|
-
|
|
89
|
+
const shortcut = document.createElement("context_menu_item_shortcut");
|
|
72
90
|
shortcut.innerText = item.shortcut;
|
|
73
91
|
row.appendChild(shortcut);
|
|
74
92
|
}
|
|
75
|
-
|
|
93
|
+
const arrow = document.createElement("context_menu_item_arrow");
|
|
76
94
|
arrow.innerHTML = new Icon(Icons.NAVIGATION.CHEVRON_RIGHT).getHTML();
|
|
77
95
|
arrow.setAttribute("aria-hidden", "true");
|
|
78
96
|
if (!item.shortcut) arrow.classList.add("context_menu_item_arrow_end");
|
|
79
97
|
row.appendChild(arrow);
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
const submenu = document.createElement("context_menu_submenu");
|
|
99
|
+
const subpanel = document.createElement("context_menu_panel");
|
|
100
|
+
submenu.setAttribute("role", "menu");
|
|
101
|
+
submenu.appendChild(subpanel);
|
|
102
|
+
this.#renderItems(subpanel, item.items);
|
|
82
103
|
row.appendChild(submenu);
|
|
83
104
|
// Подменю — свой уровень меню: Escape/ArrowLeft закрывают его и возвращают фокус на строку
|
|
84
105
|
new MenuKeyboard(submenu, {
|
|
85
106
|
itemSelector: "context_menu_item",
|
|
86
107
|
submenuSelector: "context_menu_submenu",
|
|
87
|
-
onSubmenuOpen: (
|
|
108
|
+
onSubmenuOpen: (innerRow) => this.#openSubMenu(innerRow),
|
|
88
109
|
onEscape: () => this.#closeSubMenu(row),
|
|
89
110
|
submenu: true,
|
|
90
111
|
trigger: row,
|
|
@@ -100,8 +121,10 @@ class ContextMenu {
|
|
|
100
121
|
this.hide();
|
|
101
122
|
});
|
|
102
123
|
}
|
|
124
|
+
// Опасный пункт (удалить, очистить) — подписью красного цвета
|
|
125
|
+
if (item.danger === true) row.dataset.danger = "true";
|
|
103
126
|
if (item.shortcut) {
|
|
104
|
-
|
|
127
|
+
const shortcut = document.createElement("context_menu_item_shortcut");
|
|
105
128
|
shortcut.innerText = item.shortcut;
|
|
106
129
|
row.appendChild(shortcut);
|
|
107
130
|
}
|
|
@@ -112,19 +135,19 @@ class ContextMenu {
|
|
|
112
135
|
|
|
113
136
|
// === ПОДМЕНЮ ===
|
|
114
137
|
#openSubMenu(row) {
|
|
115
|
-
|
|
138
|
+
const submenu = row.querySelector("context_menu_submenu");
|
|
116
139
|
if (submenu === null) return;
|
|
117
140
|
// Закрыть соседние подменю того же уровня
|
|
118
|
-
|
|
119
|
-
for (
|
|
141
|
+
const parent = row.parentElement;
|
|
142
|
+
for (const child of parent.children) {
|
|
120
143
|
if (child === row) continue;
|
|
121
|
-
|
|
144
|
+
const sibling = child.querySelector("context_menu_submenu");
|
|
122
145
|
if (sibling !== null) sibling.style.display = "none";
|
|
123
146
|
}
|
|
124
147
|
submenu.style.display = "flex";
|
|
125
148
|
submenu.style.left = "100%";
|
|
126
149
|
submenu.style.top = "0px";
|
|
127
|
-
|
|
150
|
+
const rowRect = row.getBoundingClientRect();
|
|
128
151
|
let subRect = submenu.getBoundingClientRect();
|
|
129
152
|
// Флип влево у правого края окна
|
|
130
153
|
if (subRect.right > window.innerWidth - 8) {
|
|
@@ -132,41 +155,97 @@ class ContextMenu {
|
|
|
132
155
|
subRect = submenu.getBoundingClientRect();
|
|
133
156
|
}
|
|
134
157
|
// Флип вверх у нижнего края окна
|
|
135
|
-
|
|
158
|
+
const overflow = rowRect.top + subRect.height - (window.innerHeight - 8);
|
|
136
159
|
if (overflow > 0) {
|
|
137
|
-
|
|
160
|
+
const shift = Math.min(overflow, rowRect.top - 8);
|
|
138
161
|
submenu.style.top = -shift + "px";
|
|
139
162
|
}
|
|
163
|
+
// Кадр — после расстановки: он начинается с уменьшенного состояния, а по нему положение
|
|
164
|
+
// и высоту уже не измерить. Перезапускать явно не нужно: панель до этого была скрыта
|
|
165
|
+
// (`display: none`), поэтому при показе кадр начинается заново. Кадр играет слой пунктов,
|
|
166
|
+
// поверхность подменю стоит на месте — так блюр видно с первого кадра
|
|
167
|
+
const panel = submenu.querySelector("context_menu_panel");
|
|
168
|
+
if (panel !== null) this.#play(panel, "context-scale-in", "enter");
|
|
169
|
+
this.#plate(submenu, "enter");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Снять вид движения по концу кадра появления.
|
|
174
|
+
*
|
|
175
|
+
* Вид держит конечное состояние (`animation-fill-mode: both`), поэтому на слое остаётся
|
|
176
|
+
* вычисленный `transform` — пусть и единичный. Элемент с `transform` становится собственным
|
|
177
|
+
* «backdrop root», и размытие подложки перестаёт видеть содержимое под слоем: меню шло
|
|
178
|
+
* прозрачной подложкой без блюра. После кадра вид снимается, а `animation-name` остаётся:
|
|
179
|
+
* по нему видно, что узел уже открыт.
|
|
180
|
+
*/
|
|
181
|
+
#stripMotion(element = new HTMLElement()) {
|
|
182
|
+
const done = (event) => {
|
|
183
|
+
if (event.target !== element) return;
|
|
184
|
+
element.removeEventListener("animationend", done);
|
|
185
|
+
element.removeAttribute("data-motion");
|
|
186
|
+
};
|
|
187
|
+
element.addEventListener("animationend", done);
|
|
140
188
|
}
|
|
141
189
|
#closeSubMenu(row) {
|
|
142
|
-
|
|
190
|
+
const submenu = row.querySelector("context_menu_submenu");
|
|
143
191
|
if (submenu !== null) submenu.style.display = "none";
|
|
144
192
|
}
|
|
145
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Замена пунктов: меню собирается заново. Нужно там, где меню строят по тому, на чём его
|
|
196
|
+
* открыли: список разговоров собирает пункты по выбранной строке («Отметить прочитанным»
|
|
197
|
+
* есть только у строки с непрочитанными) — для этого подходит и функция-строитель
|
|
198
|
+
*/
|
|
199
|
+
setItems(items = []) {
|
|
200
|
+
this.#panel.innerHTML = "";
|
|
201
|
+
this.#renderItems(this.#panel, items);
|
|
202
|
+
this.#keyboard.refresh();
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
/** Имя меню для скринридера: оно нужно слою с ролью меню, то есть самому меню */
|
|
206
|
+
setLabel(text = String()) {
|
|
207
|
+
this.#label = String(text);
|
|
208
|
+
if (this.#label === "") this.#menu.removeAttribute("aria-label");
|
|
209
|
+
else this.#menu.setAttribute("aria-label", this.#label);
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
getLabel() {
|
|
213
|
+
return this.#label === undefined ? "" : this.#label;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Слой пунктов. Пункты лежат в нём, а не прямо в меню, только ради кадра: кадр появления
|
|
217
|
+
* с прозрачностью и усадкой должен играть внутри, а поверхность меню с блюром — стоять
|
|
218
|
+
* недвижимо. Роли на нём нет: меню для скринридера — сам слой (`role="menu"` ставит
|
|
219
|
+
* `MenuKeyboard`), и по нему же клавиатура отбирает свои пункты
|
|
220
|
+
*/
|
|
221
|
+
getPanel() {
|
|
222
|
+
return this.#panel;
|
|
223
|
+
}
|
|
224
|
+
|
|
146
225
|
// === ПОКАЗ / СКРЫТИЕ ===
|
|
147
226
|
attach(target) {
|
|
148
|
-
|
|
149
|
-
|
|
227
|
+
const element = typeof target.set === "function" ? target.set() : target;
|
|
228
|
+
const listener = (event) => {
|
|
150
229
|
event.preventDefault();
|
|
151
|
-
this.#
|
|
230
|
+
this.#returnFocus = event.currentTarget === null ? element : event.currentTarget;
|
|
152
231
|
this.show(event.clientX, event.clientY);
|
|
153
232
|
};
|
|
154
233
|
// Клавиатура: клавиша ContextMenu или Shift+F10 открывают меню у элемента
|
|
155
|
-
|
|
234
|
+
const keyListener = (event) => {
|
|
156
235
|
if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
|
|
157
236
|
event.preventDefault();
|
|
158
|
-
this.#
|
|
159
|
-
|
|
237
|
+
this.#returnFocus = element;
|
|
238
|
+
const rect = element.getBoundingClientRect();
|
|
160
239
|
this.show(rect.left + 8, rect.top + rect.height / 2);
|
|
161
240
|
this.#keyboard.focusFirst();
|
|
162
241
|
};
|
|
163
242
|
element.addEventListener("contextmenu", listener);
|
|
164
|
-
element.addEventListener("keydown",
|
|
165
|
-
this.#targets.push({ element: element, listener: listener, key_listener:
|
|
243
|
+
element.addEventListener("keydown", keyListener);
|
|
244
|
+
this.#targets.push({ element: element, listener: listener, key_listener: keyListener });
|
|
166
245
|
return this;
|
|
167
246
|
}
|
|
168
247
|
detach(target) {
|
|
169
|
-
|
|
248
|
+
const element = typeof target.set === "function" ? target.set() : target;
|
|
170
249
|
for (let i = 0; i < this.#targets.length; i++) {
|
|
171
250
|
if (this.#targets[i].element === element) {
|
|
172
251
|
element.removeEventListener("contextmenu", this.#targets[i].listener);
|
|
@@ -179,7 +258,7 @@ class ContextMenu {
|
|
|
179
258
|
show(x = 0, y = 0) {
|
|
180
259
|
this.#cancelPendingHide();
|
|
181
260
|
// Сбросить геометрию подменю от прошлого показа
|
|
182
|
-
for (
|
|
261
|
+
for (const submenu of this.#menu.querySelectorAll("context_menu_submenu")) {
|
|
183
262
|
submenu.style.display = "none";
|
|
184
263
|
submenu.style.left = "100%";
|
|
185
264
|
submenu.style.top = "0px";
|
|
@@ -200,22 +279,24 @@ class ContextMenu {
|
|
|
200
279
|
this.#menu.style.left = "0px";
|
|
201
280
|
this.#menu.style.top = "0px";
|
|
202
281
|
// Позиция с учётом краёв окна
|
|
203
|
-
|
|
204
|
-
|
|
282
|
+
const left = Math.max(8, Math.min(x, window.innerWidth - this.#menu.offsetWidth - 8));
|
|
283
|
+
const top = Math.max(8, Math.min(y, window.innerHeight - this.#menu.offsetHeight - 8));
|
|
205
284
|
this.#menu.style.left = left + "px";
|
|
206
285
|
this.#menu.style.top = top + "px";
|
|
207
286
|
// Зафиксировать финальную геометрию до показа
|
|
208
287
|
void this.#menu.offsetWidth;
|
|
209
|
-
// Показ и перезапуск анимации уже в финальной
|
|
288
|
+
// Показ и перезапуск анимации уже в финальной позиции: кадр играет слой пунктов,
|
|
289
|
+
// а поверхность растворяется своим кадром по псевдоэлементу — ни `transform`, ни
|
|
290
|
+
// смена размера у неё не появляются, поэтому блюр подложки виден от начала и до конца,
|
|
291
|
+
// а рамка ни на миг не оказывается уже пунктов
|
|
210
292
|
this.#menu.style.visibility = "visible";
|
|
211
|
-
this.#menu
|
|
212
|
-
|
|
213
|
-
this.#menu.style.animationName = "context-scale-in";
|
|
293
|
+
this.#plate(this.#menu, "enter");
|
|
294
|
+
this.#play(this.#panel, "context-scale-in", "enter");
|
|
214
295
|
// Забрать фокус меню: без этого после правого клика стрелки и набор букв уходили бы
|
|
215
296
|
// элементу под курсором. Пункт при этом не подсвечивается — набор начнётся с найденного.
|
|
216
297
|
if (!this.#menu.contains(document.activeElement)) {
|
|
217
298
|
// Где фокус был до открытия: по нему меню вернёт его, если сама цель нефокусируема
|
|
218
|
-
this.#
|
|
299
|
+
this.#previousFocus = document.activeElement;
|
|
219
300
|
this.#menu.focus();
|
|
220
301
|
}
|
|
221
302
|
this.#keyboard.refresh();
|
|
@@ -230,18 +311,44 @@ class ContextMenu {
|
|
|
230
311
|
window.removeEventListener("keydown", this.#onWindowKeyDown);
|
|
231
312
|
window.removeEventListener("resize", this.#onWindowResize);
|
|
232
313
|
window.removeEventListener("blur", this.#onWindowBlur);
|
|
233
|
-
// Анимация
|
|
234
|
-
this.#menu
|
|
235
|
-
|
|
236
|
-
this.#menu.style.animationName = "context-scale-out";
|
|
314
|
+
// Анимация скрытия: уход играет слой пунктов, поверхность убирается тем же кругом
|
|
315
|
+
this.#plate(this.#menu, "exit");
|
|
316
|
+
this.#play(this.#panel, "context-scale-out", "exit");
|
|
237
317
|
this.#hideHandler = (event) => {
|
|
238
|
-
if (event.target !== this.#
|
|
239
|
-
this.#
|
|
318
|
+
if (event.target !== this.#panel) return;
|
|
319
|
+
this.#panel.removeEventListener("animationend", this.#hideHandler);
|
|
320
|
+
this.#stop(this.#menu);
|
|
321
|
+
this.#stop(this.#panel);
|
|
240
322
|
this.#menu.style.display = "none";
|
|
241
|
-
this.#menu.style.removeProperty("animation-name");
|
|
242
323
|
this.#hideHandler = undefined;
|
|
243
324
|
};
|
|
244
|
-
this.#
|
|
325
|
+
this.#panel.addEventListener("animationend", this.#hideHandler);
|
|
326
|
+
}
|
|
327
|
+
/** Пустить кадр по слою: имя, вид движения и снятие вида по концу кадра */
|
|
328
|
+
#play(element, frame, motion) {
|
|
329
|
+
element.style.animationName = "none";
|
|
330
|
+
void element.offsetWidth;
|
|
331
|
+
element.style.animationName = frame;
|
|
332
|
+
element.dataset.motion = motion;
|
|
333
|
+
this.#stripMotion(element);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Кадр поверхности. На самом слое его нет намеренно: с `transform` слой становится
|
|
337
|
+
* «backdrop root» и перестаёт видеть страницу — блюр подложки пропадал бы до конца кадра.
|
|
338
|
+
* Поэтому кадр идёт по псевдоэлементу поверхности (`::before`) и только по прозрачности:
|
|
339
|
+
* он ничего не двигает и не меняет размер, так что рамка ни на миг не оказывается уже
|
|
340
|
+
* пунктов, а блюр виден всю дорогу. Вид движения на слое открывает кадру его время и кривую
|
|
341
|
+
*/
|
|
342
|
+
#plate(element, motion) {
|
|
343
|
+
element.removeAttribute("data-motion");
|
|
344
|
+
void element.offsetWidth;
|
|
345
|
+
element.dataset.motion = motion;
|
|
346
|
+
this.#stripMotion(element);
|
|
347
|
+
}
|
|
348
|
+
/** Снять следы кадра: имя и вид движения с инлайновых стилей и атрибутов */
|
|
349
|
+
#stop(element) {
|
|
350
|
+
element.style.removeProperty("animation-name");
|
|
351
|
+
element.removeAttribute("data-motion");
|
|
245
352
|
}
|
|
246
353
|
/**
|
|
247
354
|
* Вернуть фокус после закрытия. Сначала — на цель `attach()`; сама цель бывает
|
|
@@ -249,7 +356,7 @@ class ContextMenu {
|
|
|
249
356
|
* где он был до открытия меню (правый клик фокусирует внутреннюю кнопку сам).
|
|
250
357
|
*/
|
|
251
358
|
#restoreFocus() {
|
|
252
|
-
const target = this.#
|
|
359
|
+
const target = this.#returnFocus;
|
|
253
360
|
if (
|
|
254
361
|
target !== undefined &&
|
|
255
362
|
target !== null &&
|
|
@@ -260,23 +367,24 @@ class ContextMenu {
|
|
|
260
367
|
target.focus();
|
|
261
368
|
return;
|
|
262
369
|
}
|
|
263
|
-
const previous = this.#
|
|
264
|
-
this.#
|
|
370
|
+
const previous = this.#previousFocus;
|
|
371
|
+
this.#previousFocus = undefined;
|
|
265
372
|
if (previous === undefined || previous === null || previous === document.body) return;
|
|
266
373
|
if (this.#menu.contains(previous) || !document.body.contains(previous)) return;
|
|
267
374
|
if (typeof previous.focus === "function") previous.focus();
|
|
268
375
|
}
|
|
269
376
|
#cancelPendingHide() {
|
|
270
377
|
if (this.#hideHandler !== undefined) {
|
|
271
|
-
this.#
|
|
272
|
-
this.#menu
|
|
378
|
+
this.#panel.removeEventListener("animationend", this.#hideHandler);
|
|
379
|
+
this.#stop(this.#menu);
|
|
380
|
+
this.#stop(this.#panel);
|
|
273
381
|
this.#hideHandler = undefined;
|
|
274
382
|
}
|
|
275
383
|
}
|
|
276
384
|
destroy() {
|
|
277
385
|
this.hide();
|
|
278
386
|
this.#keyboard.destroy();
|
|
279
|
-
for (
|
|
387
|
+
for (const target of this.#targets) {
|
|
280
388
|
target.element.removeEventListener("contextmenu", target.listener);
|
|
281
389
|
target.element.removeEventListener("keydown", target.key_listener);
|
|
282
390
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/* ── ContextMenu — контекстное меню ───────────────────────── */
|
|
2
|
+
/* Кадры слоя пунктов: прозрачность и усадка. Слой пунктов не носит блюра, поэтому его
|
|
3
|
+
`opacity` и `transform` блюру поверхности не мешают */
|
|
2
4
|
@keyframes context-scale-in {
|
|
3
5
|
from {
|
|
4
6
|
opacity: 0;
|
|
5
|
-
transform: scale(0.
|
|
7
|
+
transform: scale(0.96);
|
|
6
8
|
}
|
|
7
9
|
to {
|
|
8
10
|
opacity: 1;
|
|
@@ -16,7 +18,22 @@
|
|
|
16
18
|
}
|
|
17
19
|
to {
|
|
18
20
|
opacity: 0;
|
|
19
|
-
transform: scale(0.
|
|
21
|
+
transform: scale(0.96);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/* Кадры поверхности: растворение на месте. Размер она не меняет намеренно: с ростом по `inset`
|
|
25
|
+
рамка оказывалась меньше пунктов — на уходе это видно как вылезающая за кромку подпись
|
|
26
|
+
(замер: на 60-й мс ухода поверхность была на 7px меньше пунктов, а последний пункт ещё
|
|
27
|
+
не погас). Кадр идёт по псевдоэлементу и только по прозрачности: `transform` на нём или
|
|
28
|
+
на его предке сделал бы слой «backdrop root», и блюр подложки пропал бы до конца кадра */
|
|
29
|
+
@keyframes context-plate-in {
|
|
30
|
+
from {
|
|
31
|
+
opacity: 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
@keyframes context-plate-out {
|
|
35
|
+
to {
|
|
36
|
+
opacity: 0;
|
|
20
37
|
}
|
|
21
38
|
}
|
|
22
39
|
|
|
@@ -32,13 +49,56 @@ context_menu_submenu {
|
|
|
32
49
|
width: max-content;
|
|
33
50
|
min-width: 180px;
|
|
34
51
|
height: max-content;
|
|
35
|
-
padding: var(--
|
|
52
|
+
padding: var(--block_margin);
|
|
36
53
|
background-color: transparent;
|
|
37
54
|
color: var(--text_color);
|
|
38
55
|
border: none;
|
|
39
56
|
font-size: var(--font_default_size);
|
|
40
57
|
}
|
|
41
58
|
|
|
59
|
+
/* Слой пунктов: в нём живут пункты меню и подменю, и он же — само меню для скринридера.
|
|
60
|
+
Кадр появления играет он, поэтому поверхность меню остаётся неанимированной */
|
|
61
|
+
context_menu_panel {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
align-items: stretch;
|
|
65
|
+
outline: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Вид движения ставит context_menu.js: время и кривая — те же, что у остальных всплывающих
|
|
69
|
+
слоёв, а уход короче входа. Кадр играет слой пунктов: он не носит блюра, и его прозрачность
|
|
70
|
+
с усадкой поверхности меню не мешают */
|
|
71
|
+
context_menu_panel[data-motion="enter"] {
|
|
72
|
+
animation-duration: var(--motion_base);
|
|
73
|
+
animation-timing-function: var(--motion_ease);
|
|
74
|
+
animation-fill-mode: both;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
context_menu_panel[data-motion="exit"] {
|
|
78
|
+
animation-duration: var(--motion_fast);
|
|
79
|
+
animation-timing-function: var(--motion_ease);
|
|
80
|
+
animation-fill-mode: both;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Поверхность растворяется своим кадром: он идёт по псевдоэлементу и только по прозрачности,
|
|
84
|
+
поэтому сама поверхность не двигается и не меняет размер — блюр подложки виден всю дорогу,
|
|
85
|
+
а рамка ни на миг не оказывается уже пунктов */
|
|
86
|
+
chui_context_menu[data-motion="enter"]::before,
|
|
87
|
+
context_menu_submenu[data-motion="enter"]::before {
|
|
88
|
+
animation-name: context-plate-in;
|
|
89
|
+
animation-duration: var(--motion_base);
|
|
90
|
+
animation-timing-function: var(--motion_ease);
|
|
91
|
+
animation-fill-mode: both;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
chui_context_menu[data-motion="exit"]::before,
|
|
95
|
+
context_menu_submenu[data-motion="exit"]::before {
|
|
96
|
+
animation-name: context-plate-out;
|
|
97
|
+
animation-duration: var(--motion_fast);
|
|
98
|
+
animation-timing-function: var(--motion_ease);
|
|
99
|
+
animation-fill-mode: both;
|
|
100
|
+
}
|
|
101
|
+
|
|
42
102
|
chui_context_menu::before,
|
|
43
103
|
context_menu_submenu::before {
|
|
44
104
|
content: "";
|
|
@@ -47,7 +107,7 @@ context_menu_submenu::before {
|
|
|
47
107
|
z-index: -1;
|
|
48
108
|
background-color: var(--header_background);
|
|
49
109
|
border: 1px solid var(--border_color);
|
|
50
|
-
border-radius: var(--
|
|
110
|
+
border-radius: var(--radius_sm);
|
|
51
111
|
/* Меню — всплывающий слой: тень отрыва (как у меню SplitButton и палитры),
|
|
52
112
|
а не контактная тень карточки */
|
|
53
113
|
box-shadow: var(--box_shadow_drop);
|
|
@@ -56,7 +116,8 @@ context_menu_submenu::before {
|
|
|
56
116
|
pointer-events: none;
|
|
57
117
|
}
|
|
58
118
|
|
|
59
|
-
chui_context_menu
|
|
119
|
+
chui_context_menu,
|
|
120
|
+
context_menu_submenu {
|
|
60
121
|
position: fixed;
|
|
61
122
|
z-index: 5000;
|
|
62
123
|
transform-origin: top left;
|
|
@@ -87,8 +148,8 @@ context_menu_item {
|
|
|
87
148
|
white-space: nowrap;
|
|
88
149
|
cursor: pointer;
|
|
89
150
|
/* Компактный шаг контрола: 5px по вертикали дают те же 29px, что у пунктов меню */
|
|
90
|
-
padding: var(--
|
|
91
|
-
border-radius: var(--
|
|
151
|
+
padding: var(--block_padding_sm);
|
|
152
|
+
border-radius: var(--radius_sm);
|
|
92
153
|
color: var(--text_color);
|
|
93
154
|
gap: 6px;
|
|
94
155
|
box-sizing: border-box;
|
|
@@ -160,7 +221,7 @@ context_menu_item_shortcut {
|
|
|
160
221
|
}
|
|
161
222
|
|
|
162
223
|
context_menu_item:hover context_menu_item_shortcut {
|
|
163
|
-
color: var(--
|
|
224
|
+
color: var(--text_color_hover_disabled);
|
|
164
225
|
}
|
|
165
226
|
|
|
166
227
|
context_menu_separator {
|
|
@@ -169,6 +230,19 @@ context_menu_separator {
|
|
|
169
230
|
background: var(--border_color);
|
|
170
231
|
}
|
|
171
232
|
|
|
233
|
+
/* Опасный пункт (удалить, очистить) — подписью и значком красного цвета: значок
|
|
234
|
+
часть подписи, и красный текст с обычным значком читались бы нарочно недоведёнными */
|
|
235
|
+
context_menu_item[data-danger="true"] context_menu_item_title,
|
|
236
|
+
context_menu_item[data-danger="true"] context_menu_item_icon chui_icon {
|
|
237
|
+
color: var(--red_prime_background);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Наведение не должно гасить красноту: правило `:hover chui_icon` ниже по файлу
|
|
241
|
+
иначе перекрашивало значок опасного пункта в обычный цвет */
|
|
242
|
+
context_menu_item[data-danger="true"]:hover chui_icon {
|
|
243
|
+
color: var(--red_prime_background);
|
|
244
|
+
}
|
|
245
|
+
|
|
172
246
|
.context_menu_item_disabled {
|
|
173
247
|
cursor: default;
|
|
174
248
|
opacity: 0.5;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class CustomElement {
|
|
2
|
-
#
|
|
2
|
+
#chuiCustomElement = undefined;
|
|
3
3
|
constructor(
|
|
4
4
|
options = {
|
|
5
5
|
tag: String(),
|
|
@@ -12,27 +12,27 @@ class CustomElement {
|
|
|
12
12
|
// Пустая заготовка опции равносильна отсутствующей: `createElement("")` падал TypeError
|
|
13
13
|
const tag = optionOr(options.tag, undefined);
|
|
14
14
|
if (tag === undefined) throw new Error("Не установлен тэг элемента");
|
|
15
|
-
this.#
|
|
15
|
+
this.#chuiCustomElement = document.createElement(tag);
|
|
16
16
|
const id = optionOr(options.id, undefined);
|
|
17
17
|
if (id === undefined) throw new Error("Не установлен идентификатор элемента");
|
|
18
18
|
// Файл стилей — необязательная опция: пустой путь читался как файл и падал ENOENT
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
this.#
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
19
|
+
const pathToCss = optionOr(options.pathToCSS, undefined);
|
|
20
|
+
if (pathToCss !== undefined) setStyles(pathToCss, `chUiJS_CustomElement_${id}`);
|
|
21
|
+
this.#chuiCustomElement.id = id;
|
|
22
|
+
const className = optionOr(options.className, undefined);
|
|
23
|
+
if (className !== undefined) this.#chuiCustomElement.className = className;
|
|
24
24
|
}
|
|
25
25
|
addEventListener(type = String(), listener = () => {}) {
|
|
26
|
-
this.#
|
|
26
|
+
this.#chuiCustomElement.addEventListener(type, listener);
|
|
27
27
|
}
|
|
28
28
|
innerText(innerText = String()) {
|
|
29
|
-
this.#
|
|
29
|
+
this.#chuiCustomElement.innerText = innerText;
|
|
30
30
|
}
|
|
31
31
|
innerHTML(innerHTML = String()) {
|
|
32
|
-
this.#
|
|
32
|
+
this.#chuiCustomElement.innerHTML = innerHTML;
|
|
33
33
|
}
|
|
34
34
|
set() {
|
|
35
|
-
return this.#
|
|
35
|
+
return this.#chuiCustomElement;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|