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,167 @@
|
|
|
1
|
+
const { Spinner } = require("../chui_spinner/spinner");
|
|
2
|
+
const { I18n } = require("../../modules/chui_i18n/chui_i18n");
|
|
3
|
+
const { setStyles, optionOr } = require("../../modules/chui_functions");
|
|
4
|
+
const { Animation } = require("../../modules/chui_animations/animations");
|
|
5
|
+
const { OverlayA11y } = require("../../modules/chui_a11y/chui_a11y");
|
|
6
|
+
const { tokenTime } = require("../../modules/chui_animations/motion");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Экран занят: слой поверх всего окна с индикатором загрузки. Пока он открыт, до содержимого
|
|
10
|
+
* под ним не добраться — ни мышью, ни клавиатурой, ни скринридером.
|
|
11
|
+
*
|
|
12
|
+
* От `Spinner` отличается тем, что тот просто рисует индикатор в потоке, а этот занимает экран:
|
|
13
|
+
* слой встаёт в `body` (в карточке с `backdrop-filter` Chromium собирает такой узел отдельной
|
|
14
|
+
* поверхностью и рисует её под слоями из `body` — так уже теряли диалог), фокус удерживается
|
|
15
|
+
* внутри, а фон становится инертным. Роль слоя — `dialog` с именем загрузки, внутри объявляется
|
|
16
|
+
* `progressbar`; Escape слой не закрывает: это не сообщение, а признак занятости.
|
|
17
|
+
*
|
|
18
|
+
* Опции: `id`, `label` (подпись и имя для скринридера, по умолчанию «{loading}» из словаря),
|
|
19
|
+
* `delay` (мс до появления — против мигания на коротких операциях).
|
|
20
|
+
*
|
|
21
|
+
* Методы: `show()`, `hide()`, `isShown()`, `setLabel(text)`, `destroy()`, `set()`.
|
|
22
|
+
* Статические: `ScreenSpinner.show(options)` (создать и показать), `ScreenSpinner.hide()`,
|
|
23
|
+
* `ScreenSpinner.current`.
|
|
24
|
+
*
|
|
25
|
+
* ```javascript
|
|
26
|
+
* const busy = new ScreenSpinner({ delay: 200 });
|
|
27
|
+
* busy.show();
|
|
28
|
+
* await load();
|
|
29
|
+
* busy.hide();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
class ScreenSpinner {
|
|
33
|
+
// На экране один такой слой: два спиннера наложились бы друг на друга
|
|
34
|
+
static #current = undefined;
|
|
35
|
+
|
|
36
|
+
#root = document.createElement("chui_screen_spinner");
|
|
37
|
+
#card = document.createElement("screen_spinner_card");
|
|
38
|
+
#labelNode = document.createElement("screen_spinner_label");
|
|
39
|
+
#spinner = undefined;
|
|
40
|
+
#a11y = undefined;
|
|
41
|
+
#delay = 0;
|
|
42
|
+
#timer = undefined;
|
|
43
|
+
#shown = false;
|
|
44
|
+
#removed = false;
|
|
45
|
+
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
setStyles(__dirname + "/styles.css", "chUiJS_ScreenSpinner");
|
|
48
|
+
if (options.id !== undefined) this.#root.id = options.id;
|
|
49
|
+
const delay = Number(options.delay);
|
|
50
|
+
this.#delay = Number.isFinite(delay) && delay > 0 ? delay : 0;
|
|
51
|
+
this.#spinner = new Spinner();
|
|
52
|
+
this.#card.append(this.#spinner.set(), this.#labelNode);
|
|
53
|
+
this.#root.appendChild(this.#card);
|
|
54
|
+
this.#root.setAttribute("aria-busy", "true");
|
|
55
|
+
this.setLabel(optionOr(options.label, I18n.t("feedback.loading")));
|
|
56
|
+
this.#a11y = new OverlayA11y(this.#root, {
|
|
57
|
+
role: "dialog",
|
|
58
|
+
label: this.#labelNode.innerText,
|
|
59
|
+
// Escape здесь ничего не закрывает: занятость — не сообщение, которое можно отклонить
|
|
60
|
+
onEscape: () => {},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isShown() {
|
|
65
|
+
return this.#shown;
|
|
66
|
+
}
|
|
67
|
+
getLabel() {
|
|
68
|
+
return this.#labelNode.innerText;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setLabel(text = String()) {
|
|
72
|
+
const value = String(text);
|
|
73
|
+
this.#labelNode.innerText = value;
|
|
74
|
+
// Подпись пуста — под ней и места не надо: индикатор говорит сам
|
|
75
|
+
this.#labelNode.hidden = value.trim() === "";
|
|
76
|
+
if (this.#a11y !== undefined) this.#a11y.setLabel(value.trim() === "" ? I18n.t("feedback.loading") : value);
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Показ: слой встаёт в `body` и объявляет себя слоем. Задержка нужна против мигания —
|
|
82
|
+
* короткая операция успевает закончиться раньше, чем слой появится, и экран не дрожит.
|
|
83
|
+
*/
|
|
84
|
+
show() {
|
|
85
|
+
if (this.#shown === true) return this;
|
|
86
|
+
this.#shown = true;
|
|
87
|
+
// Слой показывают заново: след уборки прошлого показа снимаем, иначе новый слой
|
|
88
|
+
// не уберётся по концу кадра (`finish` посчитал бы работу сделанной)
|
|
89
|
+
this.#removed = false;
|
|
90
|
+
if (ScreenSpinner.#current !== undefined && ScreenSpinner.#current !== this) ScreenSpinner.#current.hide();
|
|
91
|
+
ScreenSpinner.#current = this;
|
|
92
|
+
const reveal = () => {
|
|
93
|
+
this.#timer = undefined;
|
|
94
|
+
if (this.#shown === false) return;
|
|
95
|
+
if (document.body.contains(this.#root) === false) document.body.appendChild(this.#root);
|
|
96
|
+
new Animation(this.#root).fadeIn();
|
|
97
|
+
new Animation(this.#card).panelIn();
|
|
98
|
+
this.#a11y.open();
|
|
99
|
+
this.#focusIn();
|
|
100
|
+
};
|
|
101
|
+
if (this.#delay > 0) this.#timer = setTimeout(reveal, this.#delay);
|
|
102
|
+
else reveal();
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Уход: слой гаснет, узел убирается по концу кадра. Слушатель одноразовый (проверка цели) —
|
|
108
|
+
* иначе он поймал бы любой конец кадра на слое, а срок из токена страхует случай, когда
|
|
109
|
+
* кадр не проиграется вовсе (слой скрыт вместе с родителем).
|
|
110
|
+
*/
|
|
111
|
+
hide() {
|
|
112
|
+
if (this.#shown === false) return this;
|
|
113
|
+
this.#shown = false;
|
|
114
|
+
if (this.#timer !== undefined) {
|
|
115
|
+
clearTimeout(this.#timer);
|
|
116
|
+
this.#timer = undefined;
|
|
117
|
+
}
|
|
118
|
+
if (ScreenSpinner.#current === this) ScreenSpinner.#current = undefined;
|
|
119
|
+
// Слой ещё не успел появиться (сработала задержка) — убирать нечего
|
|
120
|
+
if (this.#root.parentElement === null) return this;
|
|
121
|
+
this.#a11y.close();
|
|
122
|
+
new Animation(this.#root).fadeOut();
|
|
123
|
+
new Animation(this.#card).fadeOut();
|
|
124
|
+
const finish = () => {
|
|
125
|
+
if (this.#removed === true) return;
|
|
126
|
+
this.#removed = true;
|
|
127
|
+
this.#root.removeEventListener("animationend", finish);
|
|
128
|
+
this.#root.remove();
|
|
129
|
+
};
|
|
130
|
+
this.#root.addEventListener("animationend", finish);
|
|
131
|
+
setTimeout(finish, tokenTime("--motion_fast"));
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
set() {
|
|
136
|
+
return this.#root;
|
|
137
|
+
}
|
|
138
|
+
/** Полное удаление: снять слой (вместе с инертным фоном и слушателями) и убрать узел */
|
|
139
|
+
destroy() {
|
|
140
|
+
this.hide();
|
|
141
|
+
this.#removed = true;
|
|
142
|
+
this.#root.remove();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// === СТАТИЧЕСКОЕ ===
|
|
146
|
+
/** Показать спиннер, не заводя переменную: `ScreenSpinner.show({ label: "Сохраняем" })` */
|
|
147
|
+
static show(options = {}) {
|
|
148
|
+
const spinner = new ScreenSpinner(options);
|
|
149
|
+
spinner.show();
|
|
150
|
+
return spinner;
|
|
151
|
+
}
|
|
152
|
+
static hide() {
|
|
153
|
+
if (ScreenSpinner.#current !== undefined) ScreenSpinner.#current.hide();
|
|
154
|
+
}
|
|
155
|
+
static get current() {
|
|
156
|
+
return ScreenSpinner.#current;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// === ВНУТРЕННЕЕ ===
|
|
160
|
+
/** Фокус — в слой: иначе он остался бы на кнопке под ним, и Tab уходил бы в фон */
|
|
161
|
+
#focusIn() {
|
|
162
|
+
this.#root.setAttribute("tabindex", "-1");
|
|
163
|
+
this.#root.focus({ preventScroll: true });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
exports.ScreenSpinner = ScreenSpinner;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* Экран занят: слой поверх окна с индикатором загрузки.
|
|
2
|
+
Занавес держит сам слой (он и есть затемнение), а материал карточки — размытие: у слоя
|
|
3
|
+
`backdrop-filter` нет, иначе карточка внутри не увидела бы страницу (элемент с фильтром
|
|
4
|
+
становится собственным «backdrop root»). */
|
|
5
|
+
|
|
6
|
+
chui_screen_spinner {
|
|
7
|
+
position: fixed;
|
|
8
|
+
inset: 0;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
/* Выше модальных слоёв (у диалога 1000, у занавеса 999): занятость перекрывает всё */
|
|
14
|
+
z-index: 1100;
|
|
15
|
+
background: var(--modal_scrim);
|
|
16
|
+
/* Слой принимает на себя мышь и клавиатуру: под ним ничего не нажать */
|
|
17
|
+
cursor: progress;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
chui_screen_spinner:focus-visible {
|
|
21
|
+
outline: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
screen_spinner_card {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 10px;
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
padding: 18px 22px;
|
|
31
|
+
border: 1px solid var(--border_color);
|
|
32
|
+
border-radius: var(--radius_md);
|
|
33
|
+
background: var(--modal_background);
|
|
34
|
+
backdrop-filter: var(--global_blur);
|
|
35
|
+
-webkit-backdrop-filter: var(--global_blur);
|
|
36
|
+
box-shadow: var(--box_shadow_modal);
|
|
37
|
+
color: var(--text_color);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
screen_spinner_label {
|
|
41
|
+
font-size: var(--font_labels_size);
|
|
42
|
+
color: var(--text_color_disabled);
|
|
43
|
+
text-align: center;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Подписи нет — под неё не остаётся и зазора */
|
|
47
|
+
screen_spinner_label[hidden] {
|
|
48
|
+
display: none;
|
|
49
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const {setStyles, optionOr} = require("../../modules/chui_functions");
|
|
2
|
-
const {makeKeyboardButton} = require("../../modules/chui_a11y/chui_a11y");
|
|
1
|
+
const { setStyles, optionOr } = require("../../modules/chui_functions");
|
|
2
|
+
const { makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Сегментированный переключатель: один выбор из небольшого набора равнозначных вариантов
|
|
@@ -35,7 +35,7 @@ const {makeKeyboardButton} = require("../../modules/chui_a11y/chui_a11y");
|
|
|
35
35
|
class SegmentedControl {
|
|
36
36
|
static SIZE = {
|
|
37
37
|
SMALL: "small",
|
|
38
|
-
DEFAULT: "default"
|
|
38
|
+
DEFAULT: "default",
|
|
39
39
|
};
|
|
40
40
|
static #counter = 0;
|
|
41
41
|
|
|
@@ -48,24 +48,29 @@ 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
|
-
constructor(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
constructor(
|
|
58
|
+
options = {
|
|
59
|
+
id: String(),
|
|
60
|
+
options: [],
|
|
61
|
+
value: String(),
|
|
62
|
+
label: String(),
|
|
63
|
+
size: String(),
|
|
64
|
+
stretch: Boolean(),
|
|
65
|
+
activateOnFocus: Boolean(),
|
|
66
|
+
onChange: undefined,
|
|
67
|
+
}
|
|
68
|
+
) {
|
|
64
69
|
setStyles(__dirname + "/styles.css", "chUiJS_SegmentedControl");
|
|
65
70
|
SegmentedControl.#counter += 1;
|
|
66
71
|
this.#id = options.id === undefined ? `chui_segmented_control_${SegmentedControl.#counter}` : options.id;
|
|
67
|
-
this.#
|
|
68
|
-
this.#
|
|
72
|
+
this.#onChange = options.onChange;
|
|
73
|
+
this.#activateOnFocus = options.activateOnFocus === true;
|
|
69
74
|
this.#root.id = this.#id;
|
|
70
75
|
this.#root.setAttribute("role", "radiogroup");
|
|
71
76
|
this.setSize(options.size === undefined ? SegmentedControl.SIZE.DEFAULT : options.size);
|
|
@@ -78,6 +83,28 @@ class SegmentedControl {
|
|
|
78
83
|
// Пустая заготовка опции — это «выбор не сделан», а не значение «»
|
|
79
84
|
const value = optionOr(options.value, undefined);
|
|
80
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;
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
// GET
|
|
@@ -94,7 +121,7 @@ class SegmentedControl {
|
|
|
94
121
|
getOptions() {
|
|
95
122
|
return this.#order.map((value) => {
|
|
96
123
|
const record = this.#options.get(value);
|
|
97
|
-
return {value: record.value, title: record.title};
|
|
124
|
+
return { value: record.value, title: record.title };
|
|
98
125
|
});
|
|
99
126
|
}
|
|
100
127
|
|
|
@@ -125,7 +152,8 @@ class SegmentedControl {
|
|
|
125
152
|
|
|
126
153
|
/** Размер вариантов: `SegmentedControl.SIZE.{SMALL, DEFAULT}` */
|
|
127
154
|
setSize(size = String()) {
|
|
128
|
-
this.#root.dataset.size =
|
|
155
|
+
this.#root.dataset.size =
|
|
156
|
+
size === SegmentedControl.SIZE.SMALL ? SegmentedControl.SIZE.SMALL : SegmentedControl.SIZE.DEFAULT;
|
|
129
157
|
return this;
|
|
130
158
|
}
|
|
131
159
|
|
|
@@ -149,12 +177,12 @@ class SegmentedControl {
|
|
|
149
177
|
this.#value = next;
|
|
150
178
|
this.#active = next;
|
|
151
179
|
this.#sync();
|
|
152
|
-
if (!silent && changed && this.#
|
|
180
|
+
if (!silent && changed && this.#onChange !== undefined) this.#onChange(next);
|
|
153
181
|
return this;
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
/** Добавить вариант: `{value, title, icon, disabled}` */
|
|
157
|
-
addOption(option = {value: String(), title: String(), icon: undefined, disabled: Boolean()}) {
|
|
185
|
+
addOption(option = { value: String(), title: String(), icon: undefined, disabled: Boolean() }) {
|
|
158
186
|
this.#root.appendChild(this.#createOption(option));
|
|
159
187
|
this.#sync();
|
|
160
188
|
return this;
|
|
@@ -174,7 +202,8 @@ class SegmentedControl {
|
|
|
174
202
|
this.#value = undefined;
|
|
175
203
|
this.#active = undefined;
|
|
176
204
|
for (const option of list) this.#root.appendChild(this.#createOption(option));
|
|
177
|
-
if (previous !== undefined && this.#options.has(previous) && !this.#disabled.has(previous))
|
|
205
|
+
if (previous !== undefined && this.#options.has(previous) && !this.#disabled.has(previous))
|
|
206
|
+
this.#value = previous;
|
|
178
207
|
this.#sync();
|
|
179
208
|
return this;
|
|
180
209
|
}
|
|
@@ -229,11 +258,12 @@ class SegmentedControl {
|
|
|
229
258
|
makeKeyboardButton(node, "radio");
|
|
230
259
|
node.setAttribute("aria-checked", "false");
|
|
231
260
|
node.dataset.value = value;
|
|
232
|
-
if (option.icon !== undefined && option.icon !== null)
|
|
261
|
+
if (option.icon !== undefined && option.icon !== null)
|
|
262
|
+
node.insertAdjacentHTML("afterbegin", option.icon.getHTML());
|
|
233
263
|
const title = document.createElement("segmented_title");
|
|
234
264
|
title.innerText = option.title === undefined ? value : String(option.title);
|
|
235
265
|
node.appendChild(title);
|
|
236
|
-
this.#options.set(value, {value: value, title: title.innerText, node: node});
|
|
266
|
+
this.#options.set(value, { value: value, title: title.innerText, node: node });
|
|
237
267
|
this.#order.push(value);
|
|
238
268
|
// Запрет мог быть выставлен программно (setDisabled) и остаться после пересборки набора
|
|
239
269
|
if (option.disabled === true || this.#disabled.has(value)) this.setDisabled(value, true);
|
|
@@ -261,13 +291,45 @@ class SegmentedControl {
|
|
|
261
291
|
record.node.setAttribute("aria-checked", value === this.#value ? "true" : "false");
|
|
262
292
|
record.node.setAttribute("tabindex", value === this.#active ? "0" : "-1");
|
|
263
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`);
|
|
264
326
|
}
|
|
265
327
|
|
|
266
328
|
#focus(value = String()) {
|
|
267
329
|
this.#active = value;
|
|
268
330
|
this.#sync();
|
|
269
331
|
this.#options.get(value).node.focus();
|
|
270
|
-
if (this.#
|
|
332
|
+
if (this.#activateOnFocus) this.setValue(value);
|
|
271
333
|
}
|
|
272
334
|
|
|
273
335
|
// Варианты — кастомные теги, поэтому клик приходит и от мыши, и от Enter/Space
|
|
@@ -294,10 +356,11 @@ class SegmentedControl {
|
|
|
294
356
|
let next;
|
|
295
357
|
if (event.key === "Home") next = 0;
|
|
296
358
|
else if (event.key === "End") next = available.length - 1;
|
|
297
|
-
else if (event.key === "ArrowLeft" || event.key === "ArrowUp")
|
|
359
|
+
else if (event.key === "ArrowLeft" || event.key === "ArrowUp")
|
|
360
|
+
next = (current - 1 + available.length) % available.length;
|
|
298
361
|
else next = (current + 1) % available.length;
|
|
299
362
|
this.#focus(available[next]);
|
|
300
363
|
};
|
|
301
364
|
}
|
|
302
365
|
|
|
303
|
-
exports.SegmentedControl = SegmentedControl
|
|
366
|
+
exports.SegmentedControl = SegmentedControl;
|
|
@@ -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 задаёт собственный цвет, поэтому без этого правила глиф остаётся белым */
|