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
|
@@ -88,32 +88,32 @@ class DataGridBody {
|
|
|
88
88
|
static #counter = 0;
|
|
89
89
|
|
|
90
90
|
#root = document.createElement("data_grid_body");
|
|
91
|
-
#
|
|
92
|
-
#
|
|
91
|
+
#spacerTop = document.createElement("data_grid_spacer");
|
|
92
|
+
#spacerBottom = document.createElement("data_grid_spacer");
|
|
93
93
|
#empty = document.createElement("data_grid_body_empty");
|
|
94
94
|
#loader = document.createElement("data_grid_body_loader");
|
|
95
95
|
// Окно в DOM: индекс строки → узел
|
|
96
96
|
#nodes = new Map();
|
|
97
97
|
#id = undefined;
|
|
98
98
|
#items = [];
|
|
99
|
-
#
|
|
99
|
+
#itemHeight = 34;
|
|
100
100
|
#buffer = DataGridBody.DEFAULT_BUFFER;
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
101
|
+
#itemTag = "data_grid_row";
|
|
102
|
+
#renderItem = undefined;
|
|
103
|
+
#onItemClickCallback = undefined;
|
|
104
|
+
#onActivate = undefined;
|
|
105
|
+
#onRender = undefined;
|
|
106
|
+
#onLoadMore = undefined;
|
|
107
|
+
#loadingText = undefined;
|
|
108
|
+
#loadThreshold = DataGridBody.DEFAULT_LOAD_THRESHOLD;
|
|
109
|
+
#hasMore = false;
|
|
110
|
+
#loadingMore = false;
|
|
111
111
|
// Длина, для которой запрос уже сделан: не просим ещё раз, пока данных не прибавилось
|
|
112
|
-
#
|
|
113
|
-
#
|
|
112
|
+
#requestedAt = -1;
|
|
113
|
+
#windowState = { from: 0, to: 0 };
|
|
114
114
|
#raf = null;
|
|
115
|
-
#
|
|
116
|
-
#
|
|
115
|
+
#refreshRaf = null;
|
|
116
|
+
#resizeObserver = undefined;
|
|
117
117
|
#bound = false;
|
|
118
118
|
|
|
119
119
|
constructor(
|
|
@@ -136,23 +136,23 @@ class DataGridBody {
|
|
|
136
136
|
) {
|
|
137
137
|
DataGridBody.#counter += 1;
|
|
138
138
|
this.#id = optionOr(options.id, `chui_data_grid_body_${DataGridBody.#counter}`);
|
|
139
|
-
this.#
|
|
140
|
-
this.#
|
|
139
|
+
this.#itemTag = optionOr(options.itemTag, "data_grid_row");
|
|
140
|
+
this.#itemHeight = options.itemHeight === undefined ? 34 : options.itemHeight;
|
|
141
141
|
this.#buffer = options.buffer === undefined ? DataGridBody.DEFAULT_BUFFER : options.buffer;
|
|
142
|
-
this.#
|
|
143
|
-
this.#
|
|
144
|
-
this.#
|
|
145
|
-
this.#
|
|
146
|
-
this.#
|
|
147
|
-
this.#
|
|
148
|
-
this.#
|
|
142
|
+
this.#renderItem = options.renderItem;
|
|
143
|
+
this.#onItemClickCallback = options.onItemClick;
|
|
144
|
+
this.#onActivate = options.onActivate;
|
|
145
|
+
this.#onRender = options.onRender;
|
|
146
|
+
this.#onLoadMore = options.onLoadMore;
|
|
147
|
+
this.#loadingText = optionOr(options.loadingText, I18n.t("common.loading"));
|
|
148
|
+
this.#loadThreshold =
|
|
149
149
|
options.loadMoreThreshold === undefined ? DataGridBody.DEFAULT_LOAD_THRESHOLD : options.loadMoreThreshold;
|
|
150
150
|
// Без onLoadMore тело считает, что загружено всё
|
|
151
|
-
this.#
|
|
151
|
+
this.#hasMore = options.hasMore === undefined ? options.onLoadMore !== undefined : options.hasMore === true;
|
|
152
152
|
this.#root.id = this.#id;
|
|
153
|
-
this.#
|
|
154
|
-
this.#
|
|
155
|
-
for (const spacer of [this.#
|
|
153
|
+
this.#spacerTop.dataset.pos = "top";
|
|
154
|
+
this.#spacerBottom.dataset.pos = "bottom";
|
|
155
|
+
for (const spacer of [this.#spacerTop, this.#spacerBottom]) {
|
|
156
156
|
spacer.setAttribute("aria-hidden", "true");
|
|
157
157
|
spacer.style.height = "0px";
|
|
158
158
|
this.#root.appendChild(spacer);
|
|
@@ -171,8 +171,8 @@ class DataGridBody {
|
|
|
171
171
|
// по нему считается окно, поэтому пересобираем его при изменении размера. Работу
|
|
172
172
|
// уводим в кадр: правки layout внутри колбэка ResizeObserver дают
|
|
173
173
|
// «ResizeObserver loop completed with undelivered notifications»
|
|
174
|
-
this.#
|
|
175
|
-
this.#
|
|
174
|
+
this.#resizeObserver = new ResizeObserver(() => this.#scheduleRefresh());
|
|
175
|
+
this.#resizeObserver.observe(this.#root);
|
|
176
176
|
this.#bound = true;
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -181,11 +181,11 @@ class DataGridBody {
|
|
|
181
181
|
return this.#id;
|
|
182
182
|
}
|
|
183
183
|
getItemHeight() {
|
|
184
|
-
return this.#
|
|
184
|
+
return this.#itemHeight;
|
|
185
185
|
}
|
|
186
186
|
/** Окно, которое сейчас в DOM (вместе с запасом `buffer`) */
|
|
187
187
|
getRenderedRange() {
|
|
188
|
-
return { ...this.#
|
|
188
|
+
return { ...this.#windowState };
|
|
189
189
|
}
|
|
190
190
|
/** Только видимые строки — без запаса; это то, что реально видит пользователь */
|
|
191
191
|
getVisibleRange() {
|
|
@@ -213,7 +213,7 @@ class DataGridBody {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
setItemHeight(height = Number()) {
|
|
216
|
-
this.#
|
|
216
|
+
this.#itemHeight = height;
|
|
217
217
|
for (const node of this.#nodes.values()) node.style.height = `${height}px`;
|
|
218
218
|
return this.refresh();
|
|
219
219
|
}
|
|
@@ -227,18 +227,18 @@ class DataGridBody {
|
|
|
227
227
|
return this;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
setHasMore(
|
|
231
|
-
this.#
|
|
230
|
+
setHasMore(hasMore = true) {
|
|
231
|
+
this.#hasMore = hasMore === true;
|
|
232
232
|
this.#applyLoader();
|
|
233
|
-
if (this.#
|
|
233
|
+
if (this.#hasMore) this.#checkLoadMore(this.#windowState.to);
|
|
234
234
|
return this;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
setLoadingMore(loading = true) {
|
|
238
|
-
this.#
|
|
238
|
+
this.#loadingMore = loading === true;
|
|
239
239
|
this.#applyLoader();
|
|
240
240
|
// Загрузка завершилась, но окно всё ещё у конца — возможно, нужен следующий запрос
|
|
241
|
-
if (!this.#
|
|
241
|
+
if (!this.#loadingMore) this.#checkLoadMore(this.#windowState.to);
|
|
242
242
|
return this;
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -252,7 +252,7 @@ class DataGridBody {
|
|
|
252
252
|
const count = this.#items.length;
|
|
253
253
|
if (count === 0) return this;
|
|
254
254
|
const target = Math.max(0, Math.min(index, count - 1));
|
|
255
|
-
const height = this.#
|
|
255
|
+
const height = this.#itemHeight;
|
|
256
256
|
const view = this.#root.clientHeight || this.#visibleCount() * height;
|
|
257
257
|
let top = this.#root.scrollTop;
|
|
258
258
|
if (align === DataGridBody.ALIGN.START) {
|
|
@@ -268,8 +268,8 @@ class DataGridBody {
|
|
|
268
268
|
if (target < from) top = target * height;
|
|
269
269
|
else if (target >= to) top = (target + 1) * height - view;
|
|
270
270
|
}
|
|
271
|
-
const
|
|
272
|
-
top = Math.max(0, Math.min(top,
|
|
271
|
+
const maximumTop = Math.max(0, count * height - view);
|
|
272
|
+
top = Math.max(0, Math.min(top, maximumTop));
|
|
273
273
|
if (top !== this.#root.scrollTop) this.#root.scrollTop = top;
|
|
274
274
|
// Окно пересобираем сразу: не ждём события scroll, иначе состояние видно «на кадр позже»
|
|
275
275
|
this.#renderWindow(this.#firstVisible());
|
|
@@ -284,14 +284,14 @@ class DataGridBody {
|
|
|
284
284
|
cancelAnimationFrame(this.#raf);
|
|
285
285
|
this.#raf = null;
|
|
286
286
|
}
|
|
287
|
-
if (this.#
|
|
288
|
-
cancelAnimationFrame(this.#
|
|
289
|
-
this.#
|
|
287
|
+
if (this.#refreshRaf !== null) {
|
|
288
|
+
cancelAnimationFrame(this.#refreshRaf);
|
|
289
|
+
this.#refreshRaf = null;
|
|
290
290
|
}
|
|
291
291
|
this.#root.removeEventListener("scroll", this.#onScroll);
|
|
292
|
-
if (this.#
|
|
293
|
-
this.#
|
|
294
|
-
this.#
|
|
292
|
+
if (this.#resizeObserver !== undefined) {
|
|
293
|
+
this.#resizeObserver.disconnect();
|
|
294
|
+
this.#resizeObserver = undefined;
|
|
295
295
|
}
|
|
296
296
|
for (const node of this.#nodes.values()) {
|
|
297
297
|
node.removeEventListener("click", this.#onItemClick);
|
|
@@ -302,35 +302,35 @@ class DataGridBody {
|
|
|
302
302
|
|
|
303
303
|
// === ВНУТРЕННЕЕ ===
|
|
304
304
|
#scheduleRefresh() {
|
|
305
|
-
if (this.#
|
|
306
|
-
this.#
|
|
307
|
-
this.#
|
|
305
|
+
if (this.#refreshRaf !== null) return;
|
|
306
|
+
this.#refreshRaf = requestAnimationFrame(() => {
|
|
307
|
+
this.#refreshRaf = null;
|
|
308
308
|
if (this.#bound) this.refresh();
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
#applyLoader() {
|
|
313
|
-
this.#loader.innerText = this.#
|
|
314
|
-
this.#loader.hidden = !this.#
|
|
315
|
-
this.#root.dataset.loading = this.#
|
|
313
|
+
this.#loader.innerText = this.#loadingText;
|
|
314
|
+
this.#loader.hidden = !this.#loadingMore;
|
|
315
|
+
this.#root.dataset.loading = this.#loadingMore ? "true" : "false";
|
|
316
316
|
this.#syncEmpty();
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
// «Нет данных» показываем только когда данных нет и новых не ждём: иначе текст пустого
|
|
320
320
|
// состояния спорил бы с индикатором догрузки на время запроса
|
|
321
321
|
#syncEmpty() {
|
|
322
|
-
this.#empty.hidden = this.#items.length > 0 || this.#
|
|
322
|
+
this.#empty.hidden = this.#items.length > 0 || this.#hasMore || this.#loadingMore;
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
#firstVisible() {
|
|
326
326
|
const count = this.#items.length;
|
|
327
327
|
if (count === 0) return 0;
|
|
328
|
-
return Math.min(count - 1, Math.max(0, Math.floor(Math.max(0, this.#root.scrollTop) / this.#
|
|
328
|
+
return Math.min(count - 1, Math.max(0, Math.floor(Math.max(0, this.#root.scrollTop) / this.#itemHeight)));
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
#visibleCount() {
|
|
332
|
-
const view = this.#root.clientHeight || this.#
|
|
333
|
-
return Math.max(1, Math.ceil(view / this.#
|
|
332
|
+
const view = this.#root.clientHeight || this.#itemHeight * 8;
|
|
333
|
+
return Math.max(1, Math.ceil(view / this.#itemHeight));
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
/** Последний индекс окна: запас `buffer` считается в строках, а не в пикселях */
|
|
@@ -349,7 +349,7 @@ class DataGridBody {
|
|
|
349
349
|
this.#nodes.delete(index);
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
let previous = this.#
|
|
352
|
+
let previous = this.#spacerTop;
|
|
353
353
|
for (let index = from; index < to; index++) {
|
|
354
354
|
let node = this.#nodes.get(index);
|
|
355
355
|
if (node === undefined) {
|
|
@@ -357,49 +357,49 @@ class DataGridBody {
|
|
|
357
357
|
this.#nodes.set(index, node);
|
|
358
358
|
}
|
|
359
359
|
// Высоту задаём инлайном: иначе фактическая геометрия разошлась бы с распорками
|
|
360
|
-
const height = `${this.#
|
|
360
|
+
const height = `${this.#itemHeight}px`;
|
|
361
361
|
if (node.style.height !== height) node.style.height = height;
|
|
362
362
|
// insertBefore(previous.nextSibling) ставит узел сразу за предыдущим и не трогает
|
|
363
363
|
// остальные, если порядок уже верный
|
|
364
364
|
if (node.previousSibling !== previous) this.#root.insertBefore(node, previous.nextSibling);
|
|
365
365
|
previous = node;
|
|
366
366
|
}
|
|
367
|
-
this.#
|
|
367
|
+
this.#windowState = { from: from, to: to };
|
|
368
368
|
this.#syncSpacers();
|
|
369
|
-
if (this.#
|
|
369
|
+
if (this.#onRender !== undefined) this.#onRender({ from: from, to: to });
|
|
370
370
|
this.#checkLoadMore(to);
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
#syncSpacers() {
|
|
374
374
|
const count = this.#items.length;
|
|
375
|
-
const { from, to } = this.#
|
|
376
|
-
this.#
|
|
377
|
-
this.#
|
|
375
|
+
const { from, to } = this.#windowState;
|
|
376
|
+
this.#spacerTop.style.height = count === 0 ? "0px" : `${from * this.#itemHeight}px`;
|
|
377
|
+
this.#spacerBottom.style.height = count === 0 ? "0px" : `${Math.max(0, (count - to) * this.#itemHeight)}px`;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
#createItem(index = Number()) {
|
|
381
|
-
const node = document.createElement(this.#
|
|
381
|
+
const node = document.createElement(this.#itemTag);
|
|
382
382
|
node.id = `${this.#id}_item_${index}`;
|
|
383
383
|
node.dataset.index = String(index);
|
|
384
|
-
if (this.#
|
|
385
|
-
else this.#
|
|
384
|
+
if (this.#renderItem === undefined) node.innerText = String(this.#items[index]);
|
|
385
|
+
else this.#renderItem(this.#items[index], index, node);
|
|
386
386
|
node.addEventListener("click", this.#onItemClick);
|
|
387
387
|
node.addEventListener("dblclick", this.#onItemDblClick);
|
|
388
388
|
return node;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
#checkLoadMore(to = Number()) {
|
|
392
|
-
if (this.#
|
|
392
|
+
if (this.#onLoadMore === undefined || this.#loadingMore || !this.#hasMore) return;
|
|
393
393
|
const count = this.#items.length;
|
|
394
394
|
// Тело пусто или окно дошло до конца загруженного
|
|
395
|
-
if (count > 0 && to < count - this.#
|
|
395
|
+
if (count > 0 && to < count - this.#loadThreshold) return;
|
|
396
396
|
// Запрос на такую же длину уже был: ждём данных, а не повторяем в пустоту
|
|
397
|
-
if (this.#
|
|
398
|
-
this.#
|
|
399
|
-
this.#
|
|
397
|
+
if (this.#requestedAt === count) return;
|
|
398
|
+
this.#requestedAt = count;
|
|
399
|
+
this.#loadingMore = true;
|
|
400
400
|
this.#applyLoader();
|
|
401
401
|
try {
|
|
402
|
-
this.#
|
|
402
|
+
this.#onLoadMore(count);
|
|
403
403
|
} catch (error) {
|
|
404
404
|
// Ошибка обработчика не должна оставлять тело «в загрузке»;
|
|
405
405
|
// #requested_at не сбрасываем — повтор это `DataGrid.loadMore()`
|
|
@@ -423,13 +423,13 @@ class DataGridBody {
|
|
|
423
423
|
const { index, node } = this.#itemFromEvent(event);
|
|
424
424
|
if (index === -1) return;
|
|
425
425
|
// Событие сетке нужно целиком: по нему она читает модификаторы Ctrl/Shift
|
|
426
|
-
if (this.#
|
|
426
|
+
if (this.#onItemClickCallback !== undefined) this.#onItemClickCallback(this.#items[index], index, node, event);
|
|
427
427
|
};
|
|
428
428
|
|
|
429
429
|
#onItemDblClick = (event) => {
|
|
430
430
|
const { index, node } = this.#itemFromEvent(event);
|
|
431
431
|
if (index === -1) return;
|
|
432
|
-
if (this.#
|
|
432
|
+
if (this.#onActivate !== undefined) this.#onActivate(this.#items[index], index, node);
|
|
433
433
|
};
|
|
434
434
|
|
|
435
435
|
#onScroll = () => {
|
|
@@ -437,7 +437,7 @@ class DataGridBody {
|
|
|
437
437
|
this.#raf = requestAnimationFrame(() => {
|
|
438
438
|
this.#raf = null;
|
|
439
439
|
const from = this.#firstVisible();
|
|
440
|
-
const state = this.#
|
|
440
|
+
const state = this.#windowState;
|
|
441
441
|
// Видимая часть уже внутри отрисованного окна — перерисовка не нужна
|
|
442
442
|
if (from >= state.from && this.#endIndex(from) <= state.to) return;
|
|
443
443
|
this.#renderWindow(from);
|
|
@@ -505,13 +505,13 @@ class DataGrid {
|
|
|
505
505
|
#scroll = document.createElement("data_grid_scroll");
|
|
506
506
|
#bar = document.createElement("data_grid_bar");
|
|
507
507
|
#head = document.createElement("data_grid_head");
|
|
508
|
-
#
|
|
508
|
+
#headRow = document.createElement("data_grid_row");
|
|
509
509
|
#error = document.createElement("data_grid_error");
|
|
510
|
-
#
|
|
510
|
+
#errorText = document.createElement("data_grid_error_text");
|
|
511
511
|
#status = document.createElement("data_grid_status");
|
|
512
512
|
#list = undefined;
|
|
513
|
-
#
|
|
514
|
-
#
|
|
513
|
+
#chooserDialog = undefined;
|
|
514
|
+
#chooserBlock = undefined;
|
|
515
515
|
|
|
516
516
|
// === СОСТОЯНИЕ ===
|
|
517
517
|
#id = String();
|
|
@@ -520,26 +520,26 @@ class DataGrid {
|
|
|
520
520
|
// Видимость по колонкам: ключ скрытой колонки лежит здесь, поэтому DOM собирается по видимым
|
|
521
521
|
#hidden = new Set();
|
|
522
522
|
#pinned = [];
|
|
523
|
-
#
|
|
523
|
+
#columnReorder = false;
|
|
524
524
|
#data = [];
|
|
525
|
-
#
|
|
525
|
+
#rowHeight = DataGrid.DEFAULT_ROW_HEIGHT;
|
|
526
526
|
#selection = DataGrid.SELECTION.SINGLE;
|
|
527
527
|
#selected = new Set();
|
|
528
528
|
#anchor = undefined;
|
|
529
529
|
// Индекс строки в исходных данных: `render(row, index)` и ключ по умолчанию
|
|
530
|
-
#
|
|
530
|
+
#rowIndex = new Map();
|
|
531
531
|
#filter = "";
|
|
532
|
-
#
|
|
532
|
+
#filterKeys = [];
|
|
533
533
|
#sort = { key: undefined, direction: DataGrid.SORT.NONE };
|
|
534
|
-
#
|
|
535
|
-
#
|
|
534
|
+
#groupBy = undefined;
|
|
535
|
+
#groupLabel = undefined;
|
|
536
536
|
#aggregates = {};
|
|
537
|
-
#
|
|
537
|
+
#groupCollapsedDefault = false;
|
|
538
538
|
#collapsed = new Map();
|
|
539
539
|
#editable = undefined;
|
|
540
540
|
#editing = undefined;
|
|
541
541
|
#active = { row: 0, col: 0 };
|
|
542
|
-
#
|
|
542
|
+
#entryCount = 0;
|
|
543
543
|
|
|
544
544
|
// Производные данные (пересобираются одним проходом — см. #recompute)
|
|
545
545
|
#filtered = [];
|
|
@@ -548,27 +548,27 @@ class DataGrid {
|
|
|
548
548
|
#entries = [];
|
|
549
549
|
|
|
550
550
|
// Серверный режим
|
|
551
|
-
#
|
|
552
|
-
#
|
|
553
|
-
#
|
|
551
|
+
#dataSource = undefined;
|
|
552
|
+
#pageSize = DataGrid.DEFAULT_PAGE_SIZE;
|
|
553
|
+
#serverTotal = undefined;
|
|
554
554
|
#loading = false;
|
|
555
555
|
#failed = false;
|
|
556
|
-
#
|
|
556
|
+
#hasMore = false;
|
|
557
557
|
#request = 0;
|
|
558
558
|
|
|
559
559
|
// Подписчики
|
|
560
|
-
#
|
|
561
|
-
#
|
|
562
|
-
#
|
|
563
|
-
#
|
|
564
|
-
#
|
|
565
|
-
#
|
|
566
|
-
#
|
|
567
|
-
#
|
|
568
|
-
#
|
|
569
|
-
#
|
|
570
|
-
#
|
|
571
|
-
#
|
|
560
|
+
#onRowActivate = undefined;
|
|
561
|
+
#onSelectionChange = undefined;
|
|
562
|
+
#onCellEdit = undefined;
|
|
563
|
+
#onActiveCellChange = undefined;
|
|
564
|
+
#onColumnOrderChange = undefined;
|
|
565
|
+
#onDataLoad = undefined;
|
|
566
|
+
#onDataError = undefined;
|
|
567
|
+
#rowClass = undefined;
|
|
568
|
+
#customKey = undefined;
|
|
569
|
+
#emptyText = undefined;
|
|
570
|
+
#contextMenu = undefined;
|
|
571
|
+
#resizeObserver = undefined;
|
|
572
572
|
|
|
573
573
|
// Перестановка колонок
|
|
574
574
|
#drag = undefined;
|
|
@@ -613,39 +613,39 @@ class DataGrid {
|
|
|
613
613
|
this.#columns = DataGrid.#normalizeColumns(options.columns);
|
|
614
614
|
this.#hidden = new Set(DataGrid.#normalizeKeys(options.hiddenColumns, this.#columns));
|
|
615
615
|
this.#data = Array.isArray(options.data) ? options.data : [];
|
|
616
|
-
this.#
|
|
616
|
+
this.#rowHeight = options.rowHeight === undefined ? DataGrid.DEFAULT_ROW_HEIGHT : options.rowHeight;
|
|
617
617
|
this.#selection = DataGrid.#normalizeSelection(options.selectable);
|
|
618
|
-
this.#
|
|
619
|
-
this.#
|
|
620
|
-
this.#
|
|
618
|
+
this.#customKey = options.rowKey;
|
|
619
|
+
this.#filterKeys = DataGrid.#normalizeKeys(options.filterKeys, this.#columns);
|
|
620
|
+
this.#emptyText = options.emptyText;
|
|
621
621
|
this.#editable = options.editable;
|
|
622
|
-
this.#
|
|
623
|
-
this.#
|
|
622
|
+
this.#groupBy = options.groupBy;
|
|
623
|
+
this.#groupLabel = options.groupLabel;
|
|
624
624
|
this.#aggregates = options.aggregates === undefined ? {} : options.aggregates;
|
|
625
|
-
this.#
|
|
626
|
-
this.#
|
|
627
|
-
this.#
|
|
628
|
-
this.#
|
|
629
|
-
this.#
|
|
630
|
-
this.#
|
|
631
|
-
this.#
|
|
632
|
-
this.#
|
|
633
|
-
this.#
|
|
634
|
-
this.#
|
|
635
|
-
this.#
|
|
636
|
-
this.#
|
|
625
|
+
this.#groupCollapsedDefault = options.groupCollapsed === true;
|
|
626
|
+
this.#rowClass = options.rowClass;
|
|
627
|
+
this.#onRowActivate = options.onRowActivate;
|
|
628
|
+
this.#onSelectionChange = options.onSelectionChange;
|
|
629
|
+
this.#onCellEdit = options.onCellEdit;
|
|
630
|
+
this.#onActiveCellChange = options.onActiveCellChange;
|
|
631
|
+
this.#onColumnOrderChange = options.onColumnOrderChange;
|
|
632
|
+
this.#onDataLoad = options.onDataLoad;
|
|
633
|
+
this.#onDataError = options.onDataError;
|
|
634
|
+
this.#dataSource = options.dataSource;
|
|
635
|
+
this.#pageSize = DataGrid.#normalizePageSize(options.pageSize);
|
|
636
|
+
this.#columnReorder = options.columnReorder === true;
|
|
637
637
|
this.setPinned(options.pinned);
|
|
638
638
|
|
|
639
639
|
// === сборка разметки ===
|
|
640
640
|
this.#root.id = this.#id;
|
|
641
641
|
this.#root.setAttribute("role", "grid");
|
|
642
642
|
this.#head.setAttribute("role", "rowgroup");
|
|
643
|
-
this.#
|
|
644
|
-
this.#
|
|
645
|
-
this.#head.appendChild(this.#
|
|
643
|
+
this.#headRow.setAttribute("role", "row");
|
|
644
|
+
this.#headRow.setAttribute("aria-rowindex", "1");
|
|
645
|
+
this.#head.appendChild(this.#headRow);
|
|
646
646
|
this.#scroll.appendChild(this.#head);
|
|
647
647
|
this.#root.appendChild(this.#scroll);
|
|
648
|
-
this.#error.appendChild(this.#
|
|
648
|
+
this.#error.appendChild(this.#errorText);
|
|
649
649
|
this.#error.appendChild(
|
|
650
650
|
new Button({ title: I18n.t("dataGrid.retry"), clickEvent: () => this.retryLoad() }).set()
|
|
651
651
|
);
|
|
@@ -661,10 +661,10 @@ class DataGrid {
|
|
|
661
661
|
this.#syncColumns();
|
|
662
662
|
this.#list = new DataGridBody({
|
|
663
663
|
itemTag: "data_grid_row",
|
|
664
|
-
itemHeight: this.#
|
|
664
|
+
itemHeight: this.#rowHeight,
|
|
665
665
|
buffer: options.buffer,
|
|
666
666
|
height: options.height === undefined ? DataGrid.DEFAULT_HEIGHT : options.height,
|
|
667
|
-
emptyText: this.#
|
|
667
|
+
emptyText: this.#emptyText,
|
|
668
668
|
hasMore: false,
|
|
669
669
|
onLoadMore: () => this.loadMore(),
|
|
670
670
|
renderItem: (entry, index, node) => this.#renderRow(entry, index, node),
|
|
@@ -689,26 +689,26 @@ class DataGrid {
|
|
|
689
689
|
this.#root.appendChild(this.#status);
|
|
690
690
|
|
|
691
691
|
// Заголовок: делегирование вместо подписки на каждую ячейку
|
|
692
|
-
this.#
|
|
693
|
-
this.#
|
|
694
|
-
this.#
|
|
695
|
-
this.#
|
|
696
|
-
this.#
|
|
697
|
-
this.#
|
|
698
|
-
this.#
|
|
692
|
+
this.#headRow.addEventListener("click", this.#onHeaderClick);
|
|
693
|
+
this.#headRow.addEventListener("keydown", this.#onHeaderKeyDown);
|
|
694
|
+
this.#headRow.addEventListener("dragstart", this.#onHeaderDragStart);
|
|
695
|
+
this.#headRow.addEventListener("dragover", this.#onHeaderDragOver);
|
|
696
|
+
this.#headRow.addEventListener("dragleave", this.#onHeaderDragLeave);
|
|
697
|
+
this.#headRow.addEventListener("drop", this.#onHeaderDrop);
|
|
698
|
+
this.#headRow.addEventListener("dragend", this.#onHeaderDragEnd);
|
|
699
699
|
// capture: правку начинает перехват, иначе тело списка успеет сообщить об активации
|
|
700
700
|
this.#root.addEventListener("dblclick", this.#onDoubleClick, true);
|
|
701
701
|
this.#root.addEventListener("keydown", this.#onKeyDown);
|
|
702
702
|
// Ширины закреплённых колонок зависят от размера окна — пересчитываем при изменении
|
|
703
|
-
this.#
|
|
704
|
-
this.#
|
|
703
|
+
this.#resizeObserver = new ResizeObserver(() => this.#syncPinned());
|
|
704
|
+
this.#resizeObserver.observe(this.#scroll);
|
|
705
705
|
// Горизонтальная прокрутка: закреплённые колонки удерживаем на месте
|
|
706
706
|
this.#scroll.addEventListener("scroll", this.#onScrollX, { passive: true });
|
|
707
707
|
|
|
708
708
|
this.#reindex();
|
|
709
709
|
this.#buildHeader();
|
|
710
710
|
this.#recompute();
|
|
711
|
-
if (this.#
|
|
711
|
+
if (this.#dataSource !== undefined) this.reload();
|
|
712
712
|
}
|
|
713
713
|
|
|
714
714
|
// === НОРМАЛИЗАЦИЯ ВХОДА ===
|
|
@@ -843,16 +843,16 @@ class DataGrid {
|
|
|
843
843
|
}
|
|
844
844
|
}
|
|
845
845
|
}
|
|
846
|
-
this.#
|
|
846
|
+
this.#entryCount = this.#entries.length;
|
|
847
847
|
// Число строк для скринридера: элементы списка плюс строка заголовка, а в серверном
|
|
848
848
|
// режиме — то, что сообщил сервер (иначе он знал бы только про загруженное окно)
|
|
849
849
|
this.#root.setAttribute(
|
|
850
850
|
"aria-rowcount",
|
|
851
|
-
String((this.#
|
|
851
|
+
String((this.#serverTotal === undefined ? this.#entryCount : this.#serverTotal) + 1)
|
|
852
852
|
);
|
|
853
853
|
this.#syncColumns();
|
|
854
854
|
this.#list.setItems(this.#entries);
|
|
855
|
-
this.#list.setHasMore(this.#
|
|
855
|
+
this.#list.setHasMore(this.#hasMore);
|
|
856
856
|
this.#syncActiveCells();
|
|
857
857
|
this.#syncPinned();
|
|
858
858
|
}
|
|
@@ -877,11 +877,11 @@ class DataGrid {
|
|
|
877
877
|
|
|
878
878
|
#filterRows(rows = []) {
|
|
879
879
|
// Серверный режим фильтрует сам: локальный фильтр включился бы поверх серверного
|
|
880
|
-
if (this.#
|
|
880
|
+
if (this.#dataSource !== undefined) return rows.slice();
|
|
881
881
|
const text = this.#filter.trim().toLowerCase();
|
|
882
|
-
if (text === "" || this.#
|
|
882
|
+
if (text === "" || this.#filterKeys.length === 0) return rows.slice();
|
|
883
883
|
return rows.filter((row) =>
|
|
884
|
-
this.#
|
|
884
|
+
this.#filterKeys.some((key) => {
|
|
885
885
|
return DataGrid.#valueText(row[key]).toLowerCase().includes(text);
|
|
886
886
|
})
|
|
887
887
|
);
|
|
@@ -922,8 +922,8 @@ class DataGrid {
|
|
|
922
922
|
|
|
923
923
|
/** Группы: порядок — первое появление ключа, поэтому он не зависит от сортировки */
|
|
924
924
|
#buildGroups(rows = []) {
|
|
925
|
-
if (this.#
|
|
926
|
-
const keyOf = typeof this.#
|
|
925
|
+
if (this.#groupBy === undefined) return [];
|
|
926
|
+
const keyOf = typeof this.#groupBy === "function" ? this.#groupBy : (row) => row[this.#groupBy];
|
|
927
927
|
const map = new Map();
|
|
928
928
|
for (const row of rows) {
|
|
929
929
|
const raw = keyOf(row);
|
|
@@ -932,12 +932,12 @@ class DataGrid {
|
|
|
932
932
|
map.get(key).push(row);
|
|
933
933
|
}
|
|
934
934
|
const out = [];
|
|
935
|
-
for (const [key,
|
|
935
|
+
for (const [key, groupRows] of map) {
|
|
936
936
|
out.push({
|
|
937
937
|
key: key,
|
|
938
|
-
rows:
|
|
939
|
-
aggregates: this.#aggregateValues(
|
|
940
|
-
collapsed: this.#collapsed.has(key) ? this.#collapsed.get(key) : this.#
|
|
938
|
+
rows: groupRows,
|
|
939
|
+
aggregates: this.#aggregateValues(groupRows),
|
|
940
|
+
collapsed: this.#collapsed.has(key) ? this.#collapsed.get(key) : this.#groupCollapsedDefault,
|
|
941
941
|
});
|
|
942
942
|
}
|
|
943
943
|
return out;
|
|
@@ -988,7 +988,7 @@ class DataGrid {
|
|
|
988
988
|
#syncColumns() {
|
|
989
989
|
const visible = this.#visibleColumns();
|
|
990
990
|
this.#root.style.setProperty("--data_grid_columns", visible.map((entry) => entry.column.width).join(" "));
|
|
991
|
-
this.#root.style.setProperty("--data_grid_row_height", `${this.#
|
|
991
|
+
this.#root.style.setProperty("--data_grid_row_height", `${this.#rowHeight}px`);
|
|
992
992
|
const explicit = DataGrid.#explicitWidth(visible.map((entry) => entry.column));
|
|
993
993
|
if (explicit > 0) this.#root.style.setProperty("--data_grid_min_width", `${explicit}px`);
|
|
994
994
|
else this.#root.style.removeProperty("--data_grid_min_width");
|
|
@@ -1022,7 +1022,7 @@ class DataGrid {
|
|
|
1022
1022
|
}
|
|
1023
1023
|
}
|
|
1024
1024
|
this.#scroll.classList.toggle("data_grid_h_scroll", this.#scroll.scrollWidth > this.#scroll.clientWidth + 1);
|
|
1025
|
-
const nodes = [this.#
|
|
1025
|
+
const nodes = [this.#headRow, ...this.#renderedRows()];
|
|
1026
1026
|
for (const row of nodes) {
|
|
1027
1027
|
for (const cell of row.querySelectorAll("data_grid_cell[data-column]")) {
|
|
1028
1028
|
const index = Number(cell.dataset.column);
|
|
@@ -1037,7 +1037,7 @@ class DataGrid {
|
|
|
1037
1037
|
}
|
|
1038
1038
|
|
|
1039
1039
|
#cellWidth(index = Number()) {
|
|
1040
|
-
const cell = this.#
|
|
1040
|
+
const cell = this.#headRow.querySelector(`data_grid_cell[data-column="${index}"]`);
|
|
1041
1041
|
return cell === null ? 0 : Math.round(cell.getBoundingClientRect().width);
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
@@ -1057,7 +1057,7 @@ class DataGrid {
|
|
|
1057
1057
|
|
|
1058
1058
|
// === ШАПКА ===
|
|
1059
1059
|
#buildHeader() {
|
|
1060
|
-
this.#
|
|
1060
|
+
this.#headRow.innerHTML = "";
|
|
1061
1061
|
for (const entry of this.#visibleColumns()) {
|
|
1062
1062
|
const column = entry.column;
|
|
1063
1063
|
const cell = document.createElement("data_grid_cell");
|
|
@@ -1080,11 +1080,11 @@ class DataGrid {
|
|
|
1080
1080
|
// Подпись заголовка объясняет, что Enter и Space сортируют колонку
|
|
1081
1081
|
cell.setAttribute("aria-label", I18n.t("table.sortBy", { head: column.title }));
|
|
1082
1082
|
}
|
|
1083
|
-
if (this.#
|
|
1083
|
+
if (this.#columnReorder) {
|
|
1084
1084
|
cell.setAttribute("draggable", "true");
|
|
1085
1085
|
if (column.sortable === false) cell.setAttribute("tabindex", "0");
|
|
1086
1086
|
}
|
|
1087
|
-
this.#
|
|
1087
|
+
this.#headRow.appendChild(cell);
|
|
1088
1088
|
this.#syncSortMark(cell, entry.index);
|
|
1089
1089
|
}
|
|
1090
1090
|
}
|
|
@@ -1112,7 +1112,7 @@ class DataGrid {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
|
|
1114
1114
|
#onHeaderClick = (event) => {
|
|
1115
|
-
const cell = DataGrid.#columnFromEvent(event, this.#
|
|
1115
|
+
const cell = DataGrid.#columnFromEvent(event, this.#headRow);
|
|
1116
1116
|
if (cell === undefined) return;
|
|
1117
1117
|
const index = Number(cell.dataset.column);
|
|
1118
1118
|
const column = this.#columns[index];
|
|
@@ -1121,7 +1121,7 @@ class DataGrid {
|
|
|
1121
1121
|
};
|
|
1122
1122
|
|
|
1123
1123
|
#onHeaderKeyDown = (event) => {
|
|
1124
|
-
const cell = DataGrid.#columnFromEvent(event, this.#
|
|
1124
|
+
const cell = DataGrid.#columnFromEvent(event, this.#headRow);
|
|
1125
1125
|
if (cell === undefined) return;
|
|
1126
1126
|
const index = Number(cell.dataset.column);
|
|
1127
1127
|
if (event.key === "Enter" || event.key === " ") {
|
|
@@ -1130,7 +1130,7 @@ class DataGrid {
|
|
|
1130
1130
|
return;
|
|
1131
1131
|
}
|
|
1132
1132
|
if (event.altKey && (event.key === "ArrowLeft" || event.key === "ArrowRight")) {
|
|
1133
|
-
if (this.#
|
|
1133
|
+
if (this.#columnReorder === false) return;
|
|
1134
1134
|
event.preventDefault();
|
|
1135
1135
|
this.#moveColumn(index, event.key === "ArrowLeft" ? -1 : 1);
|
|
1136
1136
|
return;
|
|
@@ -1142,7 +1142,7 @@ class DataGrid {
|
|
|
1142
1142
|
const place = visible.findIndex((entry) => entry.index === index);
|
|
1143
1143
|
const next = visible[place + (event.key === "ArrowRight" ? 1 : -1)];
|
|
1144
1144
|
if (next === undefined) return;
|
|
1145
|
-
const node = this.#
|
|
1145
|
+
const node = this.#headRow.querySelector(`data_grid_cell[data-column="${next.index}"]`);
|
|
1146
1146
|
if (node !== null) node.focus();
|
|
1147
1147
|
}
|
|
1148
1148
|
};
|
|
@@ -1161,7 +1161,7 @@ class DataGrid {
|
|
|
1161
1161
|
|
|
1162
1162
|
#applySort() {
|
|
1163
1163
|
this.#recompute();
|
|
1164
|
-
for (const cell of this.#
|
|
1164
|
+
for (const cell of this.#headRow.querySelectorAll("data_grid_cell[data-column]")) {
|
|
1165
1165
|
this.#syncSortMark(cell, Number(cell.dataset.column));
|
|
1166
1166
|
}
|
|
1167
1167
|
this.#announce(this.#sortSummary());
|
|
@@ -1202,8 +1202,8 @@ class DataGrid {
|
|
|
1202
1202
|
// Свой класс строки ставим ДО класса выделения: запись в `className` затирает уже
|
|
1203
1203
|
// проставленные классы, и после перерисовки строки (правка ячейки) выделенная
|
|
1204
1204
|
// строка теряла бы подсветку при включённом `rowClass`
|
|
1205
|
-
if (typeof this.#
|
|
1206
|
-
const classes = this.#
|
|
1205
|
+
if (typeof this.#rowClass === "function") {
|
|
1206
|
+
const classes = this.#rowClass(row, entry.rowIndex);
|
|
1207
1207
|
if (classes !== undefined && classes !== null && String(classes).trim() !== "")
|
|
1208
1208
|
node.className = String(classes).trim();
|
|
1209
1209
|
}
|
|
@@ -1279,8 +1279,8 @@ class DataGrid {
|
|
|
1279
1279
|
// иначе он повторял бы то же число (прежде там стояла подпись «групп: N», которая
|
|
1280
1280
|
// называла строки группы группами)
|
|
1281
1281
|
title.innerText =
|
|
1282
|
-
typeof this.#
|
|
1283
|
-
? String(this.#
|
|
1282
|
+
typeof this.#groupLabel === "function"
|
|
1283
|
+
? String(this.#groupLabel(entry.group.key, entry.group.rows))
|
|
1284
1284
|
: I18n.t("dataGrid.groupTitle", { key: entry.group.key, count: entry.group.rows.length });
|
|
1285
1285
|
cell.appendChild(title);
|
|
1286
1286
|
node.appendChild(cell);
|
|
@@ -1312,9 +1312,9 @@ class DataGrid {
|
|
|
1312
1312
|
#syncRowActive(node = new HTMLElement(), index = Number()) {
|
|
1313
1313
|
const active = this.#active.row === index;
|
|
1314
1314
|
for (const cell of node.querySelectorAll("data_grid_cell[data-column]")) {
|
|
1315
|
-
const
|
|
1316
|
-
cell.classList.toggle(A11Y_ACTIVE_CLASS,
|
|
1317
|
-
cell.tabIndex =
|
|
1315
|
+
const isActive = active && Number(cell.dataset.column) === this.#active.col;
|
|
1316
|
+
cell.classList.toggle(A11Y_ACTIVE_CLASS, isActive);
|
|
1317
|
+
cell.tabIndex = isActive ? 0 : -1;
|
|
1318
1318
|
}
|
|
1319
1319
|
}
|
|
1320
1320
|
|
|
@@ -1330,8 +1330,8 @@ class DataGrid {
|
|
|
1330
1330
|
// === ВЫБОР СТРОК ВНУТРЕННЕЕ ===
|
|
1331
1331
|
/** Ключ строки: имя поля, функция или место в исходных данных */
|
|
1332
1332
|
#keyOf(entry = new DataGridEntry()) {
|
|
1333
|
-
if (typeof this.#
|
|
1334
|
-
if (this.#
|
|
1333
|
+
if (typeof this.#customKey === "function") return this.#customKey(entry.row);
|
|
1334
|
+
if (this.#customKey !== undefined) return entry.row[this.#customKey];
|
|
1335
1335
|
return this.#rowIndexOf(entry.row);
|
|
1336
1336
|
}
|
|
1337
1337
|
|
|
@@ -1377,19 +1377,19 @@ class DataGrid {
|
|
|
1377
1377
|
node.setAttribute("aria-selected", selected ? "true" : "false");
|
|
1378
1378
|
}
|
|
1379
1379
|
this.#syncPinned();
|
|
1380
|
-
if (this.#
|
|
1380
|
+
if (this.#onSelectionChange !== undefined) {
|
|
1381
1381
|
const rows = this.#viewRows().filter((row) =>
|
|
1382
1382
|
this.#selected.has(this.#keyOf(DataGrid.#entryOf(row, this.#rowIndexOf(row))))
|
|
1383
1383
|
);
|
|
1384
|
-
this.#
|
|
1384
|
+
this.#onSelectionChange(rows, Array.from(this.#selected));
|
|
1385
1385
|
}
|
|
1386
1386
|
this.#announce(I18n.t("dataGrid.rowsSummary", { shown: this.#selected.size }));
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
1389
|
// === АКТИВНАЯ ЯЧЕЙКА И КЛАВИАТУРА ===
|
|
1390
1390
|
#setActive(row = Number(), col = Number(), focus = true) {
|
|
1391
|
-
if (this.#
|
|
1392
|
-
const place = Math.min(Math.max(row, 0), this.#
|
|
1391
|
+
if (this.#entryCount === 0) return;
|
|
1392
|
+
const place = Math.min(Math.max(row, 0), this.#entryCount - 1);
|
|
1393
1393
|
const entry = this.#entryAt(place);
|
|
1394
1394
|
const visible = this.#visibleColumns();
|
|
1395
1395
|
if (visible.length === 0) return;
|
|
@@ -1401,8 +1401,8 @@ class DataGrid {
|
|
|
1401
1401
|
this.#syncActiveCells();
|
|
1402
1402
|
const node = this.#cellNode(place, known);
|
|
1403
1403
|
if (node !== undefined && focus) node.focus({ preventScroll: true });
|
|
1404
|
-
if (this.#
|
|
1405
|
-
this.#
|
|
1404
|
+
if (this.#onActiveCellChange !== undefined && entry !== undefined && entry.kind === "row") {
|
|
1405
|
+
this.#onActiveCellChange(place, known, entry.row);
|
|
1406
1406
|
}
|
|
1407
1407
|
}
|
|
1408
1408
|
|
|
@@ -1419,19 +1419,19 @@ class DataGrid {
|
|
|
1419
1419
|
const place = visible.findIndex((item) => item.index === col);
|
|
1420
1420
|
if (place === -1) return undefined;
|
|
1421
1421
|
let next = place + step;
|
|
1422
|
-
let
|
|
1422
|
+
let nextRow = row;
|
|
1423
1423
|
if (next >= visible.length) {
|
|
1424
1424
|
next = 0;
|
|
1425
|
-
|
|
1425
|
+
nextRow = row + 1;
|
|
1426
1426
|
}
|
|
1427
1427
|
if (next < 0) {
|
|
1428
1428
|
next = visible.length - 1;
|
|
1429
|
-
|
|
1429
|
+
nextRow = row - 1;
|
|
1430
1430
|
}
|
|
1431
|
-
if (
|
|
1432
|
-
const entry = this.#entryAt(
|
|
1431
|
+
if (nextRow < 0 || nextRow >= this.#entryCount) return undefined;
|
|
1432
|
+
const entry = this.#entryAt(nextRow);
|
|
1433
1433
|
if (entry === undefined || entry.kind !== "row") return undefined;
|
|
1434
|
-
return { row:
|
|
1434
|
+
return { row: nextRow, col: visible[next].index };
|
|
1435
1435
|
}
|
|
1436
1436
|
|
|
1437
1437
|
#onRowClick = (entry = new DataGridEntry(), index = Number(), _node = new HTMLElement(), event = Object()) => {
|
|
@@ -1468,13 +1468,13 @@ class DataGrid {
|
|
|
1468
1468
|
// Space добавлял бы в выборку несуществующую строку, а перестановка колонки
|
|
1469
1469
|
// (Alt+←/→) пересобирает шапку и ячейка под уже всплывающим событием осиротеет —
|
|
1470
1470
|
// `closest` вернул бы null и обработчик падал бы с TypeError
|
|
1471
|
-
const
|
|
1472
|
-
if (
|
|
1473
|
-
const row = Number(
|
|
1471
|
+
const rowNode = cell.closest("data_grid_row");
|
|
1472
|
+
if (rowNode === null || rowNode.dataset.index === undefined) return;
|
|
1473
|
+
const row = Number(rowNode.dataset.index);
|
|
1474
1474
|
const col = Number(cell.dataset.column);
|
|
1475
1475
|
const visible = this.#visibleColumns();
|
|
1476
1476
|
const place = visible.findIndex((item) => item.index === col);
|
|
1477
|
-
const last = this.#
|
|
1477
|
+
const last = this.#entryCount - 1;
|
|
1478
1478
|
const entry = this.#entryAt(row);
|
|
1479
1479
|
if (entry !== undefined && entry.kind === "group") {
|
|
1480
1480
|
if (event.key === "Enter" || event.key === " ") {
|
|
@@ -1495,9 +1495,9 @@ class DataGrid {
|
|
|
1495
1495
|
return;
|
|
1496
1496
|
}
|
|
1497
1497
|
}
|
|
1498
|
-
const move = (
|
|
1498
|
+
const move = (nextRow, nextColumn) => {
|
|
1499
1499
|
event.preventDefault();
|
|
1500
|
-
this.#setActive(
|
|
1500
|
+
this.#setActive(nextRow, nextColumn === undefined ? col : nextColumn, true);
|
|
1501
1501
|
};
|
|
1502
1502
|
if (event.key === "ArrowDown") return move(Math.min(row + 1, last));
|
|
1503
1503
|
if (event.key === "ArrowUp") return move(Math.max(row - 1, 0));
|
|
@@ -1537,7 +1537,7 @@ class DataGrid {
|
|
|
1537
1537
|
#activateRow(index = Number()) {
|
|
1538
1538
|
const entry = this.#entryAt(index);
|
|
1539
1539
|
if (entry === undefined || entry.kind !== "row") return;
|
|
1540
|
-
if (this.#
|
|
1540
|
+
if (this.#onRowActivate !== undefined) this.#onRowActivate(entry.row, entry.rowIndex);
|
|
1541
1541
|
}
|
|
1542
1542
|
|
|
1543
1543
|
// === ПРАВКА ЯЧЕЕК ===
|
|
@@ -1727,7 +1727,7 @@ class DataGrid {
|
|
|
1727
1727
|
editing.component.destroy();
|
|
1728
1728
|
if (entry !== undefined && entry.kind === "row" && save && value !== editing.old_value) {
|
|
1729
1729
|
entry.row[editing.key] = value;
|
|
1730
|
-
if (this.#
|
|
1730
|
+
if (this.#onCellEdit !== undefined) this.#onCellEdit(entry.row, editing.key, value, editing.old_value);
|
|
1731
1731
|
this.#announce(I18n.t("dataGrid.saved", { title: column.title, value: DataGrid.#valueText(value) }));
|
|
1732
1732
|
} else if (save === false && parsed.ok === false) {
|
|
1733
1733
|
this.#announce(I18n.t("dataGrid.editCanceledReason", { message: parsed.message }));
|
|
@@ -1771,8 +1771,8 @@ class DataGrid {
|
|
|
1771
1771
|
|
|
1772
1772
|
// === ПЕРЕСТАНОВКА КОЛОНОК ===
|
|
1773
1773
|
#onHeaderDragStart = (event) => {
|
|
1774
|
-
if (this.#
|
|
1775
|
-
const cell = DataGrid.#columnFromEvent(event, this.#
|
|
1774
|
+
if (this.#columnReorder === false) return;
|
|
1775
|
+
const cell = DataGrid.#columnFromEvent(event, this.#headRow);
|
|
1776
1776
|
if (cell === undefined) return;
|
|
1777
1777
|
this.#drag = Number(cell.dataset.column);
|
|
1778
1778
|
cell.classList.add("data_grid_cell_dragging");
|
|
@@ -1803,14 +1803,14 @@ class DataGrid {
|
|
|
1803
1803
|
|
|
1804
1804
|
#onHeaderDragEnd = () => {
|
|
1805
1805
|
this.#markDrop(undefined);
|
|
1806
|
-
for (const cell of this.#
|
|
1806
|
+
for (const cell of this.#headRow.querySelectorAll("data_grid_cell"))
|
|
1807
1807
|
cell.classList.remove("data_grid_cell_dragging");
|
|
1808
1808
|
this.#drag = undefined;
|
|
1809
1809
|
};
|
|
1810
1810
|
|
|
1811
1811
|
/** Место вставки по координате курсора: середина каждой видимой колонки */
|
|
1812
1812
|
#dropSlot(clientX = Number()) {
|
|
1813
|
-
const cells = Array.from(this.#
|
|
1813
|
+
const cells = Array.from(this.#headRow.querySelectorAll("data_grid_cell[data-column]"));
|
|
1814
1814
|
for (let place = 0; place < cells.length; place++) {
|
|
1815
1815
|
const rect = cells[place].getBoundingClientRect();
|
|
1816
1816
|
if (clientX < rect.left + rect.width / 2) return place;
|
|
@@ -1819,10 +1819,10 @@ class DataGrid {
|
|
|
1819
1819
|
}
|
|
1820
1820
|
|
|
1821
1821
|
#markDrop(slot = undefined) {
|
|
1822
|
-
for (const cell of this.#
|
|
1822
|
+
for (const cell of this.#headRow.querySelectorAll("data_grid_cell"))
|
|
1823
1823
|
cell.classList.remove("data_grid_cell_drop_after");
|
|
1824
1824
|
if (slot === undefined) return;
|
|
1825
|
-
const cells = Array.from(this.#
|
|
1825
|
+
const cells = Array.from(this.#headRow.querySelectorAll("data_grid_cell[data-column]"));
|
|
1826
1826
|
const target = cells[slot === 0 ? 0 : slot - 1];
|
|
1827
1827
|
if (target !== undefined) target.classList.add("data_grid_cell_drop_after");
|
|
1828
1828
|
}
|
|
@@ -1845,8 +1845,8 @@ class DataGrid {
|
|
|
1845
1845
|
this.#onHeaderDragEnd();
|
|
1846
1846
|
this.#recompute();
|
|
1847
1847
|
this.#buildHeader();
|
|
1848
|
-
if (this.#
|
|
1849
|
-
this.#
|
|
1848
|
+
if (this.#onColumnOrderChange !== undefined) {
|
|
1849
|
+
this.#onColumnOrderChange(this.getColumnOrder(), column.key, from, target);
|
|
1850
1850
|
}
|
|
1851
1851
|
}
|
|
1852
1852
|
|
|
@@ -1864,13 +1864,13 @@ class DataGrid {
|
|
|
1864
1864
|
this.#buildHeader();
|
|
1865
1865
|
const from = index;
|
|
1866
1866
|
const to = this.#columns.indexOf(column);
|
|
1867
|
-
const node = this.#
|
|
1867
|
+
const node = this.#headRow.querySelector(`data_grid_cell[data-column="${to}"]`);
|
|
1868
1868
|
if (node !== null) node.focus();
|
|
1869
1869
|
this.#announce(
|
|
1870
1870
|
I18n.t("dataGrid.columnPlace", { title: column.title, place: to + 1, total: this.#columns.length })
|
|
1871
1871
|
);
|
|
1872
|
-
if (this.#
|
|
1873
|
-
this.#
|
|
1872
|
+
if (this.#onColumnOrderChange !== undefined)
|
|
1873
|
+
this.#onColumnOrderChange(this.getColumnOrder(), column.key, from, to);
|
|
1874
1874
|
}
|
|
1875
1875
|
|
|
1876
1876
|
// === ПОЛОСА НАД СЕТКОЙ И ДИАЛОГ КОЛОНОК ===
|
|
@@ -1887,9 +1887,9 @@ class DataGrid {
|
|
|
1887
1887
|
}
|
|
1888
1888
|
|
|
1889
1889
|
#ensureChooser() {
|
|
1890
|
-
if (this.#
|
|
1890
|
+
if (this.#chooserDialog !== undefined) {
|
|
1891
1891
|
this.#refreshChooser();
|
|
1892
|
-
return this.#
|
|
1892
|
+
return this.#chooserDialog;
|
|
1893
1893
|
}
|
|
1894
1894
|
const dialog = new Dialog({
|
|
1895
1895
|
width: "340px",
|
|
@@ -1900,16 +1900,16 @@ class DataGrid {
|
|
|
1900
1900
|
const block = new ContentBlock({ direction: "column", wrap: "nowrap", width: "-webkit-fill-available" });
|
|
1901
1901
|
dialog.addToBody(block);
|
|
1902
1902
|
dialog.addToFooter(new Button({ title: I18n.t("dataGrid.showAll"), clickEvent: () => this.showAllColumns() }));
|
|
1903
|
-
this.#
|
|
1904
|
-
this.#
|
|
1903
|
+
this.#chooserDialog = dialog;
|
|
1904
|
+
this.#chooserBlock = block;
|
|
1905
1905
|
document.body.appendChild(dialog.set());
|
|
1906
1906
|
this.#refreshChooser();
|
|
1907
1907
|
return dialog;
|
|
1908
1908
|
}
|
|
1909
1909
|
|
|
1910
1910
|
#refreshChooser() {
|
|
1911
|
-
if (this.#
|
|
1912
|
-
this.#
|
|
1911
|
+
if (this.#chooserBlock === undefined) return;
|
|
1912
|
+
this.#chooserBlock.clear();
|
|
1913
1913
|
for (const column of this.#columns) {
|
|
1914
1914
|
const checkbox = new CheckBox({
|
|
1915
1915
|
name: `${this.#id}_column_${column.key}`,
|
|
@@ -1918,7 +1918,7 @@ class DataGrid {
|
|
|
1918
1918
|
});
|
|
1919
1919
|
// У CheckBox нет опции начального значения: ставим его отдельно
|
|
1920
1920
|
checkbox.setValue(this.isColumnVisible(column.key));
|
|
1921
|
-
this.#
|
|
1921
|
+
this.#chooserBlock.add(checkbox);
|
|
1922
1922
|
}
|
|
1923
1923
|
}
|
|
1924
1924
|
|
|
@@ -1926,28 +1926,28 @@ class DataGrid {
|
|
|
1926
1926
|
#requestParams(offset = Number()) {
|
|
1927
1927
|
return {
|
|
1928
1928
|
offset: offset,
|
|
1929
|
-
limit: this.#
|
|
1929
|
+
limit: this.#pageSize,
|
|
1930
1930
|
filter: this.#filter,
|
|
1931
|
-
filterKeys: this.#
|
|
1931
|
+
filterKeys: this.#filterKeys,
|
|
1932
1932
|
sort: { key: this.#sort.key, direction: this.#sort.direction },
|
|
1933
1933
|
};
|
|
1934
1934
|
}
|
|
1935
1935
|
|
|
1936
1936
|
async #load(mode = "first") {
|
|
1937
|
-
if (this.#
|
|
1937
|
+
if (this.#dataSource === undefined || this.#loading) return;
|
|
1938
1938
|
const request = this.#request + 1;
|
|
1939
1939
|
this.#request = request;
|
|
1940
1940
|
this.#loading = true;
|
|
1941
1941
|
this.#setError(undefined);
|
|
1942
1942
|
this.#list.setLoadingMore(true);
|
|
1943
1943
|
try {
|
|
1944
|
-
const payload = await this.#
|
|
1944
|
+
const payload = await this.#dataSource(this.#requestParams(mode === "first" ? 0 : this.#data.length));
|
|
1945
1945
|
if (request !== this.#request) return;
|
|
1946
1946
|
const rows = DataGrid.#payloadRows(payload);
|
|
1947
1947
|
const total = DataGrid.#payloadTotal(payload);
|
|
1948
1948
|
this.#data = mode === "first" ? rows : this.#data.concat(rows);
|
|
1949
|
-
this.#
|
|
1950
|
-
this.#
|
|
1949
|
+
this.#serverTotal = total;
|
|
1950
|
+
this.#hasMore = total === undefined ? rows.length >= this.#pageSize : this.#data.length < total;
|
|
1951
1951
|
this.#reindex();
|
|
1952
1952
|
this.#recompute();
|
|
1953
1953
|
this.#announce(
|
|
@@ -1955,7 +1955,7 @@ class DataGrid {
|
|
|
1955
1955
|
? I18n.t("dataGrid.loadedRows", { shown: this.#data.length })
|
|
1956
1956
|
: I18n.t("dataGrid.loadedRowsOf", { shown: this.#data.length, total: total })
|
|
1957
1957
|
);
|
|
1958
|
-
if (this.#
|
|
1958
|
+
if (this.#onDataLoad !== undefined) this.#onDataLoad(rows, total);
|
|
1959
1959
|
} catch (error) {
|
|
1960
1960
|
if (request !== this.#request) return;
|
|
1961
1961
|
this.#failed = true;
|
|
@@ -1965,12 +1965,12 @@ class DataGrid {
|
|
|
1965
1965
|
: I18n.t("dataGrid.unknownError");
|
|
1966
1966
|
this.#setError(I18n.t("dataGrid.loadFailed", { message: message }));
|
|
1967
1967
|
this.#announce(I18n.t("dataGrid.loadFailedAnnounce", { message: message }));
|
|
1968
|
-
if (this.#
|
|
1968
|
+
if (this.#onDataError !== undefined) this.#onDataError(error, this.#requestParams(this.#data.length));
|
|
1969
1969
|
} finally {
|
|
1970
1970
|
if (request === this.#request) {
|
|
1971
1971
|
this.#loading = false;
|
|
1972
1972
|
this.#list.setLoadingMore(false);
|
|
1973
|
-
this.#list.setHasMore(this.#
|
|
1973
|
+
this.#list.setHasMore(this.#hasMore && this.#failed === false);
|
|
1974
1974
|
}
|
|
1975
1975
|
}
|
|
1976
1976
|
}
|
|
@@ -1978,10 +1978,10 @@ class DataGrid {
|
|
|
1978
1978
|
#setError(message = undefined) {
|
|
1979
1979
|
const broken = message !== undefined;
|
|
1980
1980
|
this.#error.hidden = broken === false;
|
|
1981
|
-
this.#
|
|
1981
|
+
this.#errorText.innerText = broken ? String(message) : "";
|
|
1982
1982
|
// «Нет данных» и «не удалось загрузить» — разное, поэтому подпись пустого списка меняется
|
|
1983
1983
|
this.#list.setEmptyText(
|
|
1984
|
-
broken ? I18n.t("dataGrid.notLoaded") : optionOr(this.#
|
|
1984
|
+
broken ? I18n.t("dataGrid.notLoaded") : optionOr(this.#emptyText, I18n.t("dataGrid.empty"))
|
|
1985
1985
|
);
|
|
1986
1986
|
}
|
|
1987
1987
|
|
|
@@ -2055,13 +2055,13 @@ class DataGrid {
|
|
|
2055
2055
|
// === ДАННЫЕ И СОСТОЯНИЕ СЕТКИ ===
|
|
2056
2056
|
/** Индекс строки в исходных данных: нужен и `render(row, index)`, и ключу по умолчанию */
|
|
2057
2057
|
#rowIndexOf(row = Object()) {
|
|
2058
|
-
const index = this.#
|
|
2058
|
+
const index = this.#rowIndex.get(row);
|
|
2059
2059
|
return index === undefined ? -1 : index;
|
|
2060
2060
|
}
|
|
2061
2061
|
|
|
2062
2062
|
#reindex() {
|
|
2063
|
-
this.#
|
|
2064
|
-
for (let index = 0; index < this.#data.length; index++) this.#
|
|
2063
|
+
this.#rowIndex = new Map();
|
|
2064
|
+
for (let index = 0; index < this.#data.length; index++) this.#rowIndex.set(this.#data[index], index);
|
|
2065
2065
|
}
|
|
2066
2066
|
|
|
2067
2067
|
/** Замыкание выравнивания и прокрутки: после смены колонок всё считается заново */
|
|
@@ -2100,10 +2100,10 @@ class DataGrid {
|
|
|
2100
2100
|
}
|
|
2101
2101
|
/** Элементов списка: заголовки групп тоже считаются */
|
|
2102
2102
|
getEntryCount() {
|
|
2103
|
-
return this.#
|
|
2103
|
+
return this.#entryCount;
|
|
2104
2104
|
}
|
|
2105
2105
|
getRowHeight() {
|
|
2106
|
-
return this.#
|
|
2106
|
+
return this.#rowHeight;
|
|
2107
2107
|
}
|
|
2108
2108
|
getVisibleRange() {
|
|
2109
2109
|
return this.#list.getVisibleRange();
|
|
@@ -2136,16 +2136,16 @@ class DataGrid {
|
|
|
2136
2136
|
return this.#selection;
|
|
2137
2137
|
}
|
|
2138
2138
|
isServer() {
|
|
2139
|
-
return this.#
|
|
2139
|
+
return this.#dataSource !== undefined;
|
|
2140
2140
|
}
|
|
2141
2141
|
isLoading() {
|
|
2142
2142
|
return this.#loading;
|
|
2143
2143
|
}
|
|
2144
2144
|
getServerTotal() {
|
|
2145
|
-
return this.#
|
|
2145
|
+
return this.#serverTotal;
|
|
2146
2146
|
}
|
|
2147
2147
|
getPageSize() {
|
|
2148
|
-
return this.#
|
|
2148
|
+
return this.#pageSize;
|
|
2149
2149
|
}
|
|
2150
2150
|
isEditing() {
|
|
2151
2151
|
return this.#editing !== undefined;
|
|
@@ -2178,21 +2178,21 @@ class DataGrid {
|
|
|
2178
2178
|
return this.#hidden.has(key) === false;
|
|
2179
2179
|
}
|
|
2180
2180
|
isColumnReorder() {
|
|
2181
|
-
return this.#
|
|
2181
|
+
return this.#columnReorder;
|
|
2182
2182
|
}
|
|
2183
2183
|
getColumnOrder() {
|
|
2184
2184
|
return this.#columns.map((column) => column.key);
|
|
2185
2185
|
}
|
|
2186
2186
|
getGroupBy() {
|
|
2187
|
-
return this.#
|
|
2187
|
+
return this.#groupBy;
|
|
2188
2188
|
}
|
|
2189
2189
|
/** Группы с агрегатами и состоянием свёрнутости */
|
|
2190
2190
|
getGroups() {
|
|
2191
2191
|
return this.#groups.map((group) => ({
|
|
2192
2192
|
key: group.key,
|
|
2193
2193
|
title:
|
|
2194
|
-
typeof this.#
|
|
2195
|
-
? String(this.#
|
|
2194
|
+
typeof this.#groupLabel === "function"
|
|
2195
|
+
? String(this.#groupLabel(group.key, group.rows))
|
|
2196
2196
|
: I18n.t("dataGrid.groupTitle", { key: group.key, count: group.rows.length }),
|
|
2197
2197
|
rows: group.rows.slice(),
|
|
2198
2198
|
aggregates: group.aggregates,
|
|
@@ -2226,9 +2226,9 @@ class DataGrid {
|
|
|
2226
2226
|
setData(data = []) {
|
|
2227
2227
|
this.#data = Array.isArray(data) ? data : [];
|
|
2228
2228
|
// Ручная замена данных выключает догрузку сервера: иначе она перезаписала бы их
|
|
2229
|
-
this.#
|
|
2230
|
-
this.#
|
|
2231
|
-
this.#
|
|
2229
|
+
this.#dataSource = undefined;
|
|
2230
|
+
this.#hasMore = false;
|
|
2231
|
+
this.#serverTotal = undefined;
|
|
2232
2232
|
this.#selected.clear();
|
|
2233
2233
|
this.#editing = undefined;
|
|
2234
2234
|
this.#failed = false;
|
|
@@ -2248,26 +2248,26 @@ class DataGrid {
|
|
|
2248
2248
|
}
|
|
2249
2249
|
|
|
2250
2250
|
setPageSize(size = Number()) {
|
|
2251
|
-
this.#
|
|
2251
|
+
this.#pageSize = DataGrid.#normalizePageSize(size);
|
|
2252
2252
|
return this;
|
|
2253
2253
|
}
|
|
2254
2254
|
|
|
2255
2255
|
setRowHeight(height = Number()) {
|
|
2256
|
-
this.#
|
|
2257
|
-
this.#list.setItemHeight(this.#
|
|
2256
|
+
this.#rowHeight = Number(height);
|
|
2257
|
+
this.#list.setItemHeight(this.#rowHeight);
|
|
2258
2258
|
this.#syncColumns();
|
|
2259
2259
|
return this;
|
|
2260
2260
|
}
|
|
2261
2261
|
|
|
2262
2262
|
/** Перечитать страницы с сервера с текущим фильтром и сортировкой */
|
|
2263
2263
|
reload() {
|
|
2264
|
-
if (this.#
|
|
2264
|
+
if (this.#dataSource === undefined) return this;
|
|
2265
2265
|
this.#request = this.#request + 1;
|
|
2266
2266
|
this.#loading = false;
|
|
2267
2267
|
this.#failed = false;
|
|
2268
2268
|
this.#data = [];
|
|
2269
|
-
this.#
|
|
2270
|
-
this.#
|
|
2269
|
+
this.#hasMore = true;
|
|
2270
|
+
this.#serverTotal = undefined;
|
|
2271
2271
|
this.#reindex();
|
|
2272
2272
|
this.#recompute();
|
|
2273
2273
|
this.#load("first");
|
|
@@ -2275,22 +2275,22 @@ class DataGrid {
|
|
|
2275
2275
|
}
|
|
2276
2276
|
|
|
2277
2277
|
loadMore() {
|
|
2278
|
-
if (this.#
|
|
2278
|
+
if (this.#dataSource === undefined || this.#hasMore === false || this.#failed) return this;
|
|
2279
2279
|
this.#load("more");
|
|
2280
2280
|
return this;
|
|
2281
2281
|
}
|
|
2282
2282
|
|
|
2283
2283
|
retryLoad() {
|
|
2284
|
-
if (this.#
|
|
2284
|
+
if (this.#dataSource === undefined) return this;
|
|
2285
2285
|
this.#failed = false;
|
|
2286
|
-
this.#list.setHasMore(this.#
|
|
2286
|
+
this.#list.setHasMore(this.#hasMore);
|
|
2287
2287
|
this.#load("more");
|
|
2288
2288
|
return this;
|
|
2289
2289
|
}
|
|
2290
2290
|
|
|
2291
2291
|
setFilter(text = String()) {
|
|
2292
2292
|
this.#filter = String(text);
|
|
2293
|
-
if (this.#
|
|
2293
|
+
if (this.#dataSource !== undefined) return this.reload();
|
|
2294
2294
|
this.#recompute();
|
|
2295
2295
|
// Про фильтр сообщаем числом строк: «фильтр «n»: 3 из 12»
|
|
2296
2296
|
const shown = this.#sorted.length;
|
|
@@ -2310,7 +2310,7 @@ class DataGrid {
|
|
|
2310
2310
|
setSort(key = undefined, direction = DataGrid.SORT.NONE) {
|
|
2311
2311
|
const known = this.#columns.some((column) => column.key === key) ? key : undefined;
|
|
2312
2312
|
this.#sort = { key: known, direction: known === undefined ? DataGrid.SORT.NONE : direction };
|
|
2313
|
-
if (this.#
|
|
2313
|
+
if (this.#dataSource !== undefined) return this.reload();
|
|
2314
2314
|
this.#applySort();
|
|
2315
2315
|
return this;
|
|
2316
2316
|
}
|
|
@@ -2326,15 +2326,15 @@ class DataGrid {
|
|
|
2326
2326
|
}
|
|
2327
2327
|
|
|
2328
2328
|
setOnActiveCellChange(listener = undefined) {
|
|
2329
|
-
this.#
|
|
2329
|
+
this.#onActiveCellChange = listener;
|
|
2330
2330
|
return this;
|
|
2331
2331
|
}
|
|
2332
2332
|
|
|
2333
2333
|
setContextMenu(ctx = undefined) {
|
|
2334
|
-
if (this.#
|
|
2335
|
-
if (this.#
|
|
2336
|
-
this.#
|
|
2337
|
-
this.#
|
|
2334
|
+
if (this.#contextMenu === ctx) return this;
|
|
2335
|
+
if (this.#contextMenu !== undefined && typeof this.#contextMenu.detach === "function")
|
|
2336
|
+
this.#contextMenu.detach();
|
|
2337
|
+
this.#contextMenu = ctx;
|
|
2338
2338
|
if (ctx !== undefined && typeof ctx.attach === "function") ctx.attach(this.#root);
|
|
2339
2339
|
return this;
|
|
2340
2340
|
}
|
|
@@ -2429,13 +2429,13 @@ class DataGrid {
|
|
|
2429
2429
|
}
|
|
2430
2430
|
|
|
2431
2431
|
setColumnReorder(enabled = Boolean()) {
|
|
2432
|
-
this.#
|
|
2432
|
+
this.#columnReorder = enabled === true;
|
|
2433
2433
|
this.#buildHeader();
|
|
2434
2434
|
return this;
|
|
2435
2435
|
}
|
|
2436
2436
|
|
|
2437
2437
|
setGroupBy(groupBy = undefined) {
|
|
2438
|
-
this.#
|
|
2438
|
+
this.#groupBy = groupBy;
|
|
2439
2439
|
this.#collapsed.clear();
|
|
2440
2440
|
this.#recompute();
|
|
2441
2441
|
return this;
|
|
@@ -2483,27 +2483,27 @@ class DataGrid {
|
|
|
2483
2483
|
|
|
2484
2484
|
/** Снять подписки и отпустить ресурсы; узел остаётся в документе */
|
|
2485
2485
|
destroy() {
|
|
2486
|
-
this.#
|
|
2487
|
-
this.#
|
|
2488
|
-
this.#
|
|
2489
|
-
this.#
|
|
2490
|
-
this.#
|
|
2491
|
-
this.#
|
|
2492
|
-
this.#
|
|
2493
|
-
this.#
|
|
2486
|
+
this.#resizeObserver.disconnect();
|
|
2487
|
+
this.#headRow.removeEventListener("click", this.#onHeaderClick);
|
|
2488
|
+
this.#headRow.removeEventListener("keydown", this.#onHeaderKeyDown);
|
|
2489
|
+
this.#headRow.removeEventListener("dragstart", this.#onHeaderDragStart);
|
|
2490
|
+
this.#headRow.removeEventListener("dragover", this.#onHeaderDragOver);
|
|
2491
|
+
this.#headRow.removeEventListener("dragleave", this.#onHeaderDragLeave);
|
|
2492
|
+
this.#headRow.removeEventListener("drop", this.#onHeaderDrop);
|
|
2493
|
+
this.#headRow.removeEventListener("dragend", this.#onHeaderDragEnd);
|
|
2494
2494
|
this.#root.removeEventListener("dblclick", this.#onDoubleClick, true);
|
|
2495
2495
|
this.#root.removeEventListener("keydown", this.#onKeyDown);
|
|
2496
2496
|
this.#scroll.removeEventListener("scroll", this.#onScrollX);
|
|
2497
2497
|
if (this.#editing !== undefined) this.#finishEdit(false, false, false);
|
|
2498
|
-
if (this.#
|
|
2499
|
-
this.#
|
|
2498
|
+
if (this.#contextMenu !== undefined && typeof this.#contextMenu.detach === "function")
|
|
2499
|
+
this.#contextMenu.detach();
|
|
2500
2500
|
this.#list.destroy();
|
|
2501
|
-
if (this.#
|
|
2501
|
+
if (this.#chooserDialog !== undefined) {
|
|
2502
2502
|
// У диалога нет своего «удали себя»: узел снимаем с документа сами
|
|
2503
|
-
const node = this.#
|
|
2503
|
+
const node = this.#chooserDialog.set();
|
|
2504
2504
|
if (node !== undefined && node.parentNode !== null) node.parentNode.removeChild(node);
|
|
2505
|
-
this.#
|
|
2506
|
-
this.#
|
|
2505
|
+
this.#chooserDialog = undefined;
|
|
2506
|
+
this.#chooserBlock = undefined;
|
|
2507
2507
|
}
|
|
2508
2508
|
return this;
|
|
2509
2509
|
}
|