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
|
@@ -48,8 +48,11 @@ class SegmentedControl {
|
|
|
48
48
|
// Вариант с tabindex="0": с ним же работает обход Tab; за фокусом и выбором следит отдельно
|
|
49
49
|
#active = undefined;
|
|
50
50
|
#disabled = new Set();
|
|
51
|
-
#
|
|
52
|
-
#
|
|
51
|
+
#onChange = undefined;
|
|
52
|
+
#activateOnFocus = false;
|
|
53
|
+
// Подложка выбора измеряется по вёрстке: наблюдатель и отложенный кадр держат её на месте
|
|
54
|
+
#observer = undefined;
|
|
55
|
+
#frame = undefined;
|
|
53
56
|
|
|
54
57
|
constructor(
|
|
55
58
|
options = {
|
|
@@ -66,8 +69,8 @@ class SegmentedControl {
|
|
|
66
69
|
setStyles(__dirname + "/styles.css", "chUiJS_SegmentedControl");
|
|
67
70
|
SegmentedControl.#counter += 1;
|
|
68
71
|
this.#id = options.id === undefined ? `chui_segmented_control_${SegmentedControl.#counter}` : options.id;
|
|
69
|
-
this.#
|
|
70
|
-
this.#
|
|
72
|
+
this.#onChange = options.onChange;
|
|
73
|
+
this.#activateOnFocus = options.activateOnFocus === true;
|
|
71
74
|
this.#root.id = this.#id;
|
|
72
75
|
this.#root.setAttribute("role", "radiogroup");
|
|
73
76
|
this.setSize(options.size === undefined ? SegmentedControl.SIZE.DEFAULT : options.size);
|
|
@@ -80,6 +83,28 @@ class SegmentedControl {
|
|
|
80
83
|
// Пустая заготовка опции — это «выбор не сделан», а не значение «»
|
|
81
84
|
const value = optionOr(options.value, undefined);
|
|
82
85
|
if (value !== undefined) this.setValue(value, true);
|
|
86
|
+
// Ширина меняется от раскладки (растянутый контрол, поворот окна): без пересчёта
|
|
87
|
+
// подложка осталась бы на прежнем варианте
|
|
88
|
+
if (typeof ResizeObserver === "function") {
|
|
89
|
+
this.#observer = new ResizeObserver(() => this.#syncIndicator());
|
|
90
|
+
this.#observer.observe(this.#root);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Полное удаление: снять наблюдатель, отложенный кадр и слушатели */
|
|
95
|
+
destroy() {
|
|
96
|
+
if (this.#observer !== undefined) {
|
|
97
|
+
this.#observer.disconnect();
|
|
98
|
+
this.#observer = undefined;
|
|
99
|
+
}
|
|
100
|
+
if (this.#frame !== undefined) {
|
|
101
|
+
cancelAnimationFrame(this.#frame);
|
|
102
|
+
this.#frame = undefined;
|
|
103
|
+
}
|
|
104
|
+
this.#root.removeEventListener("keydown", this.#onKeyDown);
|
|
105
|
+
this.#root.removeEventListener("click", this.#onClick);
|
|
106
|
+
this.#root.remove();
|
|
107
|
+
return this;
|
|
83
108
|
}
|
|
84
109
|
|
|
85
110
|
// GET
|
|
@@ -152,7 +177,7 @@ class SegmentedControl {
|
|
|
152
177
|
this.#value = next;
|
|
153
178
|
this.#active = next;
|
|
154
179
|
this.#sync();
|
|
155
|
-
if (!silent && changed && this.#
|
|
180
|
+
if (!silent && changed && this.#onChange !== undefined) this.#onChange(next);
|
|
156
181
|
return this;
|
|
157
182
|
}
|
|
158
183
|
|
|
@@ -266,13 +291,45 @@ class SegmentedControl {
|
|
|
266
291
|
record.node.setAttribute("aria-checked", value === this.#value ? "true" : "false");
|
|
267
292
|
record.node.setAttribute("tabindex", value === this.#active ? "0" : "-1");
|
|
268
293
|
}
|
|
294
|
+
this.#scheduleIndicator();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Подложку двигают после раскладки: место варианта читается только из вёрстки */
|
|
298
|
+
#scheduleIndicator() {
|
|
299
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
300
|
+
this.#syncIndicator();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (this.#frame !== undefined) return;
|
|
304
|
+
this.#frame = requestAnimationFrame(() => {
|
|
305
|
+
this.#frame = undefined;
|
|
306
|
+
this.#syncIndicator();
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Место и ширина подложки выбора. Ноль — выбора нет или вёрстки ещё нет: подложки
|
|
312
|
+
* нулевой ширины не видно, поэтому до первой раскладки контрол выглядит как прежде.
|
|
313
|
+
*/
|
|
314
|
+
#syncIndicator() {
|
|
315
|
+
const record = this.#value === undefined ? undefined : this.#options.get(this.#value);
|
|
316
|
+
if (record === undefined || record.node.offsetWidth === 0) {
|
|
317
|
+
this.#root.style.setProperty("--segmented_width", "0px");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// В RTL началом ряда служит правый край: подложка стоит у него и едет влево
|
|
321
|
+
const rtl = window.getComputedStyle(this.#root).direction === "rtl";
|
|
322
|
+
const node = record.node;
|
|
323
|
+
const x = rtl ? this.#root.clientWidth - (node.offsetLeft + node.offsetWidth) : node.offsetLeft;
|
|
324
|
+
this.#root.style.setProperty("--segmented_offset", `${rtl ? -x : x}px`);
|
|
325
|
+
this.#root.style.setProperty("--segmented_width", `${node.offsetWidth}px`);
|
|
269
326
|
}
|
|
270
327
|
|
|
271
328
|
#focus(value = String()) {
|
|
272
329
|
this.#active = value;
|
|
273
330
|
this.#sync();
|
|
274
331
|
this.#options.get(value).node.focus();
|
|
275
|
-
if (this.#
|
|
332
|
+
if (this.#activateOnFocus) this.setValue(value);
|
|
276
333
|
}
|
|
277
334
|
|
|
278
335
|
// Варианты — кастомные теги, поэтому клик приходит и от мыши, и от Enter/Space
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Сегментированный переключатель: рамка — общая заливка контрола,
|
|
2
|
-
выбранный вариант —
|
|
2
|
+
выбранный вариант — подвижная подложка акцента */
|
|
3
3
|
chui_segmented_control {
|
|
4
4
|
display: inline-flex;
|
|
5
5
|
align-items: center;
|
|
@@ -7,8 +7,30 @@ chui_segmented_control {
|
|
|
7
7
|
padding: 2px;
|
|
8
8
|
width: max-content;
|
|
9
9
|
box-sizing: border-box;
|
|
10
|
-
border-radius: var(--
|
|
10
|
+
border-radius: var(--radius_sm);
|
|
11
11
|
background: var(--element_background);
|
|
12
|
+
/* Место и ширина подложки: считает JS по вёрстке, нули — выбор не сделан */
|
|
13
|
+
--segmented_offset: 0px;
|
|
14
|
+
--segmented_width: 0px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Подвижная подложка выбранного варианта: одна и та же деталь переезжает между
|
|
18
|
+
вариантами, вместо мгновенной смены заливки — как у системного переключателя.
|
|
19
|
+
Внутренний отступ равен отступу контейнера, поэтому подложка встаёт в ряд вариантов */
|
|
20
|
+
chui_segmented_control::before {
|
|
21
|
+
content: "";
|
|
22
|
+
position: absolute;
|
|
23
|
+
z-index: 0;
|
|
24
|
+
inset-block: 2px;
|
|
25
|
+
inset-inline-start: 0;
|
|
26
|
+
width: var(--segmented_width);
|
|
27
|
+
border-radius: var(--radius_sm);
|
|
28
|
+
background: var(--blue_prime_background);
|
|
29
|
+
transform: translateX(var(--segmented_offset));
|
|
30
|
+
transition:
|
|
31
|
+
transform var(--motion_base) var(--motion_ease),
|
|
32
|
+
width var(--motion_base) var(--motion_ease);
|
|
33
|
+
pointer-events: none;
|
|
12
34
|
}
|
|
13
35
|
|
|
14
36
|
segmented_option {
|
|
@@ -17,7 +39,7 @@ segmented_option {
|
|
|
17
39
|
gap: 6px;
|
|
18
40
|
padding: 4px 10px;
|
|
19
41
|
border: none;
|
|
20
|
-
border-radius: var(--
|
|
42
|
+
border-radius: var(--radius_sm);
|
|
21
43
|
background: transparent;
|
|
22
44
|
color: var(--text_color);
|
|
23
45
|
font-family: inherit;
|
|
@@ -26,14 +48,18 @@ segmented_option {
|
|
|
26
48
|
cursor: pointer;
|
|
27
49
|
outline: none;
|
|
28
50
|
user-select: none;
|
|
51
|
+
/* Вариант выше подложки: иначе акцент остался бы за текстом */
|
|
52
|
+
z-index: 1;
|
|
29
53
|
}
|
|
30
54
|
|
|
31
|
-
|
|
55
|
+
/* Наведение — только у невыбранных: у выбранного заливку несёт подложка, и вторая
|
|
56
|
+
заливка читалась бы как другой цвет */
|
|
57
|
+
segmented_option:not([aria-checked="true"]):hover {
|
|
32
58
|
background: var(--element_background_hover);
|
|
33
59
|
}
|
|
34
60
|
|
|
35
61
|
segmented_option[aria-checked="true"] {
|
|
36
|
-
background:
|
|
62
|
+
background: transparent;
|
|
37
63
|
color: var(--text_color_hover);
|
|
38
64
|
}
|
|
39
65
|
|
|
@@ -56,7 +82,7 @@ segmented_option[aria-disabled="true"]:hover {
|
|
|
56
82
|
|
|
57
83
|
segmented_option[aria-disabled="true"][aria-checked="true"] {
|
|
58
84
|
background: var(--blue_prime_background);
|
|
59
|
-
color: var(--
|
|
85
|
+
color: var(--text_color_hover_disabled);
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
/* chui_icon задаёт собственный цвет, поэтому без этого правила глиф остаётся белым */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { setStyles } = require("../../modules/chui_functions");
|
|
2
2
|
const { A11Y_FOCUSABLE_SELECTOR } = require("../../modules/chui_a11y/chui_a11y");
|
|
3
|
-
const { store:
|
|
3
|
+
const { store: sharedStore } = require("../../modules/chui_store/chui_store");
|
|
4
4
|
const { ThemeToggle } = require("../chui_theme_toggle/theme_toggle");
|
|
5
5
|
const { Toggle } = require("../chui_inputs/chui_toggle/toggle");
|
|
6
6
|
const { I18n } = require("../../modules/chui_i18n/chui_i18n");
|
|
@@ -74,10 +74,10 @@ class Settings {
|
|
|
74
74
|
#rows = [];
|
|
75
75
|
/** Хранилище и ключ, под которым панель держит свои значения (без ключа — не держит) */
|
|
76
76
|
#store = undefined;
|
|
77
|
-
#
|
|
77
|
+
#storeKey = undefined;
|
|
78
78
|
/** Подписчики правки строки и её обработчик из опций */
|
|
79
|
-
#
|
|
80
|
-
#
|
|
79
|
+
#changeListeners = [];
|
|
80
|
+
#onChange = undefined;
|
|
81
81
|
/** Последние известные значения строк: расхождение со снимком и есть правка пользователя */
|
|
82
82
|
#seen = new Map();
|
|
83
83
|
|
|
@@ -95,9 +95,9 @@ class Settings {
|
|
|
95
95
|
setStyles(__dirname + "/styles.css", "chUiJS_Settings");
|
|
96
96
|
Settings.#counter += 1;
|
|
97
97
|
this.#id = options.id === undefined ? `chui_settings_${Settings.#counter}` : options.id;
|
|
98
|
-
this.#store = options.store === undefined ?
|
|
99
|
-
this.#
|
|
100
|
-
this.#
|
|
98
|
+
this.#store = options.store === undefined ? sharedStore : options.store;
|
|
99
|
+
this.#storeKey = hasText(options.storeKey) ? String(options.storeKey) : undefined;
|
|
100
|
+
this.#onChange = options.onChange;
|
|
101
101
|
this.#root.id = this.#id;
|
|
102
102
|
if (options.title !== undefined) {
|
|
103
103
|
const title = document.createElement("settings_title");
|
|
@@ -114,7 +114,7 @@ class Settings {
|
|
|
114
114
|
// контролы (сегментированный, оценка), у которых своего события в DOM нет
|
|
115
115
|
this.#root.addEventListener("change", this.#onUserEdit);
|
|
116
116
|
this.#root.addEventListener("click", this.#onUserEdit);
|
|
117
|
-
if (this.#
|
|
117
|
+
if (this.#storeKey !== undefined) this.restore();
|
|
118
118
|
// Снимок значений: от него панель отсчитывает правки пользователя
|
|
119
119
|
this.#remember();
|
|
120
120
|
}
|
|
@@ -146,8 +146,8 @@ class Settings {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/** Значение строки: адрес — описание из `Settings.Row(...)`, название или имя */
|
|
149
|
-
getValue(
|
|
150
|
-
const record = this.#findRow(
|
|
149
|
+
getValue(rowOrTitle = undefined) {
|
|
150
|
+
const record = this.#findRow(rowOrTitle);
|
|
151
151
|
return record === undefined ? undefined : this.#readValue(record.control);
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -161,14 +161,14 @@ class Settings {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/** Значения строк раздела: адрес — описание из `Settings.Section(...)` или заголовок */
|
|
164
|
-
getSectionValues(
|
|
165
|
-
const section = this.#findSection(
|
|
164
|
+
getSectionValues(sectionOrTitle = undefined) {
|
|
165
|
+
const section = this.#findSection(sectionOrTitle);
|
|
166
166
|
return section === undefined ? {} : this.#valuesOf((record) => record.section === section);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/** Выключена ли строка: `false` и для несуществующей */
|
|
170
|
-
isRowDisabled(
|
|
171
|
-
const record = this.#findRow(
|
|
170
|
+
isRowDisabled(rowOrTitle = undefined) {
|
|
171
|
+
const record = this.#findRow(rowOrTitle);
|
|
172
172
|
return record === undefined ? false : record.disabled;
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -179,27 +179,27 @@ class Settings {
|
|
|
179
179
|
* может вернуть), а программная запись уведомления не рождает.
|
|
180
180
|
*/
|
|
181
181
|
addChangeListener(listener = () => {}) {
|
|
182
|
-
this.#
|
|
182
|
+
this.#changeListeners.push(listener);
|
|
183
183
|
return this;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// ХРАНЕНИЕ
|
|
187
187
|
/** Ключ панели в хранилище; `undefined` — панель значения не хранит */
|
|
188
188
|
getStoreKey() {
|
|
189
|
-
return this.#
|
|
189
|
+
return this.#storeKey;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/** Записать значения панели в хранилище; без ключа — ничего не делает */
|
|
193
193
|
save() {
|
|
194
|
-
if (this.#
|
|
195
|
-
this.#store.set(this.#
|
|
194
|
+
if (this.#storeKey === undefined) return this;
|
|
195
|
+
this.#store.set(this.#storeKey, this.#storageValues());
|
|
196
196
|
return this;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/** Прочитать значения из хранилища и заполнить строки; без сохранённого панель не меняется */
|
|
200
200
|
restore() {
|
|
201
|
-
if (this.#
|
|
202
|
-
return this.setValues(this.#store.get(this.#
|
|
201
|
+
if (this.#storeKey === undefined) return this;
|
|
202
|
+
return this.setValues(this.#store.get(this.#storeKey, {}));
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// SET
|
|
@@ -207,8 +207,8 @@ class Settings {
|
|
|
207
207
|
* Записать значение строки: адрес — описание из `Settings.Row(...)`, название или имя.
|
|
208
208
|
* Несуществующая строка и строка без писателя (кнопка) — без изменений.
|
|
209
209
|
*/
|
|
210
|
-
setValue(
|
|
211
|
-
const record = this.#findRow(
|
|
210
|
+
setValue(rowOrTitle = undefined, value = undefined) {
|
|
211
|
+
const record = this.#findRow(rowOrTitle);
|
|
212
212
|
if (record === undefined) return this;
|
|
213
213
|
this.#writeValue(record.control, value);
|
|
214
214
|
this.#remember();
|
|
@@ -225,8 +225,8 @@ class Settings {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
/** Заполнить строки раздела: адрес — описание из `Settings.Section(...)` или заголовок */
|
|
228
|
-
setSectionValues(
|
|
229
|
-
const section = this.#findSection(
|
|
228
|
+
setSectionValues(sectionOrTitle = undefined, values = Object()) {
|
|
229
|
+
const section = this.#findSection(sectionOrTitle);
|
|
230
230
|
if (section === undefined) return this;
|
|
231
231
|
return this.#writeValues(values, (record) => record.section === section);
|
|
232
232
|
}
|
|
@@ -242,8 +242,8 @@ class Settings {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
/** Выключить или включить строку: адресуется описанием из `Settings.Row(...)`, названием или именем */
|
|
245
|
-
setRowDisabled(
|
|
246
|
-
const record = this.#findRow(
|
|
245
|
+
setRowDisabled(rowOrTitle = undefined, disabled = Boolean()) {
|
|
246
|
+
const record = this.#findRow(rowOrTitle);
|
|
247
247
|
if (record === undefined) return this;
|
|
248
248
|
this.#setRowDisabled(record, disabled);
|
|
249
249
|
return this;
|
|
@@ -262,11 +262,10 @@ class Settings {
|
|
|
262
262
|
#onUserEdit = (event) => {
|
|
263
263
|
if (event.type === "click" && event.target instanceof Element && event.target.closest("button") !== null)
|
|
264
264
|
return;
|
|
265
|
-
if (this.#
|
|
266
|
-
return;
|
|
265
|
+
if (this.#storeKey === undefined && this.#changeListeners.length === 0 && this.#onChange === undefined) return;
|
|
267
266
|
const changed = this.#changedRows();
|
|
268
267
|
if (changed.length === 0) return;
|
|
269
|
-
if (this.#
|
|
268
|
+
if (this.#storeKey !== undefined) this.save();
|
|
270
269
|
for (const record of changed) this.#emitChange(record);
|
|
271
270
|
};
|
|
272
271
|
|
|
@@ -296,8 +295,8 @@ class Settings {
|
|
|
296
295
|
name: record.name,
|
|
297
296
|
value: this.#readValue(record.control),
|
|
298
297
|
};
|
|
299
|
-
for (const listener of [...this.#
|
|
300
|
-
if (this.#
|
|
298
|
+
for (const listener of [...this.#changeListeners]) listener(change, this);
|
|
299
|
+
if (this.#onChange !== undefined) this.#onChange(change, this);
|
|
301
300
|
}
|
|
302
301
|
|
|
303
302
|
/** Снимок значений строк: от него панель отсчитывает следующие правки */
|
|
@@ -361,13 +360,13 @@ class Settings {
|
|
|
361
360
|
const named = hasText(row.name);
|
|
362
361
|
node.classList.toggle("settings_row_control_only", solo);
|
|
363
362
|
node.classList.toggle("settings_row_stretch", row.stretch === true);
|
|
364
|
-
const
|
|
363
|
+
const descriptionId = solo ? undefined : this.#buildRowText(row, node);
|
|
365
364
|
const holder = document.createElement("settings_row_control");
|
|
366
365
|
const control =
|
|
367
366
|
row.control === undefined ? undefined : row.control instanceof Element ? row.control : row.control.set();
|
|
368
367
|
if (control !== undefined) {
|
|
369
368
|
holder.appendChild(control);
|
|
370
|
-
this.#applyControlA11y(control, titled ? String(row.title) : String(),
|
|
369
|
+
this.#applyControlA11y(control, titled ? String(row.title) : String(), descriptionId);
|
|
371
370
|
}
|
|
372
371
|
node.appendChild(holder);
|
|
373
372
|
// Храним исходный контрол (компонент или узел): findControl() возвращает его вместе с методами
|
|
@@ -395,7 +394,7 @@ class Settings {
|
|
|
395
394
|
/** Текст строки — название и описание; возвращает id описания (на него ссылается контрол) */
|
|
396
395
|
#buildRowText(row = Object(), node = new HTMLElement()) {
|
|
397
396
|
const text = document.createElement("settings_row_text");
|
|
398
|
-
let
|
|
397
|
+
let descriptionId = undefined;
|
|
399
398
|
if (hasText(row.title)) {
|
|
400
399
|
const title = document.createElement("settings_row_title");
|
|
401
400
|
title.innerText = String(row.title);
|
|
@@ -405,11 +404,11 @@ class Settings {
|
|
|
405
404
|
const description = document.createElement("settings_row_description");
|
|
406
405
|
description.innerText = String(row.description);
|
|
407
406
|
description.id = `${this.#id}_row_${this.#rows.length}_description`;
|
|
408
|
-
|
|
407
|
+
descriptionId = description.id;
|
|
409
408
|
text.appendChild(description);
|
|
410
409
|
}
|
|
411
410
|
node.appendChild(text);
|
|
412
|
-
return
|
|
411
|
+
return descriptionId;
|
|
413
412
|
}
|
|
414
413
|
|
|
415
414
|
/**
|
|
@@ -430,22 +429,22 @@ class Settings {
|
|
|
430
429
|
}
|
|
431
430
|
|
|
432
431
|
/** Строка по описанию из `Settings.Row(...)`, названию или имени (у строки-контрола их может и не быть) */
|
|
433
|
-
#findRow(
|
|
434
|
-
if (
|
|
432
|
+
#findRow(rowOrTitle = undefined) {
|
|
433
|
+
if (rowOrTitle === undefined || rowOrTitle === null) return undefined;
|
|
435
434
|
return this.#rows.find(
|
|
436
435
|
(row) =>
|
|
437
|
-
row.row ===
|
|
438
|
-
(row.title.length > 0 && row.title ===
|
|
439
|
-
(row.name.length > 0 && row.name ===
|
|
436
|
+
row.row === rowOrTitle ||
|
|
437
|
+
(row.title.length > 0 && row.title === rowOrTitle) ||
|
|
438
|
+
(row.name.length > 0 && row.name === rowOrTitle)
|
|
440
439
|
);
|
|
441
440
|
}
|
|
442
441
|
|
|
443
442
|
/** Раздел по описанию из `Settings.Section(...)` или по заголовку (у раздела без заголовка — только описание) */
|
|
444
|
-
#findSection(
|
|
445
|
-
if (
|
|
443
|
+
#findSection(sectionOrTitle = undefined) {
|
|
444
|
+
if (sectionOrTitle === undefined || sectionOrTitle === null) return undefined;
|
|
446
445
|
return this.#sections.find(
|
|
447
446
|
(section) =>
|
|
448
|
-
section ===
|
|
447
|
+
section === sectionOrTitle || (hasText(section.title) && String(section.title) === sectionOrTitle)
|
|
449
448
|
);
|
|
450
449
|
}
|
|
451
450
|
|
|
@@ -538,7 +537,7 @@ class Settings {
|
|
|
538
537
|
* а связь через `<label>` признаём только когда в нём есть текст: у `Toggle` разметка —
|
|
539
538
|
* пустой `<label>` вокруг `input`, и без `aria-label` флажок остался бы без имени.
|
|
540
539
|
*/
|
|
541
|
-
#applyControlA11y(control = new HTMLElement(), label = String(),
|
|
540
|
+
#applyControlA11y(control = new HTMLElement(), label = String(), descriptionId = undefined) {
|
|
542
541
|
const role = control.getAttribute("role");
|
|
543
542
|
const target =
|
|
544
543
|
role === null || role === "presentation"
|
|
@@ -547,7 +546,7 @@ class Settings {
|
|
|
547
546
|
: control.querySelector(Settings.#FOCUSABLE)
|
|
548
547
|
: control;
|
|
549
548
|
if (target === null) return;
|
|
550
|
-
if (
|
|
549
|
+
if (descriptionId !== undefined) target.setAttribute("aria-describedby", descriptionId);
|
|
551
550
|
if (label.length === 0) return;
|
|
552
551
|
if (target.getAttribute("aria-label") !== null) return;
|
|
553
552
|
const wrapper = target.closest("label");
|
|
@@ -3,7 +3,7 @@ chui_settings {
|
|
|
3
3
|
display: block;
|
|
4
4
|
width: -webkit-fill-available;
|
|
5
5
|
box-sizing: border-box;
|
|
6
|
-
margin: var(--
|
|
6
|
+
margin: var(--block_margin);
|
|
7
7
|
color: var(--text_color);
|
|
8
8
|
font-size: var(--font_default_size);
|
|
9
9
|
}
|
|
@@ -28,14 +28,14 @@ settings_section {
|
|
|
28
28
|
display: block;
|
|
29
29
|
margin-bottom: 12px;
|
|
30
30
|
border: 1px solid var(--border_color);
|
|
31
|
-
border-radius: var(--
|
|
31
|
+
border-radius: var(--radius_sm);
|
|
32
32
|
background: var(--element_background_2);
|
|
33
33
|
overflow: hidden;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
settings_section_head {
|
|
37
37
|
display: block;
|
|
38
|
-
padding: var(--
|
|
38
|
+
padding: var(--block_padding);
|
|
39
39
|
border-bottom: 1px solid var(--border_color);
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -60,7 +60,7 @@ settings_row {
|
|
|
60
60
|
align-items: center;
|
|
61
61
|
justify-content: space-between;
|
|
62
62
|
gap: 16px;
|
|
63
|
-
padding: var(--
|
|
63
|
+
padding: var(--block_padding);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/* Разделитель только между строками: у первой он уже есть в шапке раздела */
|
|
@@ -41,23 +41,23 @@ class ShortcutHelp {
|
|
|
41
41
|
|
|
42
42
|
#root = document.createElement("chui_shortcut_help");
|
|
43
43
|
#id = undefined;
|
|
44
|
-
#
|
|
44
|
+
#idTitle = undefined;
|
|
45
45
|
#title = document.createElement("shortcut_help_title");
|
|
46
46
|
#search = document.createElement("shortcut_help_search");
|
|
47
47
|
#input = undefined;
|
|
48
48
|
#status = document.createElement("shortcut_help_status");
|
|
49
|
-
#
|
|
49
|
+
#groupsNode = document.createElement("shortcut_help_groups");
|
|
50
50
|
#empty = document.createElement("shortcut_help_empty");
|
|
51
51
|
#source = [];
|
|
52
52
|
#groups = [];
|
|
53
|
-
#
|
|
54
|
-
#
|
|
53
|
+
#titleHidden = false;
|
|
54
|
+
#titleValue = undefined;
|
|
55
55
|
#label = undefined;
|
|
56
56
|
#columns = 1;
|
|
57
57
|
#searchable = false;
|
|
58
58
|
#placeholder = undefined;
|
|
59
|
-
#
|
|
60
|
-
#
|
|
59
|
+
#emptyText = undefined;
|
|
60
|
+
#kbdSize = Kbd.SIZE.DEFAULT;
|
|
61
61
|
|
|
62
62
|
constructor(
|
|
63
63
|
options = {
|
|
@@ -76,9 +76,9 @@ class ShortcutHelp {
|
|
|
76
76
|
setStyles(__dirname + "/styles.css", "chUiJS_ShortcutHelp");
|
|
77
77
|
ShortcutHelp.#counter += 1;
|
|
78
78
|
this.#id = optionOr(options.id, `chui_shortcut_help_${ShortcutHelp.#counter}`);
|
|
79
|
-
this.#
|
|
79
|
+
this.#idTitle = `${this.#id}_title`;
|
|
80
80
|
this.#root.id = this.#id;
|
|
81
|
-
this.#title.id = this.#
|
|
81
|
+
this.#title.id = this.#idTitle;
|
|
82
82
|
// Заголовок — настоящий заголовок: к шпаргалке можно перейти навигацией по заголовкам
|
|
83
83
|
this.#title.setAttribute("role", "heading");
|
|
84
84
|
this.#title.setAttribute("aria-level", "2");
|
|
@@ -89,16 +89,16 @@ class ShortcutHelp {
|
|
|
89
89
|
this.#root.appendChild(this.#title);
|
|
90
90
|
this.#root.appendChild(this.#search);
|
|
91
91
|
this.#root.appendChild(this.#status);
|
|
92
|
-
this.#root.appendChild(this.#
|
|
92
|
+
this.#root.appendChild(this.#groupsNode);
|
|
93
93
|
this.#root.appendChild(this.#empty);
|
|
94
94
|
this.#label = options.label;
|
|
95
|
-
this.#
|
|
96
|
-
this.#
|
|
95
|
+
this.#titleHidden = options.title === null;
|
|
96
|
+
this.#titleValue = typeof options.title === "string" && options.title !== "" ? options.title : undefined;
|
|
97
97
|
this.#placeholder = options.searchPlaceholder;
|
|
98
|
-
this.#
|
|
99
|
-
if (options.kbdSize !== undefined) this.#
|
|
98
|
+
this.#emptyText = options.emptyText;
|
|
99
|
+
if (options.kbdSize !== undefined) this.#kbdSize = options.kbdSize;
|
|
100
100
|
this.setColumns(options.columns === undefined ? 1 : options.columns);
|
|
101
|
-
this.setTitle(this.#
|
|
101
|
+
this.setTitle(this.#titleHidden ? null : this.#titleValue);
|
|
102
102
|
if (options.searchable === true) this.setSearchable(true);
|
|
103
103
|
else this.#syncSearch();
|
|
104
104
|
this.#source = ShortcutHelp.#normalizeSource(options);
|
|
@@ -112,7 +112,7 @@ class ShortcutHelp {
|
|
|
112
112
|
|
|
113
113
|
/** Заголовок на экране (`""`, если он убран) */
|
|
114
114
|
getTitle() {
|
|
115
|
-
return this.#
|
|
115
|
+
return this.#titleHidden ? "" : this.#title.innerText;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/** Группы со строками: `[{title, items: [{keys, description, label}]}]` */
|
|
@@ -157,12 +157,10 @@ class ShortcutHelp {
|
|
|
157
157
|
|
|
158
158
|
/** Заголовок; `null` убирает его (тогда блок называется по `label`) */
|
|
159
159
|
setTitle(text = undefined) {
|
|
160
|
-
this.#
|
|
161
|
-
this.#
|
|
162
|
-
this.#title.innerText = this.#
|
|
163
|
-
|
|
164
|
-
: optionOr(this.#title_value, I18n.t("navigation.shortcutHelp"));
|
|
165
|
-
this.#title.hidden = this.#title_hidden;
|
|
160
|
+
this.#titleHidden = text === null;
|
|
161
|
+
this.#titleValue = typeof text === "string" && text !== "" ? text : undefined;
|
|
162
|
+
this.#title.innerText = this.#titleHidden ? "" : optionOr(this.#titleValue, I18n.t("navigation.shortcutHelp"));
|
|
163
|
+
this.#title.hidden = this.#titleHidden;
|
|
166
164
|
this.#syncAria();
|
|
167
165
|
return this;
|
|
168
166
|
}
|
|
@@ -193,9 +191,9 @@ class ShortcutHelp {
|
|
|
193
191
|
inputListener: () => this.#syncSearch(),
|
|
194
192
|
});
|
|
195
193
|
// Видимой подписи у поля нет, а пл҃ейсхолдер — не имя: имя ставим явно
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
198
|
-
|
|
194
|
+
const searchField = this.#input.set().querySelector("input");
|
|
195
|
+
if (searchField !== null) {
|
|
196
|
+
searchField.setAttribute(
|
|
199
197
|
"aria-label",
|
|
200
198
|
optionOr(this.#placeholder, I18n.t("navigation.shortcutHelpSearch"))
|
|
201
199
|
);
|
|
@@ -266,21 +264,21 @@ class ShortcutHelp {
|
|
|
266
264
|
}
|
|
267
265
|
|
|
268
266
|
#render() {
|
|
269
|
-
this.#
|
|
267
|
+
this.#groupsNode.innerHTML = "";
|
|
270
268
|
this.#groups = [];
|
|
271
269
|
this.#source.forEach((group, index) => {
|
|
272
270
|
const node = document.createElement("div");
|
|
273
271
|
node.className = "shortcut_help_group";
|
|
274
|
-
const
|
|
272
|
+
const idGroupTitle = `${this.#id}_group_${index}`;
|
|
275
273
|
node.setAttribute("role", "group");
|
|
276
274
|
const title = document.createElement("shortcut_help_group_title");
|
|
277
|
-
title.id =
|
|
275
|
+
title.id = idGroupTitle;
|
|
278
276
|
title.setAttribute("role", "heading");
|
|
279
277
|
title.setAttribute("aria-level", "3");
|
|
280
278
|
title.innerText = group.title;
|
|
281
279
|
title.hidden = group.title === "";
|
|
282
280
|
node.appendChild(title);
|
|
283
|
-
if (group.title !== "") node.setAttribute("aria-labelledby",
|
|
281
|
+
if (group.title !== "") node.setAttribute("aria-labelledby", idGroupTitle);
|
|
284
282
|
const list = document.createElement("ul");
|
|
285
283
|
list.className = "shortcut_help_list";
|
|
286
284
|
const rows = [];
|
|
@@ -294,7 +292,7 @@ class ShortcutHelp {
|
|
|
294
292
|
if (item.keys.length > 0) {
|
|
295
293
|
const keys = document.createElement("shortcut_help_keys");
|
|
296
294
|
// Сочетание рисует Kbd: клавиша — настоящий <kbd>, значковым нужен label
|
|
297
|
-
keys.appendChild(new Kbd({ keys: item.keys, size: this.#
|
|
295
|
+
keys.appendChild(new Kbd({ keys: item.keys, size: this.#kbdSize, label: item.label }).set());
|
|
298
296
|
line.appendChild(keys);
|
|
299
297
|
}
|
|
300
298
|
const text = document.createElement("shortcut_help_text");
|
|
@@ -305,10 +303,10 @@ class ShortcutHelp {
|
|
|
305
303
|
rows.push({ node: row, search: `${item.description} ${item.keys.join(" ")}`.toLowerCase() });
|
|
306
304
|
}
|
|
307
305
|
node.appendChild(list);
|
|
308
|
-
this.#
|
|
306
|
+
this.#groupsNode.appendChild(node);
|
|
309
307
|
this.#groups.push({ node: node, rows: rows });
|
|
310
308
|
});
|
|
311
|
-
this.#empty.innerText = optionOr(this.#
|
|
309
|
+
this.#empty.innerText = optionOr(this.#emptyText, I18n.t("navigation.shortcutHelpEmpty"));
|
|
312
310
|
this.#syncSearch();
|
|
313
311
|
this.#syncAria();
|
|
314
312
|
}
|
|
@@ -328,7 +326,7 @@ class ShortcutHelp {
|
|
|
328
326
|
visible += shown;
|
|
329
327
|
}
|
|
330
328
|
const nothing = visible === 0 && this.#groups.length > 0;
|
|
331
|
-
this.#
|
|
329
|
+
this.#groupsNode.hidden = nothing;
|
|
332
330
|
this.#empty.hidden = !nothing;
|
|
333
331
|
return visible;
|
|
334
332
|
}
|
|
@@ -344,7 +342,7 @@ class ShortcutHelp {
|
|
|
344
342
|
}
|
|
345
343
|
|
|
346
344
|
#syncAria() {
|
|
347
|
-
if (this.#
|
|
345
|
+
if (this.#titleHidden) {
|
|
348
346
|
this.#root.setAttribute("role", "group");
|
|
349
347
|
this.#root.removeAttribute("aria-labelledby");
|
|
350
348
|
this.#root.setAttribute("aria-label", optionOr(this.#label, I18n.t("navigation.shortcutHelp")));
|
|
@@ -352,7 +350,7 @@ class ShortcutHelp {
|
|
|
352
350
|
}
|
|
353
351
|
this.#root.setAttribute("role", "region");
|
|
354
352
|
this.#root.removeAttribute("aria-label");
|
|
355
|
-
this.#root.setAttribute("aria-labelledby", this.#
|
|
353
|
+
this.#root.setAttribute("aria-labelledby", this.#idTitle);
|
|
356
354
|
}
|
|
357
355
|
}
|
|
358
356
|
|